tapajos-braid 0.4.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ = braid
2
+ A simple tool for tracking vendor branches in git.
3
+
4
+ http://evil.che.lu/projects/braid/
5
+
6
+ == Requirements
7
+
8
+ You will need git 1.5.4.5 or higher to run this version.
9
+
10
+ == Installation
11
+
12
+ git clone git://github.com/evilchelu/braid.git
13
+ cd braid
14
+ gem build braid.gemspec
15
+ sudo gem install braid-x.y.z.gem
16
+
17
+ == Usage
18
+
19
+ braid help
20
+ braid help COMMANDNAME
21
+
22
+ For more usage examples and documentation check the project wiki at http://github.com/evilchelu/braid/wikis.
23
+ Also see the bug tracker at http://evilchelu.lighthouseapp.com/projects/10600-braid for current issues and future plans.
24
+
25
+ == Contributing
26
+
27
+ If you want to send a patch please fork the project on GitHub and send a pull request.
@@ -0,0 +1,13 @@
1
+ require "date"
2
+ require "fileutils"
3
+ require "rubygems"
4
+ require "rake/gempackagetask"
5
+ require "lib/braid"
6
+
7
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
8
+
9
+ task :test do
10
+ Dir[File.dirname(__FILE__) + '/test/**/*_spec.rb'].each do |file|
11
+ load file
12
+ end
13
+ end
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
4
+ require 'braid'
5
+
6
+ require 'rubygems'
7
+ require 'main'
8
+
9
+ Home = File.expand_path(ENV['HOME'] || '~')
10
+
11
+ # mostly blantantly stolen from ara's punch script
12
+ # main kicks ass!
13
+ Main {
14
+ description <<-TXT
15
+ braid is a simple tool to help track git or svn repositories inside a git repository.
16
+
17
+ Run 'braid help commandname' for more details.
18
+
19
+ All operations will be executed in the braid/track branch.
20
+ You can then merge back or cherry-pick changes.
21
+ TXT
22
+
23
+ mode(:add) {
24
+ description <<-TXT
25
+ Add a new mirror to be tracked.
26
+
27
+ * adds metadata about the mirror to .braids
28
+ * adds the git or git svn remotes to .git/config
29
+ * fetches and merges remote code into given directory
30
+
31
+ --type defaults:
32
+
33
+ * svn://path # => svn
34
+ * git://path # => git
35
+ * http://path/trunk # => svn
36
+ * http://path.git # => git
37
+
38
+ Name defaults:
39
+
40
+ * remote/path # => path
41
+ * remote/path/trunk # => path
42
+ * remote/path.git # => path
43
+ TXT
44
+
45
+ examples <<-TXT
46
+ . braid add svn://remote/path
47
+ . braid add svn://remote/path local/dir
48
+ . braid add git://remote/path local/dir
49
+ . braid add http://remote/path.git local/dir
50
+ . braid add http://remote/path --type git local/dir
51
+ . braid add svn://remote/path --branch notmaster
52
+ TXT
53
+
54
+ mixin :argument_url, :option_type, :optional_path, :option_branch, :option_rails_plugin, :option_revision, :option_full
55
+
56
+ run {
57
+ Braid::Command.run(:add, url, { "type" => type, "path" => path, "branch" => branch, "rails_plugin" => rails_plugin, "revision" => revision, "full" => full })
58
+ }
59
+ }
60
+
61
+ mode(:update) {
62
+ description <<-TXT
63
+ Update a braid mirror.
64
+
65
+ * get new changes from remote
66
+ * always creates a merge commit
67
+ * updates metadata in .braids when revisions are changed
68
+
69
+ Defaults to updating all unlocked mirrors if none is specified.
70
+ TXT
71
+
72
+ examples <<-TXT
73
+ . braid update
74
+ . braid update local/dir
75
+ TXT
76
+
77
+ mixin :optional_path, :option_revision, :option_head, :option_break
78
+
79
+ run {
80
+ Braid::Command.run(:update, path, { "revision" => revision, "head" => head , "break" => break? })
81
+ }
82
+ }
83
+
84
+ mode(:remove) {
85
+ description <<-TXT
86
+ Remove a mirror.
87
+
88
+ * removes metadata from .braids
89
+ * removes the local directory and commits the removal
90
+ * does NOT remove the git and git svn remotes in case you still need them around
91
+ TXT
92
+
93
+ examples <<-TXT
94
+ . braid remove local/dir
95
+ TXT
96
+
97
+ mixin :argument_path
98
+
99
+ run {
100
+ Braid::Command.run(:remove, path)
101
+ }
102
+ }
103
+
104
+ mode(:setup) {
105
+ description <<-TXT
106
+ Set up git and git-svn remotes.
107
+ TXT
108
+
109
+ examples <<-TXT
110
+ . braid setup local/dir
111
+ TXT
112
+
113
+ mixin :optional_path
114
+
115
+ run {
116
+ Braid::Command.run(:setup, path)
117
+ }
118
+ }
119
+
120
+ mode(:diff) {
121
+ description <<-TXT
122
+ Show diff of local changes to mirror.
123
+ TXT
124
+
125
+ examples <<-TXT
126
+ . braid diff local/dir
127
+ TXT
128
+
129
+ mixin :argument_path
130
+
131
+ run {
132
+ Braid::Command.run(:diff, path)
133
+ }
134
+ }
135
+
136
+ mode(:version) {
137
+ description 'Show braid version.'
138
+
139
+ run {
140
+ puts "braid #{Braid::VERSION}"
141
+ }
142
+ }
143
+
144
+ mixin(:argument_path) {
145
+ argument(:path) {
146
+ attr
147
+ }
148
+ }
149
+
150
+ mixin(:optional_path) {
151
+ argument(:path) {
152
+ optional
153
+ attr
154
+ }
155
+ }
156
+
157
+ mixin(:argument_url) {
158
+ argument(:url) {
159
+ attr
160
+ }
161
+ }
162
+
163
+ mixin(:option_type) {
164
+ option(:type, :t) {
165
+ optional
166
+ argument :required
167
+ desc 'mirror type'
168
+ attr
169
+ }
170
+ }
171
+
172
+ mixin(:option_branch) {
173
+ option(:branch, :b) {
174
+ optional
175
+ argument :required
176
+ desc 'remote branch name'
177
+ attr
178
+ }
179
+ }
180
+
181
+ mixin(:option_rails_plugin) {
182
+ option(:rails_plugin, :p) {
183
+ optional
184
+ desc 'added mirror is a Rails plugin'
185
+ attr
186
+ }
187
+ }
188
+
189
+ mixin(:option_revision) {
190
+ option(:revision, :r) {
191
+ optional
192
+ argument :required
193
+ desc 'revision to track'
194
+ attr
195
+ }
196
+ }
197
+
198
+ mixin(:option_head) {
199
+ option(:head) {
200
+ optional
201
+ desc 'mirror head'
202
+ attr
203
+ }
204
+ }
205
+
206
+ mixin(:option_full) {
207
+ option(:full) {
208
+ optional
209
+ desc 'include mirror history' # FIXME
210
+ attr
211
+ }
212
+ }
213
+
214
+ mixin(:option_break) {
215
+ option(:break) {
216
+ optional
217
+ desc 'break on merge errors'
218
+ attr
219
+ }
220
+ }
221
+
222
+ run { help! }
223
+ }
@@ -0,0 +1,16 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Braid
4
+ CONFIG_FILE = ".braids"
5
+ REQUIRED_GIT_VERSION = "1.5.4.5"
6
+ end
7
+
8
+ require 'braid/version'
9
+ require 'braid/error'
10
+ require 'braid/operations'
11
+ require 'braid/mirror'
12
+ require 'braid/config'
13
+ require 'braid/command'
14
+ Dir[File.dirname(__FILE__) + '/braid/commands/*'].each do |file|
15
+ require file
16
+ end
@@ -0,0 +1,119 @@
1
+ module Braid
2
+ class Command
3
+ class InvalidRevision < BraidError
4
+ end
5
+
6
+ extend Operations::VersionControl
7
+ include Operations::VersionControl
8
+
9
+ def self.run(command, *args)
10
+ verify_git_version!
11
+
12
+ klass = Commands.const_get(command.to_s.capitalize)
13
+ klass.new.run(*args)
14
+
15
+ rescue BraidError => error
16
+ case error
17
+ when Operations::ShellExecutionError
18
+ msg "Shell error: #{error.message}"
19
+ else
20
+ msg "Error: #{error.message}"
21
+ end
22
+ exit(1)
23
+ end
24
+
25
+ def self.msg(str)
26
+ puts str
27
+ end
28
+
29
+ def msg(str)
30
+ self.class.msg(str)
31
+ end
32
+
33
+ def config
34
+ @config ||= load_and_migrate_config
35
+ end
36
+
37
+ private
38
+ def self.verify_git_version!
39
+ git.require_version!(REQUIRED_GIT_VERSION)
40
+ end
41
+
42
+ def bail_on_local_changes!
43
+ git.ensure_clean!
44
+ end
45
+
46
+ def with_reset_on_error
47
+ work_head = git.head
48
+
49
+ begin
50
+ yield
51
+ rescue => error
52
+ msg "Resetting to '#{work_head[0, 7]}'."
53
+ git.reset_hard(work_head)
54
+ raise error
55
+ end
56
+ end
57
+
58
+ def load_and_migrate_config
59
+ config = Config.new
60
+ unless config.valid?
61
+ msg "Configuration is outdated. Migrating."
62
+ bail_on_local_changes!
63
+ config.migrate!
64
+ git.commit("Upgrade .braids", "-- .braids")
65
+ end
66
+ config
67
+ end
68
+
69
+ def add_config_file
70
+ git.add(CONFIG_FILE)
71
+ end
72
+
73
+ def display_revision(mirror, revision = nil)
74
+ revision ||= mirror.revision
75
+ mirror.type == "svn" ? "r#{revision}" : "'#{revision[0, 7]}'"
76
+ end
77
+
78
+ def validate_new_revision(mirror, new_revision)
79
+ unless new_revision
80
+ unless mirror.type == "svn"
81
+ return git.rev_parse(mirror.remote)
82
+ else
83
+ return svn.head_revision(mirror.url)
84
+ end
85
+ end
86
+
87
+ unless mirror.type == "svn"
88
+ new_revision = git.rev_parse(new_revision)
89
+ else
90
+ new_revision = svn.clean_revision(new_revision)
91
+ end
92
+ old_revision = mirror.revision
93
+
94
+ if new_revision == old_revision
95
+ raise InvalidRevision, "mirror is already at requested revision"
96
+ end
97
+
98
+ if mirror.type == "svn"
99
+ if old_revision && new_revision < old_revision
100
+ raise InvalidRevision, "local revision is higher than request revision"
101
+ end
102
+
103
+ if svn.head_revision(mirror.url) < new_revision
104
+ raise InvalidRevision, "requested revision is higher than remote revision"
105
+ end
106
+ end
107
+
108
+ new_revision
109
+ end
110
+
111
+ def determine_target_commit(mirror, new_revision)
112
+ unless mirror.type == "svn"
113
+ git.rev_parse(new_revision)
114
+ else
115
+ git_svn.commit_hash(mirror.remote, new_revision)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,44 @@
1
+ module Braid
2
+ module Commands
3
+ class Add < Command
4
+ def run(url, options = {})
5
+ bail_on_local_changes!
6
+
7
+ with_reset_on_error do
8
+ mirror = config.add_from_options(url, options)
9
+
10
+ branch_message = (mirror.type == "svn" || mirror.branch == "master") ? "" : " branch '#{mirror.branch}'"
11
+ revision_message = options["revision"] ? " at #{display_revision(mirror)}" : ""
12
+ msg "Adding #{mirror.type} mirror of '#{mirror.url}'#{branch_message}#{revision_message}."
13
+
14
+ # these commands are explained in the subtree merge guide
15
+ # http://www.kernel.org/pub/software/scm/git/docs/howto/using-merge-subtree.html
16
+
17
+ setup_remote(mirror)
18
+ mirror.fetch
19
+
20
+ new_revision = validate_new_revision(mirror, options["revision"])
21
+ target_hash = determine_target_commit(mirror, new_revision)
22
+
23
+ unless mirror.squashed?
24
+ git.merge_ours(target_hash)
25
+ end
26
+ git.read_tree(target_hash, mirror.path)
27
+
28
+ mirror.revision = new_revision
29
+ mirror.lock = new_revision if options["revision"]
30
+ config.update(mirror)
31
+ add_config_file
32
+
33
+ commit_message = "Add mirror '#{mirror.path}/'#{revision_message}"
34
+ git.commit(commit_message)
35
+ end
36
+ end
37
+
38
+ private
39
+ def setup_remote(mirror)
40
+ Command.run(:setup, mirror.path)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ module Braid
2
+ module Commands
3
+ class Diff < Command
4
+ def run(path)
5
+ mirror = config.get!(path)
6
+ diff = mirror.diff
7
+ puts diff unless diff.empty?
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ module Braid
2
+ module Commands
3
+ class Remove < Command
4
+ def run(path)
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
+ commit_message = "Remove mirror '#{mirror.path}/'"
18
+ git.commit(commit_message)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ 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_exists?(mirror.remote)
20
+ msg "Mirror '#{mirror.path}/' already has a remote. Skipping."
21
+ return
22
+ end
23
+
24
+ msg "Setting up remote for '#{mirror.path}/'."
25
+ unless mirror.type == "svn"
26
+ git.remote_add(mirror.remote, mirror.url, mirror.branch)
27
+ else
28
+ git_svn.init(mirror.remote, mirror.url)
29
+ end
30
+ mirror.fetch
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,78 @@
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
+ # check options for lock modification
25
+ if mirror.locked?
26
+ if options["head"]
27
+ msg "Unlocking mirror '#{mirror.path}/'."
28
+ mirror.lock = nil
29
+ elsif !options["revision"]
30
+ msg "Mirror '#{mirror.path}/' is locked to #{display_revision(mirror, mirror.lock)}. Skipping."
31
+ return
32
+ end
33
+ end
34
+
35
+ mirror.fetch
36
+
37
+ new_revision = validate_new_revision(mirror, options["revision"])
38
+ target_hash = determine_target_commit(mirror, new_revision)
39
+
40
+ if mirror.merged?(target_hash)
41
+ msg "Mirror '#{mirror.path}/' is already up to date. Skipping."
42
+ return
43
+ end
44
+
45
+ diff = mirror.diff if mirror.squashed? # get diff before setting revision
46
+
47
+ mirror.revision = new_revision
48
+ mirror.lock = new_revision if options["revision"]
49
+ config.update(mirror)
50
+
51
+ msg "Updating mirror '#{mirror.path}/'."
52
+ if mirror.squashed?
53
+ git.rm_r(mirror.path)
54
+ git.read_tree(target_hash, mirror.path)
55
+ unless diff.empty?
56
+ git.apply(diff, *(options["break"] ? ["--reject"] : []))
57
+ end
58
+ else
59
+ git.merge_subtree(target_hash)
60
+ end
61
+
62
+ add_config_file
63
+
64
+ revision_message = " to " + (options["revision"] ? display_revision(mirror) : "HEAD")
65
+ commit_message = "Update mirror '#{mirror.path}/'#{revision_message}"
66
+ git.commit(commit_message)
67
+
68
+ rescue Operations::ShellExecutionError => error
69
+ if options["break"]
70
+ msg "Caught shell error. Breaking."
71
+ exit(0)
72
+ else
73
+ raise error
74
+ end
75
+ end
76
+ end
77
+ end
78
+ 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,8 @@
1
+ module Braid
2
+ class BraidError < StandardError
3
+ def message
4
+ value = super
5
+ value if value != self.class.name
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,174 @@
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.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, path) : ""
90
+ end
91
+
92
+ def fetch
93
+ unless type == "svn"
94
+ git.fetch(remote)
95
+ else
96
+ git_svn.fetch(remote)
97
+ end
98
+ end
99
+
100
+ private
101
+ def method_missing(name, *args)
102
+ if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
103
+ unless $2
104
+ attributes[$1]
105
+ else
106
+ attributes[$1] = args[0]
107
+ end
108
+ else
109
+ raise NameError, "unknown attribute `#{name}'"
110
+ end
111
+ end
112
+
113
+ def base_revision
114
+ if revision
115
+ unless type == "svn"
116
+ git.rev_parse(revision)
117
+ else
118
+ git_svn.commit_hash(remote, revision)
119
+ end
120
+ else
121
+ inferred_revision
122
+ end
123
+ end
124
+
125
+ def inferred_revision
126
+ time = list_revisions("-E --grep='^(Add|Update) mirror'", "-1", "HEAD", "-- #{path}").first[0]
127
+ revs = list_revisions("--reverse", remote)
128
+ hash = nil
129
+ revs.each_with_index do |rev, idx|
130
+ if !revs[idx + 1] || revs[idx + 1][0] > time
131
+ hash = revs[idx][1]
132
+ break
133
+ end
134
+ end
135
+ hash
136
+ end
137
+
138
+ def list_revisions(*args)
139
+ out = git.rev_list("--timestamp", *args)
140
+ out.split("\n").map do |line|
141
+ parts = line.split(' ', 2)
142
+ parts[0] = parts[0].to_i
143
+ parts
144
+ end
145
+ end
146
+
147
+ def self.extract_type_from_url(url)
148
+ return nil unless url
149
+ url.sub!(/\/$/, '')
150
+
151
+ # check for git:// and svn:// URLs
152
+ url_scheme = url.split(":").first
153
+ return url_scheme if TYPES.include?(url_scheme)
154
+
155
+ return "svn" if url[-6..-1] == "/trunk"
156
+ return "git" if url[-4..-1] == ".git"
157
+ end
158
+
159
+ def self.extract_path_from_url(url)
160
+ return nil unless url
161
+ name = File.basename(url)
162
+
163
+ if File.extname(name) == ".git"
164
+ # strip .git
165
+ name[0..-5]
166
+ elsif name == "trunk"
167
+ # use parent
168
+ File.basename(File.dirname(url))
169
+ else
170
+ name
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,295 @@
1
+ require 'singleton'
2
+ require 'rubygems'
3
+ require 'open4'
4
+
5
+ module Braid
6
+ module Operations
7
+ class ShellExecutionError < BraidError
8
+ def initialize(err = nil)
9
+ @err = err
10
+ end
11
+
12
+ def message
13
+ @err.to_s.split("\n").first
14
+ end
15
+ end
16
+ class VersionTooLow < BraidError
17
+ def initialize(command, version)
18
+ @command = command
19
+ @version = version.to_s.split("\n").first
20
+ end
21
+
22
+ def message
23
+ "#{@command} version too low: #{@version}"
24
+ end
25
+ end
26
+ class UnknownRevision < BraidError
27
+ def message
28
+ "unknown revision: #{super}"
29
+ end
30
+ end
31
+ class LocalChangesPresent < BraidError
32
+ def message
33
+ "local changes are present"
34
+ end
35
+ end
36
+
37
+ # The command proxy is meant to encapsulate commands such as git, git-svn and svn, that work with subcommands.
38
+ class Proxy
39
+ include Singleton
40
+
41
+ def self.command; name.split('::').last.downcase; end # hax!
42
+
43
+ def version
44
+ status, out, err = exec!("#{self.class.command} --version")
45
+ out.sub(/^.* version/, "").strip
46
+ end
47
+
48
+ def require_version(required)
49
+ required = required.split(".")
50
+ actual = version.split(".")
51
+
52
+ actual.each_with_index do |actual_piece, idx|
53
+ required_piece = required[idx]
54
+
55
+ return true unless required_piece
56
+
57
+ case (actual_piece <=> required_piece)
58
+ when -1
59
+ return false
60
+ when 1
61
+ return true
62
+ when 0
63
+ next
64
+ end
65
+ end
66
+
67
+ return actual.length >= required.length
68
+ end
69
+
70
+ def require_version!(required)
71
+ require_version(required) || raise(VersionTooLow.new(self.class.command, version))
72
+ end
73
+
74
+ private
75
+ def command(name)
76
+ # stub
77
+ name
78
+ end
79
+
80
+ def invoke(arg, *args)
81
+ exec!("#{command(arg)} #{args.join(' ')}".strip)[1].strip # return stdout
82
+ end
83
+
84
+ def method_missing(name, *args)
85
+ invoke(name, *args)
86
+ end
87
+
88
+ def exec(cmd)
89
+ cmd.strip!
90
+
91
+ previous_lang = ENV['LANG']
92
+ ENV['LANG'] = 'C'
93
+
94
+ out, err = nil
95
+ status = Open4.popen4(cmd) do |pid, stdin, stdout, stderr|
96
+ out = stdout.read
97
+ err = stderr.read
98
+ end.exitstatus
99
+ [status, out, err]
100
+
101
+ ensure
102
+ ENV['LANG'] = previous_lang
103
+ end
104
+
105
+ def exec!(cmd)
106
+ status, out, err = exec(cmd)
107
+ raise ShellExecutionError, err unless status == 0
108
+ [status, out, err]
109
+ end
110
+ end
111
+
112
+ class Git < Proxy
113
+ def commit(message, *args)
114
+ status, out, err = exec("git commit -m #{message.inspect} --no-verify #{args.join(' ')}")
115
+
116
+ if status == 0
117
+ true
118
+ elsif out.match(/nothing.* to commit/)
119
+ false
120
+ else
121
+ raise ShellExecutionError, err
122
+ end
123
+ end
124
+
125
+ def fetch(remote)
126
+ # open4 messes with the pipes of index-pack
127
+ system("git fetch -n #{remote} &> /dev/null")
128
+ raise ShellExecutionError, "could not fetch" unless $? == 0
129
+ true
130
+ end
131
+
132
+ def checkout(treeish)
133
+ # TODO debug
134
+ msg "Checking out '#{treeish}'."
135
+ invoke(:checkout, treeish)
136
+ true
137
+ end
138
+
139
+ # Returns the base commit or nil.
140
+ def merge_base(target, source)
141
+ invoke(:merge_base, target, source)
142
+ rescue ShellExecutionError
143
+ nil
144
+ end
145
+
146
+ def rev_parse(opt)
147
+ invoke(:rev_parse, opt)
148
+ rescue ShellExecutionError
149
+ raise UnknownRevision, opt
150
+ end
151
+
152
+ # Implies tracking.
153
+ def remote_add(remote, path, branch)
154
+ invoke(:remote, "add", "-t #{branch} -m #{branch}", remote, path)
155
+ true
156
+ end
157
+
158
+ # Checks git and svn remotes.
159
+ def remote_exists?(remote)
160
+ # TODO clean up and maybe return more information
161
+ !!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{Regexp.escape(remote)}"\]/ }
162
+ end
163
+
164
+ def reset_hard(target)
165
+ invoke(:reset, "--hard", target)
166
+ true
167
+ end
168
+
169
+ # Implies no commit.
170
+ def merge_ours(opt)
171
+ invoke(:merge, "-s ours --no-commit", opt)
172
+ true
173
+ end
174
+
175
+ # Implies no commit.
176
+ def merge_subtree(opt)
177
+ # TODO which options are needed?
178
+ invoke(:merge, "-s subtree --no-commit --no-ff", opt)
179
+ true
180
+ end
181
+
182
+ def read_tree(treeish, prefix)
183
+ invoke(:read_tree, "--prefix=#{prefix}/ -u", treeish)
184
+ true
185
+ end
186
+
187
+ def rm_r(path)
188
+ invoke(:rm, "-r", path)
189
+ true
190
+ end
191
+
192
+ def tree_hash(path, treeish = "HEAD")
193
+ out = invoke(:ls_tree, treeish, "-d", path)
194
+ out.split[2]
195
+ end
196
+
197
+ def diff_tree(src_tree, dst_tree, prefix = nil)
198
+ cmd = "git diff-tree -p --binary #{src_tree} #{dst_tree}"
199
+ cmd << " --src-prefix=a/#{prefix}/ --dst-prefix=b/#{prefix}/" if prefix
200
+ status, out, err = exec!(cmd)
201
+ out
202
+ end
203
+
204
+ def status_clean?
205
+ status, out, err = exec("git status")
206
+ !out.split("\n").grep(/nothing to commit/).empty?
207
+ end
208
+
209
+ def ensure_clean!
210
+ status_clean? || raise(LocalChangesPresent)
211
+ end
212
+
213
+ def head
214
+ rev_parse("HEAD")
215
+ end
216
+
217
+ def branch
218
+ status, out, err = exec!("git branch | grep '*'")
219
+ out[2..-1]
220
+ end
221
+
222
+ def apply(diff, *args)
223
+ err = nil
224
+ status = Open4.popen4("git apply --index --whitespace=nowarn #{args.join(' ')} -") do |pid, stdin, stdout, stderr|
225
+ stdin.puts(diff)
226
+ stdin.close
227
+
228
+ err = stderr.read
229
+ end.exitstatus
230
+ raise ShellExecutionError, err unless status == 0
231
+ true
232
+ end
233
+
234
+ private
235
+ def command(name)
236
+ "#{self.class.command} #{name.to_s.gsub('_', '-')}"
237
+ end
238
+ end
239
+
240
+ class GitSvn < Proxy
241
+ def self.command; "git svn"; end
242
+
243
+ def commit_hash(remote, revision)
244
+ out = invoke(:log, "--show-commit --oneline", "-r #{revision}", remote)
245
+ part = out.to_s.split(" | ")[1]
246
+ raise UnknownRevision, "r#{revision}" unless part
247
+ Git.instance.rev_parse(part) # FIXME ugly ugly ugly
248
+ end
249
+
250
+ def fetch(remote)
251
+ # open4 messes with the pipes of index-pack
252
+ system("git svn fetch #{remote} &> /dev/null")
253
+ raise ShellExecutionError, "could not fetch" unless $? == 0
254
+ true
255
+ end
256
+
257
+ def init(remote, path)
258
+ invoke(:init, "-R", remote, "--id=#{remote}", path)
259
+ true
260
+ end
261
+
262
+ private
263
+ def command(name)
264
+ "#{self.class.command} #{name}"
265
+ end
266
+ end
267
+
268
+ class Svn < Proxy
269
+ def clean_revision(revision)
270
+ revision.to_i if revision
271
+ end
272
+
273
+ def head_revision(path)
274
+ # not using svn info because it's retarded and doesn't show the actual last changed rev for the url
275
+ # git svn has no clue on how to get the actual HEAD revision number on it's own
276
+ status, out, err = exec!("svn log -q --limit 1 #{path}")
277
+ out.split(/\n/).find { |x| x.match /^r\d+/ }.split(" | ")[0][1..-1].to_i
278
+ end
279
+ end
280
+
281
+ module VersionControl
282
+ def git
283
+ Git.instance
284
+ end
285
+
286
+ def git_svn
287
+ GitSvn.instance
288
+ end
289
+
290
+ def svn
291
+ Svn.instance
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,3 @@
1
+ module Braid
2
+ VERSION = "0.4.9.1"
3
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tapajos-braid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.9.1
5
+ platform: ruby
6
+ authors:
7
+ - Cristi Balan
8
+ - Norbert Crombach
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-08-21 00:00:00 -07:00
14
+ default_executable: braid
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: main
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.8.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: open4
27
+ version_requirement:
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.6
33
+ version:
34
+ description: A simple tool for tracking vendor branches in git.
35
+ email: evil@che.lu
36
+ executables:
37
+ - braid
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README.rdoc
42
+ files:
43
+ - README.rdoc
44
+ - Rakefile
45
+ - bin/braid
46
+ - lib/braid
47
+ - lib/braid/command.rb
48
+ - lib/braid/commands
49
+ - lib/braid/commands/add.rb
50
+ - lib/braid/commands/diff.rb
51
+ - lib/braid/commands/remove.rb
52
+ - lib/braid/commands/setup.rb
53
+ - lib/braid/commands/update.rb
54
+ - lib/braid/config.rb
55
+ - lib/braid/error.rb
56
+ - lib/braid/mirror.rb
57
+ - lib/braid/operations.rb
58
+ - lib/braid/version.rb
59
+ - lib/braid.rb
60
+ has_rdoc: true
61
+ homepage: http://evil.che.lu/projects/braid
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --line-numbers
65
+ - --inline-source
66
+ - --title
67
+ - braid
68
+ - --main
69
+ - README.rdoc
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project: braid
87
+ rubygems_version: 1.2.0
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: A simple tool for tracking vendor branches in git.
91
+ test_files: []
92
+