examples: annotate examples
[nit.git] / contrib / github_merge.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Query the Github PR API to perform a merge
16 module github_merge
17
18 import github::github_curl
19 import template
20 import opts
21
22 redef class Object
23 # Factorize cast
24 fun json_as_a: JsonArray do return self.as(JsonArray)
25 # Factorize cast
26 fun json_as_map: JsonObject do return self.as(JsonObject)
27 end
28
29 redef class GithubCurl
30 # Get a given pull request (PR)
31 fun getpr(repo: String, number: Int): nullable JsonObject
32 do
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}"
46 else
47 print "\tmergeable: unknown"
48 end
49 var state = statuses["state"]
50 if state == null then
51 print "\tstatus: not tested"
52 else
53 print "\tstatus: {state}"
54 var sts = statuses["statuses"].json_as_a
55 for st in sts do
56 st = st.json_as_map
57 var ctx = st["context"].to_s
58 state = st["state"].to_s
59 print "\tstatus {ctx}: {state}"
60 prm["status-{ctx}"] = state
61 end
62 end
63 return prm
64 end
65
66 # Get reviewers of a PR
67 fun getrev(repo: String, pr: JsonObject): Array[String]
68 do
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]
75 for c in comments do
76 var cm = c.json_as_map
77 var l = cm["user"].json_as_map["login"]
78 assert l isa String
79 if l != user and not logins.has(l) then logins.add(l)
80 end
81 var res = new Array[String]
82 for l in logins do
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}"
86 continue
87 end
88 var r = "{u["name"].to_s} <{u["email"].to_s}>"
89 res.add r
90
91 end
92 return res
93 end
94
95 end
96
97 if "NIT_TESTING".environ == "true" then exit 0
98
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")
105 var opts = new OptionContext
106 opts.add_option(opt_repo, opt_auth, opt_query, opt_status, opt_all, opt_keepgoing)
107
108 opts.parse(sys.args)
109 var args = opts.rest
110
111 var auth = opt_auth.value or else ""
112 if auth == "" then auth = get_github_oauth
113 if auth == "" then
114 print "Warning: no github oauth token, you can configure one with"
115 print " git config --add github.oauthtoken MYOAUTHTOKEN"
116 end
117
118 var repo = opt_repo.value or else "nitlang/nit"
119
120 var query = opt_query.value or else "labels=ok_will_merge"
121
122 var curl = new GithubCurl(auth, "Merge-o-matic (nitlang/nit)")
123
124 if args.is_empty then
125 # Without args, list `ok_will_merge`
126 var x = curl.get_and_check("https://api.github.com/repos/{repo}/issues?{query}")
127 var list = new Array[String]
128 for y in x.json_as_a do
129 var number = y.json_as_map["number"].as(Int)
130 var pr = curl.getpr(repo, number)
131 if pr == null then continue
132 for ctx in opt_status.value do
133 if pr.get_or_null("status-{ctx}") != "success" then
134 print "No \"success\" for {ctx}. Skip."
135 continue label
136 end
137 end
138 list.add number.to_s
139 end label
140
141 if not opt_all.value then return
142 args = list
143 end
144
145 for arg in args do
146 # With a arg, merge the PR
147 var number = arg.to_i
148 var pr = curl.getpr(repo, number)
149 if pr == null then
150 print "Not a PR: {number}"
151 return
152 end
153 var revs = curl.getrev(repo, pr)
154
155 var mergemsg = new Template
156 mergemsg.add "Merge: {pr["title"].to_s}\n\n"
157 mergemsg.add "{pr["body"].to_s}\n\n"
158 mergemsg.add "Pull-Request: #{pr["number"].to_s}\n"
159 for r in revs do
160 mergemsg.add "Reviewed-by: {r}\n"
161 end
162 mergemsg.write_to_file("mergemsg")
163
164 var sha = pr["head"].json_as_map["sha"].as(String)
165 if system("git show -s --pretty=format:%h {sha}") != 0 then
166 print "Commit {sha} not in local repository; did you fetch github?"
167 return
168 end
169 if system("git merge-base --is-ancestor {sha} HEAD") == 0 then
170 print "Is already merged."
171 continue
172 end
173 if system("git merge --no-ff --no-commit {sha}") != 0 then
174 if opt_keepgoing.value then
175 system("git reset --merge")
176 continue
177 end
178 system("cp mergemsg `git rev-parse --git-dir`/MERGE_MSG")
179 print "Problem during merge... Let's do the commit manually."
180 return
181 end
182 system("git commit -F mergemsg")
183 print "The merge is made"
184 mergemsg.write_to(stdout)
185 end
186