norbert-braid 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007-2008 Cristi Balan, Norbert Crombach
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -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.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ task :default => :test
5
+
6
+ def test_task(name, pattern)
7
+ Rake::TestTask.new(name) do |t|
8
+ ENV['TESTOPTS'] = '--runner=s'
9
+
10
+ t.libs << 'lib'
11
+ t.pattern = pattern
12
+ t.verbose = true
13
+ end
14
+ end
15
+
16
+ test_task(:test, "test/*_test.rb")
17
+ namespace(:test) { test_task(:integration, "test/integration/*_test.rb") }
data/bin/braid ADDED
@@ -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_safe
78
+
79
+ run {
80
+ Braid::Command.run(:update, path, { "revision" => revision, "head" => head , "safe" => safe })
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_safe) {
215
+ option(:safe) {
216
+ optional
217
+ desc 'safe on merge errors'
218
+ attr
219
+ }
220
+ }
221
+
222
+ run { help! }
223
+ }
data/braid.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{braid}
3
+ s.version = "0.4.9"
4
+
5
+ s.specification_version = 2 if s.respond_to? :specification_version=
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Cristi Balan", "Norbert Crombach"]
9
+ s.date = %q{2008-09-06}
10
+ s.default_executable = %q{braid}
11
+ s.description = %q{A simple tool for tracking vendor branches in git.}
12
+ s.email = %q{evil@che.lu}
13
+ s.executables = ["braid"]
14
+ s.extra_rdoc_files = ["README.rdoc"]
15
+ s.files = ["LICENSE", "README.rdoc", "Rakefile", "braid.gemspec", "bin/braid", "lib/braid/command.rb", "lib/braid/commands/add.rb", "lib/braid/commands/diff.rb", "lib/braid/commands/remove.rb", "lib/braid/commands/setup.rb", "lib/braid/commands/update.rb", "lib/braid/config.rb", "lib/braid/mirror.rb", "lib/braid/operations.rb", "lib/braid.rb", "test/braid_test.rb", "test/config_test.rb", "test/mirror_test.rb", "test/operations_test.rb", "test/test_helper.rb"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://evil.che.lu/projects/braid}
18
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "braid", "--main", "README.rdoc"]
19
+ s.require_paths = ["lib"]
20
+ s.rubyforge_project = %q{braid}
21
+ s.rubygems_version = %q{1.1.0}
22
+ s.summary = %q{A simple tool for tracking vendor branches in git.}
23
+
24
+ s.add_dependency(%q<main>, [">= 2.8.0"])
25
+ s.add_dependency(%q<open4>, [">= 0.9.6"])
26
+ 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,33 @@
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
+ end
31
+ end
32
+ end
33
+ 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["safe"] ? ["--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["safe"]
70
+ msg "Caught shell error. Breaking."
71
+ exit(0)
72
+ else
73
+ raise error
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end