Merge: lib/config: fix doc
[nit.git] / contrib / github_merge.nit
index 3a056ad..ca61f39 100644 (file)
 # Query the Github PR API to perform a merge
 module github_merge
 
-import github_api
+import github::github_curl
 import template
+import opts
 
 redef class Object
        # Factorize cast
-       fun json_as_a: Array[nullable Object] do return self.as(Array[nullable Object])
+       fun json_as_a: JsonArray do return self.as(JsonArray)
        # Factorize cast
-       fun json_as_map: Map[String, nullable Object] do return self.as(Map[String, nullable Object])
+       fun json_as_map: JsonObject do return self.as(JsonObject)
 end
 
 redef class GithubCurl
        # Get a given pull request (PR)
-       fun getpr(number: Int): Map[String, nullable Object]
+       fun getpr(repo: String, number: Int): nullable JsonObject
        do
-               var pr = get_and_check("https://api.github.com/repos/privat/nit/pulls/{number}")
+               var ir = get_and_check("https://api.github.com/repos/{repo}/issues/{number}")
+               var irm = ir.json_as_map
+               if not irm.has_key("pull_request") then return null
+               var pr = get_and_check("https://api.github.com/repos/{repo}/pulls/{number}")
                var prm = pr.json_as_map
-               var sha = prm["head"].json_as_map["sha"]
-               var statuses = get_and_check("https://api.github.com/repos/privat/nit/statuses/{sha}")
+               var sha = prm["head"].json_as_map["sha"].to_s
+               var statuses = get_and_check("https://api.github.com/repos/{repo}/commits/{sha}/status")
+               statuses = statuses.json_as_map
                prm["statuses"] = statuses
-               print "{prm["title"]}: by {prm["user"].json_as_map["login"]} (# {prm["number"]})"
-               print "\tmergable: {prm["mergeable"]}"
-               var st = prm["statuses"].json_as_a
-               if not st.is_empty then
-                       print "\tstatus: {st[0].json_as_map["state"]}"
+               print "{prm["title"].to_s}: by {prm["user"].json_as_map["login"].to_s} (# {prm["number"].to_s})"
+               var mergeable = prm["mergeable"]
+               if mergeable != null then
+                       print "\tmergeable: {mergeable.to_s}"
                else
+                       print "\tmergeable: unknown"
+               end
+               var state = statuses["state"]
+               if state == null then
                        print "\tstatus: not tested"
+               else
+                       print "\tstatus: {state}"
+                       var sts = statuses["statuses"].json_as_a
+                       for st in sts do
+                               st = st.json_as_map
+                               var ctx = st["context"].to_s
+                               state = st["state"].to_s
+                               print "\tstatus {ctx}: {state}"
+                               prm["status-{ctx}"] = state
+                       end
                end
                return prm
        end
 
        # Get reviewers of a PR
-       fun getrev(pr: Map[String, nullable Object]): Array[String]
+       fun getrev(repo: String, pr: JsonObject): Array[String]
        do
                var number = pr["number"].as(Int)
                var user = pr["user"].json_as_map["login"].as(String)
                var comments = new Array[nullable Object]
-               comments.add_all(get_and_check("https://api.github.com/repos/privat/nit/issues/{number}/comments").json_as_a)
-               comments.add_all(get_and_check("https://api.github.com/repos/privat/nit/pulls/{number}/comments").json_as_a)
+               comments.add_all(get_and_check("https://api.github.com/repos/{repo}/issues/{number}/comments").json_as_a)
+               comments.add_all(get_and_check("https://api.github.com/repos/{repo}/pulls/{number}/comments").json_as_a)
                var logins = new Array[String]
                for c in comments do
                        var cm = c.json_as_map
@@ -63,11 +81,11 @@ redef class GithubCurl
                var res = new Array[String]
                for l in logins do
                        var u = get_and_check("https://api.github.com/users/{l}").json_as_map
-                       if not u.has_key("name") then
-                               print "No public name for user {l}"
+                       if not u.has_key("name") or u["name"] == null or not u.has_key("email")or u["email"] == null then
+                               print "No public name/email for user {l}"
                                continue
                        end
-                       var r = "{u["name"]} <{u["email"]}>"
+                       var r = "{u["name"].to_s} <{u["email"].to_s}>"
                        res.add r
 
                end
@@ -78,33 +96,66 @@ end
 
 if "NIT_TESTING".environ == "true" then exit 0
 
-var auth = get_github_oauth
+var opt_repo = new OptionString("Repository (e.g. nitlang/nit)", "-r", "--repo")
+var opt_auth = new OptionString("OAuth token", "--auth")
+var opt_query = new OptionString("Query to get issues (e.g. label=ok_will_merge)", "-q", "--query")
+var opt_keepgoing = new OptionBool("Skip merge conflicts", "-k", "--keep-going")
+var opt_all = new OptionBool("Merge all", "-a", "--all")
+var opt_status = new OptionArray("A status context that must be \"success\" (e.g. default)", "--status")
+var opts = new OptionContext
+opts.add_option(opt_repo, opt_auth, opt_query, opt_status, opt_all, opt_keepgoing)
+
+opts.parse(sys.args)
+var args = opts.rest
 
+var auth = opt_auth.value or else ""
+if auth == "" then auth = get_github_oauth
 if auth == "" then
-       print "Not github token, please configure one with"
+       print "Warning: no github oauth token, you can configure one with"
        print "    git config --add github.oauthtoken MYOAUTHTOKEN"
-       return
 end
 
-var curl = new GithubCurl(auth, "Merge-o-matic (privat/nit)")
+var repo = opt_repo.value or else "nitlang/nit"
 
-if args.length != 1 then
+var query = opt_query.value or else "labels=ok_will_merge"
+
+var curl = new GithubCurl(auth, "Merge-o-matic (nitlang/nit)")
+
+if args.is_empty then
        # Without args, list `ok_will_merge`
-       var x = curl.get_and_check("https://api.github.com/repos/privat/nit/issues?labels=ok_will_merge")
+       var x = curl.get_and_check("https://api.github.com/repos/{repo}/issues?{query}")
+       var list = new Array[String]
        for y in x.json_as_a do
                var number = y.json_as_map["number"].as(Int)
-               var pr = curl.getpr(number)
-       end
-else
+               var pr = curl.getpr(repo, number)
+               if pr == null then continue
+               for ctx in opt_status.value do
+                       if pr.get_or_null("status-{ctx}") != "success" then
+                               print "No \"success\" for {ctx}. Skip."
+                               continue label
+                       end
+               end
+               list.add number.to_s
+       end label
+
+       if not opt_all.value then return
+       args = list
+end
+
+for arg in args do
        # With a arg, merge the PR
-       var number = args.first.to_i
-       var pr = curl.getpr(number)
-       var revs = curl.getrev(pr)
+       var number = arg.to_i
+       var pr = curl.getpr(repo, number)
+       if pr == null then
+               print "Not a PR: {number}"
+               return
+       end
+       var revs = curl.getrev(repo, pr)
 
        var mergemsg = new Template
-       mergemsg.add "Merge: {pr["title"]}\n\n"
-       mergemsg.add "{pr["body"]}\n\n"
-       mergemsg.add "Pull-Request: #{pr["number"]}\n"
+       mergemsg.add "Merge: {pr["title"].to_s}\n\n"
+       mergemsg.add "{pr["body"].to_s}\n\n"
+       mergemsg.add "Pull-Request: #{pr["number"].to_s}\n"
        for r in revs do
                mergemsg.add "Reviewed-by: {r}\n"
        end
@@ -115,12 +166,21 @@ else
                print "Commit {sha} not in local repository; did you fetch github?"
                return
        end
-       if system("git merge --no-commit {sha}") != 0 then
+       if system("git merge-base --is-ancestor {sha} HEAD") == 0 then
+               print "Is already merged."
+               continue
+       end
+       if system("git merge --no-ff --no-commit {sha}") != 0 then
+               if opt_keepgoing.value then
+                        system("git reset --merge")
+                        continue
+               end
                system("cp mergemsg `git rev-parse --git-dir`/MERGE_MSG")
                print "Problem during merge... Let's do the commit manually."
                return
        end
        system("git commit -F mergemsg")
        print "The merge is made"
+       mergemsg.write_to(stdout)
 end