forked from cockpit-project/bots
-
Notifications
You must be signed in to change notification settings - Fork 0
/
push-rewrite
executable file
·99 lines (76 loc) · 3.13 KB
/
push-rewrite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#! /usr/bin/env python3
# push-rewrite -- Force push to a PR after rewriting history, with benefits.
#
# This tool force pushes your local changes to origin but only if that
# doesn't change any files. If that is the case, the tool will also
# copy the test results over.
#
# The idea is that you use this tool after squashing fixups in a pull
# request or after other similar last minute rewriting activity before
# merging a pull request. Then you can merge the PR using the GitHub
# UI and get a nice "merged" label for it, and the tests will still be
# green.
import subprocess
import argparse
import sys
import time
from task import github, label, labels_of_pull
def execute(*args):
try:
sys.stderr.write("+ " + " ".join(args) + "\n")
output = subprocess.check_output(args, universal_newlines=True)
except subprocess.CalledProcessError as ex:
sys.exit(ex.returncode)
return output
def git(*args):
return execute("git", *args).strip()
def find_pr_with_sha(api, sha):
pulls = api.pulls()
for pull in pulls:
if pull["head"]["sha"] == sha:
return pull
sys.stderr.write("Could not find pull with revision {0}.\n".format(sha))
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description='Force push after a rewrite')
parser.add_argument('--repo', help="The GitHub repository to work with", default=None)
parser.add_argument('remote', help='The remote to push to')
parser.add_argument('branch', nargs='?', help='The branch to push')
opts = parser.parse_args()
if not opts.branch:
opts.branch = git("rev-parse", "--abbrev-ref", "HEAD")
local = git("rev-parse", opts.branch)
remote = git("rev-parse", opts.remote + "/" + opts.branch)
if local == remote:
sys.stderr.write("Nothing to push.\n")
return 0
if git("diff", "--stat", local, remote) != "":
sys.stderr.write("You have local changes, aborting.\n")
return 1
api = github.GitHub(repo=opts.repo)
old_statuses = api.statuses(remote)
pull = find_pr_with_sha(api, remote)
labels = labels_of_pull(pull)
tests_disabled = "no-test" in labels or "[no-test]" in pull["title"]
# add no-test label to prevent tests bring triggered
if not tests_disabled:
label(pull, {"labels": labels + ["no-test"]})
git("push", "--force-with-lease=" + opts.branch + ":" + remote, opts.remote, opts.branch + ":" + opts.branch)
for n in range(100, 0, -1):
try:
if api.get("commits/%s" % local):
break
except RuntimeError as e:
if "Unprocessable Entity" not in str(e) or n <= 1:
raise
print("(new commits not yet in the GiHub API...please stand by. *beep*)")
time.sleep(1)
for key in old_statuses:
if old_statuses[key]["state"] != "pending":
print("Copying results for %s" % old_statuses[key]["context"])
api.post("statuses/" + local, old_statuses[key])
# remove no-test label
if not tests_disabled:
api.delete("issues/%i/labels/no-test" % pull["number"])
if __name__ == '__main__':
sys.exit(main())