git_pr 0.0.2 → 0.0.3.beta1
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.
- 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
|