realityforge-braid 0.7.2

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