[go: nahoru, domu]

blob: bbf98564a9b18fdd58ca1bd42ce5343d180d09d9 [file] [log] [blame]
Joshua Peraza2fc3a432022-03-09 22:39:361#!/usr/bin/env python3
markadcc2032015-12-14 15:27:262# coding: utf-8
3
Avi Drissmanb06ae612022-10-08 19:24:314# Copyright 2015 The Chromium Authors
markadcc2032015-12-14 15:27:265# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
Mark Mentovai032fd5d2017-11-29 21:38:238from __future__ import print_function
9
markadcc2032015-12-14 15:27:2610import argparse
11import os
12import re
Prashanth Swaminathan8dc44aae2023-06-12 21:06:3613import shlex
markadcc2032015-12-14 15:27:2614import subprocess
15import sys
scottmgcf004602016-11-02 22:27:2216import tempfile
markadcc2032015-12-14 15:27:2617import textwrap
18
Mark Mentovai032fd5d2017-11-29 21:38:2319if sys.version_info[0] < 3:
20 input = raw_input
21
markadcc2032015-12-14 15:27:2622
scottmg609f6c22016-01-06 19:17:0223IS_WINDOWS = sys.platform.startswith('win')
24
25
markadcc2032015-12-14 15:27:2626def SubprocessCheckCall0Or1(args):
27 """Like subprocss.check_call(), but allows a return code of 1.
28
29 Returns True if the subprocess exits with code 0, False if it exits with
30 code 1, and re-raises the subprocess.check_call() exception otherwise.
31 """
32 try:
scottmg609f6c22016-01-06 19:17:0233 subprocess.check_call(args, shell=IS_WINDOWS)
Mark Mentovai032fd5d2017-11-29 21:38:2334 except subprocess.CalledProcessError as e:
markadcc2032015-12-14 15:27:2635 if e.returncode != 1:
36 raise
37 return False
38
39 return True
40
41
42def GitMergeBaseIsAncestor(ancestor, descendant):
43 """Determines whether |ancestor| is an ancestor of |descendant|.
44 """
45 return SubprocessCheckCall0Or1(
46 ['git', 'merge-base', '--is-ancestor', ancestor, descendant])
47
48
49def main(args):
50 parser = argparse.ArgumentParser(
51 description='Update the in-tree copy of an imported project')
52 parser.add_argument(
53 '--repository',
54 default='https://chromium.googlesource.com/crashpad/crashpad',
55 help='The imported project\'s remote fetch URL',
56 metavar='URL')
57 parser.add_argument(
58 '--subtree',
59 default='third_party/crashpad/crashpad',
60 help='The imported project\'s location in this project\'s tree',
61 metavar='PATH')
62 parser.add_argument(
63 '--update-to',
64 default='FETCH_HEAD',
65 help='What to update the imported project to',
66 metavar='COMMITISH')
67 parser.add_argument(
68 '--fetch-ref',
69 default='HEAD',
70 help='The remote ref to fetch',
71 metavar='REF')
72 parser.add_argument(
73 '--readme',
74 help='The README.chromium file describing the imported project',
75 metavar='FILE',
76 dest='readme_path')
mark6af56c562017-03-03 18:08:0977 parser.add_argument(
78 '--exclude',
Joshua Peraza47771d52021-12-16 21:07:5279 default=['codereview.settings', 'infra'],
mark6af56c562017-03-03 18:08:0980 action='append',
81 help='Files to exclude from the imported copy',
82 metavar='PATH')
markadcc2032015-12-14 15:27:2683 parsed = parser.parse_args(args)
84
85 original_head = (
scottmg609f6c22016-01-06 19:17:0286 subprocess.check_output(['git', 'rev-parse', 'HEAD'],
87 shell=IS_WINDOWS).rstrip())
Bruce Dawson13964542022-02-13 00:43:4188 original_head = original_head.decode('utf-8')
markadcc2032015-12-14 15:27:2689
90 # Read the README, because that’s what it’s for. Extract some things from
91 # it, and save it to be able to update it later.
92 readme_path = (parsed.readme_path or
93 os.path.join(os.path.dirname(__file__ or '.'),
94 'README.chromium'))
Mark Mentovai032fd5d2017-11-29 21:38:2395 readme_content_old = open(readme_path, 'rb').read().decode('utf-8')
markadcc2032015-12-14 15:27:2696
97 project_name_match = re.search(
98 r'^Name:\s+(.*)$', readme_content_old, re.MULTILINE)
99 project_name = project_name_match.group(1)
100
101 # Extract the original commit hash from the README.
102 revision_match = re.search(r'^Revision:\s+([0-9a-fA-F]{40})($|\s)',
103 readme_content_old,
104 re.MULTILINE)
105 revision_old = revision_match.group(1)
106
scottmg609f6c22016-01-06 19:17:02107 subprocess.check_call(['git', 'fetch', parsed.repository, parsed.fetch_ref],
108 shell=IS_WINDOWS)
markadcc2032015-12-14 15:27:26109
110 # Make sure that parsed.update_to is an ancestor of FETCH_HEAD, and
111 # revision_old is an ancestor of parsed.update_to. This prevents the use of
112 # hashes that are known to git but that don’t make sense in the context of
113 # the update operation.
114 if not GitMergeBaseIsAncestor(parsed.update_to, 'FETCH_HEAD'):
115 raise Exception('update_to is not an ancestor of FETCH_HEAD',
116 parsed.update_to,
117 'FETCH_HEAD')
118 if not GitMergeBaseIsAncestor(revision_old, parsed.update_to):
119 raise Exception('revision_old is not an ancestor of update_to',
120 revision_old,
121 parsed.update_to)
122
mark6af56c562017-03-03 18:08:09123 # git-filter-branch needs a ref to update. It’s not enough to just tell it
124 # to operate on a range of commits ending at parsed.update_to, because
125 # parsed.update_to is a commit hash that can’t be updated to point to
126 # anything else.
127 subprocess.check_call(['git', 'update-ref', 'UPDATE_TO', parsed.update_to],
128 shell=IS_WINDOWS)
markadcc2032015-12-14 15:27:26129
mark6af56c562017-03-03 18:08:09130 # Filter the range being updated over to exclude files that ought to be
131 # missing. This points UPDATE_TO to the rewritten (filtered) version.
132 # git-filter-branch insists on running from the top level of the working
133 # tree.
134 toplevel = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'],
135 shell=IS_WINDOWS).rstrip()
136 subprocess.check_call(
137 ['git',
138 'filter-branch',
139 '--force',
140 '--index-filter',
Joshua Peraza47771d52021-12-16 21:07:52141 'git rm -r --cached --ignore-unmatch ' +
Prashanth Swaminathan8dc44aae2023-06-12 21:06:36142 ' '.join(shlex.quote(path) for path in parsed.exclude),
mark6af56c562017-03-03 18:08:09143 revision_old + '..UPDATE_TO'],
144 cwd=toplevel,
145 shell=IS_WINDOWS)
146
147 # git-filter-branch saved a copy of the original UPDATE_TO at
148 # original/UPDATE_TO, but this isn’t useful because it refers to the same
149 # thing as parsed.update_to, which is already known.
150 subprocess.check_call(
151 ['git', 'update-ref', '-d', 'refs/original/UPDATE_TO'],
152 shell=IS_WINDOWS)
153
154 filtered_update_range = revision_old + '..UPDATE_TO'
155 unfiltered_update_range = revision_old + '..' + parsed.update_to
156
157 # This cherry-picks each change in the window from the filtered view of the
158 # upstream project into the current branch.
markadcc2032015-12-14 15:27:26159 assisted_cherry_pick = False
160 try:
161 if not SubprocessCheckCall0Or1(['git',
162 'cherry-pick',
163 '--keep-redundant-commits',
164 '--strategy=subtree',
165 '-Xsubtree=' + parsed.subtree,
166 '-x',
mark6af56c562017-03-03 18:08:09167 filtered_update_range]):
markadcc2032015-12-14 15:27:26168 assisted_cherry_pick = True
Mark Mentovai032fd5d2017-11-29 21:38:23169 print("""
markadcc2032015-12-14 15:27:26170Please fix the errors above and run "git cherry-pick --continue".
171Press Enter when "git cherry-pick" completes.
172You may use a new shell for this, or ^Z if job control is available.
173Press ^C to abort.
Mark Mentovai032fd5d2017-11-29 21:38:23174""", file=sys.stderr)
175 input()
markadcc2032015-12-14 15:27:26176 except:
177 # ^C, signal, or something else.
Mark Mentovai032fd5d2017-11-29 21:38:23178 print('Aborting...', file=sys.stderr)
scottmg609f6c22016-01-06 19:17:02179 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS)
markadcc2032015-12-14 15:27:26180 raise
181
182 # Get an abbreviated hash and subject line for each commit in the window,
mark6af56c562017-03-03 18:08:09183 # sorted in chronological order. Use the unfiltered view so that the commit
184 # hashes are recognizable.
Mark Mentovai032fd5d2017-11-29 21:38:23185 log_lines = subprocess.check_output(
186 ['git',
187 '-c',
188 'core.abbrev=12',
189 'log',
190 '--abbrev-commit',
191 '--pretty=oneline',
192 '--reverse',
193 unfiltered_update_range],
194 shell=IS_WINDOWS).decode('utf-8').splitlines(False)
markadcc2032015-12-14 15:27:26195
196 if assisted_cherry_pick:
197 # If the user had to help, count the number of cherry-picked commits,
198 # expecting it to match.
199 cherry_picked_commits = int(subprocess.check_output(
mark6af56c562017-03-03 18:08:09200 ['git', 'rev-list', '--count', original_head + '..HEAD'],
201 shell=IS_WINDOWS))
markadcc2032015-12-14 15:27:26202 if cherry_picked_commits != len(log_lines):
Mark Mentovai032fd5d2017-11-29 21:38:23203 print('Something smells fishy, aborting anyway...', file=sys.stderr)
scottmg609f6c22016-01-06 19:17:02204 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS)
markadcc2032015-12-14 15:27:26205 raise Exception('not all commits were cherry-picked',
206 len(log_lines),
207 cherry_picked_commits)
208
209 # Make a nice commit message. Start with the full commit hash.
210 revision_new = subprocess.check_output(
Mark Mentovai032fd5d2017-11-29 21:38:23211 ['git', 'rev-parse', parsed.update_to],
212 shell=IS_WINDOWS).decode('utf-8').rstrip()
213 new_message = u'Update ' + project_name + ' to ' + revision_new + '\n\n'
markadcc2032015-12-14 15:27:26214
215 # Wrap everything to 72 characters, with a hanging indent.
216 wrapper = textwrap.TextWrapper(width=72, subsequent_indent = ' ' * 13)
217 for line in log_lines:
218 # Strip trailing periods from subjects.
219 if line.endswith('.'):
220 line = line[:-1]
221
222 # If any subjects have what look like commit hashes in them, truncate
223 # them to 12 characters.
224 line = re.sub(r'(\s)([0-9a-fA-F]{12})([0-9a-fA-F]{28})($|\s)',
225 r'\1\2\4',
226 line)
227
228 new_message += '\n'.join(wrapper.wrap(line)) + '\n'
229
230 # Update the README with the new hash.
231 readme_content_new = re.sub(
232 r'^(Revision:\s+)([0-9a-fA-F]{40})($|\s.*?$)',
233 r'\g<1>' + revision_new,
234 readme_content_old,
235 1,
236 re.MULTILINE)
237
238 # If the in-tree copy has no changes relative to the upstream, clear the
239 # “Local Modifications” section of the README.
240 has_local_modifications = True
241 if SubprocessCheckCall0Or1(['git',
242 'diff-tree',
243 '--quiet',
mark6af56c562017-03-03 18:08:09244 'UPDATE_TO',
markadcc2032015-12-14 15:27:26245 'HEAD:' + parsed.subtree]):
246 has_local_modifications = False
247
mark6af56c562017-03-03 18:08:09248 if not parsed.exclude:
249 modifications = 'None.\n'
250 elif len(parsed.exclude) == 1:
251 modifications = (
252 ' - %s has been excluded.\n' % parsed.exclude[0])
253 else:
254 modifications = (
255 ' - The following files have been excluded:\n')
256 for excluded in sorted(parsed.exclude):
257 modifications += ' - ' + excluded + '\n'
markadcc2032015-12-14 15:27:26258 readme_content_new = re.sub(r'\nLocal Modifications:\n.*$',
mark6af56c562017-03-03 18:08:09259 '\nLocal Modifications:\n' + modifications,
markadcc2032015-12-14 15:27:26260 readme_content_new,
mark6af56c562017-03-03 18:08:09261 1,
262 re.DOTALL)
markadcc2032015-12-14 15:27:26263
mark6af56c562017-03-03 18:08:09264 # The UPDATE_TO ref is no longer useful.
265 subprocess.check_call(['git', 'update-ref', '-d', 'UPDATE_TO'],
266 shell=IS_WINDOWS)
267
268 # This soft-reset causes all of the cherry-picks to show up as staged, which
269 # will have the effect of squashing them along with the README update when
270 # committed below.
scottmg609f6c22016-01-06 19:17:02271 subprocess.check_call(['git', 'reset', '--soft', original_head],
272 shell=IS_WINDOWS)
markadcc2032015-12-14 15:27:26273
274 # Write the new README.
Mark Mentovai032fd5d2017-11-29 21:38:23275 open(readme_path, 'wb').write(readme_content_new.encode('utf-8'))
markadcc2032015-12-14 15:27:26276
277 # Commit everything.
scottmg609f6c22016-01-06 19:17:02278 subprocess.check_call(['git', 'add', readme_path], shell=IS_WINDOWS)
scottmgcf004602016-11-02 22:27:22279
280 try:
281 commit_message_name = None
Mark Mentovai032fd5d2017-11-29 21:38:23282 with tempfile.NamedTemporaryFile(mode='wb',
283 delete=False) as commit_message_f:
scottmgcf004602016-11-02 22:27:22284 commit_message_name = commit_message_f.name
Mark Mentovai032fd5d2017-11-29 21:38:23285 commit_message_f.write(new_message.encode('utf-8'))
scottmgcf004602016-11-02 22:27:22286 subprocess.check_call(['git',
287 'commit', '--file=' + commit_message_name],
288 shell=IS_WINDOWS)
289 finally:
290 if commit_message_name:
291 os.unlink(commit_message_name)
markadcc2032015-12-14 15:27:26292
293 if has_local_modifications:
Mark Mentovai032fd5d2017-11-29 21:38:23294 print('Remember to check the Local Modifications section in ' +
295 readme_path, file=sys.stderr)
markadcc2032015-12-14 15:27:26296
297 return 0
298
299
300if __name__ == '__main__':
301 sys.exit(main(sys.argv[1:]))