gx 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|