honkster-braid 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ module Braid
2
+ module Commands
3
+ class Diff < Command
4
+ def run(path)
5
+ mirror = config.get!(path)
6
+ setup_remote(mirror)
7
+
8
+ diff = mirror.diff
9
+ puts diff unless diff.empty?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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)
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,34 @@
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
+ def setup_all
10
+ msg "Setting up all mirrors."
11
+ config.mirrors.each do |path|
12
+ setup_one(path)
13
+ end
14
+ end
15
+
16
+ def setup_one(path)
17
+ mirror = config.get!(path)
18
+
19
+ if git.remote_url(mirror.remote)
20
+ msg "Setup: Mirror '#{mirror.path}' already has a remote. Reusing it." if verbose?
21
+ return
22
+ end
23
+
24
+ msg "Setup: Creating remote for '#{mirror.path}'."
25
+ unless mirror.type == "svn"
26
+ url = use_local_cache? ? git_cache.path(mirror.url) : mirror.url
27
+ git.remote_add(mirror.remote, url, mirror.branch)
28
+ else
29
+ git_svn.init(mirror.remote, mirror.url)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,101 @@
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
+ def update_all(options = {})
14
+ options.reject! { |k,v| %w(revision head).include?(k) }
15
+ msg "Updating all mirrors."
16
+ config.mirrors.each do |path|
17
+ update_one(path, options)
18
+ end
19
+ end
20
+
21
+ def update_one(path, options = {})
22
+ mirror = config.get!(path)
23
+
24
+ revision_message = options["revision"] ? " to #{display_revision(mirror, options["revision"])}" : ""
25
+ msg "Updating mirror '#{mirror.path}'#{revision_message}."
26
+
27
+ # check options for lock modification
28
+ if mirror.locked?
29
+ if options["head"]
30
+ msg "Unlocking mirror '#{mirror.path}'." if verbose?
31
+ mirror.lock = nil
32
+ elsif !options["revision"]
33
+ msg "Mirror '#{mirror.path}' is locked to #{display_revision(mirror, mirror.lock)}. Use --head to force."
34
+ return
35
+ end
36
+ end
37
+
38
+ setup_remote(mirror)
39
+ msg "Fetching new commits for '#{mirror.path}'." if verbose?
40
+ mirror.fetch
41
+
42
+ new_revision = validate_new_revision(mirror, options["revision"])
43
+ target_revision = determine_target_revision(mirror, new_revision)
44
+
45
+ if mirror.merged?(target_revision)
46
+ msg "Mirror '#{mirror.path}' is already up to date."
47
+ return
48
+ end
49
+
50
+ if mirror.squashed?
51
+ diff = mirror.diff
52
+ base_revision = mirror.base_revision
53
+ end
54
+
55
+ mirror.revision = new_revision
56
+ mirror.lock = new_revision if options["revision"]
57
+
58
+ msg "Merging in mirror '#{mirror.path}'." if verbose?
59
+ begin
60
+ if mirror.squashed?
61
+ local_hash = git.rev_parse("HEAD")
62
+ if !diff.empty?
63
+ base_hash = generate_tree_hash(mirror, base_revision)
64
+ else
65
+ base_hash = local_hash
66
+ end
67
+ remote_hash = generate_tree_hash(mirror, target_revision)
68
+ ENV["GITHEAD_#{local_hash}"] = "HEAD"
69
+ ENV["GITHEAD_#{remote_hash}"] = target_revision
70
+ git.merge_recursive(base_hash, local_hash, remote_hash)
71
+ else
72
+ git.merge_subtree(target_revision)
73
+ end
74
+ rescue Operations::MergeError => error
75
+ msg "Caught merge error. Breaking."
76
+ end
77
+
78
+ config.update(mirror)
79
+ add_config_file
80
+
81
+ commit_message = "Update mirror '#{mirror.path}' to #{display_revision(mirror)}"
82
+ if error
83
+ File.open(".git/MERGE_MSG", 'w') { |f| f.puts(commit_message) }
84
+ return
85
+ end
86
+
87
+ git.commit(commit_message)
88
+ msg "Updated mirror to #{display_revision(mirror)}."
89
+ end
90
+
91
+ def generate_tree_hash(mirror, revision)
92
+ git.rm_r(mirror.path)
93
+ git.read_tree_prefix(revision, mirror.path)
94
+ success = git.commit("Temporary commit for mirror '#{mirror.path}'")
95
+ hash = git.rev_parse("HEAD")
96
+ git.reset_hard("HEAD^") if success
97
+ hash
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,101 @@
1
+ require 'yaml'
2
+ require 'yaml/store'
3
+
4
+ module Braid
5
+ class Config
6
+ class PathAlreadyInUse < BraidError
7
+ def message
8
+ "path already in use: #{super}"
9
+ end
10
+ end
11
+ class MirrorDoesNotExist < BraidError
12
+ def message
13
+ "mirror does not exist: #{super}"
14
+ end
15
+ end
16
+
17
+ def initialize(config_file = CONFIG_FILE)
18
+ @db = YAML::Store.new(config_file)
19
+ end
20
+
21
+ def add_from_options(url, options)
22
+ mirror = Mirror.new_from_options(url, options)
23
+
24
+ add(mirror)
25
+ mirror
26
+ end
27
+
28
+ def mirrors
29
+ @db.transaction(true) do
30
+ @db.roots
31
+ end
32
+ end
33
+
34
+ def get(path)
35
+ @db.transaction(true) do
36
+ if attributes = @db[path.to_s.sub(/\/$/, '')]
37
+ Mirror.new(path, attributes)
38
+ end
39
+ end
40
+ end
41
+
42
+ def get!(path)
43
+ mirror = get(path)
44
+ raise MirrorDoesNotExist, path unless mirror
45
+ mirror
46
+ end
47
+
48
+ def add(mirror)
49
+ @db.transaction do
50
+ raise PathAlreadyInUse, mirror.path if @db[mirror.path]
51
+ write_mirror(mirror)
52
+ end
53
+ end
54
+
55
+ def remove(mirror)
56
+ @db.transaction do
57
+ @db.delete(mirror.path)
58
+ end
59
+ end
60
+
61
+ def update(mirror)
62
+ @db.transaction do
63
+ raise MirrorDoesNotExist, mirror.path unless @db[mirror.path]
64
+ @db.delete(mirror.path)
65
+ write_mirror(mirror)
66
+ end
67
+ end
68
+
69
+ def valid?
70
+ @db.transaction(true) do
71
+ !@db.roots.any? do |path|
72
+ @db[path]["url"].nil?
73
+ end
74
+ end
75
+ end
76
+
77
+ def migrate!
78
+ @db.transaction do
79
+ @db.roots.each do |path|
80
+ attributes = @db[path]
81
+ if attributes["local_branch"]
82
+ attributes["url"] = attributes.delete("remote")
83
+ attributes["remote"] = attributes.delete("local_branch")
84
+ attributes["squashed"] = attributes.delete("squash")
85
+ attributes["lock"] = attributes["revision"] # so far this has always been true
86
+ end
87
+ @db[path] = clean_attributes(attributes)
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+ def write_mirror(mirror)
94
+ @db[mirror.path] = clean_attributes(mirror.attributes)
95
+ end
96
+
97
+ def clean_attributes(hash)
98
+ hash.reject { |k,v| v.nil? }
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,177 @@
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 = "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
+ private
122
+ def method_missing(name, *args)
123
+ if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
124
+ unless $2
125
+ attributes[$1]
126
+ else
127
+ attributes[$1] = args[0]
128
+ end
129
+ else
130
+ raise NameError, "unknown attribute `#{name}'"
131
+ end
132
+ end
133
+
134
+ def inferred_revision
135
+ local_commits = git.rev_list("HEAD", "-- #{path}").split("\n")
136
+ remote_hashes = git.rev_list("--pretty=format:\"%T\"", remote).split("commit ").map do |chunk|
137
+ chunk.split("\n", 2).map { |value| value.strip }
138
+ end
139
+ hash = nil
140
+ local_commits.each do |local_commit|
141
+ local_tree = git.tree_hash(path, local_commit)
142
+ if match = remote_hashes.find { |_, remote_tree| local_tree == remote_tree }
143
+ hash = match[0]
144
+ break
145
+ end
146
+ end
147
+ hash
148
+ end
149
+
150
+ def self.extract_type_from_url(url)
151
+ return nil unless url
152
+ url.sub!(/\/$/, '')
153
+
154
+ # check for git:// and svn:// URLs
155
+ url_scheme = url.split(":").first
156
+ return url_scheme if TYPES.include?(url_scheme)
157
+
158
+ return "svn" if url[-6..-1] == "/trunk"
159
+ return "git" if url[-4..-1] == ".git"
160
+ end
161
+
162
+ def self.extract_path_from_url(url)
163
+ return nil unless url
164
+ name = File.basename(url)
165
+
166
+ if File.extname(name) == ".git"
167
+ # strip .git
168
+ name[0..-5]
169
+ elsif name == "trunk"
170
+ # use parent
171
+ File.basename(File.dirname(url))
172
+ else
173
+ name
174
+ end
175
+ end
176
+ end
177
+ end