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.
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