1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Query the Github PR API to perform a merge
24 # Get a given pull request and its state
25 private fun get_pull_with_state
(repo
: String, number
: Int): nullable PullState do
26 var pull
= get_pull
(repo
, number
)
27 if not pull
isa PullRequest then return null
29 var statuses
= get_commit_status
(repo
, pull
.head
.sha
)
30 if not statuses
isa CommitStatus then return null
32 return new PullState(pull
, statuses
)
35 # Get reviewers of a PR
36 private fun get_pull_reviewers
(repo
: String, pull
: PullRequest): Array[String] do
37 var user
= pull
.user
.as(not null).login
39 var comments
= new Array[Comment]
40 comments
.add_all
(get_issue_comments
(repo
, pull
.number
))
41 comments
.add_all
(get_pull_comments
(repo
, pull
.number
))
43 var logins
= new Array[String]
44 for comment
in comments
do
45 var login
= comment
.user
.login
46 if login
!= user
and not logins
.has
(login
) then logins
.add
(login
)
48 var res
= new Array[String]
49 for login
in logins
do
50 var rev
= get_user
(login
)
51 if rev
== null or rev
.name
== null or rev
.email
== null then
52 print
"No public name/email for user {login}"
55 res
.add
"{rev.name or else "N/A"} <{rev.email or else "N/A"}>"
61 private class PullState
63 var status
: CommitStatus
67 s
.append
"{pull.title}: by {pull.user.as(not null).login} (# {pull.number})\n"
68 s
.append
"\tmergeable: {pull.mergeable or else "unknown"}\n"
69 s
.append
"\tstatus: {status.state or else "not tested"}\n"
70 for sub
in status
.statuses
do
71 s
.append
"\tstatus {sub.context or else "N/A"}: {sub.state or else "N/A"}\n"
73 return s
.write_to_string
76 fun state_for
(context
: String): nullable String do
77 for sub
in status
.statuses
do
78 if sub
.context
== context
then return sub
.state
84 if "NIT_TESTING".environ
== "true" then exit
0
86 var opt_repo
= new OptionString("Repository (e.g. nitlang/nit)", "-r", "--repo")
87 var opt_auth
= new OptionString("OAuth token", "--auth")
88 var opt_query
= new OptionString("Query to get issues (e.g. label:ok_will_merge)", "-q", "--query")
89 var opt_keepgoing
= new OptionBool("Skip merge conflicts", "-k", "--keep-going")
90 var opt_all
= new OptionBool("Merge all", "-a", "--all")
91 var opt_status
= new OptionArray("A status context that must be \"success\
" (e.g. default)", "--status")
93 var usage
= new Buffer
94 usage
.append
"Usage: github_merge [OPTION]... <PR number...>\n"
95 usage
.append
"Query the Github PR API to perform a merge."
97 var config
= new Config
98 config
.tool_description
= usage
.write_to_string
99 config
.add_option
(opt_repo
, opt_auth
, opt_query
, opt_status
, opt_all
, opt_keepgoing
)
101 config
.parse_options
(sys
.args
)
103 if config
.opt_help
.value
then
108 var args
= config
.args
110 var auth
= opt_auth
.value
or else ""
111 if auth
== "" then auth
= get_github_oauth
113 print
"Warning: no github oauth token, you can configure one with"
114 print
" git config --add github.oauthtoken MYOAUTHTOKEN"
117 var repo
= opt_repo
.value
or else "nitlang/nit"
119 var query
= opt_query
.value
or else "labels=ok_will_merge"
121 var api
= new GithubAPI(auth
, "Merge-o-matic (nitlang/nit)")
123 if args
.is_empty
then
124 # Without args, list `ok_will_merge`
125 var issues
= api
.search_repo_issues
(repo
, query
)
126 if issues
== null or issues
.items
.is_empty
then
127 print
"No issues for query `{query}`."
130 var list
= new Array[String]
131 for issue
in issues
.as(not null).items
do
132 if not issue
isa Issue then continue
133 if not issue
.is_pull_request
then continue
135 var state
= api
.get_pull_with_state
(repo
, issue
.number
)
136 if not state
isa PullState then continue
139 for ctx
in opt_status
.value
do
140 if state
.state_for
(ctx
) != "success" then
141 print
"No \"success\
" for {ctx}. Skip."
146 list
.add issue
.number
.to_s
149 if not opt_all
.value
then return
154 # With a arg, merge the PR
155 var number
= arg
.to_i
156 var pull
= api
.get_pull
(repo
, number
)
158 print
"Not a PR: {number}"
162 var revs
= api
.get_pull_reviewers
(repo
, pull
)
164 var mergemsg
= new Template
165 mergemsg
.add
"Merge: {pull.title}\n\n"
166 mergemsg
.add
"{pull.body or else "N/A"}\n\n"
167 mergemsg
.add
"Pull-Request: #{pull.number}\n"
169 mergemsg
.add
"Reviewed-by: {rev}\n"
171 mergemsg
.write_to_file
("mergemsg")
173 var sha
= pull
.head
.sha
174 if system
("git show -s --pretty=format:%h {sha}") != 0 then
175 print
"Commit {sha} not in local repository; did you fetch github?"
178 if system
("git merge-base --is-ancestor {sha} HEAD") == 0 then
179 print
"Is already merged."
182 if system
("git merge --no-ff --no-commit {sha}") != 0 then
183 if opt_keepgoing
.value
then
184 system
("git reset --merge")
187 system
("cp mergemsg `git rev-parse --git-dir`/MERGE_MSG")
188 print
"Problem during merge... Let's do the commit manually."
191 system
("git commit -F mergemsg")
192 print
"The merge is made"
193 mergemsg
.write_to
(stdout
)