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
18 import github
::github_curl
24 fun json_as_a
: JsonArray do return self.as(JsonArray)
26 fun json_as_map
: JsonObject do return self.as(JsonObject)
29 redef class GithubCurl
30 # Get a given pull request (PR)
31 fun getpr
(repo
: String, number
: Int): nullable JsonObject
33 var ir
= get_and_check
("https://api.github.com/repos/{repo}/issues/{number}")
34 var irm
= ir
.json_as_map
35 if not irm
.has_key
("pull_request") then return null
36 var pr
= get_and_check
("https://api.github.com/repos/{repo}/pulls/{number}")
37 var prm
= pr
.json_as_map
38 var sha
= prm
["head"].json_as_map
["sha"].to_s
39 var statuses
= get_and_check
("https://api.github.com/repos/{repo}/commits/{sha}/status")
40 statuses
= statuses
.json_as_map
41 prm
["statuses"] = statuses
42 print
"{prm["title"].to_s}: by {prm["user"].json_as_map["login"].to_s} (# {prm["number"].to_s})"
43 var mergeable
= prm
["mergeable"]
44 if mergeable
!= null then
45 print
"\tmergeable: {mergeable.to_s}"
47 print
"\tmergeable: unknown"
49 var state
= statuses
["state"]
51 print
"\tstatus: not tested"
53 print
"\tstatus: {state}"
54 var sts
= statuses
["statuses"].json_as_a
57 var ctx
= st
["context"].to_s
58 state
= st
["state"].to_s
59 print
"\tstatus {ctx}: {state}"
60 prm
["status-{ctx}"] = state
66 # Get reviewers of a PR
67 fun getrev
(repo
: String, pr
: JsonObject): Array[String]
69 var number
= pr
["number"].as(Int)
70 var user
= pr
["user"].json_as_map
["login"].as(String)
71 var comments
= new Array[nullable Object]
72 comments
.add_all
(get_and_check
("https://api.github.com/repos/{repo}/issues/{number}/comments").json_as_a
)
73 comments
.add_all
(get_and_check
("https://api.github.com/repos/{repo}/pulls/{number}/comments").json_as_a
)
74 var logins
= new Array[String]
76 var cm
= c
.json_as_map
77 var l
= cm
["user"].json_as_map
["login"]
79 if l
!= user
and not logins
.has
(l
) then logins
.add
(l
)
81 var res
= new Array[String]
83 var u
= get_and_check
("https://api.github.com/users/{l}").json_as_map
84 if not u
.has_key
("name") or u
["name"] == null or not u
.has_key
("email")or u
["email"] == null then
85 print
"No public name/email for user {l}"
88 var r
= "{u["name"].to_s} <{u["email"].to_s}>"
97 if "NIT_TESTING".environ
== "true" then exit
0
99 var opt_repo
= new OptionString("Repository (e.g. nitlang/nit)", "-r", "--repo")
100 var opt_auth
= new OptionString("OAuth token", "--auth")
101 var opt_query
= new OptionString("Query to get issues (e.g. label=ok_will_merge)", "-q", "--query")
102 var opt_keepgoing
= new OptionBool("Skip merge conflicts", "-k", "--keep-going")
103 var opt_all
= new OptionBool("Merge all", "-a", "--all")
104 var opt_status
= new OptionArray("A status context that must be \"success\
" (e.g. default)", "--status")
106 var usage
= new Buffer
107 usage
.append
"Usage: github_merge [OPTION]... <PR number...>\n"
108 usage
.append
"Query the Github PR API to perform a merge."
110 var config
= new Config
111 config
.tool_description
= usage
.write_to_string
112 config
.add_option
(opt_repo
, opt_auth
, opt_query
, opt_status
, opt_all
, opt_keepgoing
)
114 config
.parse_options
(sys
.args
)
116 if config
.opt_help
.value
then
121 var args
= config
.args
123 var auth
= opt_auth
.value
or else ""
124 if auth
== "" then auth
= get_github_oauth
126 print
"Warning: no github oauth token, you can configure one with"
127 print
" git config --add github.oauthtoken MYOAUTHTOKEN"
130 var repo
= opt_repo
.value
or else "nitlang/nit"
132 var query
= opt_query
.value
or else "labels=ok_will_merge"
134 var curl
= new GithubCurl(auth
, "Merge-o-matic (nitlang/nit)")
136 if args
.is_empty
then
137 # Without args, list `ok_will_merge`
138 var x
= curl
.get_and_check
("https://api.github.com/repos/{repo}/issues?{query}")
139 var list
= new Array[String]
140 for y
in x
.json_as_a
do
141 var number
= y
.json_as_map
["number"].as(Int)
142 var pr
= curl
.getpr
(repo
, number
)
143 if pr
== null then continue
144 for ctx
in opt_status
.value
do
145 if pr
.get_or_null
("status-{ctx}") != "success" then
146 print
"No \"success\
" for {ctx}. Skip."
153 if not opt_all
.value
then return
158 # With a arg, merge the PR
159 var number
= arg
.to_i
160 var pr
= curl
.getpr
(repo
, number
)
162 print
"Not a PR: {number}"
165 var revs
= curl
.getrev
(repo
, pr
)
167 var mergemsg
= new Template
168 mergemsg
.add
"Merge: {pr["title"].to_s}\n\n"
169 mergemsg
.add
"{pr["body"].to_s}\n\n"
170 mergemsg
.add
"Pull-Request: #{pr["number"].to_s}\n"
172 mergemsg
.add
"Reviewed-by: {r}\n"
174 mergemsg
.write_to_file
("mergemsg")
176 var sha
= pr
["head"].json_as_map
["sha"].as(String)
177 if system
("git show -s --pretty=format:%h {sha}") != 0 then
178 print
"Commit {sha} not in local repository; did you fetch github?"
181 if system
("git merge-base --is-ancestor {sha} HEAD") == 0 then
182 print
"Is already merged."
185 if system
("git merge --no-ff --no-commit {sha}") != 0 then
186 if opt_keepgoing
.value
then
187 system
("git reset --merge")
190 system
("cp mergemsg `git rev-parse --git-dir`/MERGE_MSG")
191 print
"Problem during merge... Let's do the commit manually."
194 system
("git commit -F mergemsg")
195 print
"The merge is made"
196 mergemsg
.write_to
(stdout
)