realityforge-braid 0.7.2

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,48 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+
4
+ module Braid
5
+ module Commands
6
+ class Push < Command
7
+ def run(path, options = {})
8
+ mirror = config.get!(path)
9
+
10
+ #mirror.fetch
11
+
12
+ base_revision = git.rev_parse(mirror.remote)
13
+ unless mirror.merged?(base_revision)
14
+ msg "Mirror is not up to date. Stopping."
15
+ return
16
+ end
17
+
18
+ diff = mirror.diff
19
+ if diff.empty?
20
+ msg "No local changes found. Stopping."
21
+ return
22
+ end
23
+
24
+ clone_dir = Dir.tmpdir + "/braid_push.#{$$}"
25
+ Dir.mkdir(clone_dir)
26
+ source_dir = Dir.pwd
27
+ remote_url = git.remote_url(mirror.remote)
28
+ if remote_url == mirror.cached_url
29
+ remote_url = mirror.url
30
+ elsif File.directory?(remote_url)
31
+ remote_url = File.expand_path(remote_url)
32
+ end
33
+ Dir.chdir(clone_dir) do
34
+ msg "Cloning mirror with local changes."
35
+ git.init
36
+ git.fetch(source_dir)
37
+ git.fetch(remote_url, "+refs/heads/#{mirror.branch}")
38
+ git.checkout(base_revision)
39
+ git.apply(diff)
40
+ system("git commit -v")
41
+ msg "Pushing changes to remote."
42
+ git.push(remote_url, "HEAD:#{mirror.branch}")
43
+ end
44
+ FileUtils.rm_r(clone_dir)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ module Braid
2
+ module Commands
3
+ class Remove < Command
4
+ def run(path, options = {})
5
+ mirror = config.get!(path)
6
+
7
+ bail_on_local_changes!
8
+
9
+ with_reset_on_error do
10
+ msg "Removing mirror from '#{mirror.path}'."
11
+
12
+ git.rm_r(mirror.path)
13
+
14
+ config.remove(mirror)
15
+ add_config_file
16
+
17
+ if options[:keep]
18
+ msg "Not removing remote '#{mirror.remote}'" if verbose?
19
+ elsif git.remote_url(mirror.remote)
20
+ msg "Removed remote '#{mirror.path}'" if verbose?
21
+ git.remote_rm mirror.remote
22
+ else
23
+ msg "Remote '#{mirror.remote}' not found, nothing to cleanup" if verbose?
24
+ end
25
+
26
+ git.commit("Remove mirror '#{mirror.path}'")
27
+ msg "Removed mirror." if verbose?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ module Braid
2
+ module Commands
3
+ class Setup < Command
4
+ def run(path = nil)
5
+ path ? setup_one(path) : setup_all
6
+ end
7
+
8
+ protected
9
+
10
+ def setup_all
11
+ msg "Setting up all mirrors."
12
+ config.mirrors.each do |path|
13
+ setup_one(path)
14
+ end
15
+ end
16
+
17
+ def setup_one(path)
18
+ mirror = config.get!(path)
19
+
20
+ if git.remote_url(mirror.remote)
21
+ if force?
22
+ msg "Setup: Mirror '#{mirror.path}' already has a remote. Replacing it (force)" if verbose?
23
+ git.remote_rm(mirror.remote)
24
+ else
25
+ msg "Setup: Mirror '#{mirror.path}' already has a remote. Reusing it." if verbose?
26
+ return
27
+ end
28
+ end
29
+
30
+ msg "Setup: Creating remote for '#{mirror.path}'."
31
+ unless mirror.type == "svn"
32
+ url = use_local_cache? ? git_cache.path(mirror.url) : mirror.url
33
+ git.remote_add(mirror.remote, url, mirror.branch)
34
+ else
35
+ git_svn.init(mirror.remote, mirror.url)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,102 @@
1
+ module Braid
2
+ module Commands
3
+ class Update < Command
4
+ def run(path, options = {})
5
+ bail_on_local_changes!
6
+
7
+ with_reset_on_error do
8
+ path ? update_one(path, options) : update_all(options)
9
+ end
10
+ end
11
+
12
+ protected
13
+
14
+ def update_all(options = {})
15
+ options.reject! { |k, v| %w(revision head).include?(k) }
16
+ msg "Updating all mirrors."
17
+ config.mirrors.each do |path|
18
+ update_one(path, options)
19
+ end
20
+ end
21
+
22
+ def update_one(path, options = {})
23
+ mirror = config.get!(path)
24
+
25
+ revision_message = options["revision"] ? " to #{display_revision(mirror, options["revision"])}" : ""
26
+ msg "Updating mirror '#{mirror.path}'#{revision_message}."
27
+
28
+ # check options for lock modification
29
+ if mirror.locked?
30
+ if options["head"]
31
+ msg "Unlocking mirror '#{mirror.path}'." if verbose?
32
+ mirror.lock = nil
33
+ elsif !options["revision"]
34
+ msg "Mirror '#{mirror.path}' is locked to #{display_revision(mirror, mirror.lock)}. Use --head to force."
35
+ return
36
+ end
37
+ end
38
+
39
+ setup_remote(mirror)
40
+ msg "Fetching new commits for '#{mirror.path}'." if verbose?
41
+ mirror.fetch
42
+
43
+ new_revision = validate_new_revision(mirror, options["revision"])
44
+ target_revision = determine_target_revision(mirror, new_revision)
45
+
46
+ if mirror.merged?(target_revision)
47
+ msg "Mirror '#{mirror.path}' is already up to date."
48
+ return
49
+ end
50
+
51
+ if mirror.squashed?
52
+ diff = mirror.diff
53
+ base_revision = mirror.base_revision
54
+ end
55
+
56
+ mirror.revision = new_revision
57
+ mirror.lock = new_revision if options["revision"]
58
+
59
+ msg "Merging in mirror '#{mirror.path}'." if verbose?
60
+ begin
61
+ if mirror.squashed?
62
+ local_hash = git.rev_parse("HEAD")
63
+ if !diff.empty?
64
+ base_hash = generate_tree_hash(mirror, base_revision)
65
+ else
66
+ base_hash = local_hash
67
+ end
68
+ remote_hash = generate_tree_hash(mirror, target_revision)
69
+ ENV["GITHEAD_#{local_hash}"] = "HEAD"
70
+ ENV["GITHEAD_#{remote_hash}"] = target_revision
71
+ git.merge_recursive(base_hash, local_hash, remote_hash)
72
+ else
73
+ git.merge_subtree(target_revision)
74
+ end
75
+ rescue Operations::MergeError => error
76
+ msg "Caught merge error. Breaking."
77
+ end
78
+
79
+ config.update(mirror)
80
+ add_config_file
81
+
82
+ commit_message = "Update mirror '#{mirror.path}' to #{display_revision(mirror)}"
83
+ if error
84
+ File.open(".git/MERGE_MSG", 'w') { |f| f.puts(commit_message) }
85
+ return
86
+ end
87
+
88
+ git.commit(commit_message)
89
+ msg "Updated mirror to #{display_revision(mirror)}."
90
+ end
91
+
92
+ def generate_tree_hash(mirror, revision)
93
+ git.rm_r(mirror.path)
94
+ git.read_tree_prefix(revision, mirror.path)
95
+ success = git.commit("Temporary commit for mirror '#{mirror.path}'")
96
+ hash = git.rev_parse("HEAD")
97
+ git.reset_hard("HEAD^") if success
98
+ hash
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,106 @@
1
+ require 'yaml'
2
+
3
+ # psych throws such wonderful errors as:
4
+ # `@vendor/rails' is not allowed as an instance variable name (NameError)
5
+ YAML::ENGINE.yamler = 'syck' if RUBY_VERSION >= '1.9.1'
6
+
7
+ require 'yaml/store'
8
+
9
+ module Braid
10
+ class Config
11
+ class PathAlreadyInUse < BraidError
12
+ def message
13
+ "path already in use: #{super}"
14
+ end
15
+ end
16
+ class MirrorDoesNotExist < BraidError
17
+ def message
18
+ "mirror does not exist: #{super}"
19
+ end
20
+ end
21
+
22
+ def initialize(config_file = CONFIG_FILE)
23
+ @db = YAML::Store.new(config_file)
24
+ end
25
+
26
+ def add_from_options(url, options)
27
+ mirror = Mirror.new_from_options(url, options)
28
+
29
+ add(mirror)
30
+ mirror
31
+ end
32
+
33
+ def mirrors
34
+ @db.transaction(true) do
35
+ @db.roots
36
+ end
37
+ end
38
+
39
+ def get(path)
40
+ @db.transaction(true) do
41
+ if attributes = @db[path.to_s.sub(/\/$/, '')]
42
+ Mirror.new(path, attributes)
43
+ end
44
+ end
45
+ end
46
+
47
+ def get!(path)
48
+ mirror = get(path)
49
+ raise MirrorDoesNotExist, path unless mirror
50
+ mirror
51
+ end
52
+
53
+ def add(mirror)
54
+ @db.transaction do
55
+ raise PathAlreadyInUse, mirror.path if @db[mirror.path]
56
+ write_mirror(mirror)
57
+ end
58
+ end
59
+
60
+ def remove(mirror)
61
+ @db.transaction do
62
+ @db.delete(mirror.path)
63
+ end
64
+ end
65
+
66
+ def update(mirror)
67
+ @db.transaction do
68
+ raise MirrorDoesNotExist, mirror.path unless @db[mirror.path]
69
+ @db.delete(mirror.path)
70
+ write_mirror(mirror)
71
+ end
72
+ end
73
+
74
+ def valid?
75
+ @db.transaction(true) do
76
+ !@db.roots.any? do |path|
77
+ @db[path]["url"].nil?
78
+ end
79
+ end
80
+ end
81
+
82
+ def migrate!
83
+ @db.transaction do
84
+ @db.roots.each do |path|
85
+ attributes = @db[path]
86
+ if attributes["local_branch"]
87
+ attributes["url"] = attributes.delete("remote")
88
+ attributes["remote"] = attributes.delete("local_branch")
89
+ attributes["squashed"] = attributes.delete("squash")
90
+ attributes["lock"] = attributes["revision"] # so far this has always been true
91
+ end
92
+ @db[path] = clean_attributes(attributes)
93
+ end
94
+ end
95
+ end
96
+
97
+ private
98
+ def write_mirror(mirror)
99
+ @db[mirror.path] = clean_attributes(mirror.attributes)
100
+ end
101
+
102
+ def clean_attributes(hash)
103
+ hash.reject { |k, v| v.nil? }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,186 @@
1
+ module Braid
2
+ class Mirror
3
+ TYPES = %w(git svn)
4
+ ATTRIBUTES = %w(url remote type branch squashed revision lock)
5
+
6
+ class UnknownType < BraidError
7
+ def message
8
+ "unknown type: #{super}"
9
+ end
10
+ end
11
+ class CannotGuessType < BraidError
12
+ def message
13
+ "cannot guess type: #{super}"
14
+ end
15
+ end
16
+ class PathRequired < BraidError
17
+ def message
18
+ "path is required"
19
+ end
20
+ end
21
+
22
+ include Operations::VersionControl
23
+
24
+ attr_reader :path, :attributes
25
+
26
+ def initialize(path, attributes = {})
27
+ @path = path.sub(/\/$/, '')
28
+ @attributes = attributes
29
+ end
30
+
31
+ def self.new_from_options(url, options = {})
32
+ url = url.sub(/\/$/, '')
33
+
34
+ branch = options["branch"] || "master"
35
+
36
+ if type = options["type"] || extract_type_from_url(url)
37
+ raise UnknownType, type unless TYPES.include?(type)
38
+ else
39
+ raise CannotGuessType, url
40
+ end
41
+
42
+ unless path = options["path"] || extract_path_from_url(url)
43
+ raise PathRequired
44
+ end
45
+
46
+ if options["rails_plugin"]
47
+ path = "vendor/plugins/#{path}"
48
+ end
49
+
50
+ remote = "#{branch}/braid/#{path}".gsub("_", '-') # stupid git svn changes all _ to ., weird
51
+ squashed = !options["full"]
52
+ branch = nil if type == "svn"
53
+
54
+ attributes = {"url" => url, "remote" => remote, "type" => type, "branch" => branch, "squashed" => squashed}
55
+ self.new(path, attributes)
56
+ end
57
+
58
+ def ==(comparison)
59
+ path == comparison.path && attributes == comparison.attributes
60
+ end
61
+
62
+ def type
63
+ # override Object#type
64
+ attributes["type"]
65
+ end
66
+
67
+ def locked?
68
+ !!lock
69
+ end
70
+
71
+ def squashed?
72
+ !!squashed
73
+ end
74
+
75
+ def merged?(commit)
76
+ # tip from spearce in #git:
77
+ # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
78
+ commit = git.rev_parse(commit)
79
+ if squashed?
80
+ !!base_revision && git.merge_base(commit, base_revision) == commit
81
+ else
82
+ git.merge_base(commit, "HEAD") == commit
83
+ end
84
+ end
85
+
86
+ def diff
87
+ remote_hash = git.rev_parse("#{base_revision}:")
88
+ local_hash = git.tree_hash(path)
89
+ remote_hash != local_hash ? git.diff_tree(remote_hash, local_hash) : ""
90
+ end
91
+
92
+ def fetch
93
+ unless type == "svn"
94
+ git_cache.fetch(url) if cached?
95
+ git.fetch(remote)
96
+ else
97
+ git_svn.fetch(remote)
98
+ end
99
+ end
100
+
101
+ def cached?
102
+ git.remote_url(remote) == cached_url
103
+ end
104
+
105
+ def base_revision
106
+ if revision
107
+ unless type == "svn"
108
+ git.rev_parse(revision)
109
+ else
110
+ git_svn.commit_hash(remote, revision)
111
+ end
112
+ else
113
+ inferred_revision
114
+ end
115
+ end
116
+
117
+ def cached_url
118
+ git_cache.path(url)
119
+ end
120
+
121
+ def remote
122
+ if (attributes["remote"] && attributes["remote"] =~ /^braid\//)
123
+ attributes["remote"] = "#{branch}/#{attributes["remote"]}"
124
+ else
125
+ attributes["remote"]
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def method_missing(name, *args)
132
+ if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
133
+ unless $2
134
+ attributes[$1]
135
+ else
136
+ attributes[$1] = args[0]
137
+ end
138
+ else
139
+ raise NameError, "unknown attribute `#{name}'"
140
+ end
141
+ end
142
+
143
+ def inferred_revision
144
+ local_commits = git.rev_list("HEAD", "-- #{path}").split("\n")
145
+ remote_hashes = git.rev_list("--pretty=format:\"%T\"", remote).split("commit ").map do |chunk|
146
+ chunk.split("\n", 2).map { |value| value.strip }
147
+ end
148
+ hash = nil
149
+ local_commits.each do |local_commit|
150
+ local_tree = git.tree_hash(path, local_commit)
151
+ if match = remote_hashes.find { |_, remote_tree| local_tree == remote_tree }
152
+ hash = match[0]
153
+ break
154
+ end
155
+ end
156
+ hash
157
+ end
158
+
159
+ def self.extract_type_from_url(url)
160
+ return nil unless url
161
+ url.sub!(/\/$/, '')
162
+
163
+ # check for git:// and svn:// URLs
164
+ url_scheme = url.split(":").first
165
+ return url_scheme if TYPES.include?(url_scheme)
166
+
167
+ return "svn" if url[-6..-1] == "/trunk"
168
+ return "git" if url[-4..-1] == ".git"
169
+ end
170
+
171
+ def self.extract_path_from_url(url)
172
+ return nil unless url
173
+ name = File.basename(url)
174
+
175
+ if File.extname(name) == ".git"
176
+ # strip .git
177
+ name[0..-5]
178
+ elsif name == "trunk"
179
+ # use parent
180
+ File.basename(File.dirname(url))
181
+ else
182
+ name
183
+ end
184
+ end
185
+ end
186
+ end