evilchelu-braid 0.4.0 → 0.4.10

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 (47) hide show
  1. data/{License.txt → LICENSE} +1 -1
  2. data/README.rdoc +27 -0
  3. data/Rakefile +17 -4
  4. data/bin/braid +29 -24
  5. data/braid.gemspec +7 -7
  6. data/lib/braid.rb +15 -18
  7. data/lib/braid/command.rb +94 -44
  8. data/lib/braid/commands/add.rb +20 -31
  9. data/lib/braid/commands/diff.rb +4 -3
  10. data/lib/braid/commands/remove.rb +8 -12
  11. data/lib/braid/commands/setup.rb +13 -18
  12. data/lib/braid/commands/update.rb +47 -48
  13. data/lib/braid/config.rb +54 -101
  14. data/lib/braid/mirror.rb +181 -0
  15. data/lib/braid/operations.rb +229 -204
  16. data/{spec/braid_spec.rb → test/braid_test.rb} +1 -1
  17. data/test/config_test.rb +62 -0
  18. data/test/fixtures/shiny/README +3 -0
  19. data/test/fixtures/skit1.1/layouts/layout.liquid +219 -0
  20. data/test/fixtures/skit1.2/layouts/layout.liquid +221 -0
  21. data/test/fixtures/skit1/layouts/layout.liquid +219 -0
  22. data/test/fixtures/skit1/preview.png +0 -0
  23. data/test/integration/adding_test.rb +80 -0
  24. data/test/integration/updating_test.rb +87 -0
  25. data/test/integration_helper.rb +69 -0
  26. data/test/mirror_test.rb +118 -0
  27. data/test/operations_test.rb +74 -0
  28. data/test/test_helper.rb +15 -0
  29. metadata +30 -33
  30. data/History.txt +0 -4
  31. data/Manifest.txt +0 -32
  32. data/README.txt +0 -53
  33. data/config/hoe.rb +0 -68
  34. data/config/requirements.rb +0 -17
  35. data/lib/braid/exceptions.rb +0 -33
  36. data/lib/braid/version.rb +0 -9
  37. data/script/destroy +0 -14
  38. data/script/generate +0 -14
  39. data/setup.rb +0 -1585
  40. data/spec/config_spec.rb +0 -117
  41. data/spec/operations_spec.rb +0 -48
  42. data/spec/spec.opts +0 -3
  43. data/spec/spec_helper.rb +0 -11
  44. data/tasks/deployment.rake +0 -27
  45. data/tasks/environment.rake +0 -7
  46. data/tasks/rspec.rake +0 -32
  47. data/tasks/website.rake +0 -9
@@ -1,297 +1,322 @@
1
+ require 'singleton'
2
+ require 'rubygems'
3
+ require 'open4'
4
+
1
5
  module Braid
2
6
  module Operations
3
- module Git
4
- def git_commit(message)
5
- status, out, err = exec("git commit -m #{message.inspect} --no-verify")
7
+ class ShellExecutionError < BraidError
8
+ def initialize(err = nil)
9
+ @err = err
10
+ end
11
+
12
+ def message
13
+ @err.to_s.split("\n").first
14
+ end
15
+ end
16
+ class VersionTooLow < BraidError
17
+ def initialize(command, version)
18
+ @command = command
19
+ @version = version.to_s.split("\n").first
20
+ end
21
+
22
+ def message
23
+ "#{@command} version too low: #{@version}"
24
+ end
25
+ end
26
+ class UnknownRevision < BraidError
27
+ def message
28
+ "unknown revision: #{super}"
29
+ end
30
+ end
31
+ class LocalChangesPresent < BraidError
32
+ def message
33
+ "local changes are present"
34
+ end
35
+ end
36
+
37
+ # The command proxy is meant to encapsulate commands such as git, git-svn and svn, that work with subcommands.
38
+ class Proxy
39
+ include Singleton
40
+
41
+ def self.command; name.split('::').last.downcase; end # hax!
42
+
43
+ def version
44
+ status, out, err = exec!("#{self.class.command} --version")
45
+ out.sub(/^.* version/, "").strip
46
+ end
47
+
48
+ def require_version(required)
49
+ required = required.split(".")
50
+ actual = version.split(".")
51
+
52
+ actual.each_with_index do |actual_piece, idx|
53
+ required_piece = required[idx]
54
+
55
+ return true unless required_piece
56
+
57
+ case (actual_piece <=> required_piece)
58
+ when -1
59
+ return false
60
+ when 1
61
+ return true
62
+ when 0
63
+ next
64
+ end
65
+ end
66
+
67
+ return actual.length >= required.length
68
+ end
69
+
70
+ def require_version!(required)
71
+ require_version(required) || raise(VersionTooLow.new(self.class.command, version))
72
+ end
73
+
74
+ private
75
+ def command(name)
76
+ # stub
77
+ name
78
+ end
79
+
80
+ def invoke(arg, *args)
81
+ exec!("#{command(arg)} #{args.join(' ')}".strip)[1].strip # return stdout
82
+ end
83
+
84
+ def method_missing(name, *args)
85
+ invoke(name, *args)
86
+ end
87
+
88
+ def exec(cmd)
89
+ cmd.strip!
90
+
91
+ previous_lang = ENV['LANG']
92
+ ENV['LANG'] = 'C'
93
+
94
+ out, err = nil
95
+ status = Open4.popen4(cmd) do |pid, stdin, stdout, stderr|
96
+ out = stdout.read
97
+ err = stderr.read
98
+ end.exitstatus
99
+ [status, out, err]
100
+
101
+ ensure
102
+ ENV['LANG'] = previous_lang
103
+ end
104
+
105
+ def exec!(cmd)
106
+ status, out, err = exec(cmd)
107
+ raise ShellExecutionError, err unless status == 0
108
+ [status, out, err]
109
+ end
110
+
111
+ def msg(str)
112
+ puts str
113
+ end
114
+
115
+ end
116
+
117
+ class Git < Proxy
118
+ def commit(message, *args)
119
+ status, out, err = exec("git commit -m #{message.inspect} --no-verify #{args.join(' ')}")
6
120
 
7
121
  if status == 0
8
122
  true
9
- elsif out.match("nothing to commit")
123
+ elsif out.match(/nothing.* to commit/)
10
124
  false
11
125
  else
12
- raise Braid::Commands::ShellExecutionError, err
126
+ raise ShellExecutionError, err
13
127
  end
14
128
  end
15
129
 
16
- def git_fetch(remote)
130
+ def fetch(remote)
17
131
  # open4 messes with the pipes of index-pack
18
132
  system("git fetch -n #{remote} &> /dev/null")
19
- raise Braid::Commands::ShellExecutionError unless $? == 0
133
+ raise ShellExecutionError, "could not fetch" unless $? == 0
20
134
  true
21
135
  end
22
136
 
23
- def git_checkout(treeish)
137
+ def checkout(treeish)
24
138
  # TODO debug
25
139
  msg "Checking out '#{treeish}'."
26
- exec!("git checkout #{treeish}")
140
+ invoke(:checkout, treeish)
27
141
  true
28
142
  end
29
143
 
30
144
  # Returns the base commit or nil.
31
- def git_merge_base(target, source)
32
- status, out, err = exec!("git merge-base #{target} #{source}")
33
- out.strip
34
- rescue Braid::Commands::ShellExecutionError
145
+ def merge_base(target, source)
146
+ invoke(:merge_base, target, source)
147
+ rescue ShellExecutionError
35
148
  nil
36
149
  end
37
150
 
38
- def git_rev_parse(commit)
39
- status, out, err = exec!("git rev-parse #{commit}")
40
- out.strip
151
+ def rev_parse(opt)
152
+ invoke(:rev_parse, opt)
153
+ rescue ShellExecutionError
154
+ raise UnknownRevision, opt
41
155
  end
42
156
 
43
157
  # Implies tracking.
44
- def git_remote_add(remote, path, branch)
45
- exec!("git remote add -t #{branch} -m #{branch} #{remote} #{path}")
158
+ def remote_add(remote, path, branch)
159
+ invoke(:remote, "add", "-t #{branch} -m #{branch}", remote, path)
46
160
  true
47
161
  end
48
162
 
49
- def git_reset_hard(target)
50
- exec!("git reset --hard #{target}")
163
+ # Checks git and svn remotes.
164
+ def remote_exists?(remote)
165
+ # TODO clean up and maybe return more information
166
+ !!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{Regexp.escape(remote)}"\]/ }
167
+ end
168
+
169
+ def reset_hard(target)
170
+ invoke(:reset, "--hard", target)
51
171
  true
52
172
  end
53
173
 
54
174
  # Implies no commit.
55
- def git_merge_ours(commit)
56
- exec!("git merge -s ours --no-commit #{commit}")
175
+ def merge_ours(opt)
176
+ invoke(:merge, "-s ours --no-commit", opt)
57
177
  true
58
178
  end
59
179
 
60
180
  # Implies no commit.
61
- def git_merge_subtree(commit)
181
+ def merge_subtree(opt)
62
182
  # TODO which options are needed?
63
- exec!("git merge -s subtree --no-commit --no-ff #{commit}")
183
+ invoke(:merge, "-s subtree --no-commit --no-ff", opt)
64
184
  true
65
185
  end
66
186
 
67
- def git_read_tree(treeish, prefix)
68
- exec!("git read-tree --prefix=#{prefix}/ -u #{treeish}")
187
+ def read_tree(treeish, prefix)
188
+ invoke(:read_tree, "--prefix=#{prefix}/ -u", treeish)
69
189
  true
70
190
  end
71
191
 
72
- def git_rm_r(path)
73
- exec!("git rm -r #{path}")
192
+ def rm_r(path)
193
+ invoke(:rm, "-r", path)
74
194
  true
75
195
  end
76
196
 
77
- def local_changes?
78
- status, out, err = exec("git status")
79
- out.split("\n").grep(/nothing to commit \(working directory clean\)/).empty?
197
+ def tree_hash(path, treeish = "HEAD")
198
+ out = invoke(:ls_tree, treeish, "-d", path)
199
+ out.split[2]
80
200
  end
81
- end
82
201
 
83
- module Svn
84
- # FIXME move
85
- def svn_remote_head_revision(path)
86
- # not using svn info because it's retarded and doesn't show the actual last changed rev for the url
87
- # git svn has no clue on how to get the actual HEAD revision number on it's own
88
- status, out, err = exec!("svn log -q --limit 1 #{path}")
89
- out.split(/\n/).find { |x| x.match /^r\d+/ }.split(" | ")[0][1..-1].to_i
90
- end
91
-
92
- # FIXME move
93
- def svn_git_commit_hash(remote, revision)
94
- status, out, err = exec!("git svn log --show-commit --oneline -r #{revision} #{remote}")
95
- part = out.split(" | ")[1]
96
- raise Braid::Svn::UnknownRevision, "unknown revision: #{revision}" unless part
97
- invoke(:git_rev_parse, part)
202
+ def diff_tree(src_tree, dst_tree, prefix = nil)
203
+ cmd = "git diff-tree -p --binary #{src_tree} #{dst_tree}"
204
+ cmd << " --src-prefix=a/#{prefix}/ --dst-prefix=b/#{prefix}/" if prefix
205
+ status, out, err = exec!(cmd)
206
+ out
98
207
  end
99
208
 
100
- def git_svn_fetch(remote)
101
- # open4 messes with the pipes of index-pack
102
- system("git svn fetch #{remote} &> /dev/null")
103
- true
209
+ def status_clean?
210
+ status, out, err = exec("git status")
211
+ !out.split("\n").grep(/nothing to commit/).empty?
104
212
  end
105
213
 
106
- def git_svn_init(remote, path)
107
- exec!("git svn init -R #{remote} --id=#{remote} #{path}")
108
- true
214
+ def ensure_clean!
215
+ status_clean? || raise(LocalChangesPresent)
109
216
  end
110
- end
111
217
 
112
- module Helpers
113
- [:invoke, :exec, :exec!].each do |method|
114
- define_method(method) do |*args|
115
- Braid::Operations.send(method, *args)
116
- end
218
+ def head
219
+ rev_parse("HEAD")
117
220
  end
118
221
 
119
- def extract_git_version
120
- status, out, err = exec!("git --version")
121
- return out.sub(/^git version/, "").strip
222
+ def branch
223
+ status, out, err = exec!("git branch | grep '*'")
224
+ out[2..-1]
122
225
  end
123
226
 
124
- def verify_git_version(required)
125
- required_version = required.split(".")
126
- actual_version = extract_git_version.split(".")
127
- actual_version.each_with_index do |actual_piece, idx|
128
- required_piece = required_version[idx]
129
-
130
- return true unless required_piece
131
-
132
- case (actual_piece <=> required_piece)
133
- when -1
134
- return false
135
- when 1
136
- return true
137
- when 0
138
- next
139
- end
140
- end
141
-
142
- return actual_version.length >= required_version.length
143
- end
227
+ def apply(diff, *args)
228
+ err = nil
229
+ status = Open4.popen4("git apply --index --whitespace=nowarn #{args.join(' ')} -") do |pid, stdin, stdout, stderr|
230
+ stdin.puts(diff)
231
+ stdin.close
144
232
 
145
- def find_git_revision(commit)
146
- invoke(:git_rev_parse, commit)
147
- rescue Braid::Commands::ShellExecutionError
148
- raise Braid::Git::UnknownRevision, "unknown revision: #{commit}"
233
+ err = stderr.read
234
+ end.exitstatus
235
+ raise ShellExecutionError, err unless status == 0
236
+ true
149
237
  end
150
238
 
151
- def clean_svn_revision(revision)
152
- if revision
153
- revision.to_i
154
- else
155
- nil
239
+ private
240
+ def command(name)
241
+ "#{self.class.command} #{name.to_s.gsub('_', '-')}"
156
242
  end
157
- end
243
+ end
158
244
 
159
- def validate_svn_revision(old_revision, new_revision, path)
160
- return unless new_revision = clean_svn_revision(new_revision)
161
- old_revision = clean_svn_revision(old_revision)
245
+ class GitSvn < Proxy
246
+ def self.command; "git svn"; end
162
247
 
163
- # TODO add checks for unlocked mirrors
164
- if old_revision
165
- if new_revision < old_revision
166
- raise Braid::Commands::LocalRevisionIsHigherThanRequestedRevision
167
- elsif new_revision == old_revision
168
- raise Braid::Commands::MirrorAlreadyUpToDate
169
- end
170
- end
171
-
172
- if path && invoke(:svn_remote_head_revision, path) < new_revision
173
- raise Braid::Commands::RequestedRevisionIsHigherThanRemoteRevision
174
- end
248
+ def commit_hash(remote, revision)
249
+ out = invoke(:log, "--show-commit --oneline", "-r #{revision}", remote)
250
+ part = out.to_s.split(" | ")[1]
251
+ raise UnknownRevision, "r#{revision}" unless part
252
+ Git.instance.rev_parse(part) # FIXME ugly ugly ugly
253
+ end
175
254
 
255
+ def fetch(remote)
256
+ # open4 messes with the pipes of index-pack
257
+ system("git svn fetch #{remote} &> /dev/null")
258
+ raise ShellExecutionError, "could not fetch" unless $? == 0
176
259
  true
177
260
  end
178
261
 
179
- # Make sure the revision is valid, then clean it.
180
- def validate_revision_option(params, options)
181
- if options["revision"]
182
- case params["type"]
183
- when "git"
184
- options["revision"] = find_git_revision(options["revision"])
185
- when "svn"
186
- validate_svn_revision(params["revision"], options["revision"], params["remote"])
187
- options["revision"] = clean_svn_revision(options["revision"])
188
- end
189
- end
190
-
262
+ def init(remote, path)
263
+ invoke(:init, "-R", remote, "--id=#{remote}", path)
191
264
  true
192
265
  end
193
266
 
194
- def determine_target_commit(params, options)
195
- if options["revision"]
196
- if params["type"] == "svn"
197
- invoke(:svn_git_commit_hash, params["local_branch"], options["revision"])
198
- else
199
- invoke(:git_rev_parse, options["revision"])
200
- end
201
- else
202
- invoke(:git_rev_parse, params["local_branch"])
267
+ private
268
+ def command(name)
269
+ "#{self.class.command} #{name}"
203
270
  end
204
- end
205
-
206
- def display_revision(type, revision)
207
- type == "svn" ? "r#{revision}" : "'#{revision[0, 7]}'"
208
- end
209
271
  end
210
272
 
211
- module Mirror
212
- def get_current_branch
213
- status, out, err = exec!("git branch | grep '*'")
214
- out[2..-1]
215
- end
216
-
217
- def create_work_branch
218
- # check if branch exists
219
- status, out, err = exec("git branch | grep '#{WORK_BRANCH}'")
220
- if status != 0
221
- # then create it
222
- msg "Creating work branch '#{WORK_BRANCH}'."
223
- exec!("git branch #{WORK_BRANCH}")
224
- end
225
-
226
- true
273
+ class Svn < Proxy
274
+ def clean_revision(revision)
275
+ revision.to_i if revision
227
276
  end
228
277
 
229
- def get_work_head
230
- find_git_revision(WORK_BRANCH)
278
+ def head_revision(path)
279
+ # not using svn info because it's retarded and doesn't show the actual last changed rev for the url
280
+ # git svn has no clue on how to get the actual HEAD revision number on it's own
281
+ status, out, err = exec!("svn log -q --limit 1 #{path}")
282
+ out.split(/\n/).find { |x| x.match /^r\d+/ }.split(" | ")[0][1..-1].to_i
231
283
  end
284
+ end
232
285
 
233
- def add_config_file
234
- exec!("git add #{CONFIG_FILE}")
235
- true
236
- end
286
+ class GitCache < Proxy
287
+ def init_or_fetch(url, dir)
288
+ if File.exists? dir
289
+ msg "Updating local cache of '#{url}' into '#{dir}'."
290
+ FileUtils.cd(dir) do |d|
291
+ status, out, err = exec!("git fetch")
292
+ end
293
+ else
294
+ FileUtils.mkdir_p(Braid::LOCAL_CACHE_DIR)
237
295
 
238
- def check_merge_status(commit)
239
- commit = find_git_revision(commit)
240
- # tip from spearce in #git:
241
- # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
242
- if invoke(:git_merge_base, commit, "HEAD") == commit
243
- raise Braid::Commands::MirrorAlreadyUpToDate
296
+ msg "Caching '#{url}' into '#{dir}'."
297
+ status, out, err = exec!("git clone --mirror #{url} #{dir}")
244
298
  end
245
-
246
- true
247
299
  end
300
+ end
248
301
 
249
- def fetch_remote(type, remote)
250
- msg "Fetching data from '#{remote}'."
251
- case type
252
- when "git"
253
- invoke(:git_fetch, remote)
254
- when "svn"
255
- invoke(:git_svn_fetch, remote)
256
- end
302
+ module VersionControl
303
+ def git
304
+ Git.instance
257
305
  end
258
306
 
259
- def find_remote(remote)
260
- # TODO clean up and maybe return more information
261
- !!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{remote}"\]/ }
307
+ def git_svn
308
+ GitSvn.instance
262
309
  end
263
- end
264
310
 
265
- extend Git
266
- extend Svn
267
-
268
- def self.invoke(*args)
269
- send(*args)
270
- end
271
-
272
- def self.exec(cmd)
273
- #puts cmd
274
- out = ""
275
- err = ""
276
- cmd.strip!
277
-
278
- ENV['LANG'] = 'C' unless ENV['LANG'] == 'C'
279
- status = Open4::popen4(cmd) do |pid, stdin, stdout, stderr|
280
- out = stdout.read.strip
281
- err = stderr.read.strip
311
+ def svn
312
+ Svn.instance
282
313
  end
283
- [status.exitstatus, out, err]
284
- end
285
-
286
- def self.exec!(cmd)
287
- status, out, err = exec(cmd)
288
- raise Braid::Commands::ShellExecutionError, err unless status == 0
289
- return status, out, err
290
- end
291
314
 
292
- private
293
- def self.msg(str)
294
- Braid::Command.msg(str)
315
+ def git_cache
316
+ GitCache.instance
295
317
  end
318
+ end
296
319
  end
297
320
  end
321
+
322
+