norbert-braid 0.4.9

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