honkster-braid 0.6.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,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