blob: baeef374050023fa5ace94fd24966758ed44cc81 [file] [log] [blame]
Chris Withersfb1f5b32019-04-26 20:29:03 +01001import re
2from argparse import ArgumentParser
Chris Withers871b5262019-04-27 15:04:13 +01003from os.path import dirname, abspath, join
Chris Withersfb1f5b32019-04-26 20:29:03 +01004from subprocess import check_output, call
5
6
7def git(command, repo):
8 return check_output('git '+command, cwd=repo, shell=True).decode()
9
10
11def repo_state_bad(mock_repo):
12 status = git('status', mock_repo)
13 if 'You are in the middle of an am session' in status:
14 print(f'Mock repo at {mock_repo} needs cleanup:\n')
15 call('git status', shell=True)
16 return True
17
18
19def cleanup_old_patches(mock_repo):
20 print('cleaning up old patches:')
21 call('rm -vf /tmp/*.mock.patch', shell=True)
22 call('find . -name "*.rej" -print -delete', shell=True, cwd=mock_repo)
23
24
25def find_initial_cpython_rev():
26 with open('lastsync.txt') as source:
27 return source.read().strip()
28
29
30def cpython_revs_affecting_mock(cpython_repo, start):
31 revs = git(f'log --no-merges --format=%H {start}.. '
32 f'-- Lib/unittest/mock.py Lib/unittest/test/testmock/',
33 repo=cpython_repo).split()
34 revs.reverse()
Chris Withers76523d32019-04-27 15:47:35 +010035 print(f'{len(revs)} patches that may need backporting')
Chris Withersfb1f5b32019-04-26 20:29:03 +010036 return revs
37
38
39def has_been_backported(mock_repo, cpython_rev):
40 backport_rev = git(f'log --format=%H --grep "Backports: {cpython_rev}"',
41 repo=mock_repo).strip()
42 if backport_rev:
43 print(f'{cpython_rev} backported in {backport_rev}')
44 return True
45 print(f'{cpython_rev} has not been backported')
46
47
48def extract_patch_for(cpython_repo, rev):
49 return git(f'format-patch -1 --no-stat --keep-subject --signoff --stdout {rev}',
50 repo=cpython_repo)
51
52
53def munge(rev, patch):
54
55 sign_off = 'Signed-off-by:'
56 patch = patch.replace(sign_off, f'Backports: {rev}\n{sign_off}', 1)
57
58 for pattern, sub in (
59 ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'),
60 ('(a|b)/Lib/unittest/test/testmock/(.+)', r'\1/mock/tests/\2'),
61 ('(a|b)/Misc/NEWS', r'\1/NEWS'),
Chris Withersf4bc0812019-04-28 17:05:38 +010062 ('(a|b)/NEWS.d/next/Library/(.+\.rst)', r'\1/NEWS.d/\2'),
Chris Withersfb1f5b32019-04-26 20:29:03 +010063 ):
64 patch = re.sub(pattern, sub, patch)
65 return patch
66
67
68def apply_patch(mock_repo, rev, patch):
69 patch_path = f'/tmp/{rev}.mock.patch'
70
71 with open(patch_path, 'w') as target:
72 target.write(patch)
73 print(f'wrote {patch_path}')
74
75 call(f'git am -k --reject {patch_path}', cwd=mock_repo, shell=True)
76
77
Chris Withers871b5262019-04-27 15:04:13 +010078def update_last_sync(mock_repo, rev):
79 with open(join(mock_repo, 'lastsync.txt'), 'w') as target:
80 target.write(rev+'\n')
81 print(f'update lastsync.txt to {rev}')
82
Chris Withersa3365182019-04-27 17:35:49 +010083
84def rev_from_mock_patch(text):
85 match = re.search('Backports: ([a-z0-9]+)', text)
86 return match.group(1)
87
88
89def skip_current(mock_repo, reason):
90 text = git('am --show-current-patch', repo=mock_repo)
91 rev = rev_from_mock_patch(text)
92 git('am --abort', repo=mock_repo)
93 print(f'skipping {rev}')
94 update_last_sync(mock_repo, rev)
Chris Withers4f25a9b2019-04-28 15:57:58 +010095 call(f'git commit -m "Backports: {rev}, skipped: {reason}" lastsync.txt', shell=True, cwd=mock_repo)
Chris Withersa3365182019-04-27 17:35:49 +010096 cleanup_old_patches(mock_repo)
97
98
Chris Withersfb1f5b32019-04-26 20:29:03 +010099def main():
100 args = parse_args()
101
Chris Withersa3365182019-04-27 17:35:49 +0100102 if args.skip_current:
103 return skip_current(args.mock, args.skip_reason)
104
Chris Withersfb1f5b32019-04-26 20:29:03 +0100105 if repo_state_bad(args.mock):
106 return
107
108 cleanup_old_patches(args.mock)
109
110 initial_cpython_rev = find_initial_cpython_rev()
111
112 revs = cpython_revs_affecting_mock(args.cpython, initial_cpython_rev)
113 for rev in revs:
114
115 if has_been_backported(args.mock, rev):
Chris Withers871b5262019-04-27 15:04:13 +0100116 update_last_sync(args.mock, rev)
Chris Withersfb1f5b32019-04-26 20:29:03 +0100117 continue
118
119 patch = extract_patch_for(args.cpython, rev)
120 patch = munge(rev, patch)
121 apply_patch(args.mock, rev, patch)
122 break
123
124
125def parse_args():
126 parser = ArgumentParser()
127 parser.add_argument('--cpython', default='../cpython')
128 parser.add_argument('--mock', default=abspath(dirname(__file__)))
Chris Withersa3365182019-04-27 17:35:49 +0100129 parser.add_argument('--skip-current', action='store_true')
130 parser.add_argument('--skip-reason', default='it has no changes needed here.')
Chris Withersfb1f5b32019-04-26 20:29:03 +0100131 return parser.parse_args()
132
133
134if __name__ == '__main__':
135 main()