misaka-braid 0.4.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.
@@ -0,0 +1,79 @@
1
+ module Braid
2
+ module Commands
3
+ class Update < Command
4
+ def run(mirror, options = {})
5
+ raise Braid::Git::LocalChangesPresent if invoke(:local_changes?)
6
+
7
+ in_work_branch do
8
+ mirror ? update_one(mirror, options) : update_all
9
+ end
10
+ end
11
+
12
+ protected
13
+ def update_all
14
+ msg "Updating all mirrors."
15
+ config.mirrors.each do |mirror|
16
+ update_one(mirror)
17
+ end
18
+ end
19
+
20
+ def update_one(mirror, options = {})
21
+ params = config.get(mirror)
22
+ unless params
23
+ msg "Mirror '#{mirror}/' does not exist. Skipping."
24
+ return
25
+ end
26
+ local_branch = params["local_branch"]
27
+
28
+ if check_for_lock(params, options)
29
+ msg "Mirror '#{mirror}/' is locked to #{display_revision(params["type"], params["revision"])}. Skipping."
30
+ return
31
+ end
32
+
33
+ # unlock
34
+ if params["revision"] && options["head"]
35
+ msg "Unlocking mirror '#{mirror}/'."
36
+ options["revision"] = nil
37
+ end
38
+
39
+ begin
40
+ fetch_remote(params["type"], local_branch)
41
+
42
+ validate_revision_option(params, options)
43
+ target = determine_target_commit(params, options)
44
+
45
+ check_merge_status(target)
46
+ rescue Braid::Commands::MirrorAlreadyUpToDate
47
+ msg "Mirror '#{mirror}/' is already up to date. Skipping."
48
+ update_revision(mirror, options["revision"])
49
+ return
50
+ end
51
+
52
+ msg "Updating #{params["type"]} mirror '#{mirror}/'."
53
+
54
+ if params["squash"]
55
+ invoke(:git_rm_r, mirror)
56
+ invoke(:git_read_tree, target, mirror)
57
+ else
58
+ invoke(:git_merge_subtree, target)
59
+ end
60
+
61
+ update_revision(mirror, options["revision"])
62
+ add_config_file
63
+
64
+ revision_message = " to " + (options["revision"] ? display_revision(params["type"], options["revision"]) : "HEAD")
65
+ commit_message = "Update mirror '#{mirror}/'#{revision_message}."
66
+ invoke(:git_commit, commit_message)
67
+ end
68
+
69
+ private
70
+ def check_for_lock(params, options)
71
+ params["revision"] && !options["revision"] && !options["head"]
72
+ end
73
+
74
+ def update_revision(mirror, revision)
75
+ config.update(mirror, { "revision" => revision })
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,148 @@
1
+ require 'yaml'
2
+ require 'yaml/store'
3
+
4
+ module Braid
5
+ class Config
6
+ attr_accessor :db
7
+
8
+ def initialize(config_file = nil)
9
+ config_file ||= CONFIG_FILE
10
+ @db = YAML::Store.new(config_file)
11
+ end
12
+
13
+ def add_from_options(remote, options)
14
+ mirror, params = self.class.options_to_mirror(remote, options)
15
+
16
+ raise Braid::Config::RemoteIsRequired unless params["remote"]
17
+ raise Braid::Config::MirrorTypeIsRequired unless params["type"]
18
+ raise Braid::Config::BranchIsRequired unless params["type"] == "svn" || params["branch"]
19
+ raise Braid::Config::MirrorNameIsRequired unless mirror
20
+ raise Braid::Config::UnknownMirrorType unless MIRROR_TYPES.include?(params["type"])
21
+
22
+ params.delete("rails_plugin")
23
+ params.delete("branch") if params["type"] == "svn"
24
+ add(mirror, params)
25
+ [mirror, get(mirror)]
26
+ end
27
+
28
+ def mirrors
29
+ @db.transaction(true) do
30
+ @db.roots
31
+ end
32
+ end
33
+
34
+ def add(mirror, params)
35
+ mirror = remove_trailing_slash(mirror)
36
+ @db.transaction do
37
+ raise Braid::Config::MirrorNameAlreadyInUse if @db[mirror]
38
+ @db[mirror] = params.merge("remote" => remove_trailing_slash(params["remote"]))
39
+ end
40
+ end
41
+
42
+ def get(mirror)
43
+ mirror = remove_trailing_slash(mirror)
44
+ @db.transaction(true) do
45
+ @db[mirror]
46
+ end
47
+ end
48
+
49
+ def get!(mirror)
50
+ params = get(mirror)
51
+ raise Braid::Config::MirrorDoesNotExist unless params
52
+ params
53
+ end
54
+
55
+ def get_by_remote(remote)
56
+ remote = remove_trailing_slash(remote)
57
+ mirror = nil
58
+ @db.transaction(true) do
59
+ mirror = @db.roots.detect { |mirror| @db[mirror]["remote"] == remote }
60
+ end
61
+ [mirror, get(mirror)]
62
+ end
63
+
64
+ def remove(mirror)
65
+ mirror = remove_trailing_slash(mirror)
66
+ @db.transaction do
67
+ @db.delete(mirror)
68
+ end
69
+ end
70
+
71
+ def update(mirror, params)
72
+ mirror = remove_trailing_slash(mirror)
73
+ @db.transaction do
74
+ raise Braid::Config::MirrorDoesNotExist unless @db[mirror]
75
+ tmp = @db[mirror].merge(params)
76
+ @db[mirror] = tmp.merge("remote" => remove_trailing_slash(tmp["remote"]))
77
+ end
78
+ end
79
+
80
+ def replace(mirror, params)
81
+ mirror = remove_trailing_slash(mirror)
82
+ @db.transaction do
83
+ raise Braid::Config::MirrorDoesNotExist unless @db[mirror]
84
+ params["remote"] = remove_trailing_slash(params["remote"]) if params["remote"]
85
+ @db[mirror] = params
86
+ end
87
+ end
88
+
89
+ def self.options_to_mirror(remote, options = {})
90
+ remote = remove_trailing_slash(remote)
91
+ branch = options["branch"] || "master"
92
+
93
+ if options["type"]
94
+ type = options["type"]
95
+ else
96
+ type = extract_type_from_path(remote)
97
+ raise Braid::Config::CannotGuessMirrorType unless type
98
+ end
99
+
100
+ mirror = options["mirror"] || extract_mirror_from_path(remote)
101
+
102
+ if options["rails_plugin"]
103
+ mirror = "vendor/plugins/#{mirror}"
104
+ end
105
+
106
+ squash = !options["full"]
107
+
108
+ [remove_trailing_slash(mirror), { "type" => type, "remote" => remote, "branch" => branch, "squash" => squash }]
109
+ end
110
+
111
+ private
112
+ def remove_trailing_slash(path)
113
+ self.class.send(:remove_trailing_slash, path)
114
+ end
115
+
116
+ def self.remove_trailing_slash(path)
117
+ # bluh.
118
+ path.sub(/\/$/, '')
119
+ end
120
+
121
+ def self.extract_type_from_path(path)
122
+ return nil unless path
123
+ path = remove_trailing_slash(path)
124
+
125
+ # check for git:// and svn:// URLs
126
+ path_scheme = path.split(":").first
127
+ return path_scheme if %w[git svn].include?(path_scheme)
128
+
129
+ return "svn" if path[-6..-1] == "/trunk"
130
+ return "git" if path[-4..-1] == ".git"
131
+ end
132
+
133
+ def self.extract_mirror_from_path(path)
134
+ return nil unless path
135
+ name = File.basename(path)
136
+
137
+ if File.extname(name) == ".git"
138
+ # strip .git
139
+ name[0..-5]
140
+ elsif name == "trunk"
141
+ # use parent
142
+ File.basename(File.dirname(path))
143
+ else
144
+ name
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,34 @@
1
+ module Braid
2
+ class Exception < StandardError
3
+ end
4
+
5
+ module Commands
6
+ class ShellExecutionError < Braid::Exception; end
7
+ class LocalRevisionIsHigherThanRequestedRevision < Braid::Exception; end
8
+ class MirrorAlreadyUpToDate < Braid::Exception; end
9
+ class RequestedRevisionIsHigherThanRemoteRevision < Braid::Exception; end
10
+ end
11
+
12
+ class Config
13
+ class UnknownMirrorType < Braid::Exception; end
14
+ class MirrorNameAlreadyInUse < Braid::Exception; end
15
+ class MirrorDoesNotExist < Braid::Exception; end
16
+ class CannotGuessMirrorType < Braid::Exception; end
17
+
18
+ class RemoteIsRequired < Braid::Exception; end
19
+ class MirrorTypeIsRequired < Braid::Exception; end
20
+ class BranchIsRequired < Braid::Exception; end
21
+ class MirrorNameIsRequired < Braid::Exception; end
22
+ end
23
+
24
+ class Git
25
+ class UnknownRevision < Braid::Exception; end
26
+ class GitVersionTooLow < Braid::Exception; end
27
+ class GitSvnVersionTooLow < Braid::Exception; end
28
+ class LocalChangesPresent < Braid::Exception; end
29
+ end
30
+
31
+ class Svn
32
+ class UnknownRevision < Braid::Exception; end
33
+ end
34
+ end
@@ -0,0 +1,297 @@
1
+ module Braid
2
+ module Operations
3
+ module Git
4
+ def git_commit(message)
5
+ status, out, err = exec("git commit -m #{message.inspect} --no-verify")
6
+
7
+ if status == 0
8
+ true
9
+ elsif out.match(/nothing.* to commit/)
10
+ false
11
+ else
12
+ raise Braid::Commands::ShellExecutionError, err
13
+ end
14
+ end
15
+
16
+ def git_fetch(remote)
17
+ # open4 messes with the pipes of index-pack
18
+ system("git fetch -n #{remote} 2>&1 > /dev/null")
19
+ raise Braid::Commands::ShellExecutionError unless $? == 0
20
+ true
21
+ end
22
+
23
+ def git_checkout(treeish)
24
+ # TODO debug
25
+ msg "Checking out '#{treeish}'."
26
+ exec!("git checkout #{treeish}")
27
+ true
28
+ end
29
+
30
+ # 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
35
+ nil
36
+ end
37
+
38
+ def git_rev_parse(commit)
39
+ status, out, err = exec!("git rev-parse #{commit}")
40
+ out.strip
41
+ end
42
+
43
+ # Implies tracking.
44
+ def git_remote_add(remote, path, branch)
45
+ exec!("git remote add -t #{branch} -m #{branch} #{remote} #{path}")
46
+ true
47
+ end
48
+
49
+ def git_reset_hard(target)
50
+ exec!("git reset --hard #{target}")
51
+ true
52
+ end
53
+
54
+ # Implies no commit.
55
+ def git_merge_ours(commit)
56
+ exec!("git merge -s ours --no-commit #{commit}")
57
+ true
58
+ end
59
+
60
+ # Implies no commit.
61
+ def git_merge_subtree(commit)
62
+ # TODO which options are needed?
63
+ exec!("git merge -s subtree --no-commit --no-ff #{commit}")
64
+ true
65
+ end
66
+
67
+ def git_read_tree(treeish, prefix)
68
+ exec!("git read-tree --prefix=#{prefix}/ -u #{treeish}")
69
+ true
70
+ end
71
+
72
+ def git_rm_r(path)
73
+ exec!("git rm -r #{path}")
74
+ true
75
+ end
76
+
77
+ def local_changes?
78
+ status, out, err = exec("git status")
79
+ out.split("\n").grep(/nothing to commit \(working directory clean\)/).empty?
80
+ end
81
+ end
82
+
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)
98
+ end
99
+
100
+ def git_svn_fetch(remote)
101
+ # open4 messes with the pipes of index-pack
102
+ system("git svn fetch #{remote} 2>&1 > /dev/null")
103
+ true
104
+ end
105
+
106
+ def git_svn_init(remote, path)
107
+ exec!("git svn init -R #{remote} --id=#{remote} #{path}")
108
+ true
109
+ end
110
+ end
111
+
112
+ module Helpers
113
+ [:invoke, :exec, :exec!].each do |method|
114
+ define_method(method) do |*args|
115
+ Braid::Operations.send(method, *args)
116
+ end
117
+ end
118
+
119
+ def extract_version(cmd)
120
+ status, out, err = exec!("#{cmd} --version")
121
+ return out.sub(/^.* version/, "").strip
122
+ end
123
+
124
+ def verify_version(cmd, required)
125
+ required_version = required.split(".")
126
+ actual_version = extract_version(cmd).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
144
+
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}"
149
+ end
150
+
151
+ def clean_svn_revision(revision)
152
+ if revision
153
+ revision.to_i
154
+ else
155
+ nil
156
+ end
157
+ end
158
+
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)
162
+
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
175
+
176
+ true
177
+ end
178
+
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
+
191
+ true
192
+ end
193
+
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"])
203
+ end
204
+ end
205
+
206
+ def display_revision(type, revision)
207
+ type == "svn" ? "r#{revision}" : "'#{revision[0, 7]}'"
208
+ end
209
+ end
210
+
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
227
+ end
228
+
229
+ def get_work_head
230
+ find_git_revision(WORK_BRANCH)
231
+ end
232
+
233
+ def add_config_file
234
+ exec!("git add #{CONFIG_FILE}")
235
+ true
236
+ end
237
+
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
244
+ end
245
+
246
+ true
247
+ end
248
+
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
257
+ end
258
+
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}"\]/ }
262
+ end
263
+ end
264
+
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
282
+ 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
+
292
+ private
293
+ def self.msg(str)
294
+ Braid::Command.msg(str)
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,9 @@
1
+ module Braid #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 4
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/braid.rb ADDED
@@ -0,0 +1,29 @@
1
+ $:.unshift dir = File.dirname(__FILE__)
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ require 'open4'
9
+ require 'yaml'
10
+ require 'yaml/store'
11
+
12
+ module Braid
13
+ MIRROR_TYPES = %w[git svn]
14
+ WORK_BRANCH = "braid/track"
15
+ CONFIG_FILE = ".braids"
16
+ REQUIRED_GIT_VERSION = "1.5.4.5"
17
+ REQUIRED_GIT_SVN_VERSION = "1.5.4.5"
18
+ end
19
+
20
+ require 'braid/version'
21
+ require 'braid/exceptions'
22
+
23
+ require 'braid/config'
24
+ require 'braid/operations'
25
+
26
+ require 'braid/command'
27
+ Dir["#{dir}/braid/commands/*"].each do |file|
28
+ require file
29
+ end
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! :rubygems
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! :rubygems, :newgem
14
+ RubiGen::Scripts::Generate.new.run(ARGV)