evilchelu-braid 0.4.0 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
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
+