git_pr 0.0.2 → 0.0.3.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/git-pr +117 -206
- data/lib/git_pr/diff.rb +7 -0
- data/lib/git_pr/github.rb +58 -3
- data/lib/git_pr/merge.rb +151 -0
- data/lib/git_pr/version.rb +1 -1
- data/lib/git_pr.rb +42 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 658d65e69d1e82754cf3a52a1dbc2acb6f7e6c7f
|
4
|
+
data.tar.gz: e4755905156ddc6de7dde59f8cdc244daeec8258
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 076c502311bb4c1cd68c82a22dadd5cbffa3ee7df1f5cafb1e638f64293707281b58f7e8532aefc57fde4a96f8971c8439717339464931e94b463ab87b3d1a3c
|
7
|
+
data.tar.gz: 78e4a335f11d88519db214dfeaabf1a824fbd97a4070b4242bf3edda6131346f7b067d765d7e11f94c3c8ec1cad27b4ce015553694d790744ca311bd6a83f056
|
data/bin/git-pr
CHANGED
@@ -9,80 +9,98 @@
|
|
9
9
|
|
10
10
|
require 'colorize'
|
11
11
|
require 'git'
|
12
|
+
require 'git_pr'
|
12
13
|
require 'highline/import'
|
13
14
|
require 'io/console'
|
14
15
|
require 'octokit'
|
15
16
|
require 'optparse'
|
16
|
-
require 'pp'
|
17
|
-
require 'git_pr'
|
18
17
|
require 'ostruct'
|
18
|
+
require 'pp'
|
19
19
|
|
20
|
+
$verbose = false
|
20
21
|
options = OpenStruct.new(:help => false,
|
21
22
|
:verbose => false,
|
22
|
-
:
|
23
|
+
:project => "origin",
|
24
|
+
:pull_request => nil,
|
25
|
+
:diff => OpenStruct.new(),
|
26
|
+
:difftool => OpenStruct.new(),
|
23
27
|
:list => OpenStruct.new(),
|
24
|
-
:merge => OpenStruct.new(
|
28
|
+
:merge => OpenStruct.new(),
|
25
29
|
:open => OpenStruct.new())
|
26
30
|
|
27
31
|
global_options = OptionParser.new do |opts|
|
28
|
-
opts.banner =
|
32
|
+
opts.banner = <<eos
|
33
|
+
git_pr version #{GitPr::VERSION}
|
34
|
+
|
35
|
+
Usage: git pr [options] subcommand [options]
|
36
|
+
eos
|
29
37
|
|
30
38
|
opts.separator "\nGlobal options"
|
31
39
|
|
40
|
+
opts.on("-p",
|
41
|
+
"--project [REMOTE|PROJECT]",
|
42
|
+
"The GitHub project to access. Can be a named remote, or a GitHub project in",
|
43
|
+
"<user>/<project> form. Defaults to the GitHub project that the \"origin\" remote",
|
44
|
+
"points to.") do |project|
|
45
|
+
options.project = project
|
46
|
+
end
|
32
47
|
opts.on("-h", "--help", "Show help") do
|
33
48
|
options.help = true
|
34
49
|
end
|
35
50
|
opts.on("-v", "--verbose", "Verbose output") do
|
36
|
-
|
51
|
+
$verbose = true
|
37
52
|
end
|
38
53
|
opts.on("-V", "--version", "Print version") do
|
39
54
|
puts GitPr::VERSION
|
40
55
|
exit
|
41
56
|
end
|
42
57
|
|
43
|
-
# auth: Check GitHub auth credentials, and prompt to update them if necessary
|
44
|
-
# list: List open pull requests
|
45
|
-
# open: Open the webpage for a pull request
|
46
58
|
opts.separator <<eos
|
47
59
|
|
48
60
|
Valid subcommands:
|
49
61
|
merge: Merge and close a pull request
|
62
|
+
list: List open pull requests
|
63
|
+
|
64
|
+
Run "git pr <subcommand> -h" for help with subcommands.
|
50
65
|
|
51
66
|
eos
|
52
67
|
end
|
53
68
|
|
54
|
-
|
55
|
-
|
56
|
-
|
69
|
+
def make_diff_argument_parser command_name
|
70
|
+
OptionParser.new do |opts|
|
71
|
+
opts.banner = <<eos
|
72
|
+
Usage: git pr #{command_name} [PR number] [-- [additional options]]
|
57
73
|
|
58
|
-
|
74
|
+
Fetch the latest changes for the specified PR, and then run "git
|
75
|
+
#{command_name}". Additional options are passed to the "git #{command_name}" command.
|
76
|
+
eos
|
59
77
|
|
60
|
-
|
61
|
-
|
62
|
-
|
78
|
+
opts.separator ""
|
79
|
+
end
|
80
|
+
end
|
63
81
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
82
|
+
subcommands = {
|
83
|
+
'diff' => make_diff_argument_parser("diff"),
|
84
|
+
'difftool' => make_diff_argument_parser("difftool"),
|
85
|
+
'list' => OptionParser.new do |opts|
|
86
|
+
opts.banner = "Usage: git pr list [options]"
|
68
87
|
|
69
|
-
|
88
|
+
opts.separator "\nList command options"
|
70
89
|
|
71
|
-
|
72
|
-
|
73
|
-
|
90
|
+
opts.on("-u", "--user [USERNAME]", "Only list PRs for the named GitHub user") do |user|
|
91
|
+
options.list.user = user
|
92
|
+
end
|
74
93
|
|
75
|
-
|
76
|
-
|
94
|
+
opts.separator ""
|
95
|
+
end,
|
77
96
|
'merge' => OptionParser.new do |opts|
|
78
|
-
opts.banner =
|
97
|
+
opts.banner = <<eos
|
98
|
+
Usage: git pr merge [PR number]
|
79
99
|
|
80
|
-
|
100
|
+
If a PR number isn't passed, a menu of open PRs will be displayed.
|
101
|
+
eos
|
81
102
|
|
82
|
-
opts.
|
83
|
-
puts remote
|
84
|
-
options.merge.remote = remote
|
85
|
-
end
|
103
|
+
# opts.separator "\nMerge command options"
|
86
104
|
|
87
105
|
opts.separator ""
|
88
106
|
end,
|
@@ -121,42 +139,6 @@ rescue OptionParser::InvalidOption => e
|
|
121
139
|
exit
|
122
140
|
end
|
123
141
|
|
124
|
-
# TODO: clean up subcommand parsing
|
125
|
-
if command == "merge" and !options.merge.additional_arguments.empty?
|
126
|
-
options.merge.pr = options.merge.additional_arguments.shift.to_i
|
127
|
-
end
|
128
|
-
|
129
|
-
def run(cmd, args = { :failure => lambda {} })
|
130
|
-
puts cmd.green
|
131
|
-
puts `#{cmd}`
|
132
|
-
puts ''
|
133
|
-
if $?.exitstatus != 0
|
134
|
-
args[:failure].call()
|
135
|
-
exit -1
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def run_test_output_empty(cmd)
|
140
|
-
"" == `#{cmd}`
|
141
|
-
end
|
142
|
-
|
143
|
-
def pull_summary(pull)
|
144
|
-
return "##{pull[:number]} from #{pull[:user][:login]}: \"#{pull[:title]}\""
|
145
|
-
end
|
146
|
-
|
147
|
-
def query_for_pull_to_merge(pulls)
|
148
|
-
puts
|
149
|
-
pull_to_merge = nil
|
150
|
-
choose do |menu|
|
151
|
-
menu.prompt = "Select PR to merge: "
|
152
|
-
pulls.each do |pull|
|
153
|
-
menu.choice(pull_summary(pull)) { pull_to_merge = pull }
|
154
|
-
end
|
155
|
-
menu.choice(:Quit, "Exit program.") { exit }
|
156
|
-
end
|
157
|
-
return pull_to_merge
|
158
|
-
end
|
159
|
-
|
160
142
|
if not GitPr::GitHub.test_credentials and not GitPr::GitHub.prompt_for_credentials
|
161
143
|
exit -1
|
162
144
|
end
|
@@ -166,156 +148,85 @@ GitPr::GitHub.initialize_octokit
|
|
166
148
|
# Get local Git object pointed at our repo root
|
167
149
|
git = Git.open `git rev-parse --show-toplevel`.chomp!
|
168
150
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
organization = url_match[1]
|
173
|
-
repository = url_match[2]
|
151
|
+
def pull_summary(pull)
|
152
|
+
return "##{pull[:number]} from #{pull[:user][:login]}: \"#{pull[:title]}\""
|
153
|
+
end
|
174
154
|
|
175
|
-
|
176
|
-
|
155
|
+
# Figure out what GitHub project we're dealing with.
|
156
|
+
github_project = GitPr::GitHub.determine_project_name_from_command_line git, options.project
|
177
157
|
|
178
|
-
|
179
|
-
unless pulls.length > 0
|
180
|
-
puts "No open pull requests found for #{organization}/#{repository}.".yellow
|
181
|
-
exit
|
182
|
-
end
|
158
|
+
case command
|
183
159
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
160
|
+
when "merge"
|
161
|
+
unless options.merge.additional_arguments.empty?
|
162
|
+
pull_request = options.merge.additional_arguments.shift.to_i
|
163
|
+
end
|
164
|
+
# Load a pull request
|
165
|
+
pull = GitPr::GitHub.find_or_prompt_for_pull_request github_project, pull_request
|
166
|
+
GitPr::merge_pull_cleanly git, pull
|
167
|
+
|
168
|
+
when "list"
|
169
|
+
pulls = Octokit.pulls "#{github_project}/pulls"
|
170
|
+
if options.list.user
|
171
|
+
pulls = pulls.select { |p| p[:user][:login] == options.list.user }
|
189
172
|
end
|
190
|
-
else
|
191
|
-
pull = query_for_pull_to_merge pulls
|
192
|
-
end
|
193
|
-
|
194
173
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
174
|
+
if pulls.any?
|
175
|
+
pulls.each { |p| puts pull_summary(p) }
|
176
|
+
else
|
177
|
+
puts "No open pull requests found.".yellow
|
178
|
+
end
|
199
179
|
|
200
|
-
target_branch = pull[:base][:ref]
|
201
|
-
target_repo_ssh_url = pull[:base][:repo][:git_url]
|
202
|
-
target_repo_clone_url = pull[:base][:repo][:clone_url]
|
203
180
|
|
204
|
-
|
205
|
-
|
206
|
-
puts "#{target_repo_ssh_url}/#{target_branch} <= #{source_repo_ssh_url}/#{source_branch}".cyan
|
181
|
+
when "diff", "difftool"
|
182
|
+
pull_request = nil
|
207
183
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
pull[:head][:repo][:ssh_url]].include? x.url }
|
212
|
-
unless source_remote
|
213
|
-
puts "Adding remote: #{pull[:user][:login]} from #{pull[:head][:repo][:ssh_url]}"
|
214
|
-
source_remote = git.add_remote pull[:user][:login], pull[:head][:repo][:ssh_url]
|
215
|
-
end
|
184
|
+
unless options[command].additional_arguments.empty?
|
185
|
+
pull_request = options[command].additional_arguments.shift.to_i
|
186
|
+
end
|
216
187
|
|
217
|
-
|
218
|
-
|
219
|
-
puts
|
220
|
-
|
221
|
-
|
222
|
-
target_remote.fetch
|
223
|
-
|
224
|
-
# Get the target branch up to date
|
225
|
-
run "git checkout #{target_branch}"
|
226
|
-
run "git pull --no-rebase --ff-only", :failure => lambda {
|
227
|
-
"Unable to update local target branch (#{target_branch}). Please repair manually before continuing.".red
|
228
|
-
}
|
188
|
+
unless pull_request
|
189
|
+
puts "Must specify a pull request to diff.\n".red
|
190
|
+
puts subcommands[command]
|
191
|
+
exit -1
|
192
|
+
end
|
229
193
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
194
|
+
puts "Getting pull request information..."
|
195
|
+
begin
|
196
|
+
pull = Octokit.pull github_project, pull_request
|
197
|
+
rescue Octokit::NotFound
|
198
|
+
puts "Pull request #{pull_request} not found in #{github_project}.\n".red
|
199
|
+
puts subcommands[command]
|
200
|
+
exit -1
|
201
|
+
end
|
237
202
|
|
238
|
-
#
|
239
|
-
|
240
|
-
# manually.
|
241
|
-
remote_source_branch = "#{source_remote}/#{source_branch}"
|
242
|
-
if (not run_test_output_empty "git branch --list #{source_branch}" and not run_test_output_empty "git diff #{remote_source_branch} #{source_branch}")
|
243
|
-
puts "Local branch (#{source_branch}) differs from remote branch (#{remote_source_branch}). Please reconcile before continuing.".red
|
244
|
-
exit -1
|
245
|
-
end
|
203
|
+
# Make sure we have the source and destination remotes
|
204
|
+
source_remote, target_remote = GitPr.ensure_remotes_for_pull_request git, pull
|
246
205
|
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
}
|
206
|
+
# Figure out if we need to fetch; skip it if we can
|
207
|
+
def have_commit_locally git, sha
|
208
|
+
begin
|
209
|
+
git.object sha
|
210
|
+
rescue Git::GitExecuteError
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
true
|
214
|
+
end
|
215
|
+
source_remote.fetch unless have_commit_locally git, pull[:head][:sha]
|
216
|
+
target_remote.fetch unless have_commit_locally git, pull[:base][:sha]
|
217
|
+
|
218
|
+
source_branch = pull[:head][:ref]
|
219
|
+
target_branch = pull[:base][:ref]
|
220
|
+
merge_base = `git merge-base #{source_remote}/#{source_branch} #{target_remote}/#{target_branch}`.strip!
|
221
|
+
diff_command = "git #{command} #{options[command].additional_arguments.join " "} #{merge_base} #{source_remote}/#{source_branch}".gsub /\s* /, " "
|
222
|
+
puts "Executing #{diff_command}"
|
223
|
+
|
224
|
+
# Fork a child so that it gets access to the terminal. This makes "git diff" work with paging.
|
225
|
+
child = Kernel.fork do ||
|
226
|
+
Kernel.exec "#{diff_command} ;"
|
227
|
+
end
|
263
228
|
|
264
|
-
#
|
265
|
-
|
266
|
-
|
267
|
-
# Merge the source branch into the target. Use --no-ff so that an explicit
|
268
|
-
# merge commit is created.
|
269
|
-
run "git checkout #{target_branch}"
|
270
|
-
run "git merge --no-ff #{rebase_branch} -m 'Merge #{pull_summary(pull)}'"
|
271
|
-
|
272
|
-
# Now that the rebase branch is merged, we can safely delete it.
|
273
|
-
run "git branch -D #{rebase_branch}"
|
274
|
-
|
275
|
-
# Print a log of the merge with branch structure visible. Jump through hoops to
|
276
|
-
# get the right branch to start the log revision range with. If origin/develop
|
277
|
-
# is a merge commit, we need the right parent of the merge.
|
278
|
-
#
|
279
|
-
# The goal is to get output like this:
|
280
|
-
#
|
281
|
-
# * 5be2a77 (HEAD, develop) PR #1269. Merge branch floatplane/feature/categories into develop.
|
282
|
-
# |\
|
283
|
-
# | * 2242141 (floatplane/feature/categories, feature/categories) Process CR feedback. Remove StaticCreatorListDataSource, will just rework Streamed* version to meet needs instead.
|
284
|
-
# | * d7cf231 Implement StaticCreatorListDataSource for categories, rename CreatorListDataSource => StreamedCreatorListDataSource
|
285
|
-
# | * ef034d0 Don't animate profile pic transitions when we're re-using a cell and needing to replace someone else's picture. Only animate from the blank thumbnail to an actual picture.
|
286
|
-
# | * 25cda8b Refactor CreatorListViewController.
|
287
|
-
# | * 682b7ba Adjust search dialog size and default position. Remove temp close button. Stub categories into search dialog.
|
288
|
-
# | * e8ba0b1 Rename CollaboratorsListViewController => CreatorListViewController. Add CollaboratorListViewController as a subclass of CreatorListViewController, will refactor behavior into it in future commits.
|
289
|
-
# | * e901256 Make dismissWithBackgroundTouch work for all CustomModalDialogs, even those that don't set useCustomPopover. Fix latent bug in ApplicationInfoNavigationController's implementation of the same.
|
290
|
-
# |/
|
291
|
-
# * 8d5ecbc (origin/develop, origin/HEAD) Merge branch 'feature/schemaUpgradeUtils' into develop
|
292
|
-
#
|
293
|
-
# where the log stops at origin/develop, no matter whether it's a merge commit or not.
|
294
|
-
#
|
295
|
-
origin_parent = `git rev-list --abbrev-commit --parents -n 1 origin/#{target_branch}`.split().last
|
296
|
-
run "git log --graph --decorate --pretty=oneline --abbrev-commit --color #{target_branch} #{origin_parent}..#{target_branch}"
|
297
|
-
|
298
|
-
def get_char
|
299
|
-
state = `stty -g`
|
300
|
-
`stty raw -echo -icanon isig`
|
301
|
-
|
302
|
-
STDIN.getc.chr
|
303
|
-
ensure
|
304
|
-
`stty #{state}`
|
305
|
-
puts ""
|
306
|
-
end
|
229
|
+
# Wait for the child
|
230
|
+
Process.wait child
|
307
231
|
|
308
|
-
print "Do you want to proceed with the merge (y/n)? ".cyan
|
309
|
-
if get_char.downcase == 'y'
|
310
|
-
puts "Pushing changes to #{target_remote}"
|
311
|
-
run "git push #{target_remote} #{target_branch}"
|
312
|
-
print "Do you want to delete the feature branch (y/n)? ".cyan
|
313
|
-
if get_char.downcase == 'y'
|
314
|
-
run "git push #{source_remote} :#{source_branch}"
|
315
|
-
run "git branch -d #{source_branch}"
|
316
|
-
end
|
317
|
-
puts "Merge complete!".cyan
|
318
|
-
else
|
319
|
-
puts "Undoing local merge"
|
320
|
-
run "git reset --hard #{target_remote}/#{target_branch}"
|
321
232
|
end
|
data/lib/git_pr/diff.rb
ADDED
data/lib/git_pr/github.rb
CHANGED
@@ -15,8 +15,6 @@ module GitPr
|
|
15
15
|
begin
|
16
16
|
client.user
|
17
17
|
rescue
|
18
|
-
n.delete NETRC_KEY
|
19
|
-
n.save
|
20
18
|
return false
|
21
19
|
end
|
22
20
|
return true
|
@@ -31,7 +29,7 @@ module GitPr
|
|
31
29
|
unless user
|
32
30
|
print "Enter your github username: "
|
33
31
|
user = STDIN.gets.chomp!
|
34
|
-
print "
|
32
|
+
print "Enter github password for #{user} (never stored): "
|
35
33
|
pass = STDIN.noecho(&:gets).chomp!
|
36
34
|
puts "\n"
|
37
35
|
end
|
@@ -80,5 +78,62 @@ module GitPr
|
|
80
78
|
end
|
81
79
|
end
|
82
80
|
|
81
|
+
def self.determine_project_name_from_command_line git, project_name
|
82
|
+
# Figure out what GitHub project we're dealing with. First, did they pass us a name of
|
83
|
+
# an existing remote, or did they pass a GitHub project?
|
84
|
+
project_remote = git.remotes.find { |x| x.name == project_name }
|
85
|
+
if project_remote
|
86
|
+
url_match = project_remote.url.match "^git@github.com:(.*).git"
|
87
|
+
unless url_match
|
88
|
+
puts "Specified project '#{options.project}' is not a GitHub remote.".red
|
89
|
+
exit -1
|
90
|
+
end
|
91
|
+
github_project = url_match[1]
|
92
|
+
else
|
93
|
+
github_project = project_name
|
94
|
+
end
|
95
|
+
|
96
|
+
begin
|
97
|
+
github_repo = Octokit.repo "#{github_project}"
|
98
|
+
rescue
|
99
|
+
puts "Project `#{github_project}` is not a valid GitHub project.".red
|
100
|
+
exit -1
|
101
|
+
end
|
102
|
+
|
103
|
+
github_project
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.query_for_pull_to_merge(pulls)
|
107
|
+
puts
|
108
|
+
pull_to_merge = nil
|
109
|
+
choose do |menu|
|
110
|
+
menu.prompt = "Select PR to merge: "
|
111
|
+
pulls.each do |pull|
|
112
|
+
menu.choice(pull_summary(pull)) { pull_to_merge = pull }
|
113
|
+
end
|
114
|
+
menu.choice(:Quit, "Exit program.") { exit }
|
115
|
+
end
|
116
|
+
pull_to_merge
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.find_or_prompt_for_pull_request github_project, pull_request
|
120
|
+
pulls = Octokit.pulls "#{github_project}/pulls"
|
121
|
+
unless pulls.length > 0
|
122
|
+
puts "No open pull requests found for '#{github_project}'.".yellow
|
123
|
+
exit
|
124
|
+
end
|
125
|
+
if pull_request
|
126
|
+
pull_request = pull_request
|
127
|
+
pull = pulls.find { |p| p[:number] == pull_request }
|
128
|
+
unless pull
|
129
|
+
puts "Pull request #{pull_request} not found in project '#{github_project}'!".red
|
130
|
+
exit -1
|
131
|
+
end
|
132
|
+
else
|
133
|
+
pull = self.query_for_pull_to_merge pulls
|
134
|
+
end
|
135
|
+
pull
|
136
|
+
end
|
137
|
+
|
83
138
|
end
|
84
139
|
end
|
data/lib/git_pr/merge.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module GitPr
|
2
|
+
|
3
|
+
def self.ensure_remotes_for_pull_request git, pull
|
4
|
+
source_remote = GitPr.ensure_remote_for_project(git,
|
5
|
+
pull[:head][:user][:login],
|
6
|
+
pull[:head][:repo][:git_url],
|
7
|
+
pull[:head][:repo][:ssh_url])
|
8
|
+
|
9
|
+
target_remote = GitPr.ensure_remote_for_project(git,
|
10
|
+
pull[:base][:user][:login],
|
11
|
+
pull[:base][:repo][:git_url],
|
12
|
+
pull[:base][:repo][:ssh_url])
|
13
|
+
|
14
|
+
[source_remote, target_remote]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.merge_pull_cleanly git, pull
|
18
|
+
|
19
|
+
pull_number = pull[:number]
|
20
|
+
source_branch = pull[:head][:ref]
|
21
|
+
source_repo_ssh_url = pull[:head][:repo][:git_url]
|
22
|
+
source_repo_clone_url = pull[:head][:repo][:clone_url]
|
23
|
+
|
24
|
+
target_branch = pull[:base][:ref]
|
25
|
+
target_repo_ssh_url = pull[:base][:repo][:git_url]
|
26
|
+
target_repo_clone_url = pull[:base][:repo][:clone_url]
|
27
|
+
|
28
|
+
puts "Merging #{pull_summary(pull)}".cyan
|
29
|
+
puts "#{target_repo_ssh_url}/#{target_branch} <= #{source_repo_ssh_url}/#{source_branch}\n".cyan
|
30
|
+
|
31
|
+
# find or add a remote for the PR
|
32
|
+
source_remote, target_remote = self.ensure_remotes_for_pull_request git, pull
|
33
|
+
|
34
|
+
# Fetch latest changes from source & target remotes. Useful in case one of source or target
|
35
|
+
# branches doesn't exist locally yet, or if we've never pulled from one of the remotes.
|
36
|
+
puts "Fetching latest changes from '#{source_remote}'"
|
37
|
+
source_remote.fetch
|
38
|
+
unless target_remote.name == source_remote.name
|
39
|
+
puts "Fetching latest changes from '#{target_remote}'"
|
40
|
+
target_remote.fetch
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the target branch up to date
|
44
|
+
puts "Update branch '#{target_branch}' from remote"
|
45
|
+
GitPr.run_command "git checkout -q #{target_branch}"
|
46
|
+
GitPr.run_command "git pull --no-rebase --ff-only", :failure => lambda {
|
47
|
+
"Unable to update local target branch '#{target_branch}'. Please repair manually before continuing.".red
|
48
|
+
}
|
49
|
+
|
50
|
+
# If the local target branch differs from the remote target branch, they
|
51
|
+
# must be reconciled manually.
|
52
|
+
remote_target_branch = "#{target_remote}/#{target_branch}"
|
53
|
+
if git.diff("remotes/#{remote_target_branch}", target_branch).any?
|
54
|
+
puts "Local branch '#{target_branch}' differs from remote branch '#{remote_target_branch}'. Please reconcile before continuing.".red
|
55
|
+
exit -1
|
56
|
+
end
|
57
|
+
|
58
|
+
# If a local branch exists with the name source_branch, check that it has the
|
59
|
+
# same contents as the remote source branch. If not, it must be reconciled
|
60
|
+
# manually.
|
61
|
+
remote_source_branch = "#{source_remote}/#{source_branch}"
|
62
|
+
if git.is_branch? source_branch and
|
63
|
+
git.diff("remotes/#{remote_source_branch}", source_branch).any?
|
64
|
+
puts "Local branch '#{source_branch}' differs from remote branch '#{remote_source_branch}'. Please reconcile before continuing.".red
|
65
|
+
exit -1
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check out the remote source branch using a temporary branch name,
|
69
|
+
# failing if the temporary name already exists.
|
70
|
+
rebase_branch = "#{source_branch}-rebase"
|
71
|
+
puts "Create temporary branch '#{rebase_branch}'"
|
72
|
+
if git.is_branch? rebase_branch
|
73
|
+
puts "Local rebase branch '#{rebase_branch}' already exists. Please remove before continuing.".red
|
74
|
+
exit -1
|
75
|
+
end
|
76
|
+
GitPr.run_command "git checkout -q -b #{rebase_branch} #{remote_source_branch}"
|
77
|
+
|
78
|
+
# Add an at_exit handler to blow away the temp branch when we exit
|
79
|
+
at_exit do
|
80
|
+
if git.is_branch? rebase_branch
|
81
|
+
puts "Removing temporary branch #{rebase_branch}" if $verbose
|
82
|
+
GitPr.run_command "git checkout -q #{target_branch}"
|
83
|
+
GitPr.run_command "git branch -D #{rebase_branch}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Rebase the rebase branch on top of the target branch
|
88
|
+
puts "Rebasing '#{rebase_branch}' on top of '#{target_branch}'"
|
89
|
+
GitPr.run_command "git rebase #{target_branch} 2>&1", :failure => lambda {
|
90
|
+
GitPr.run_command "git rebase --abort"
|
91
|
+
|
92
|
+
puts "Unable to automatically rebase #{remote_source_branch} on top of #{target_branch}. Rebase manually and push before trying again.".red
|
93
|
+
puts "Run: " + "git checkout #{source_branch}".yellow
|
94
|
+
puts " " + "git rebase #{target_branch}".yellow + " and fix up any conflicts."
|
95
|
+
puts " " + "git push -f".yellow
|
96
|
+
}
|
97
|
+
|
98
|
+
# Force push the rebased branch to the source remote.
|
99
|
+
puts "Pushing changes from '#{rebase_branch}' to '#{source_remote.name}/#{source_branch}'"
|
100
|
+
GitPr.run_command "git push -f #{source_remote.name} HEAD:#{source_branch} 2>&1"
|
101
|
+
|
102
|
+
# Merge the source branch into the target. Use --no-ff so that an explicit
|
103
|
+
# merge commit is created.
|
104
|
+
puts "Merging changes from '#{rebase_branch}' to '#{target_branch}'"
|
105
|
+
GitPr.run_command "git checkout -q #{target_branch}"
|
106
|
+
GitPr.run_command "git merge --no-ff #{rebase_branch} -m 'Merge #{pull_summary(pull)}'"
|
107
|
+
|
108
|
+
# Print a log of the merge with branch structure visible. Jump through hoops to
|
109
|
+
# get the right branch to start the log revision range with. If origin/develop
|
110
|
+
# is a merge commit, we need the right parent of the merge.
|
111
|
+
#
|
112
|
+
# The goal is to get output like this:
|
113
|
+
#
|
114
|
+
# * 5be2a77 (HEAD, develop) PR #1269. Merge branch floatplane/feature/categories into develop.
|
115
|
+
# |\
|
116
|
+
# | * 2242141 (floatplane/feature/categories, feature/categories) Process CR feedback. Remove StaticCreatorListDataSource, will just rework Streamed* version to meet needs instead.
|
117
|
+
# | * d7cf231 Implement StaticCreatorListDataSource for categories, rename CreatorListDataSource => StreamedCreatorListDataSource
|
118
|
+
# | * ef034d0 Don't animate profile pic transitions when we're re-using a cell and needing to replace someone else's picture. Only animate from the blank thumbnail to an actual picture.
|
119
|
+
# | * 25cda8b Refactor CreatorListViewController.
|
120
|
+
# | * 682b7ba Adjust search dialog size and default position. Remove temp close button. Stub categories into search dialog.
|
121
|
+
# | * e8ba0b1 Rename CollaboratorsListViewController => CreatorListViewController. Add CollaboratorListViewController as a subclass of CreatorListViewController, will refactor behavior into it in future commits.
|
122
|
+
# | * e901256 Make dismissWithBackgroundTouch work for all CustomModalDialogs, even those that don't set useCustomPopover. Fix latent bug in ApplicationInfoNavigationController's implementation of the same.
|
123
|
+
# |/
|
124
|
+
# * 8d5ecbc (origin/develop, origin/HEAD) Merge branch 'feature/schemaUpgradeUtils' into develop
|
125
|
+
#
|
126
|
+
# where the log stops at origin/develop, no matter whether it's a merge commit or not.
|
127
|
+
#
|
128
|
+
puts "\nVerify that the merge looks clean:\n".cyan
|
129
|
+
origin_parent = `git rev-list --abbrev-commit --parents -n 1 #{target_remote}/#{target_branch}`.split().last
|
130
|
+
GitPr.run_command "git log --graph --decorate --pretty=oneline --abbrev-commit --color #{target_branch} #{origin_parent}..#{target_branch}", :force_print_output => true
|
131
|
+
|
132
|
+
if GitPr.prompt "\nDo you want to proceed with the merge (y/n)? ".cyan
|
133
|
+
puts "Pushing changes to '#{target_remote}'"
|
134
|
+
GitPr.run_command "git push #{target_remote} #{target_branch} 2>&1"
|
135
|
+
if GitPr.prompt "\nDo you want to delete the feature branch (y/n)? ".cyan
|
136
|
+
source_branch_sha = git.branches["#{source_remote}/#{source_branch}"].gcommit.sha[0..6]
|
137
|
+
GitPr.run_command "git push #{source_remote} :#{source_branch} 2>&1"
|
138
|
+
if git.is_branch? source_branch
|
139
|
+
source_branch_sha = git.branches[source_branch].gcommit.sha[0..6]
|
140
|
+
GitPr.run_command "git branch -D #{source_branch}"
|
141
|
+
end
|
142
|
+
puts "Feature branch '#{source_branch}' deleted. To restore it, run: " + "git branch #{source_branch} #{source_branch_sha}".green
|
143
|
+
end
|
144
|
+
puts "\nMerge complete!".cyan
|
145
|
+
else
|
146
|
+
puts "\nUndoing local merge"
|
147
|
+
GitPr.run_command "git reset --hard #{target_remote}/#{target_branch}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/git_pr/version.rb
CHANGED
data/lib/git_pr.rb
CHANGED
@@ -1,7 +1,49 @@
|
|
1
1
|
require "git_pr/github"
|
2
2
|
require "git_pr/pull_request"
|
3
3
|
require "git_pr/version"
|
4
|
+
require "git_pr/merge"
|
5
|
+
require "git_pr/diff"
|
4
6
|
|
5
7
|
module GitPr
|
6
8
|
# Your code goes here...
|
9
|
+
|
10
|
+
def self.run_command(cmd,
|
11
|
+
args = {
|
12
|
+
:failure => lambda {},
|
13
|
+
:force_print_output => false
|
14
|
+
})
|
15
|
+
puts cmd.green if $verbose
|
16
|
+
result = `#{cmd}`
|
17
|
+
puts result if $verbose || args[:force_print_output]
|
18
|
+
puts '' if $verbose
|
19
|
+
if $?.exitstatus != 0
|
20
|
+
args[:failure].call()
|
21
|
+
exit -1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get_char
|
26
|
+
state = `stty -g`
|
27
|
+
`stty raw -echo -icanon isig`
|
28
|
+
|
29
|
+
STDIN.getc.chr
|
30
|
+
ensure
|
31
|
+
`stty #{state}`
|
32
|
+
puts ""
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.prompt prompt
|
36
|
+
print prompt
|
37
|
+
return GitPr.get_char.downcase == 'y'
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.ensure_remote_for_project git, username, ssh_url, git_url
|
41
|
+
# find or add a remote for the PR
|
42
|
+
remote = git.remotes.find { |r| [git_url, ssh_url].include? r.url }
|
43
|
+
unless remote
|
44
|
+
puts "Adding remote '#{username}' from #{ssh_url}"
|
45
|
+
remote = git.add_remote username, ssh_url
|
46
|
+
end
|
47
|
+
remote
|
48
|
+
end
|
7
49
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_pr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Sharon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -138,7 +138,9 @@ files:
|
|
138
138
|
- bin/git-pr
|
139
139
|
- git_pr.gemspec
|
140
140
|
- lib/git_pr.rb
|
141
|
+
- lib/git_pr/diff.rb
|
141
142
|
- lib/git_pr/github.rb
|
143
|
+
- lib/git_pr/merge.rb
|
142
144
|
- lib/git_pr/pull_request.rb
|
143
145
|
- lib/git_pr/version.rb
|
144
146
|
homepage: ''
|
@@ -156,9 +158,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
156
158
|
version: '0'
|
157
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
160
|
requirements:
|
159
|
-
- - '
|
161
|
+
- - '>'
|
160
162
|
- !ruby/object:Gem::Version
|
161
|
-
version:
|
163
|
+
version: 1.3.1
|
162
164
|
requirements: []
|
163
165
|
rubyforge_project:
|
164
166
|
rubygems_version: 2.0.14
|