dreamcat4-braid 0.5.1

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,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