gx 1.0.0
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/.autotest +23 -0
- data/.gemtest +0 -0
- data/History.txt +6 -0
- data/Manifest.txt +10 -0
- data/README.txt +66 -0
- data/Rakefile +23 -0
- data/bin/gx-publish +94 -0
- data/bin/gx-update +370 -0
- data/lib/gx.rb +3 -0
- data/lib/gx/enhance.rb +144 -0
- data/test/test_gx.rb +8 -0
- metadata +101 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/.gemtest
ADDED
File without changes
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= gx
|
2
|
+
|
3
|
+
* http://github.com/evanphx/gx
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Gx is 2 git related tools: gx-update and gx-publish.
|
8
|
+
|
9
|
+
gx-update is a replacement for 'git pull' that includes an integrated
|
10
|
+
conflict resolver.
|
11
|
+
|
12
|
+
== FEATURES/PROBLEMS:
|
13
|
+
|
14
|
+
* Birthday!
|
15
|
+
|
16
|
+
== SYNOPSIS:
|
17
|
+
|
18
|
+
gx-update
|
19
|
+
|
20
|
+
== REQUIREMENTS:
|
21
|
+
|
22
|
+
* git
|
23
|
+
* grit
|
24
|
+
* ruby
|
25
|
+
|
26
|
+
== INSTALL:
|
27
|
+
|
28
|
+
* Edit your ~/.gitconfig and add:
|
29
|
+
update = !gx-update
|
30
|
+
|
31
|
+
to your [alias] section.
|
32
|
+
|
33
|
+
|
34
|
+
== DEVELOPERS:
|
35
|
+
|
36
|
+
After checking out the source, run:
|
37
|
+
|
38
|
+
$ rake newb
|
39
|
+
|
40
|
+
This task will install any missing dependencies, run the tests/specs,
|
41
|
+
and generate the RDoc.
|
42
|
+
|
43
|
+
== LICENSE:
|
44
|
+
|
45
|
+
(The MIT License)
|
46
|
+
|
47
|
+
Copyright (c) 2011 FIX
|
48
|
+
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
50
|
+
a copy of this software and associated documentation files (the
|
51
|
+
'Software'), to deal in the Software without restriction, including
|
52
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
53
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
54
|
+
permit persons to whom the Software is furnished to do so, subject to
|
55
|
+
the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
62
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
63
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
64
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
65
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
66
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
# Hoe.plugin :compiler
|
7
|
+
# Hoe.plugin :gem_prelude_sucks
|
8
|
+
# Hoe.plugin :inline
|
9
|
+
# Hoe.plugin :inline
|
10
|
+
# Hoe.plugin :racc
|
11
|
+
# Hoe.plugin :rubyforge
|
12
|
+
|
13
|
+
Hoe.spec "gx" do
|
14
|
+
# HEY! If you fill these out in ~/.hoe_template/Rakefile.erb then
|
15
|
+
# you'll never have to touch them again!
|
16
|
+
# (delete this comment too, of course)
|
17
|
+
|
18
|
+
developer "Evan Phoenix", "evan@fallingsnow.net"
|
19
|
+
|
20
|
+
# self.rubyforge_name = 'gxx' # if different than 'gx'
|
21
|
+
end
|
22
|
+
|
23
|
+
# vim: syntax=ruby
|
data/bin/gx-publish
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
require 'gx/enhance'
|
6
|
+
require 'optparse'
|
7
|
+
require 'ostruct'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'readline'
|
10
|
+
|
11
|
+
opts = OpenStruct.new
|
12
|
+
|
13
|
+
op = OptionParser.new do |o|
|
14
|
+
o.on "-z", "--analyze", "Output information on what update would do" do
|
15
|
+
opts.analyze = true
|
16
|
+
end
|
17
|
+
|
18
|
+
o.on "-v", "--verbose", "Be verbose" do
|
19
|
+
opts.verbose = true
|
20
|
+
end
|
21
|
+
|
22
|
+
o.on "--debug", "Show all git commands run" do
|
23
|
+
Grit.debug = true
|
24
|
+
end
|
25
|
+
|
26
|
+
o.on "-q", "--quiet", "Show the minimal output" do
|
27
|
+
opts.quiet = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
op.parse!(ARGV)
|
32
|
+
|
33
|
+
STDOUT.sync = true
|
34
|
+
|
35
|
+
repo = Grit::Repo.current
|
36
|
+
|
37
|
+
# Boost the timeout
|
38
|
+
Grit::Git.git_timeout = 120
|
39
|
+
|
40
|
+
current = repo.resolve_rev "HEAD"
|
41
|
+
branch = repo.git.symbolic_ref({:q => true}, "HEAD").strip
|
42
|
+
|
43
|
+
branch_name = branch.gsub %r!^refs/heads/!, ""
|
44
|
+
|
45
|
+
origin_ref = repo.merge_ref branch_name
|
46
|
+
|
47
|
+
unless origin_ref
|
48
|
+
puts "Sorry, it appears your current branch is not setup with merge info."
|
49
|
+
puts "Please set 'branch.#{branch_name}.remote' and 'branch.#{branch_name}.merge'"
|
50
|
+
puts "and try again."
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
origin = repo.resolve_rev origin_ref
|
55
|
+
|
56
|
+
# See if there are actually any commits to publish first.
|
57
|
+
|
58
|
+
if current == origin
|
59
|
+
puts "Already up to date, no commits to publish."
|
60
|
+
exit 0
|
61
|
+
end
|
62
|
+
|
63
|
+
# ok, there are commits, now make sure our origin and
|
64
|
+
# everything is update to date.
|
65
|
+
|
66
|
+
common = repo.find_ancestor(origin, current)
|
67
|
+
|
68
|
+
url = repo.merge_url branch_name
|
69
|
+
|
70
|
+
push_repo, push_branch = repo.merge_info branch_name
|
71
|
+
remote_hash = repo.remote_info push_repo, push_branch
|
72
|
+
|
73
|
+
|
74
|
+
if common != origin or remote_hash != origin
|
75
|
+
puts "The upstream contains unmerged commits. Please update/pull first."
|
76
|
+
if opts.verbose
|
77
|
+
puts "Local branch: #{current}"
|
78
|
+
puts "Local origin: #{origin}"
|
79
|
+
puts "Remote origin: #{remote_hash}"
|
80
|
+
end
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
|
84
|
+
print "Publishing local commits to #{url}... "
|
85
|
+
|
86
|
+
out = repo.git.push({:v => true}, push_repo, "#{branch_name}:#{push_branch}")
|
87
|
+
if $?.exitstatus != 0
|
88
|
+
puts "error!"
|
89
|
+
puts "Sorry, I'm not sure what happened. Here is what git said:"
|
90
|
+
puts out
|
91
|
+
end
|
92
|
+
|
93
|
+
puts "done!"
|
94
|
+
|
data/bin/gx-update
ADDED
@@ -0,0 +1,370 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# TODO:
|
4
|
+
# * Handle a conflict in a remote commit that is in a renamed file.
|
5
|
+
|
6
|
+
require 'pathname'
|
7
|
+
require 'gx/enhance'
|
8
|
+
require 'optparse'
|
9
|
+
require 'ostruct'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'readline'
|
12
|
+
|
13
|
+
opts = OpenStruct.new
|
14
|
+
|
15
|
+
op = OptionParser.new do |o|
|
16
|
+
o.on "-z", "--analyze", "Output information on what update would do" do
|
17
|
+
opts.analyze = true
|
18
|
+
end
|
19
|
+
|
20
|
+
o.on "-v", "--verbose", "Be verbose" do
|
21
|
+
opts.verbose = true
|
22
|
+
end
|
23
|
+
|
24
|
+
o.on "--debug", "Show all git commands run" do
|
25
|
+
Grit.debug = true
|
26
|
+
end
|
27
|
+
|
28
|
+
o.on "-q", "--quiet", "Show the minimal output" do
|
29
|
+
opts.quiet = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
op.parse!(ARGV)
|
34
|
+
|
35
|
+
STDOUT.sync = true
|
36
|
+
|
37
|
+
# Boost the timeout
|
38
|
+
Grit::Git.git_timeout = 240
|
39
|
+
|
40
|
+
class Update
|
41
|
+
HELP = <<-TXT
|
42
|
+
You're currently inside the conflict resolver. The following commands
|
43
|
+
are available to help you.
|
44
|
+
|
45
|
+
When the conflict resolver is first started, the contents of the file
|
46
|
+
will contain the file populated with conflict markers for you to edit.
|
47
|
+
|
48
|
+
[D]iff View the diffs between the (original version and local version)
|
49
|
+
and (original version and remote version).
|
50
|
+
[E]dit Launch your editor to edit the file.
|
51
|
+
[T]ool Run git-mergetool on the file.
|
52
|
+
[O]riginal Set the contents of the file to the original version. This is
|
53
|
+
version from the common ancestor of your commit and the remote
|
54
|
+
commit.
|
55
|
+
[M]ine Set the contents of the file to be your version.
|
56
|
+
[R]emote Set the contents of the file to be the remote version.
|
57
|
+
co[N]flict Set the contents of the file to contain the merged between the
|
58
|
+
local version and remote version, with conflict markers.
|
59
|
+
[P]rompt Launch a subshell to deal with the conflict. Simply exit
|
60
|
+
from the shell to continue with conflict resolution.
|
61
|
+
[I]nfo View information about the commit and the current file.
|
62
|
+
[A]bort Cancel the update altogether, restore everything to before
|
63
|
+
the update was started.
|
64
|
+
[C]ontinue You're done dealing with this conflict, move on to the next one.
|
65
|
+
[H]elp Detail all available options, you're looking at it now.
|
66
|
+
TXT
|
67
|
+
|
68
|
+
|
69
|
+
def initialize(opts)
|
70
|
+
@opts = opts
|
71
|
+
@repo = Grit::Repo.current
|
72
|
+
|
73
|
+
@current = @repo.resolve_rev "HEAD"
|
74
|
+
@branch = @repo.git.symbolic_ref({:q => true}, "HEAD").strip
|
75
|
+
|
76
|
+
@branch_name = @branch.gsub %r!^refs/heads/!, ""
|
77
|
+
|
78
|
+
@origin_ref = @repo.merge_ref @branch_name
|
79
|
+
|
80
|
+
unless @origin_ref
|
81
|
+
puts "Sorry, it appears your current branch is not setup with merge info."
|
82
|
+
puts "Please set 'branch.#{@branch_name}.remote' and 'branch.#{@branch_name}.merge'"
|
83
|
+
puts "and try again."
|
84
|
+
exit 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def fetch
|
89
|
+
print "Fetching new commits: "
|
90
|
+
out = @repo.git.fetch :timeout => false
|
91
|
+
puts "done."
|
92
|
+
|
93
|
+
# TODO parse +out+ for details to show the user.
|
94
|
+
end
|
95
|
+
|
96
|
+
def includes_conflict_markers?(path)
|
97
|
+
/^<<<<<<< HEAD/.match(File.read(path))
|
98
|
+
end
|
99
|
+
|
100
|
+
def cat_file(ref, file)
|
101
|
+
File.open(file, "w") do |f|
|
102
|
+
f << @repo.git.cat_file({}, ref)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_unmerged(patch_info, files)
|
107
|
+
files.each do |name, info|
|
108
|
+
system "cp #{name} .git/with_markers"
|
109
|
+
|
110
|
+
puts
|
111
|
+
puts "Conflict discovered in '#{name}'"
|
112
|
+
|
113
|
+
loop do
|
114
|
+
|
115
|
+
# If there are conflict markers, default is edit.
|
116
|
+
if includes_conflict_markers?(name)
|
117
|
+
default = "E"
|
118
|
+
|
119
|
+
# otherwise it's continue.
|
120
|
+
else
|
121
|
+
default = "C"
|
122
|
+
end
|
123
|
+
|
124
|
+
ans = Readline.readline "Select: [D]iff, [E]dit, [C]ontinue, [H]elp: [#{default}] "
|
125
|
+
ans = default if ans.empty?
|
126
|
+
want = ans.downcase[0]
|
127
|
+
case want
|
128
|
+
when ?d
|
129
|
+
orig = ".git/diff/original/#{name}"
|
130
|
+
FileUtils.mkdir_p File.dirname(orig)
|
131
|
+
cat_file info.original, orig
|
132
|
+
|
133
|
+
mine = ".git/diff/mine/#{name}"
|
134
|
+
FileUtils.mkdir_p File.dirname(mine)
|
135
|
+
cat_file info.mine, mine
|
136
|
+
|
137
|
+
remote = ".git/diff/remote/#{name}"
|
138
|
+
FileUtils.mkdir_p File.dirname(remote)
|
139
|
+
cat_file info.yours, remote
|
140
|
+
|
141
|
+
system "cd .git/diff; diff -u original/#{name} mine/#{name}"
|
142
|
+
system "cd .git/diff; diff -u original/#{name} remote/#{name}"
|
143
|
+
system "rm -rf .git/diff"
|
144
|
+
when ?e
|
145
|
+
system "#{ENV['EDITOR']} #{name}"
|
146
|
+
when ?t
|
147
|
+
system "git mergetool #{name}"
|
148
|
+
when ?o
|
149
|
+
cat_file info.original, name
|
150
|
+
when ?m
|
151
|
+
cat_file info.mine, name
|
152
|
+
when ?r
|
153
|
+
cat_file info.yours, name
|
154
|
+
when ?n
|
155
|
+
system "cp .git/with_markers #{name}"
|
156
|
+
when ?p
|
157
|
+
puts "Starting a sub-shell to handle conflicts for #{name}."
|
158
|
+
puts "Exit the shell to continue resolution."
|
159
|
+
system "$SHELL"
|
160
|
+
when ?i
|
161
|
+
puts "Current file: #{name}"
|
162
|
+
puts "Current commit:"
|
163
|
+
puts " Subject: #{patch_info[:subject]}"
|
164
|
+
puts " Date: #{patch_info[:date]}"
|
165
|
+
puts " Author: #{patch_info[:author]} (#{patch_info[:email]})"
|
166
|
+
when ?a
|
167
|
+
raise "abort!"
|
168
|
+
when ?h
|
169
|
+
puts HELP
|
170
|
+
when ?c
|
171
|
+
if includes_conflict_markers?(name)
|
172
|
+
puts
|
173
|
+
puts "It looks like this file still contains conflict markers."
|
174
|
+
a = Readline.readline "Are you sure that you want to commit it? [Y/N]: "
|
175
|
+
break if a.downcase[0] == ?y
|
176
|
+
else
|
177
|
+
break
|
178
|
+
end
|
179
|
+
else
|
180
|
+
puts "Unknown option. Try again."
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
File.unlink ".git/with_markers" rescue nil
|
185
|
+
@repo.git.add({}, name)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def analyze
|
190
|
+
puts "Automatically merging in refs from: #{@origin_ref} / #{@origin[0,7]}"
|
191
|
+
puts "Closest ancestor between HEAD and origin: #{@common[0,7]}"
|
192
|
+
puts
|
193
|
+
|
194
|
+
if @to_receive.empty?
|
195
|
+
puts "Current history is up to date."
|
196
|
+
exit 0
|
197
|
+
end
|
198
|
+
|
199
|
+
puts "#{@to_receive.size} new commits."
|
200
|
+
if @opts.verbose
|
201
|
+
system "git log --pretty=oneline #{@common}..#{@origin_ref}"
|
202
|
+
puts
|
203
|
+
end
|
204
|
+
|
205
|
+
puts "#{@to_replay.size} commits to adapt."
|
206
|
+
if @opts.verbose
|
207
|
+
system "git log --pretty=oneline #{@common}..HEAD"
|
208
|
+
puts
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def run
|
213
|
+
|
214
|
+
fetch
|
215
|
+
|
216
|
+
@origin = @repo.resolve_rev @origin_ref
|
217
|
+
|
218
|
+
@common = @repo.find_ancestor(@origin, @current)
|
219
|
+
|
220
|
+
@to_replay = @repo.revs_between(@common, @current)
|
221
|
+
@to_receive = @repo.revs_between(@common, @origin)
|
222
|
+
|
223
|
+
if @opts.analyze
|
224
|
+
analyze
|
225
|
+
exit 0
|
226
|
+
end
|
227
|
+
|
228
|
+
if @to_receive.empty?
|
229
|
+
puts "Up to date."
|
230
|
+
exit 0
|
231
|
+
end
|
232
|
+
|
233
|
+
if @opts.verbose
|
234
|
+
puts "Extracting commits between #{@common[0,7]} and HEAD..."
|
235
|
+
end
|
236
|
+
|
237
|
+
# DANGER. Before here, we can abort anytime, after here, we're making
|
238
|
+
# changes, so we need to be able to recover.
|
239
|
+
#
|
240
|
+
begin
|
241
|
+
port_changes
|
242
|
+
rescue Exception => e
|
243
|
+
puts "Error detected, aborting update: #{e.message} (#{e.class})"
|
244
|
+
puts e.backtrace
|
245
|
+
recover
|
246
|
+
exit 1
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def recover
|
251
|
+
@repo.git.reset({:hard => true}, @current)
|
252
|
+
@repo.git.checkout({}, @branch.gsub(%r!^refs/heads/!, ""))
|
253
|
+
|
254
|
+
if @used_wip
|
255
|
+
@repo.git.reset({:mixed => true}, "HEAD^")
|
256
|
+
end
|
257
|
+
|
258
|
+
system "rm -rf #{Grit.rebase_dir}" rescue nil
|
259
|
+
end
|
260
|
+
|
261
|
+
def sh(cmd)
|
262
|
+
Grit.log cmd if Grit.debug
|
263
|
+
out = `#{cmd}`
|
264
|
+
Grit.log out if Grit.debug
|
265
|
+
return out
|
266
|
+
end
|
267
|
+
|
268
|
+
def port_changes
|
269
|
+
# Switch back in time so we can re-apply commits. checkout
|
270
|
+
# will return non-zero if there it can't be done. In that case
|
271
|
+
# we perform a WIP commit, and unwind that WIP commit later,
|
272
|
+
# leaving the working copy the same way it was.
|
273
|
+
|
274
|
+
@used_wip = false
|
275
|
+
|
276
|
+
list = @repo.git.ls_files(:m => true).split("\n")
|
277
|
+
if list.size > 0
|
278
|
+
@repo.git.commit({:m => "++WIP++", :a => true})
|
279
|
+
@used_wip = true
|
280
|
+
|
281
|
+
# Because we've introduced a new commit, we need to repoint current.
|
282
|
+
@current = @repo.resolve_rev "HEAD"
|
283
|
+
|
284
|
+
# And the list of commits to replay.
|
285
|
+
@to_replay = @repo.revs_between(@common, @current)
|
286
|
+
|
287
|
+
# Ok, try again.
|
288
|
+
# Use sh, since the git proxy seems to fuck up $?
|
289
|
+
error = sh "git checkout -q #{@origin} 2>&1"
|
290
|
+
if $?.exitstatus != 0
|
291
|
+
# Ok, give up.
|
292
|
+
recover
|
293
|
+
|
294
|
+
# Now tell the user what happened.
|
295
|
+
puts "ERROR: Sorry, 'git checkout' can't figure out how to properly switch"
|
296
|
+
puts "the working copy. Please fix this and run 'git update' again."
|
297
|
+
puts "Here is the error that 'git checkout' reported:"
|
298
|
+
puts
|
299
|
+
puts error
|
300
|
+
puts
|
301
|
+
exit 1
|
302
|
+
end
|
303
|
+
else
|
304
|
+
error = sh "git checkout -q #{@origin} 2>&1"
|
305
|
+
|
306
|
+
if $?.exitstatus != 0
|
307
|
+
# Ok, give up.
|
308
|
+
recover
|
309
|
+
|
310
|
+
# Now tell the user what happened.
|
311
|
+
puts "ERROR: Sorry, 'git checkout' can't figure out how to properly switch"
|
312
|
+
puts "the working copy. Please fix this and run 'git update' again."
|
313
|
+
puts "Here is the error that 'git checkout' reported:"
|
314
|
+
puts
|
315
|
+
puts error
|
316
|
+
puts
|
317
|
+
exit 1
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
sh "git format-patch --full-index --stdout #{@common}..#{@current} > .git/update-patch"
|
322
|
+
out = sh "git am --rebasing < .git/update-patch 2> /dev/null"
|
323
|
+
while $?.exitstatus != 0
|
324
|
+
info = @repo.am_info
|
325
|
+
if @opts.verbose
|
326
|
+
if info[:subject] == "++WIP++"
|
327
|
+
puts "Conflict detected in working copy."
|
328
|
+
else
|
329
|
+
puts "Conflict detected applying: #{info[:subject]}"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
unmerged = @repo.unmerged_files
|
334
|
+
handle_unmerged info, unmerged
|
335
|
+
|
336
|
+
if @repo.to_be_committed.empty?
|
337
|
+
out = @repo.git.am({:skip => true, "3" => true})
|
338
|
+
else
|
339
|
+
out = @repo.git.am({:resolved => true, "3" => true})
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Remove the patch we created contain all the rebased commits
|
344
|
+
File.unlink ".git/update-patch" rescue nil
|
345
|
+
|
346
|
+
rev = @repo.resolve_rev "HEAD"
|
347
|
+
|
348
|
+
# Update the branch ref to point to our new commit
|
349
|
+
|
350
|
+
@repo.git.update_ref({:m => "updated"}, @branch, rev, @current)
|
351
|
+
@repo.git.symbolic_ref({}, "HEAD", @branch)
|
352
|
+
|
353
|
+
# If we inserted a WIP commit on the top, remove the commit, but leave
|
354
|
+
# the work.
|
355
|
+
if @used_wip
|
356
|
+
@repo.git.reset({:mixed => true}, "HEAD^")
|
357
|
+
end
|
358
|
+
|
359
|
+
puts
|
360
|
+
puts "Updated. Imported #{@to_receive.size} commits, HEAD now pointed to #{rev[0,7]}."
|
361
|
+
puts
|
362
|
+
|
363
|
+
unless @opts.quiet
|
364
|
+
system "git diff --stat #{@common}..#{@origin}"
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
Update.new(opts).run
|
data/lib/gx.rb
ADDED
data/lib/gx/enhance.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'grit'
|
3
|
+
|
4
|
+
module Grit
|
5
|
+
class Repo
|
6
|
+
|
7
|
+
def self.current(goto=true)
|
8
|
+
# cd to the top of this git tree. If run via git's alias
|
9
|
+
# infrastructure, this is done for us. We do it again, just
|
10
|
+
# to be sure.
|
11
|
+
|
12
|
+
top = `git rev-parse --show-cdup 2>&1`.strip
|
13
|
+
if goto
|
14
|
+
Dir.chdir top unless top.empty?
|
15
|
+
return Grit::Repo.new(".")
|
16
|
+
else
|
17
|
+
return Grit::Repo.new(top)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Given +hashish+, parse it and return the hash
|
22
|
+
# it refers to.
|
23
|
+
#
|
24
|
+
def resolve_rev(hashish)
|
25
|
+
hash = @git.rev_parse({:verify => true}, hashish)
|
26
|
+
return nil if $?.exitstatus != 0
|
27
|
+
return hash.strip
|
28
|
+
end
|
29
|
+
|
30
|
+
# Given +left+ and +right+, detect and return their
|
31
|
+
# closest common ancestor. Used to find the point to perform
|
32
|
+
# merges from.
|
33
|
+
#
|
34
|
+
# +right+ defaults to the current HEAD.
|
35
|
+
#
|
36
|
+
def find_ancestor(left, right=nil)
|
37
|
+
right ||= resolve_rev "HEAD"
|
38
|
+
hash = @git.merge_base({}, left, right)
|
39
|
+
return nil if $?.exitstatus != 0
|
40
|
+
return hash.strip
|
41
|
+
end
|
42
|
+
|
43
|
+
def revs_between(left, right)
|
44
|
+
@git.rev_list({}, "#{left}..#{right}").split("\n")
|
45
|
+
end
|
46
|
+
|
47
|
+
class UnmergedFile
|
48
|
+
def initialize(name)
|
49
|
+
@name = name
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_accessor :original, :mine, :yours
|
53
|
+
end
|
54
|
+
|
55
|
+
def unmerged_files
|
56
|
+
files = Hash.new { |h,k| h[k] = UnmergedFile.new(k) }
|
57
|
+
@git.ls_files({:u => true}).split("\n").each do |line|
|
58
|
+
mode, hash, stage, name = line.split(/\s+/, 4)
|
59
|
+
case stage
|
60
|
+
when "1"
|
61
|
+
files[name].original = hash
|
62
|
+
when "2"
|
63
|
+
files[name].yours = hash
|
64
|
+
when "3"
|
65
|
+
files[name].mine = hash
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
return files
|
70
|
+
end
|
71
|
+
|
72
|
+
def Grit.rebase_dir
|
73
|
+
if File.directory? ".dotest"
|
74
|
+
return ".dotest"
|
75
|
+
elsif File.directory? ".git/rebase"
|
76
|
+
return ".git/rebase"
|
77
|
+
elsif File.directory? ".git/rebase-apply"
|
78
|
+
return ".git/rebase-apply"
|
79
|
+
else
|
80
|
+
raise "No rebase info found."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def am_info
|
85
|
+
info = {}
|
86
|
+
File.open("#{Grit.rebase_dir}/info") do |f|
|
87
|
+
f.readlines.each do |line|
|
88
|
+
line.strip!
|
89
|
+
break if line.empty?
|
90
|
+
key, val = line.split(": ")
|
91
|
+
info[key.downcase.to_sym] = val
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if subject = info[:subject]
|
96
|
+
subject.gsub!(/^\[PATCH\] /,"")
|
97
|
+
end
|
98
|
+
|
99
|
+
return info
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_be_committed
|
103
|
+
@git.diff_index({:cached => true, :name_only => true}, "HEAD").split("\n")
|
104
|
+
end
|
105
|
+
|
106
|
+
def path2ref(name)
|
107
|
+
name.gsub %r!^refs/heads/!, ""
|
108
|
+
end
|
109
|
+
|
110
|
+
def merge_info(branch)
|
111
|
+
repo = @git.config({}, "branch.#{branch}.remote").strip
|
112
|
+
ref = @git.config({}, "branch.#{branch}.merge").strip
|
113
|
+
return [repo, ref]
|
114
|
+
end
|
115
|
+
|
116
|
+
def merge_ref(branch)
|
117
|
+
repo, ref = merge_info(branch)
|
118
|
+
return nil if repo.empty?
|
119
|
+
path = "#{repo}/#{path2ref(ref)}"
|
120
|
+
return path
|
121
|
+
end
|
122
|
+
|
123
|
+
def merge_url(branch)
|
124
|
+
repo = @git.config({}, "branch.#{branch}.remote").strip
|
125
|
+
return "local" if repo == "."
|
126
|
+
|
127
|
+
@git.config({}, "remote.#{repo}.url").strip
|
128
|
+
end
|
129
|
+
|
130
|
+
def remote_info(who, which=nil)
|
131
|
+
if which
|
132
|
+
hash, name = @git.ls_remote({:timeout => false}, who, which).split(/\s+/, 2)
|
133
|
+
return hash
|
134
|
+
else
|
135
|
+
ret = {}
|
136
|
+
@git.ls_remote({:timeout => false}, who).split("\n").each do |line|
|
137
|
+
hash, name = line.split(/\s+/, 2)
|
138
|
+
ret[name] = hash
|
139
|
+
end
|
140
|
+
return ret
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/test/test_gx.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Evan Phoenix
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-22 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: hoe
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 41
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 9
|
33
|
+
- 1
|
34
|
+
version: 2.9.1
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
description: |-
|
38
|
+
Gx is 2 git related tools: gx-update and gx-publish.
|
39
|
+
|
40
|
+
gx-update is a replacement for 'git pull' that includes an integrated
|
41
|
+
conflict resolver.
|
42
|
+
email:
|
43
|
+
- evan@fallingsnow.net
|
44
|
+
executables:
|
45
|
+
- gx-publish
|
46
|
+
- gx-update
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
extra_rdoc_files:
|
50
|
+
- History.txt
|
51
|
+
- Manifest.txt
|
52
|
+
- README.txt
|
53
|
+
files:
|
54
|
+
- .autotest
|
55
|
+
- History.txt
|
56
|
+
- Manifest.txt
|
57
|
+
- README.txt
|
58
|
+
- Rakefile
|
59
|
+
- bin/gx-publish
|
60
|
+
- bin/gx-update
|
61
|
+
- lib/gx.rb
|
62
|
+
- lib/gx/enhance.rb
|
63
|
+
- test/test_gx.rb
|
64
|
+
- .gemtest
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: http://github.com/evanphx/gx
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options:
|
71
|
+
- --main
|
72
|
+
- README.txt
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
hash: 3
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
requirements: []
|
94
|
+
|
95
|
+
rubyforge_project: gx
|
96
|
+
rubygems_version: 1.6.2
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: "Gx is 2 git related tools: gx-update and gx-publish"
|
100
|
+
test_files:
|
101
|
+
- test/test_gx.rb
|