Merge: fix ci nitunit some
[nit.git] / contrib / github_merge / 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::api
19 import template
20 import config
21
22 redef class GithubAPI
23
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
28
29 var statuses = get_commit_status(repo, pull.head.sha)
30 if not statuses isa CommitStatus then return null
31
32 return new PullState(pull, statuses)
33 end
34
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
38
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))
42
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)
47 end
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}"
53 continue
54 end
55 res.add "{rev.name or else "N/A"} <{rev.email or else "N/A"}>"
56 end
57 return res
58 end
59 end
60
61 private class PullState
62 var pull: PullRequest
63 var status: CommitStatus
64
65 fun pretty: String do
66 var s = new Buffer
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"
72 end
73 return s.write_to_string
74 end
75
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
79 end
80 return null
81 end
82 end
83
84 if "NIT_TESTING".environ == "true" then exit 0
85
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")
92
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."
96
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)
100
101 config.parse_options(sys.args)
102
103 if config.opt_help.value then
104 config.usage
105 exit 0
106 end
107
108 var args = config.args
109
110 var auth = opt_auth.value or else ""
111 if auth == "" then auth = get_github_oauth
112 if auth == "" then
113 print "Warning: no github oauth token, you can configure one with"
114 print " git config --add github.oauthtoken MYOAUTHTOKEN"
115 end
116
117 var repo = opt_repo.value or else "nitlang/nit"
118
119 var query = opt_query.value or else "labels=ok_will_merge"
120
121 var api = new GithubAPI(auth, "Merge-o-matic (nitlang/nit)")
122
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}`."
128 exit 1
129 end
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
134
135 var state = api.get_pull_with_state(repo, issue.number)
136 if not state isa PullState then continue
137
138 print state.pretty
139 for ctx in opt_status.value do
140 if state.state_for(ctx) != "success" then
141 print "No \"success\" for {ctx}. Skip."
142 # continue label
143 end
144 end
145
146 list.add issue.number.to_s
147 end label
148
149 if not opt_all.value then return
150 args = list
151 end
152
153 for arg in args do
154 # With a arg, merge the PR
155 var number = arg.to_i
156 var pull = api.get_pull(repo, number)
157 if pull == null then
158 print "Not a PR: {number}"
159 return
160 end
161
162 var revs = api.get_pull_reviewers(repo, pull)
163
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"
168 for rev in revs do
169 mergemsg.add "Reviewed-by: {rev}\n"
170 end
171 mergemsg.write_to_file("mergemsg")
172
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?"
176 return
177 end
178 if system("git merge-base --is-ancestor {sha} HEAD") == 0 then
179 print "Is already merged."
180 continue
181 end
182 if system("git merge --no-ff --no-commit {sha}") != 0 then
183 if opt_keepgoing.value then
184 system("git reset --merge")
185 continue
186 end
187 system("cp mergemsg `git rev-parse --git-dir`/MERGE_MSG")
188 print "Problem during merge... Let's do the commit manually."
189 return
190 end
191 system("git commit -F mergemsg")
192 print "The merge is made"
193 mergemsg.write_to(stdout)
194 end