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.
- data/{License.txt → LICENSE} +1 -1
- data/README.rdoc +27 -0
- data/Rakefile +17 -4
- data/bin/braid +29 -24
- data/braid.gemspec +7 -7
- data/lib/braid.rb +15 -18
- data/lib/braid/command.rb +94 -44
- data/lib/braid/commands/add.rb +20 -31
- data/lib/braid/commands/diff.rb +4 -3
- data/lib/braid/commands/remove.rb +8 -12
- data/lib/braid/commands/setup.rb +13 -18
- data/lib/braid/commands/update.rb +47 -48
- data/lib/braid/config.rb +54 -101
- data/lib/braid/mirror.rb +181 -0
- data/lib/braid/operations.rb +229 -204
- data/{spec/braid_spec.rb → test/braid_test.rb} +1 -1
- data/test/config_test.rb +62 -0
- data/test/fixtures/shiny/README +3 -0
- data/test/fixtures/skit1.1/layouts/layout.liquid +219 -0
- data/test/fixtures/skit1.2/layouts/layout.liquid +221 -0
- data/test/fixtures/skit1/layouts/layout.liquid +219 -0
- data/test/fixtures/skit1/preview.png +0 -0
- data/test/integration/adding_test.rb +80 -0
- data/test/integration/updating_test.rb +87 -0
- data/test/integration_helper.rb +69 -0
- data/test/mirror_test.rb +118 -0
- data/test/operations_test.rb +74 -0
- data/test/test_helper.rb +15 -0
- metadata +30 -33
- data/History.txt +0 -4
- data/Manifest.txt +0 -32
- data/README.txt +0 -53
- data/config/hoe.rb +0 -68
- data/config/requirements.rb +0 -17
- data/lib/braid/exceptions.rb +0 -33
- data/lib/braid/version.rb +0 -9
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/setup.rb +0 -1585
- data/spec/config_spec.rb +0 -117
- data/spec/operations_spec.rb +0 -48
- data/spec/spec.opts +0 -3
- data/spec/spec_helper.rb +0 -11
- data/tasks/deployment.rake +0 -27
- data/tasks/environment.rake +0 -7
- data/tasks/rspec.rake +0 -32
- data/tasks/website.rake +0 -9
data/lib/braid/operations.rb
CHANGED
@@ -1,297 +1,322 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'open4'
|
4
|
+
|
1
5
|
module Braid
|
2
6
|
module Operations
|
3
|
-
|
4
|
-
def
|
5
|
-
|
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(
|
123
|
+
elsif out.match(/nothing.* to commit/)
|
10
124
|
false
|
11
125
|
else
|
12
|
-
raise
|
126
|
+
raise ShellExecutionError, err
|
13
127
|
end
|
14
128
|
end
|
15
129
|
|
16
|
-
def
|
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
|
133
|
+
raise ShellExecutionError, "could not fetch" unless $? == 0
|
20
134
|
true
|
21
135
|
end
|
22
136
|
|
23
|
-
def
|
137
|
+
def checkout(treeish)
|
24
138
|
# TODO debug
|
25
139
|
msg "Checking out '#{treeish}'."
|
26
|
-
|
140
|
+
invoke(:checkout, treeish)
|
27
141
|
true
|
28
142
|
end
|
29
143
|
|
30
144
|
# Returns the base commit or nil.
|
31
|
-
def
|
32
|
-
|
33
|
-
|
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
|
39
|
-
|
40
|
-
|
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
|
45
|
-
|
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
|
-
|
50
|
-
|
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
|
56
|
-
|
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
|
181
|
+
def merge_subtree(opt)
|
62
182
|
# TODO which options are needed?
|
63
|
-
|
183
|
+
invoke(:merge, "-s subtree --no-commit --no-ff", opt)
|
64
184
|
true
|
65
185
|
end
|
66
186
|
|
67
|
-
def
|
68
|
-
|
187
|
+
def read_tree(treeish, prefix)
|
188
|
+
invoke(:read_tree, "--prefix=#{prefix}/ -u", treeish)
|
69
189
|
true
|
70
190
|
end
|
71
191
|
|
72
|
-
def
|
73
|
-
|
192
|
+
def rm_r(path)
|
193
|
+
invoke(:rm, "-r", path)
|
74
194
|
true
|
75
195
|
end
|
76
196
|
|
77
|
-
def
|
78
|
-
|
79
|
-
out.split
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
101
|
-
|
102
|
-
|
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
|
107
|
-
|
108
|
-
true
|
214
|
+
def ensure_clean!
|
215
|
+
status_clean? || raise(LocalChangesPresent)
|
109
216
|
end
|
110
|
-
end
|
111
217
|
|
112
|
-
|
113
|
-
|
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
|
120
|
-
status, out, err = exec!("git
|
121
|
-
|
222
|
+
def branch
|
223
|
+
status, out, err = exec!("git branch | grep '*'")
|
224
|
+
out[2..-1]
|
122
225
|
end
|
123
226
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
233
|
+
err = stderr.read
|
234
|
+
end.exitstatus
|
235
|
+
raise ShellExecutionError, err unless status == 0
|
236
|
+
true
|
149
237
|
end
|
150
238
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
else
|
155
|
-
nil
|
239
|
+
private
|
240
|
+
def command(name)
|
241
|
+
"#{self.class.command} #{name.to_s.gsub('_', '-')}"
|
156
242
|
end
|
157
|
-
|
243
|
+
end
|
158
244
|
|
159
|
-
|
160
|
-
|
161
|
-
old_revision = clean_svn_revision(old_revision)
|
245
|
+
class GitSvn < Proxy
|
246
|
+
def self.command; "git svn"; end
|
162
247
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
180
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
212
|
-
def
|
213
|
-
|
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
|
230
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
|
239
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
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
|
260
|
-
|
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
|
-
|
266
|
-
|
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
|
-
|
293
|
-
|
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
|
+
|