piston 1.4.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +24 -0
- data/License.txt +20 -0
- data/Manifest.txt +109 -0
- data/{README → README.txt} +14 -10
- data/VERSION.yml +4 -0
- data/bin/piston +3 -8
- data/lib/piston/cli.rb +121 -0
- data/lib/piston/commands/base.rb +44 -0
- data/lib/piston/commands/convert.rb +23 -71
- data/lib/piston/commands/diff.rb +14 -46
- data/lib/piston/commands/import.rb +48 -57
- data/lib/piston/commands/info.rb +24 -0
- data/lib/piston/commands/lock_unlock.rb +26 -0
- data/lib/piston/commands/status.rb +29 -54
- data/lib/piston/commands/update.rb +35 -122
- data/lib/piston/commands/upgrade.rb +26 -0
- data/lib/piston/commands.rb +4 -0
- data/lib/piston/git/client.rb +76 -0
- data/lib/piston/git/commit.rb +114 -0
- data/lib/piston/git/repository.rb +63 -0
- data/lib/piston/git/working_copy.rb +145 -0
- data/lib/piston/git.rb +13 -0
- data/lib/piston/repository.rb +61 -0
- data/lib/piston/revision.rb +83 -0
- data/lib/piston/svn/client.rb +88 -0
- data/lib/piston/svn/repository.rb +67 -0
- data/lib/piston/svn/revision.rb +112 -0
- data/lib/piston/svn/working_copy.rb +184 -0
- data/lib/piston/svn.rb +15 -0
- data/lib/piston/version.rb +9 -7
- data/lib/piston/working_copy.rb +334 -0
- data/lib/piston.rb +13 -64
- data/lib/subclass_responsibility_error.rb +2 -0
- data/test/integration_helpers.rb +36 -0
- data/test/spec_suite.rb +4 -0
- data/test/test_helper.rb +92 -0
- data/test/unit/git/commit/test_checkout.rb +31 -0
- data/test/unit/git/commit/test_each.rb +30 -0
- data/test/unit/git/commit/test_rememberance.rb +22 -0
- data/test/unit/git/commit/test_validation.rb +34 -0
- data/test/unit/git/repository/test_at.rb +23 -0
- data/test/unit/git/repository/test_basename.rb +12 -0
- data/test/unit/git/repository/test_branchanme.rb +15 -0
- data/test/unit/git/repository/test_guessing.rb +32 -0
- data/test/unit/git/working_copy/test_copying.rb +25 -0
- data/test/unit/git/working_copy/test_creation.rb +22 -0
- data/test/unit/git/working_copy/test_existence.rb +18 -0
- data/test/unit/git/working_copy/test_finalization.rb +15 -0
- data/test/unit/git/working_copy/test_guessing.rb +35 -0
- data/test/unit/git/working_copy/test_rememberance.rb +22 -0
- data/test/unit/svn/repository/test_at.rb +19 -0
- data/test/unit/svn/repository/test_basename.rb +24 -0
- data/test/unit/svn/repository/test_guessing.rb +45 -0
- data/test/unit/svn/revision/test_checkout.rb +28 -0
- data/test/unit/svn/revision/test_each.rb +22 -0
- data/test/unit/svn/revision/test_rememberance.rb +38 -0
- data/test/unit/svn/revision/test_validation.rb +50 -0
- data/test/unit/svn/working_copy/test_copying.rb +26 -0
- data/test/unit/svn/working_copy/test_creation.rb +16 -0
- data/test/unit/svn/working_copy/test_existence.rb +23 -0
- data/test/unit/svn/working_copy/test_externals.rb +56 -0
- data/test/unit/svn/working_copy/test_finalization.rb +17 -0
- data/test/unit/svn/working_copy/test_guessing.rb +18 -0
- data/test/unit/svn/working_copy/test_rememberance.rb +26 -0
- data/test/unit/test_info.rb +37 -0
- data/test/unit/test_lock_unlock.rb +47 -0
- data/test/unit/test_repository.rb +51 -0
- data/test/unit/test_revision.rb +31 -0
- data/test/unit/working_copy/test_guessing.rb +35 -0
- data/test/unit/working_copy/test_info.rb +14 -0
- data/test/unit/working_copy/test_rememberance.rb +42 -0
- data/test/unit/working_copy/test_validate.rb +63 -0
- metadata +132 -31
- data/CHANGELOG +0 -81
- data/LICENSE +0 -19
- data/Rakefile +0 -63
- data/contrib/piston +0 -43
- data/lib/core_ext/range.rb +0 -5
- data/lib/core_ext/string.rb +0 -9
- data/lib/piston/command.rb +0 -68
- data/lib/piston/command_error.rb +0 -6
- data/lib/piston/commands/lock.rb +0 -30
- data/lib/piston/commands/switch.rb +0 -139
- data/lib/piston/commands/unlock.rb +0 -29
- data/lib/transat/parser.rb +0 -189
@@ -0,0 +1,184 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Piston
|
4
|
+
module Svn
|
5
|
+
class WorkingCopy < Piston::WorkingCopy
|
6
|
+
# Register ourselves as a handler for working copies
|
7
|
+
Piston::WorkingCopy.add_handler self
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def understands_dir?(dir)
|
11
|
+
result = svn(:info, dir) rescue :failed
|
12
|
+
result == :failed ? false : true
|
13
|
+
end
|
14
|
+
|
15
|
+
def client
|
16
|
+
@@client ||= Piston::Svn::Client.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def svn(*args)
|
20
|
+
client.svn(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def old_repositories(*directories)
|
24
|
+
repositories = []
|
25
|
+
unless directories.empty?
|
26
|
+
folders = svn(:propget, '--recursive', Piston::Svn::ROOT, *directories)
|
27
|
+
folders.each_line do |line|
|
28
|
+
next unless line =~ /^(.+) - \S+/
|
29
|
+
logger.debug {"Found repository #{$1}"}
|
30
|
+
repositories << $1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
repositories
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def svn(*args)
|
38
|
+
self.class.svn(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def exist?
|
42
|
+
result = svn(:info, path) rescue :failed
|
43
|
+
logger.debug {"result: #{result.inspect}"}
|
44
|
+
return false if result == :failed
|
45
|
+
return false if result.nil? || result.chomp.strip.empty?
|
46
|
+
return true if YAML.load(result).has_key?("Path")
|
47
|
+
end
|
48
|
+
|
49
|
+
def create
|
50
|
+
logger.debug {"Creating #{path}"}
|
51
|
+
begin
|
52
|
+
svn(:mkdir, path)
|
53
|
+
rescue Piston::Svn::Client::CommandError
|
54
|
+
logger.error do
|
55
|
+
"Folder #{path} could not be created. Is #{path.parent} a working copy? (Tip: svn mkdir it)"
|
56
|
+
end
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_remember(path)
|
62
|
+
begin
|
63
|
+
info = svn(:info, path)
|
64
|
+
rescue Piston::Svn::Client::CommandError
|
65
|
+
ensure
|
66
|
+
return unless info =~ /\(not a versioned resource\)/i || info =~ /\(is not under version control\)/i || info.blank?
|
67
|
+
svn(:add, path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def finalize
|
72
|
+
targets = []
|
73
|
+
Dir[(path + "*").to_s].each do |item|
|
74
|
+
svn(:add, item)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def add(added)
|
79
|
+
added.each do |item|
|
80
|
+
target = path + item
|
81
|
+
target.mkdir unless target.exist?
|
82
|
+
svn(:add, target)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete(deleted)
|
87
|
+
deleted.each do |item|
|
88
|
+
svn(:rm, path + item)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def rename(renamed)
|
93
|
+
renamed.each do |from, to|
|
94
|
+
svn(:mv, path + from, path + to)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def downgrade_to(revision)
|
99
|
+
logger.debug {"Downgrading to revision when last update was made"}
|
100
|
+
svn(:update, '--revision', revision, path)
|
101
|
+
end
|
102
|
+
|
103
|
+
def merge_local_changes(revision)
|
104
|
+
logger.debug {"Update to #{revision} in order to merge local changes"}
|
105
|
+
svn(:update, "--non-interactive", path)
|
106
|
+
end
|
107
|
+
|
108
|
+
def status(subpath=nil)
|
109
|
+
svn(:status, path + subpath.to_s).split("\n").inject([]) do |memo, line|
|
110
|
+
next memo unless line =~ /^\w.+\s(.*)$/
|
111
|
+
memo << [$1, $2]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns all defined externals (recursively) of this WC.
|
116
|
+
# Returns a Hash:
|
117
|
+
# {"vendor/rails" => {:revision => :head, :url => "http://dev.rubyonrails.org/svn/rails/trunk"},
|
118
|
+
# "vendor/plugins/will_paginate" => {:revision => 1234, :url => "http://will_paginate.org/svn/trunk"}}
|
119
|
+
def externals
|
120
|
+
externals = svn(:proplist, "--recursive", "--verbose")
|
121
|
+
return Hash.new if externals.blank?
|
122
|
+
returning(Hash.new) do |result|
|
123
|
+
YAML.load(externals).each_pair do |dir, props|
|
124
|
+
next if props["svn:externals"].blank?
|
125
|
+
next unless dir =~ /Properties on '([^']+)'/
|
126
|
+
basedir = self.path + $1
|
127
|
+
exts = props["svn:externals"]
|
128
|
+
exts.split("\n").each do |external|
|
129
|
+
data = external.match(/^([^\s]+)\s+(?:(?:-r|--revision)\s*(\d+)\s+)?(.+)$/)
|
130
|
+
case data.length
|
131
|
+
when 4
|
132
|
+
subdir, rev, url = data[1], data[2].nil? ? :head : data[2].to_i, data[3]
|
133
|
+
else
|
134
|
+
raise SyntaxError, "Could not parse svn:externals on #{basedir}: #{external}"
|
135
|
+
end
|
136
|
+
|
137
|
+
result[basedir + subdir] = {:revision => rev, :url => url}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def remove_external_references(*targets)
|
144
|
+
svn(:propdel, "svn:externals", *targets)
|
145
|
+
end
|
146
|
+
|
147
|
+
def locally_modified
|
148
|
+
logger.debug {"Get last changed revision for #{yaml_path}"}
|
149
|
+
# get latest revision for .piston.yml
|
150
|
+
initial_revision = last_changed_revision(yaml_path)
|
151
|
+
logger.debug {"Get last log line for #{path} after #{initial_revision}"}
|
152
|
+
# get latest revisions for this working copy since last update
|
153
|
+
log = svn(:log, '--revision', "#{initial_revision}:HEAD", '--quiet', '--limit', '2', path)
|
154
|
+
log.count("\n") > 3
|
155
|
+
end
|
156
|
+
|
157
|
+
def exclude_for_diff
|
158
|
+
Piston::Svn::EXCLUDE
|
159
|
+
end
|
160
|
+
|
161
|
+
def upgrade
|
162
|
+
props = Hash.new
|
163
|
+
svn(:proplist, '--verbose', path).each_line do |line|
|
164
|
+
if line =~ /(piston:[-\w]+)\s*:\s*(.*)$/
|
165
|
+
props[$1] = $2
|
166
|
+
svn(:propdel, $1, path)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
remember({:repository_url => props[Piston::Svn::ROOT], :lock => props[Piston::Svn::LOCKED] || false, :repository_class => Piston::Svn::Repository.name}, {Piston::Svn::REMOTE_REV => props[Piston::Svn::REMOTE_REV], Piston::Svn::UUID => props[Piston::Svn::UUID]})
|
170
|
+
end
|
171
|
+
|
172
|
+
protected
|
173
|
+
def current_revision
|
174
|
+
data = svn(:info, path)
|
175
|
+
YAML.load(data)["Revision"].to_i
|
176
|
+
end
|
177
|
+
|
178
|
+
def last_changed_revision(path)
|
179
|
+
data = svn(:info, yaml_path)
|
180
|
+
YAML.load(data)["Last Changed Rev"].to_i
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/piston/svn.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "piston/svn/client"
|
2
|
+
require "piston/svn/repository"
|
3
|
+
require "piston/svn/revision"
|
4
|
+
require "piston/svn/working_copy"
|
5
|
+
|
6
|
+
module Piston
|
7
|
+
module Svn
|
8
|
+
ROOT = "piston:root"
|
9
|
+
UUID = "piston:uuid"
|
10
|
+
REMOTE_REV = "piston:remote-revision"
|
11
|
+
LOCAL_REV = "piston:local-revision"
|
12
|
+
LOCKED = "piston:locked"
|
13
|
+
EXCLUDE = [".svn"]
|
14
|
+
end
|
15
|
+
end
|
data/lib/piston/version.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Piston
|
1
|
+
module Piston #:nodoc:
|
4
2
|
module VERSION #:nodoc:
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
def self.config
|
4
|
+
@@version ||= YAML.load_file(File.dirname(__FILE__) + "/../../VERSION.yml")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.read_version
|
8
|
+
"%s.%s.%s" % [config[:major], config[:minor], config[:patch]]
|
9
|
+
end
|
8
10
|
|
9
|
-
STRING =
|
11
|
+
STRING = read_version
|
10
12
|
end
|
11
13
|
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
module Piston
|
2
|
+
class WorkingCopy
|
3
|
+
class UnhandledWorkingCopy < RuntimeError; end
|
4
|
+
class NotWorkingCopy < RuntimeError; end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def logger
|
8
|
+
@@logger ||= Log4r::Logger["handler"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def guess(path)
|
12
|
+
path = path.kind_of?(Pathname) ? path : Pathname.new(path.to_s)
|
13
|
+
try_path = path.exist? ? path : path.parent
|
14
|
+
logger.info {"Guessing the working copy type of #{try_path.inspect}"}
|
15
|
+
handler = handlers.detect do |handler|
|
16
|
+
logger.debug {"Asking #{handler.name} if it understands #{try_path}"}
|
17
|
+
handler.understands_dir?(try_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
raise UnhandledWorkingCopy, "Don't know what working copy type #{path} is." if handler.nil?
|
21
|
+
handler.new(File.expand_path(path))
|
22
|
+
end
|
23
|
+
|
24
|
+
@@handlers = Array.new
|
25
|
+
def add_handler(handler)
|
26
|
+
@@handlers << handler
|
27
|
+
end
|
28
|
+
|
29
|
+
def handlers
|
30
|
+
@@handlers
|
31
|
+
end
|
32
|
+
private :handlers
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :path
|
36
|
+
|
37
|
+
def initialize(path)
|
38
|
+
if path.kind_of?(Pathname)
|
39
|
+
raise ArgumentError, "#{path} must be absolute" unless path.absolute?
|
40
|
+
@path = path
|
41
|
+
else
|
42
|
+
@path = Pathname.new(File.expand_path(path))
|
43
|
+
end
|
44
|
+
logger.debug {"Initialized on #{@path}"}
|
45
|
+
end
|
46
|
+
|
47
|
+
def logger
|
48
|
+
self.class.logger
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"Piston::WorkingCopy(#{@path})"
|
53
|
+
end
|
54
|
+
|
55
|
+
def exist?
|
56
|
+
@path.exist? && @path.directory?
|
57
|
+
end
|
58
|
+
|
59
|
+
def pistonized?
|
60
|
+
yaml_path.exist? && yaml_path.file?
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate!
|
64
|
+
raise NotWorkingCopy unless self.pistonized?
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def repository
|
69
|
+
values = self.recall
|
70
|
+
repository_class = values["repository_class"]
|
71
|
+
repository_url = values["repository_url"]
|
72
|
+
repository_class.constantize.new(repository_url)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates the initial working copy for pistonizing a new repository.
|
76
|
+
def create
|
77
|
+
logger.debug {"Creating working copy at #{path}"}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Copy files from +revision+. +revision+ must
|
81
|
+
# #respond_to?(:each), and return each file that is to be copied.
|
82
|
+
# Only files must be returned.
|
83
|
+
#
|
84
|
+
# Each item yielded by Revision#each must be a relative path.
|
85
|
+
#
|
86
|
+
# WorkingCopy will call Revision#copy_to with the full path to where the
|
87
|
+
# file needs to be copied.
|
88
|
+
def copy_from(revision)
|
89
|
+
revision.each do |relpath|
|
90
|
+
target = path + relpath
|
91
|
+
target.dirname.mkdir rescue nil
|
92
|
+
|
93
|
+
logger.debug {"Copying #{relpath} to #{target}"}
|
94
|
+
revision.copy_to(relpath, target)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Copy files to +revision+ to keep local changes. +revision+ must
|
99
|
+
# #respond_to?(:each), and return each file that is to be copied.
|
100
|
+
# Only files must be returned.
|
101
|
+
#
|
102
|
+
# Each item yielded by Revision#each must be a relative path.
|
103
|
+
#
|
104
|
+
# WorkingCopy will call Revision#copy_from with the full path from where the
|
105
|
+
# file needs to be copied.
|
106
|
+
def copy_to(revision)
|
107
|
+
revision.each do |relpath|
|
108
|
+
source = path + relpath
|
109
|
+
|
110
|
+
logger.debug {"Copying #{source} to #{relpath}"}
|
111
|
+
revision.copy_from(source, relpath)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# add some files to working copy
|
116
|
+
def add(added)
|
117
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#add should be implemented by a subclass."
|
118
|
+
end
|
119
|
+
|
120
|
+
# delete some files from working copy
|
121
|
+
def delete(deleted)
|
122
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#delete should be implemented by a subclass."
|
123
|
+
end
|
124
|
+
|
125
|
+
# rename some files in working copy
|
126
|
+
def rename(renamed)
|
127
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#rename should be implemented by a subclass."
|
128
|
+
end
|
129
|
+
|
130
|
+
# Downgrade this working copy to +revision+.
|
131
|
+
def downgrade_to(revision)
|
132
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#downgrade_to should be implemented by a subclass."
|
133
|
+
end
|
134
|
+
|
135
|
+
# Merge remote changes with local changes in +revision+.
|
136
|
+
def merge_local_changes(revision)
|
137
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#merge_local_changes should be implemented by a subclass."
|
138
|
+
end
|
139
|
+
|
140
|
+
# Stores a Hash of values that can be retrieved later.
|
141
|
+
def remember(values, handler_values)
|
142
|
+
values["format"] = 1
|
143
|
+
|
144
|
+
# Stringify keys
|
145
|
+
values.keys.each do |key|
|
146
|
+
values[key.to_s] = values.delete(key)
|
147
|
+
end
|
148
|
+
|
149
|
+
logger.debug {"Remembering #{values.inspect} as well as #{handler_values.inspect}"}
|
150
|
+
File.open(yaml_path, "w+") do |f|
|
151
|
+
f.write(values.merge("handler" => handler_values).to_yaml)
|
152
|
+
end
|
153
|
+
|
154
|
+
logger.debug {"Calling \#after_remember on #{yaml_path}"}
|
155
|
+
after_remember(yaml_path)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Callback after #remember is done, to do whatever the
|
159
|
+
# working copy needs to do with the file.
|
160
|
+
def after_remember(path)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Recalls a Hash of values from the working copy.
|
164
|
+
def recall
|
165
|
+
YAML.load_file(yaml_path)
|
166
|
+
end
|
167
|
+
|
168
|
+
def finalize
|
169
|
+
logger.debug {"Finalizing #{path}"}
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns basic information about this working copy.
|
173
|
+
def info
|
174
|
+
recall
|
175
|
+
end
|
176
|
+
|
177
|
+
def import(revision, lock)
|
178
|
+
lock ||= false
|
179
|
+
repository = revision.repository
|
180
|
+
tmpdir = temp_dir_name
|
181
|
+
begin
|
182
|
+
logger.info {"Checking out the repository"}
|
183
|
+
revision.checkout_to(tmpdir)
|
184
|
+
|
185
|
+
logger.debug {"Creating the local working copy"}
|
186
|
+
create
|
187
|
+
|
188
|
+
logger.info {"Copying from #{revision}"}
|
189
|
+
copy_from(revision)
|
190
|
+
|
191
|
+
logger.debug {"Remembering values"}
|
192
|
+
remember(
|
193
|
+
{:repository_url => repository.url, :lock => lock, :repository_class => repository.class.name},
|
194
|
+
revision.remember_values
|
195
|
+
)
|
196
|
+
|
197
|
+
logger.debug {"Finalizing working copy"}
|
198
|
+
finalize
|
199
|
+
|
200
|
+
logger.info {"Checked out #{repository.url.inspect} #{revision.name} to #{path.to_s}"}
|
201
|
+
ensure
|
202
|
+
logger.debug {"Removing temporary directory: #{tmpdir}"}
|
203
|
+
tmpdir.rmtree rescue nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Update this working copy from +from+ to +to+, which means merging local changes back in
|
208
|
+
# Return true if changed, false if not
|
209
|
+
def update(revision, to, lock)
|
210
|
+
lock ||= false
|
211
|
+
tmpdir = temp_dir_name
|
212
|
+
begin
|
213
|
+
logger.info {"Checking out the repository at #{revision.revision}"}
|
214
|
+
revision.checkout_to(tmpdir)
|
215
|
+
|
216
|
+
revision_to_return = current_revision
|
217
|
+
revision_to_downgrade = last_changed_revision(yaml_path)
|
218
|
+
logger.debug {"Downgrading to #{revision_to_downgrade}"}
|
219
|
+
downgrade_to(revision_to_downgrade)
|
220
|
+
|
221
|
+
logger.debug {"Copying old changes to temporary directory in order to keep them"}
|
222
|
+
copy_to(revision)
|
223
|
+
|
224
|
+
logger.info {"Looking changes from #{revision.revision} to #{to.revision}"}
|
225
|
+
added, deleted, renamed = revision.update_to(to.revision)
|
226
|
+
|
227
|
+
logger.info {"Updating working copy"}
|
228
|
+
|
229
|
+
# rename before copy because copy_from will copy these files
|
230
|
+
logger.debug {"Renaming files"}
|
231
|
+
rename(renamed)
|
232
|
+
|
233
|
+
logger.debug {"Copying files from temporary directory"}
|
234
|
+
copy_from(revision)
|
235
|
+
|
236
|
+
logger.debug {"Adding new files to version control"}
|
237
|
+
add(added)
|
238
|
+
|
239
|
+
logger.debug {"Deleting files from version control"}
|
240
|
+
delete(deleted)
|
241
|
+
|
242
|
+
# merge local changes updating to revision before downgrade was made
|
243
|
+
logger.debug {"Merging local changes"}
|
244
|
+
merge_local_changes(revision_to_return)
|
245
|
+
|
246
|
+
remember(recall.merge(:lock => lock), to.remember_values)
|
247
|
+
|
248
|
+
status = status(path)
|
249
|
+
logger.debug { {:added => added, :deleted => deleted, :renamed => renamed, :status => status}.to_yaml }
|
250
|
+
!status.empty?
|
251
|
+
ensure
|
252
|
+
logger.debug {"Removing temporary directory: #{tmpdir}"}
|
253
|
+
tmpdir.rmtree rescue nil
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def diff
|
258
|
+
tmpdir = temp_dir_name
|
259
|
+
begin
|
260
|
+
logger.info {"Checking out the repository at #{revision.revision}"}
|
261
|
+
revision = repository.at(:head)
|
262
|
+
revision.checkout_to(tmpdir)
|
263
|
+
|
264
|
+
excludes = (['.piston.yml'] + exclude_for_diff + revision.exclude_for_diff).uniq.collect {|pattern| "--exclude=#{pattern}"}.join ' '
|
265
|
+
system("diff -urN #{excludes} '#{tmpdir}' '#{path}'")
|
266
|
+
ensure
|
267
|
+
logger.debug {"Removing temporary directory: #{tmpdir}"}
|
268
|
+
tmpdir.rmtree rescue nil
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def diff
|
273
|
+
tmpdir = temp_dir_name
|
274
|
+
begin
|
275
|
+
logger.info {"Checking out the repository at #{revision.revision}"}
|
276
|
+
revision = repository.at(:head)
|
277
|
+
revision.checkout_to(tmpdir)
|
278
|
+
|
279
|
+
excludes = (['.piston.yml'] + exclude_for_diff + revision.exclude_for_diff).uniq.collect {|pattern| "--exclude=#{pattern}"}.join ' '
|
280
|
+
system("diff -urN #{excludes} '#{tmpdir}' '#{path}'")
|
281
|
+
ensure
|
282
|
+
logger.debug {"Removing temporary directory: #{tmpdir}"}
|
283
|
+
tmpdir.rmtree rescue nil
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def diff
|
288
|
+
tmpdir = temp_dir_name
|
289
|
+
begin
|
290
|
+
logger.info {"Checking out the repository at #{revision.revision}"}
|
291
|
+
revision = repository.at(:head)
|
292
|
+
revision.checkout_to(tmpdir)
|
293
|
+
|
294
|
+
excludes = (['.piston.yml'] + exclude_for_diff + revision.exclude_for_diff).uniq.collect {|pattern| "--exclude=#{pattern}"}.join ' '
|
295
|
+
system("diff -urN #{excludes} '#{tmpdir}' '#{path}'")
|
296
|
+
ensure
|
297
|
+
logger.debug {"Removing temporary directory: #{tmpdir}"}
|
298
|
+
tmpdir.rmtree rescue nil
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def temp_dir_name
|
303
|
+
path.parent + ".#{path.basename}.tmp"
|
304
|
+
end
|
305
|
+
|
306
|
+
def locally_modified
|
307
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
|
308
|
+
end
|
309
|
+
|
310
|
+
def remotely_modified
|
311
|
+
repository.at(recall["handler"]).remotely_modified
|
312
|
+
end
|
313
|
+
|
314
|
+
def exclude_for_diff
|
315
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#exclude_for_diff should be implemented by a subclass."
|
316
|
+
end
|
317
|
+
|
318
|
+
protected
|
319
|
+
# The path to the piston YAML file.
|
320
|
+
def yaml_path
|
321
|
+
path + ".piston.yml"
|
322
|
+
end
|
323
|
+
|
324
|
+
# The current revision of this working copy.
|
325
|
+
def current_revision
|
326
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#current_revision should be implemented by a subclass."
|
327
|
+
end
|
328
|
+
|
329
|
+
# The last revision which +path+ was changed in
|
330
|
+
def last_changed_revision(path)
|
331
|
+
raise SubclassResponsibilityError, "Piston::WorkingCopy#last_changed_revision should be implemented by a subclass."
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
data/lib/piston.rb
CHANGED
@@ -1,70 +1,19 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
1
|
+
require "subclass_responsibility_error"
|
20
2
|
|
21
|
-
|
22
|
-
|
3
|
+
require "piston/repository"
|
4
|
+
require "piston/revision"
|
5
|
+
require "piston/working_copy"
|
23
6
|
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
7
|
+
require "piston/git"
|
8
|
+
require "piston/svn"
|
9
|
+
require "piston/commands"
|
27
10
|
|
28
|
-
|
29
|
-
Dir[File.join(PISTON_ROOT, 'core_ext', '*.rb')].each do |file|
|
30
|
-
require file
|
31
|
-
end
|
32
|
-
|
33
|
-
require "piston/version"
|
34
|
-
require "piston/command"
|
35
|
-
require "piston/command_error"
|
36
|
-
|
37
|
-
require "transat/parser"
|
38
|
-
Dir[File.join(PISTON_ROOT, "piston", "commands", "*.rb")].each do |file|
|
39
|
-
require file.gsub(PISTON_ROOT, "")[1..-4]
|
40
|
-
end
|
11
|
+
require "pathname"
|
41
12
|
|
42
13
|
module Piston
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
PistonCommandLineProcessor = Transat::Parser.new do
|
51
|
-
program_name "Piston"
|
52
|
-
version [Piston::VERSION::STRING]
|
53
|
-
|
54
|
-
option :verbose, :short => :v, :default => true, :message => "Show subversion commands and results as they are executed"
|
55
|
-
option :quiet, :short => :q, :default => false, :message => "Do not output any messages except errors"
|
56
|
-
option :revision, :short => :r, :param_name => "REVISION", :type => :int
|
57
|
-
option :show_updates, :short => :u, :message => "Query the remote repository for out of dateness information"
|
58
|
-
option :lock, :short => :l, :message => "Close down and lock the imported directory from further changes"
|
59
|
-
option :dry_run, :message => "Does not actually execute any commands"
|
60
|
-
option :force, :message => "Force the command to run, even if Piston thinks it would cause a problem"
|
61
|
-
|
62
|
-
command :switch, Piston::Commands::Switch, :valid_options => %w(lock dry_run force revision quiet verbose)
|
63
|
-
command :update, Piston::Commands::Update, :valid_options => %w(lock dry_run force revision quiet verbose)
|
64
|
-
command :diff, Piston::Commands::Diff, :valid_options => %w(lock dry_run force revision quiet verbose)
|
65
|
-
command :import, Piston::Commands::Import, :valid_options => %w(lock dry_run force revision quiet verbose)
|
66
|
-
command :convert, Piston::Commands::Convert, :valid_options => %w(lock verbose dry_run)
|
67
|
-
command :unlock, Piston::Commands::Unlock, :valid_options => %w(force dry_run verbose)
|
68
|
-
command :lock, Piston::Commands::Lock, :valid_options => %w(force dry_run revision verbose)
|
69
|
-
command :status, Piston::Commands::Status, :valid_options => %w(show_updates verbose)
|
14
|
+
class << self
|
15
|
+
def version_message
|
16
|
+
"Piston %s\nCopyright 2006-2008, Francois Beausoleil <francois@teksol.info>\nhttp://piston.rubyforge.org/\nDistributed under an MIT-like license." % Piston::VERSION::STRING
|
17
|
+
end
|
18
|
+
end
|
70
19
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "pathname"
|
2
|
+
PISTON_ROOT = Pathname.new(File.dirname(__FILE__)).parent.realpath
|
3
|
+
|
4
|
+
def logger
|
5
|
+
@logger ||= Log4r::Logger["test"]
|
6
|
+
end
|
7
|
+
|
8
|
+
def runcmd(cmd, *args)
|
9
|
+
cmdline = [cmd]
|
10
|
+
cmdline += args
|
11
|
+
cmdline = cmdline.flatten.map {|s| s.to_s}.join(" ")
|
12
|
+
logger.debug "> #{cmdline}"
|
13
|
+
|
14
|
+
output = `#{cmdline}`
|
15
|
+
logger.debug "< #{output}"
|
16
|
+
return output if $?.success?
|
17
|
+
raise CommandFailed, "Could not run %s, exit status: <%d>\n===\n%s\n===" % [cmdline.inspect, $?.exitstatus, output]
|
18
|
+
end
|
19
|
+
|
20
|
+
def svn(*args)
|
21
|
+
runcmd(:svn, *args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def svnadmin(*args)
|
25
|
+
runcmd(:svnadmin, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def git(*args)
|
29
|
+
runcmd(:git, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def piston(*args)
|
33
|
+
runcmd(:ruby, "-I", PISTON_ROOT + "lib", PISTON_ROOT + "bin/piston", *args)
|
34
|
+
end
|
35
|
+
|
36
|
+
class CommandFailed < RuntimeError; end
|
data/test/spec_suite.rb
ADDED