Skip to content

Commit 222d110

Browse files
committed
Merge branch 'pr-merge' into master
2 parents 705360d + 8af17bc commit 222d110

File tree

7 files changed

+385
-11
lines changed

7 files changed

+385
-11
lines changed

commands/help.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ These GitHub commands are provided by hub:
222222
fork Make a fork of a remote repository on GitHub and add as remote
223223
gist Make a gist
224224
issue List or create GitHub issues
225-
pr List or checkout GitHub pull requests
225+
pr Manage GitHub pull requests
226226
pull-request Open a pull request on GitHub
227227
release List or create GitHub releases
228228
sync Fetch git objects from upstream and update branches

commands/merge.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ change the state of the pull request. However, the pull request will get
1919
auto-closed and marked as "merged" as soon as the newly created merge commit is
2020
pushed to the default branch of the remote repository.
2121
22+
To merge a pull request remotely, use ''hub pr merge''.
23+
2224
## Examples:
2325
$ hub merge https://github.com/jingweno/gh/pull/73
2426
> git fetch origin refs/pull/73/head
2527
> git merge FETCH_HEAD --no-ff -m "Merge pull request #73 from jingweno/feature..."
2628
2729
## See also:
2830
29-
hub-checkout(1), hub(1), git-merge(1)
31+
hub-pr(1), hub-checkout(1), hub(1), git-merge(1)
3032
`,
3133
}
3234

commands/pr.go

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pr list [-s <STATE>] [-h <HEAD>] [-b <BASE>] [-o <SORT_KEY> [-^]] [-f <FORMAT>]
1919
pr checkout <PR-NUMBER> [<BRANCH>]
2020
pr show [-uc] [-f <FORMAT>] [-h <HEAD>]
2121
pr show [-uc] [-f <FORMAT>] <PR-NUMBER>
22+
pr merge [-d] [--squash | --rebase] <PR-NUMBER> [-m <MESSAGE> | -F <FILE>] [--head-sha <COMMIT-SHA>]
2223
`,
2324
Long: `Manage GitHub Pull Requests for the current repository.
2425
@@ -38,6 +39,11 @@ pr show [-uc] [-f <FORMAT>] <PR-NUMBER>
3839
the current branch name. With ''--format'', print information about the
3940
pull request instead of opening it.
4041
42+
* _merge_:
43+
Merge a pull request in the current repository remotely. Select an
44+
alternate merge method with ''--squash'' or ''--rebase''. Change the
45+
commit subject and body with ''--message'' or ''--file''.
46+
4147
## Options:
4248
4349
-s, --state <STATE>
@@ -146,6 +152,29 @@ pr show [-uc] [-f <FORMAT>] <PR-NUMBER>
146152
-c, --copy
147153
Put the pull request URL to clipboard instead of opening it.
148154
155+
-m, --message <MESSAGE>
156+
The text up to the first blank line in <MESSAGE> is treated as the commit
157+
subject for the merge commit, and the rest is used as commit body.
158+
159+
When multiple ''--message'' are passed, their values are concatenated with a
160+
blank line in-between.
161+
162+
-F, --file <FILE>
163+
Read the subject and body for the merge commit from <FILE>. Pass "-" to read
164+
from standard input instead. See ''--message'' for the formatting rules.
165+
166+
--head-sha <COMMIT-SHA>
167+
Ensure that the head of the pull request matches the commit SHA when merging.
168+
169+
--squash
170+
Squash commits instead of creating a merge commit when merging a pull request.
171+
172+
--rebase
173+
Rebase commits on top of the base branch when merging a pull request.
174+
175+
-d, --delete-branch
176+
Delete the head branch after successfully merging a pull request.
177+
149178
## See also:
150179
151180
hub-issue(1), hub-pull-request(1), hub(1)
@@ -173,14 +202,28 @@ hub-issue(1), hub-pull-request(1), hub(1)
173202
-c, --copy
174203
-f, --format FORMAT
175204
--color
176-
`,
205+
`,
206+
}
207+
208+
cmdMergePr = &Command{
209+
Key: "merge",
210+
Run: mergePr,
211+
KnownFlags: `
212+
-m, --message MESSAGE
213+
-F, --file FILE
214+
--head-sha COMMIT
215+
--squash
216+
--rebase
217+
-d, --delete-branch
218+
`,
177219
}
178220
)
179221

180222
func init() {
181223
cmdPr.Use(cmdListPulls)
182224
cmdPr.Use(cmdCheckoutPr)
183225
cmdPr.Use(cmdShowPr)
226+
cmdPr.Use(cmdMergePr)
184227
CmdRunner.Use(cmdPr)
185228
}
186229

@@ -413,6 +456,70 @@ func deducePushTarget(branch *github.Branch, owner string) (*github.Project, err
413456
return remote.Project()
414457
}
415458

459+
func mergePr(command *Command, args *Args) {
460+
words := args.Words()
461+
if len(words) == 0 {
462+
utils.Check(fmt.Errorf("Error: No pull request number given"))
463+
}
464+
465+
prNumber, err := strconv.Atoi(words[0])
466+
utils.Check(err)
467+
468+
params := map[string]interface{}{
469+
"merge_method": "merge",
470+
}
471+
if args.Flag.Bool("--squash") {
472+
params["merge_method"] = "squash"
473+
}
474+
if args.Flag.Bool("--rebase") {
475+
params["merge_method"] = "rebase"
476+
}
477+
478+
msgs := args.Flag.AllValues("--message")
479+
if len(msgs) > 0 {
480+
params["commit_title"] = msgs[0]
481+
params["commit_message"] = strings.Join(msgs[1:], "\n\n")
482+
} else if args.Flag.HasReceived("--file") {
483+
content, err := msgFromFile(args.Flag.Value("--file"))
484+
utils.Check(err)
485+
params["commit_title"], params["commit_message"] = github.SplitTitleBody(content)
486+
}
487+
488+
if headSHA := args.Flag.Value("--head-sha"); headSHA != "" {
489+
params["sha"] = args.Flag.Value("--head-sha")
490+
}
491+
492+
localRepo, err := github.LocalRepo()
493+
utils.Check(err)
494+
495+
project, err := localRepo.MainProject()
496+
utils.Check(err)
497+
498+
args.NoForward()
499+
if args.Noop {
500+
ui.Printf("Would merge pull request #%d for %s\n", prNumber, project)
501+
return
502+
}
503+
504+
gh := github.NewClient(project.Host)
505+
_, err = gh.MergePullRequest(project, prNumber, params)
506+
utils.Check(err)
507+
508+
if !args.Flag.Bool("--delete-branch") {
509+
return
510+
}
511+
512+
pr, err := gh.PullRequest(project, strconv.Itoa(prNumber))
513+
utils.Check(err)
514+
if !pr.IsSameRepo() {
515+
return
516+
}
517+
518+
branchName := pr.Head.Ref
519+
err = gh.DeleteBranch(project, branchName)
520+
utils.Check(err)
521+
}
522+
416523
func formatPullRequest(pr github.PullRequest, format string, colorize bool) string {
417524
placeholders := formatIssuePlaceholders(github.Issue(pr), colorize)
418525
delete(placeholders, "NC")

features/pr-merge.feature

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
Feature: hub pr merge
2+
Background:
3+
Given I am in "git://github.com/friederbluemle/hub.git" git repo
4+
And I am "friederbluemle" on github.com with OAuth token "OTOKEN"
5+
6+
Scenario: Default merge
7+
Given the GitHub API server:
8+
"""
9+
put('/repos/friederbluemle/hub/pulls/12/merge'){
10+
assert :merge_method => "merge",
11+
:commit_title => :no,
12+
:commit_message => :no,
13+
:sha => :no
14+
15+
json :merged => true,
16+
:sha => "MERGESHA",
17+
:message => "All done!"
18+
}
19+
"""
20+
When I successfully run `hub pr merge 12`
21+
Then the output should contain exactly ""
22+
23+
Scenario: Squash merge
24+
Given the GitHub API server:
25+
"""
26+
put('/repos/friederbluemle/hub/pulls/12/merge'){
27+
assert :merge_method => "squash",
28+
:commit_title => :no,
29+
:commit_message => :no,
30+
:sha => :no
31+
32+
json :merged => true,
33+
:sha => "MERGESHA",
34+
:message => "All done!"
35+
}
36+
"""
37+
When I successfully run `hub pr merge --squash 12`
38+
Then the output should contain exactly ""
39+
40+
Scenario: Merge with rebase
41+
Given the GitHub API server:
42+
"""
43+
put('/repos/friederbluemle/hub/pulls/12/merge'){
44+
assert :merge_method => "rebase",
45+
:commit_title => :no,
46+
:commit_message => :no,
47+
:sha => :no
48+
49+
json :merged => true,
50+
:sha => "MERGESHA",
51+
:message => "All done!"
52+
}
53+
"""
54+
When I successfully run `hub pr merge --rebase 12`
55+
Then the output should contain exactly ""
56+
57+
Scenario: Merge with title
58+
Given the GitHub API server:
59+
"""
60+
put('/repos/friederbluemle/hub/pulls/12/merge'){
61+
assert :commit_title => "mytitle",
62+
:commit_message => ""
63+
64+
json :merged => true,
65+
:sha => "MERGESHA",
66+
:message => "All done!"
67+
}
68+
"""
69+
When I successfully run `hub pr merge 12 -m mytitle`
70+
Then the output should contain exactly ""
71+
72+
Scenario: Merge with title and body
73+
Given the GitHub API server:
74+
"""
75+
put('/repos/friederbluemle/hub/pulls/12/merge'){
76+
assert :commit_title => "mytitle",
77+
:commit_message => "msg1\n\nmsg2"
78+
79+
json :merged => true,
80+
:sha => "MERGESHA",
81+
:message => "All done!"
82+
}
83+
"""
84+
When I successfully run `hub pr merge 12 -m mytitle -m msg1 -m msg2`
85+
Then the output should contain exactly ""
86+
87+
Scenario: Merge with title and body from file
88+
Given a file named "msg.txt" with:
89+
"""
90+
mytitle
91+
92+
msg1
93+
94+
msg2
95+
"""
96+
Given the GitHub API server:
97+
"""
98+
put('/repos/friederbluemle/hub/pulls/12/merge'){
99+
assert :commit_title => "mytitle",
100+
:commit_message => "msg1\n\nmsg2"
101+
102+
json :merged => true,
103+
:sha => "MERGESHA",
104+
:message => "All done!"
105+
}
106+
"""
107+
When I successfully run `hub pr merge 12 -F msg.txt`
108+
Then the output should contain exactly ""
109+
110+
Scenario: Merge with head SHA
111+
Given the GitHub API server:
112+
"""
113+
put('/repos/friederbluemle/hub/pulls/12/merge'){
114+
assert :sha => "MYSHA"
115+
116+
json :merged => true,
117+
:sha => "MERGESHA",
118+
:message => "All done!"
119+
}
120+
"""
121+
When I successfully run `hub pr merge 12 --head-sha MYSHA`
122+
Then the output should contain exactly ""
123+
124+
Scenario: Delete branch
125+
Given the GitHub API server:
126+
"""
127+
put('/repos/friederbluemle/hub/pulls/12/merge'){
128+
json :merged => true,
129+
:sha => "MERGESHA",
130+
:message => "All done!"
131+
}
132+
133+
get('/repos/friederbluemle/hub/pulls/12'){
134+
json \
135+
:number => 12,
136+
:state => "merged",
137+
:base => {
138+
:ref => "main",
139+
:label => "friederbluemle:main",
140+
:repo => { :owner => { :login => "friederbluemle" } }
141+
},
142+
:head => {
143+
:ref => "patch-1",
144+
:label => "friederbluemle:patch-1",
145+
:repo => { :owner => { :login => "friederbluemle" } }
146+
}
147+
}
148+
149+
delete('/repos/friederbluemle/hub/git/refs/heads/patch-1'){
150+
status 204
151+
}
152+
"""
153+
When I successfully run `hub pr merge -d 12`
154+
Then the output should contain exactly ""
155+
156+
Scenario: Delete already deleted branch
157+
Given the GitHub API server:
158+
"""
159+
put('/repos/friederbluemle/hub/pulls/12/merge'){
160+
json :merged => true,
161+
:sha => "MERGESHA",
162+
:message => "All done!"
163+
}
164+
165+
get('/repos/friederbluemle/hub/pulls/12'){
166+
json \
167+
:number => 12,
168+
:state => "merged",
169+
:base => {
170+
:ref => "main",
171+
:label => "friederbluemle:main",
172+
:repo => { :owner => { :login => "friederbluemle" } }
173+
},
174+
:head => {
175+
:ref => "patch-1",
176+
:label => "friederbluemle:patch-1",
177+
:repo => { :owner => { :login => "friederbluemle" } }
178+
}
179+
}
180+
181+
delete('/repos/friederbluemle/hub/git/refs/heads/patch-1'){
182+
status 422
183+
json :message => "Invalid branch name"
184+
}
185+
"""
186+
When I successfully run `hub pr merge -d 12`
187+
Then the output should contain exactly ""
188+
189+
Scenario: Delete branch on cross-repo PR
190+
Given the GitHub API server:
191+
"""
192+
put('/repos/friederbluemle/hub/pulls/12/merge'){
193+
json :merged => true,
194+
:sha => "MERGESHA",
195+
:message => "All done!"
196+
}
197+
198+
get('/repos/friederbluemle/hub/pulls/12'){
199+
json \
200+
:number => 12,
201+
:state => "merged",
202+
:base => {
203+
:ref => "main",
204+
:label => "friederbluemle:main",
205+
:repo => { :owner => { :login => "friederbluemle" } }
206+
},
207+
:head => {
208+
:ref => "patch-1",
209+
:label => "monalisa:patch-1",
210+
:repo => { :owner => { :login => "monalisa" } }
211+
}
212+
}
213+
"""
214+
When I successfully run `hub pr merge -d 12`
215+
Then the output should contain exactly ""

0 commit comments

Comments
 (0)