##// END OF EJS Templates
Merge pull request #4258 from minrk/backport-1.2...
Min RK -
r12754:f84b9ff9 merge
parent child Browse files
Show More
@@ -1,143 +1,143 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 Backport pull requests to a particular branch.
3 Backport pull requests to a particular branch.
4
4
5 Usage: backport_pr.py branch [PR]
5 Usage: backport_pr.py branch [PR]
6
6
7 e.g.:
7 e.g.:
8
8
9 python tools/backport_pr.py 0.13.1 123
9 python tools/backport_pr.py 0.13.1 123
10
10
11 to backport PR #123 onto branch 0.13.1
11 to backport PR #123 onto branch 0.13.1
12
12
13 or
13 or
14
14
15 python tools/backport_pr.py 1.x
15 python tools/backport_pr.py 1.x
16
16
17 to see what PRs are marked for backport that have yet to be applied.
17 to see what PRs are marked for backport that have yet to be applied.
18
18
19 """
19 """
20
20
21 from __future__ import print_function
21 from __future__ import print_function
22
22
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26
26
27 from subprocess import Popen, PIPE, check_call, check_output
27 from subprocess import Popen, PIPE, check_call, check_output
28 from urllib import urlopen
28 from urllib import urlopen
29
29
30 from gh_api import (
30 from gh_api import (
31 get_issues_list,
31 get_issues_list,
32 get_pull_request,
32 get_pull_request,
33 get_pull_request_files,
33 get_pull_request_files,
34 is_pull_request,
34 is_pull_request,
35 )
35 )
36
36
37 def find_rejects(root='.'):
37 def find_rejects(root='.'):
38 for dirname, dirs, files in os.walk(root):
38 for dirname, dirs, files in os.walk(root):
39 for fname in files:
39 for fname in files:
40 if fname.endswith('.rej'):
40 if fname.endswith('.rej'):
41 yield os.path.join(dirname, fname)
41 yield os.path.join(dirname, fname)
42
42
43 def get_current_branch():
43 def get_current_branch():
44 branches = check_output(['git', 'branch'])
44 branches = check_output(['git', 'branch'])
45 for branch in branches.splitlines():
45 for branch in branches.splitlines():
46 if branch.startswith('*'):
46 if branch.startswith('*'):
47 return branch[1:].strip()
47 return branch[1:].strip()
48
48
49 def backport_pr(branch, num, project='ipython/ipython'):
49 def backport_pr(branch, num, project='ipython/ipython'):
50 current_branch = get_current_branch()
50 current_branch = get_current_branch()
51 if branch != current_branch:
51 if branch != current_branch:
52 check_call(['git', 'checkout', branch])
52 check_call(['git', 'checkout', branch])
53 check_call(['git', 'pull'])
53 check_call(['git', 'pull'])
54 pr = get_pull_request(project, num, auth=True)
54 pr = get_pull_request(project, num, auth=True)
55 files = get_pull_request_files(project, num, auth=True)
55 files = get_pull_request_files(project, num, auth=True)
56 patch_url = pr['patch_url']
56 patch_url = pr['patch_url']
57 title = pr['title']
57 title = pr['title']
58 description = pr['body']
58 description = pr['body']
59 fname = "PR%i.patch" % num
59 fname = "PR%i.patch" % num
60 if os.path.exists(fname):
60 if os.path.exists(fname):
61 print("using patch from {fname}".format(**locals()))
61 print("using patch from {fname}".format(**locals()))
62 with open(fname) as f:
62 with open(fname) as f:
63 patch = f.read()
63 patch = f.read()
64 else:
64 else:
65 req = urlopen(patch_url)
65 req = urlopen(patch_url)
66 patch = req.read()
66 patch = req.read()
67
67
68 msg = "Backport PR #%i: %s" % (num, title) + '\n\n' + description
68 msg = "Backport PR #%i: %s" % (num, title) + '\n\n' + description
69 check = Popen(['git', 'apply', '--check', '--verbose'], stdin=PIPE)
69 check = Popen(['git', 'apply', '--check', '--verbose'], stdin=PIPE)
70 a,b = check.communicate(patch)
70 a,b = check.communicate(patch)
71
71
72 if check.returncode:
72 if check.returncode:
73 print("patch did not apply, saving to {fname}".format(**locals()))
73 print("patch did not apply, saving to {fname}".format(**locals()))
74 print("edit {fname} until `cat {fname} | git apply --check` succeeds".format(**locals()))
74 print("edit {fname} until `cat {fname} | git apply --check` succeeds".format(**locals()))
75 print("then run tools/backport_pr.py {num} again".format(**locals()))
75 print("then run tools/backport_pr.py {num} again".format(**locals()))
76 if not os.path.exists(fname):
76 if not os.path.exists(fname):
77 with open(fname, 'wb') as f:
77 with open(fname, 'wb') as f:
78 f.write(patch)
78 f.write(patch)
79 return 1
79 return 1
80
80
81 p = Popen(['git', 'apply'], stdin=PIPE)
81 p = Popen(['git', 'apply'], stdin=PIPE)
82 a,b = p.communicate(patch)
82 a,b = p.communicate(patch)
83
83
84 filenames = [ f['filename'] for f in files ]
84 filenames = [ f['filename'] for f in files ]
85
85
86 check_call(['git', 'add'] + filenames)
86 check_call(['git', 'add'] + filenames)
87
87
88 check_call(['git', 'commit', '-m', msg])
88 check_call(['git', 'commit', '-m', msg])
89
89
90 print("PR #%i applied, with msg:" % num)
90 print("PR #%i applied, with msg:" % num)
91 print()
91 print()
92 print(msg)
92 print(msg)
93 print()
93 print()
94
94
95 if branch != current_branch:
95 if branch != current_branch:
96 check_call(['git', 'checkout', current_branch])
96 check_call(['git', 'checkout', current_branch])
97
97
98 return 0
98 return 0
99
99
100 backport_re = re.compile(r"[Bb]ackport.*?(\d+)")
100 backport_re = re.compile(r"[Bb]ackport.*?(\d+)")
101
101
102 def already_backported(branch, since_tag=None):
102 def already_backported(branch, since_tag=None):
103 """return set of PRs that have been backported already"""
103 """return set of PRs that have been backported already"""
104 if since_tag is None:
104 if since_tag is None:
105 since_tag = check_output(['git','describe', branch, '--abbrev=0']).decode('utf8').strip()
105 since_tag = check_output(['git','describe', branch, '--abbrev=0']).decode('utf8').strip()
106 cmd = ['git', 'log', '%s..%s' % (since_tag, branch), '--oneline']
106 cmd = ['git', 'log', '%s..%s' % (since_tag, branch), '--oneline']
107 lines = check_output(cmd).decode('utf8')
107 lines = check_output(cmd).decode('utf8')
108 return set(int(num) for num in backport_re.findall(lines))
108 return set(int(num) for num in backport_re.findall(lines))
109
109
110 def should_backport(labels):
110 def should_backport(labels):
111 """return set of PRs marked for backport"""
111 """return set of PRs marked for backport"""
112 issues = get_issues_list("ipython/ipython",
112 issues = get_issues_list("ipython/ipython",
113 labels=labels,
113 labels=labels,
114 state='closed',
114 state='closed',
115 auth=True,
115 auth=True,
116 )
116 )
117 should_backport = set()
117 should_backport = set()
118 for issue in issues:
118 for issue in issues:
119 if not is_pull_request(issue):
119 if not is_pull_request(issue):
120 continue
120 continue
121 pr = get_pull_request("ipython/ipython", issue['number'], auth=True)
121 pr = get_pull_request("ipython/ipython", issue['number'], auth=True)
122 if not pr['merged']:
122 if not pr['merged']:
123 print ("Marked PR closed without merge: %i" % pr['number'])
123 print ("Marked PR closed without merge: %i" % pr['number'])
124 continue
124 continue
125 should_backport.add(pr['number'])
125 should_backport.add(pr['number'])
126 return should_backport
126 return should_backport
127
127
128 if __name__ == '__main__':
128 if __name__ == '__main__':
129
129
130 if len(sys.argv) < 2:
130 if len(sys.argv) < 2:
131 print(__doc__)
131 print(__doc__)
132 sys.exit(1)
132 sys.exit(1)
133
133
134 if len(sys.argv) < 3:
134 if len(sys.argv) < 3:
135 branch = sys.argv[1]
135 branch = sys.argv[1]
136 already = already_backported(branch)
136 already = already_backported(branch)
137 should = should_backport("backport-1.1")
137 should = should_backport("backport-1.2")
138 print ("The following PRs should be backported:")
138 print ("The following PRs should be backported:")
139 for pr in should.difference(already):
139 for pr in should.difference(already):
140 print (pr)
140 print (pr)
141 sys.exit(0)
141 sys.exit(0)
142
142
143 sys.exit(backport_pr(sys.argv[1], int(sys.argv[2])))
143 sys.exit(backport_pr(sys.argv[1], int(sys.argv[2])))
General Comments 0
You need to be logged in to leave comments. Login now