dreamcat4-braid 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
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 mirror.type == "git-clone"
20
+ return
21
+ end
22
+
23
+ if git.remote_url(mirror.remote)
24
+ msg "Setup: Mirror '#{mirror.path}' already has a remote. Reusing it." if verbose?
25
+ return
26
+ end
27
+
28
+ msg "Setup: Creating remote for '#{mirror.path}'."
29
+ unless mirror.type == "svn"
30
+ url = use_local_cache? ? git_cache.path(mirror.url) : mirror.url
31
+ git.remote_add(mirror.remote, url, mirror.branch)
32
+ else
33
+ git_svn.init(mirror.remote, mirror.url)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,107 @@
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
+ if mirror.type == "git-clone"
28
+ mirror.rspec_git.update options["revision"]
29
+ else
30
+
31
+ # check options for lock modification
32
+ if mirror.locked?
33
+ if options["head"]
34
+ msg "Unlocking mirror '#{mirror.path}'." if verbose?
35
+ mirror.lock = nil
36
+ elsif !options["revision"]
37
+ msg "Mirror '#{mirror.path}' is locked to #{display_revision(mirror, mirror.lock)}. Use --head to force."
38
+ return
39
+ end
40
+ end
41
+
42
+ setup_remote(mirror)
43
+ msg "Fetching new commits for '#{mirror.path}'." if verbose?
44
+ mirror.fetch
45
+
46
+ new_revision = validate_new_revision(mirror, options["revision"])
47
+ target_revision = determine_target_revision(mirror, new_revision)
48
+
49
+ if mirror.merged?(target_revision)
50
+ msg "Mirror '#{mirror.path}' is already up to date."
51
+ return
52
+ end
53
+
54
+ if mirror.squashed?
55
+ diff = mirror.diff
56
+ base_revision = mirror.base_revision
57
+ end
58
+
59
+ mirror.revision = new_revision
60
+ mirror.lock = new_revision if options["revision"]
61
+
62
+ msg "Merging in mirror '#{mirror.path}'." if verbose?
63
+ begin
64
+ if mirror.squashed?
65
+ local_hash = git.rev_parse("HEAD")
66
+ if diff
67
+ base_hash = generate_tree_hash(mirror, base_revision)
68
+ else
69
+ base_hash = local_hash
70
+ end
71
+ remote_hash = generate_tree_hash(mirror, target_revision)
72
+ ENV["GITHEAD_#{local_hash}"] = "HEAD"
73
+ ENV["GITHEAD_#{remote_hash}"] = target_revision
74
+ git.merge_recursive(base_hash, local_hash, remote_hash)
75
+ else
76
+ git.merge_subtree(target_revision)
77
+ end
78
+ rescue Operations::MergeError => error
79
+ msg "Caught merge error. Breaking."
80
+ end
81
+
82
+ config.update(mirror)
83
+ add_config_file
84
+
85
+ commit_message = "Updated mirror '#{mirror.path}' to #{display_revision(mirror)}"
86
+
87
+ if error
88
+ File.open(".git/MERGE_MSG", 'w') { |f| f.puts(commit_message) }
89
+ return
90
+ end
91
+
92
+ git.commit(commit_message)
93
+ msg commit_message
94
+ end
95
+ end
96
+
97
+ def generate_tree_hash(mirror, revision)
98
+ git.rm_r(mirror.path)
99
+ git.read_tree_prefix(revision, mirror.path)
100
+ success = git.commit("Temporary commit for mirror '#{mirror.path}'")
101
+ hash = git.rev_parse("HEAD")
102
+ git.reset_hard("HEAD^") if success
103
+ hash
104
+ end
105
+ end
106
+ end
107
+ 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,180 @@
1
+ require 'rspec_git.rb'
2
+
3
+ module Braid
4
+ class Mirror
5
+ TYPES = %w(git svn git-clone)
6
+ ATTRIBUTES = %w(url remote type branch squashed revision lock)
7
+
8
+ class UnknownType < BraidError
9
+ def message
10
+ "unknown type: #{super}"
11
+ end
12
+ end
13
+ class CannotGuessType < BraidError
14
+ def message
15
+ "cannot guess type: #{super}"
16
+ end
17
+ end
18
+ class PathRequired < BraidError
19
+ def message
20
+ "path is required"
21
+ end
22
+ end
23
+
24
+ include Operations::VersionControl
25
+
26
+ attr_reader :path, :attributes, :rspec_git
27
+
28
+ def initialize(path, attributes = {})
29
+ @path = path.sub(/\/$/, '')
30
+ @attributes = attributes
31
+ @rspec_git = RSpec::Git.new File.basename(@path) @path attributes["url"]
32
+ end
33
+
34
+ def self.new_from_options(url, options = {})
35
+ url = url.sub(/\/$/, '')
36
+
37
+ branch = options["branch"] || "master"
38
+
39
+ if type = options["type"] || extract_type_from_url(url)
40
+ raise UnknownType, type unless TYPES.include?(type)
41
+ else
42
+ raise CannotGuessType, url
43
+ end
44
+
45
+ unless path = options["path"] || extract_path_from_url(url)
46
+ raise PathRequired
47
+ end
48
+
49
+ if options["rails_plugin"] && ! path =~ /vendor\/plugins.*/
50
+ path = "vendor/plugins/#{path}"
51
+ end
52
+
53
+ if options["rails_gem"] && ! path =~ /vendor\/gems.*/
54
+ path = "vendor/gems/#{path}"
55
+ end
56
+
57
+ remote = "braid/#{path}".gsub("_", '-') # stupid git svn changes all _ to ., weird
58
+ squashed = !options["full"]
59
+ branch = nil if type == "svn"
60
+
61
+ attributes = { "url" => url, "remote" => remote, "type" => type, "branch" => branch, "squashed" => squashed }
62
+ self.new(path, attributes)
63
+ end
64
+
65
+ def ==(comparison)
66
+ path == comparison.path && attributes == comparison.attributes
67
+ end
68
+
69
+ def type
70
+ # override Object#type
71
+ attributes["type"]
72
+ end
73
+
74
+ def locked?
75
+ !!lock
76
+ end
77
+
78
+ def squashed?
79
+ !!squashed
80
+ end
81
+
82
+ def merged?(commit)
83
+ # tip from spearce in #git:
84
+ # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
85
+ commit = git.rev_parse(commit)
86
+ if squashed?
87
+ !!base_revision && git.merge_base(commit, base_revision) == commit
88
+ else
89
+ git.merge_base(commit, "HEAD") == commit
90
+ end
91
+ end
92
+
93
+ def diff
94
+ remote_hash = git.rev_parse("#{base_revision}:")
95
+ local_hash = git.tree_hash(path)
96
+ remote_hash != local_hash ? git.diff_tree(remote_hash, local_hash, path) : ""
97
+ end
98
+
99
+ def fetch
100
+ unless type == "svn"
101
+ git_cache.fetch(url) if cached?
102
+ git.fetch(remote)
103
+ else
104
+ git_svn.fetch(remote)
105
+ end
106
+ end
107
+
108
+ def cached?
109
+ git.remote_url(remote) == git_cache.path(url)
110
+ end
111
+
112
+ def base_revision
113
+ if revision
114
+ unless type == "svn"
115
+ git.rev_parse(revision)
116
+ else
117
+ git_svn.commit_hash(remote, revision)
118
+ end
119
+ else
120
+ inferred_revision
121
+ end
122
+ end
123
+
124
+ private
125
+ def method_missing(name, *args)
126
+ if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
127
+ unless $2
128
+ attributes[$1]
129
+ else
130
+ attributes[$1] = args[0]
131
+ end
132
+ else
133
+ raise NameError, "unknown attribute `#{name}'"
134
+ end
135
+ end
136
+
137
+ def inferred_revision
138
+ local_commits = git.rev_list("HEAD", "-- #{path}").split("\n")
139
+ remote_hashes = git.rev_list("--pretty=format:\"%T\"", remote).split("commit ").map do |chunk|
140
+ chunk.split("\n", 2).map { |value| value.strip }
141
+ end
142
+ hash = nil
143
+ local_commits.each do |local_commit|
144
+ local_tree = git.tree_hash(path, local_commit)
145
+ if match = remote_hashes.find { |_, remote_tree| local_tree == remote_tree }
146
+ hash = match[0]
147
+ break
148
+ end
149
+ end
150
+ hash
151
+ end
152
+
153
+ def self.extract_type_from_url(url)
154
+ return nil unless url
155
+ url.sub!(/\/$/, '')
156
+
157
+ # check for git:// and svn:// URLs
158
+ url_scheme = url.split(":").first
159
+ return url_scheme if TYPES.include?(url_scheme)
160
+
161
+ return "svn" if url[-6..-1] == "/trunk"
162
+ return "git-clone" if url[-4..-1] == ".git"
163
+ end
164
+
165
+ def self.extract_path_from_url(url)
166
+ return nil unless url
167
+ name = File.basename(url)
168
+
169
+ if File.extname(name) == ".git"
170
+ # strip .git
171
+ name[0..-5]
172
+ elsif name == "trunk"
173
+ # use parent
174
+ File.basename(File.dirname(url))
175
+ else
176
+ name
177
+ end
178
+ end
179
+ end
180
+ end