piston 1.4.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/History.txt +24 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +109 -0
  4. data/{README → README.txt} +14 -10
  5. data/VERSION.yml +4 -0
  6. data/bin/piston +3 -8
  7. data/lib/piston/cli.rb +121 -0
  8. data/lib/piston/commands/base.rb +44 -0
  9. data/lib/piston/commands/convert.rb +23 -71
  10. data/lib/piston/commands/diff.rb +14 -46
  11. data/lib/piston/commands/import.rb +48 -57
  12. data/lib/piston/commands/info.rb +24 -0
  13. data/lib/piston/commands/lock_unlock.rb +26 -0
  14. data/lib/piston/commands/status.rb +29 -54
  15. data/lib/piston/commands/update.rb +35 -122
  16. data/lib/piston/commands/upgrade.rb +26 -0
  17. data/lib/piston/commands.rb +4 -0
  18. data/lib/piston/git/client.rb +76 -0
  19. data/lib/piston/git/commit.rb +114 -0
  20. data/lib/piston/git/repository.rb +63 -0
  21. data/lib/piston/git/working_copy.rb +145 -0
  22. data/lib/piston/git.rb +13 -0
  23. data/lib/piston/repository.rb +61 -0
  24. data/lib/piston/revision.rb +83 -0
  25. data/lib/piston/svn/client.rb +88 -0
  26. data/lib/piston/svn/repository.rb +67 -0
  27. data/lib/piston/svn/revision.rb +112 -0
  28. data/lib/piston/svn/working_copy.rb +184 -0
  29. data/lib/piston/svn.rb +15 -0
  30. data/lib/piston/version.rb +9 -7
  31. data/lib/piston/working_copy.rb +334 -0
  32. data/lib/piston.rb +13 -64
  33. data/lib/subclass_responsibility_error.rb +2 -0
  34. data/test/integration_helpers.rb +36 -0
  35. data/test/spec_suite.rb +4 -0
  36. data/test/test_helper.rb +92 -0
  37. data/test/unit/git/commit/test_checkout.rb +31 -0
  38. data/test/unit/git/commit/test_each.rb +30 -0
  39. data/test/unit/git/commit/test_rememberance.rb +22 -0
  40. data/test/unit/git/commit/test_validation.rb +34 -0
  41. data/test/unit/git/repository/test_at.rb +23 -0
  42. data/test/unit/git/repository/test_basename.rb +12 -0
  43. data/test/unit/git/repository/test_branchanme.rb +15 -0
  44. data/test/unit/git/repository/test_guessing.rb +32 -0
  45. data/test/unit/git/working_copy/test_copying.rb +25 -0
  46. data/test/unit/git/working_copy/test_creation.rb +22 -0
  47. data/test/unit/git/working_copy/test_existence.rb +18 -0
  48. data/test/unit/git/working_copy/test_finalization.rb +15 -0
  49. data/test/unit/git/working_copy/test_guessing.rb +35 -0
  50. data/test/unit/git/working_copy/test_rememberance.rb +22 -0
  51. data/test/unit/svn/repository/test_at.rb +19 -0
  52. data/test/unit/svn/repository/test_basename.rb +24 -0
  53. data/test/unit/svn/repository/test_guessing.rb +45 -0
  54. data/test/unit/svn/revision/test_checkout.rb +28 -0
  55. data/test/unit/svn/revision/test_each.rb +22 -0
  56. data/test/unit/svn/revision/test_rememberance.rb +38 -0
  57. data/test/unit/svn/revision/test_validation.rb +50 -0
  58. data/test/unit/svn/working_copy/test_copying.rb +26 -0
  59. data/test/unit/svn/working_copy/test_creation.rb +16 -0
  60. data/test/unit/svn/working_copy/test_existence.rb +23 -0
  61. data/test/unit/svn/working_copy/test_externals.rb +56 -0
  62. data/test/unit/svn/working_copy/test_finalization.rb +17 -0
  63. data/test/unit/svn/working_copy/test_guessing.rb +18 -0
  64. data/test/unit/svn/working_copy/test_rememberance.rb +26 -0
  65. data/test/unit/test_info.rb +37 -0
  66. data/test/unit/test_lock_unlock.rb +47 -0
  67. data/test/unit/test_repository.rb +51 -0
  68. data/test/unit/test_revision.rb +31 -0
  69. data/test/unit/working_copy/test_guessing.rb +35 -0
  70. data/test/unit/working_copy/test_info.rb +14 -0
  71. data/test/unit/working_copy/test_rememberance.rb +42 -0
  72. data/test/unit/working_copy/test_validate.rb +63 -0
  73. metadata +132 -31
  74. data/CHANGELOG +0 -81
  75. data/LICENSE +0 -19
  76. data/Rakefile +0 -63
  77. data/contrib/piston +0 -43
  78. data/lib/core_ext/range.rb +0 -5
  79. data/lib/core_ext/string.rb +0 -9
  80. data/lib/piston/command.rb +0 -68
  81. data/lib/piston/command_error.rb +0 -6
  82. data/lib/piston/commands/lock.rb +0 -30
  83. data/lib/piston/commands/switch.rb +0 -139
  84. data/lib/piston/commands/unlock.rb +0 -29
  85. 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
@@ -1,11 +1,13 @@
1
- require "piston"
2
-
3
- module Piston
1
+ module Piston #:nodoc:
4
2
  module VERSION #:nodoc:
5
- MAJOR = 1
6
- MINOR = 4
7
- TINY = 0
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 = [MAJOR, MINOR, TINY].join('.')
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
- # Copyright (c) 2006 Francois Beausoleil <francois@teksol.info>
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
- # $HeadURL: svn+ssh://rubyforge.org/var/svn/piston/tags/1.4.0/lib/piston.rb $
22
- # $Id: piston.rb 139 2008-02-07 15:28:24Z fbos $
3
+ require "piston/repository"
4
+ require "piston/revision"
5
+ require "piston/working_copy"
23
6
 
24
- require 'yaml'
25
- require 'uri'
26
- require 'fileutils'
7
+ require "piston/git"
8
+ require "piston/svn"
9
+ require "piston/commands"
27
10
 
28
- PISTON_ROOT = File.dirname(__FILE__)
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
- ROOT = "piston:root"
44
- UUID = "piston:uuid"
45
- REMOTE_REV = "piston:remote-revision"
46
- LOCAL_REV = "piston:local-revision"
47
- LOCKED = "piston:locked"
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,2 @@
1
+ class SubclassResponsibilityError < RuntimeError
2
+ 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
@@ -0,0 +1,4 @@
1
+ dir = File.dirname(__FILE__)
2
+ Dir["#{dir}/**/test_*.rb"].each do |file|
3
+ require file
4
+ end