| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # coding: utf-8 | 2 # coding: utf-8 |
| 3 | 3 |
| 4 # Copyright 2015 The Chromium Authors. All rights reserved. | 4 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 5 # Use of this source code is governed by a BSD-style license that can be | 5 # Use of this source code is governed by a BSD-style license that can be |
| 6 # found in the LICENSE file. | 6 # found in the LICENSE file. |
| 7 | 7 |
| 8 import argparse | 8 import argparse |
| 9 import os | 9 import os |
| 10 import re | 10 import re |
| 11 import subprocess | 11 import subprocess |
| 12 import sys | 12 import sys |
| 13 import textwrap | 13 import textwrap |
| 14 | 14 |
| 15 | 15 |
| 16 IS_WINDOWS = sys.platform.startswith('win') |
| 17 |
| 18 |
| 16 def SubprocessCheckCall0Or1(args): | 19 def SubprocessCheckCall0Or1(args): |
| 17 """Like subprocss.check_call(), but allows a return code of 1. | 20 """Like subprocss.check_call(), but allows a return code of 1. |
| 18 | 21 |
| 19 Returns True if the subprocess exits with code 0, False if it exits with | 22 Returns True if the subprocess exits with code 0, False if it exits with |
| 20 code 1, and re-raises the subprocess.check_call() exception otherwise. | 23 code 1, and re-raises the subprocess.check_call() exception otherwise. |
| 21 """ | 24 """ |
| 22 try: | 25 try: |
| 23 subprocess.check_call(args) | 26 subprocess.check_call(args, shell=IS_WINDOWS) |
| 24 except subprocess.CalledProcessError, e: | 27 except subprocess.CalledProcessError, e: |
| 25 if e.returncode != 1: | 28 if e.returncode != 1: |
| 26 raise | 29 raise |
| 27 return False | 30 return False |
| 28 | 31 |
| 29 return True | 32 return True |
| 30 | 33 |
| 31 | 34 |
| 32 def GitMergeBaseIsAncestor(ancestor, descendant): | 35 def GitMergeBaseIsAncestor(ancestor, descendant): |
| 33 """Determines whether |ancestor| is an ancestor of |descendant|. | 36 """Determines whether |ancestor| is an ancestor of |descendant|. |
| (...skipping 26 matching lines...) Expand all Loading... |
| 60 help='The remote ref to fetch', | 63 help='The remote ref to fetch', |
| 61 metavar='REF') | 64 metavar='REF') |
| 62 parser.add_argument( | 65 parser.add_argument( |
| 63 '--readme', | 66 '--readme', |
| 64 help='The README.chromium file describing the imported project', | 67 help='The README.chromium file describing the imported project', |
| 65 metavar='FILE', | 68 metavar='FILE', |
| 66 dest='readme_path') | 69 dest='readme_path') |
| 67 parsed = parser.parse_args(args) | 70 parsed = parser.parse_args(args) |
| 68 | 71 |
| 69 original_head = ( | 72 original_head = ( |
| 70 subprocess.check_output(['git', 'rev-parse', 'HEAD']).rstrip()) | 73 subprocess.check_output(['git', 'rev-parse', 'HEAD'], |
| 74 shell=IS_WINDOWS).rstrip()) |
| 71 | 75 |
| 72 # Read the README, because that’s what it’s for. Extract some things from | 76 # Read the README, because that’s what it’s for. Extract some things from |
| 73 # it, and save it to be able to update it later. | 77 # it, and save it to be able to update it later. |
| 74 readme_path = (parsed.readme_path or | 78 readme_path = (parsed.readme_path or |
| 75 os.path.join(os.path.dirname(__file__ or '.'), | 79 os.path.join(os.path.dirname(__file__ or '.'), |
| 76 'README.chromium')) | 80 'README.chromium')) |
| 77 readme_content_old = open(readme_path).read() | 81 readme_content_old = open(readme_path).read() |
| 78 | 82 |
| 79 project_name_match = re.search( | 83 project_name_match = re.search( |
| 80 r'^Name:\s+(.*)$', readme_content_old, re.MULTILINE) | 84 r'^Name:\s+(.*)$', readme_content_old, re.MULTILINE) |
| 81 project_name = project_name_match.group(1) | 85 project_name = project_name_match.group(1) |
| 82 | 86 |
| 83 # Extract the original commit hash from the README. | 87 # Extract the original commit hash from the README. |
| 84 revision_match = re.search(r'^Revision:\s+([0-9a-fA-F]{40})($|\s)', | 88 revision_match = re.search(r'^Revision:\s+([0-9a-fA-F]{40})($|\s)', |
| 85 readme_content_old, | 89 readme_content_old, |
| 86 re.MULTILINE) | 90 re.MULTILINE) |
| 87 revision_old = revision_match.group(1) | 91 revision_old = revision_match.group(1) |
| 88 | 92 |
| 89 subprocess.check_call(['git', 'fetch', parsed.repository, parsed.fetch_ref]) | 93 subprocess.check_call(['git', 'fetch', parsed.repository, parsed.fetch_ref], |
| 94 shell=IS_WINDOWS) |
| 90 | 95 |
| 91 # Make sure that parsed.update_to is an ancestor of FETCH_HEAD, and | 96 # Make sure that parsed.update_to is an ancestor of FETCH_HEAD, and |
| 92 # revision_old is an ancestor of parsed.update_to. This prevents the use of | 97 # revision_old is an ancestor of parsed.update_to. This prevents the use of |
| 93 # hashes that are known to git but that don’t make sense in the context of | 98 # hashes that are known to git but that don’t make sense in the context of |
| 94 # the update operation. | 99 # the update operation. |
| 95 if not GitMergeBaseIsAncestor(parsed.update_to, 'FETCH_HEAD'): | 100 if not GitMergeBaseIsAncestor(parsed.update_to, 'FETCH_HEAD'): |
| 96 raise Exception('update_to is not an ancestor of FETCH_HEAD', | 101 raise Exception('update_to is not an ancestor of FETCH_HEAD', |
| 97 parsed.update_to, | 102 parsed.update_to, |
| 98 'FETCH_HEAD') | 103 'FETCH_HEAD') |
| 99 if not GitMergeBaseIsAncestor(revision_old, parsed.update_to): | 104 if not GitMergeBaseIsAncestor(revision_old, parsed.update_to): |
| (...skipping 18 matching lines...) Expand all Loading... |
| 118 print >>sys.stderr, (""" | 123 print >>sys.stderr, (""" |
| 119 Please fix the errors above and run "git cherry-pick --continue". | 124 Please fix the errors above and run "git cherry-pick --continue". |
| 120 Press Enter when "git cherry-pick" completes. | 125 Press Enter when "git cherry-pick" completes. |
| 121 You may use a new shell for this, or ^Z if job control is available. | 126 You may use a new shell for this, or ^Z if job control is available. |
| 122 Press ^C to abort. | 127 Press ^C to abort. |
| 123 """) | 128 """) |
| 124 raw_input() | 129 raw_input() |
| 125 except: | 130 except: |
| 126 # ^C, signal, or something else. | 131 # ^C, signal, or something else. |
| 127 print >>sys.stderr, 'Aborting...' | 132 print >>sys.stderr, 'Aborting...' |
| 128 subprocess.call(['git', 'cherry-pick', '--abort']) | 133 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS) |
| 129 raise | 134 raise |
| 130 | 135 |
| 131 # Get an abbreviated hash and subject line for each commit in the window, | 136 # Get an abbreviated hash and subject line for each commit in the window, |
| 132 # sorted in chronological order. | 137 # sorted in chronological order. |
| 133 log_lines = subprocess.check_output(['git', | 138 log_lines = subprocess.check_output(['git', |
| 134 '-c', | 139 '-c', |
| 135 'core.abbrev=12', | 140 'core.abbrev=12', |
| 136 'log', | 141 'log', |
| 137 '--abbrev-commit', | 142 '--abbrev-commit', |
| 138 '--pretty=oneline', | 143 '--pretty=oneline', |
| 139 '--reverse', | 144 '--reverse', |
| 140 update_range]).splitlines(False) | 145 update_range], |
| 146 shell=IS_WINDOWS).splitlines(False) |
| 141 | 147 |
| 142 if assisted_cherry_pick: | 148 if assisted_cherry_pick: |
| 143 # If the user had to help, count the number of cherry-picked commits, | 149 # If the user had to help, count the number of cherry-picked commits, |
| 144 # expecting it to match. | 150 # expecting it to match. |
| 145 cherry_picked_commits = int(subprocess.check_output( | 151 cherry_picked_commits = int(subprocess.check_output( |
| 146 ['git', 'rev-list', '--count', original_head + '..HEAD'])) | 152 ['git', 'rev-list', '--count', original_head + '..HEAD'])) |
| 147 if cherry_picked_commits != len(log_lines): | 153 if cherry_picked_commits != len(log_lines): |
| 148 print >>sys.stderr, 'Something smells fishy, aborting anyway...' | 154 print >>sys.stderr, 'Something smells fishy, aborting anyway...' |
| 149 subprocess.call(['git', 'cherry-pick', '--abort']) | 155 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS) |
| 150 raise Exception('not all commits were cherry-picked', | 156 raise Exception('not all commits were cherry-picked', |
| 151 len(log_lines), | 157 len(log_lines), |
| 152 cherry_picked_commits) | 158 cherry_picked_commits) |
| 153 | 159 |
| 154 # Make a nice commit message. Start with the full commit hash. | 160 # Make a nice commit message. Start with the full commit hash. |
| 155 revision_new = subprocess.check_output( | 161 revision_new = subprocess.check_output( |
| 156 ['git', 'rev-parse', parsed.update_to]).rstrip() | 162 ['git', 'rev-parse', parsed.update_to], shell=IS_WINDOWS).rstrip() |
| 157 new_message = 'Update ' + project_name + ' to ' + revision_new + '\n\n' | 163 new_message = 'Update ' + project_name + ' to ' + revision_new + '\n\n' |
| 158 | 164 |
| 159 # Wrap everything to 72 characters, with a hanging indent. | 165 # Wrap everything to 72 characters, with a hanging indent. |
| 160 wrapper = textwrap.TextWrapper(width=72, subsequent_indent = ' ' * 13) | 166 wrapper = textwrap.TextWrapper(width=72, subsequent_indent = ' ' * 13) |
| 161 for line in log_lines: | 167 for line in log_lines: |
| 162 # Strip trailing periods from subjects. | 168 # Strip trailing periods from subjects. |
| 163 if line.endswith('.'): | 169 if line.endswith('.'): |
| 164 line = line[:-1] | 170 line = line[:-1] |
| 165 | 171 |
| 166 # If any subjects have what look like commit hashes in them, truncate | 172 # If any subjects have what look like commit hashes in them, truncate |
| (...skipping 23 matching lines...) Expand all Loading... |
| 190 has_local_modifications = False | 196 has_local_modifications = False |
| 191 | 197 |
| 192 readme_content_new = re.sub(r'\nLocal Modifications:\n.*$', | 198 readme_content_new = re.sub(r'\nLocal Modifications:\n.*$', |
| 193 '\nLocal Modifications:\nNone.', | 199 '\nLocal Modifications:\nNone.', |
| 194 readme_content_new, | 200 readme_content_new, |
| 195 1) | 201 1) |
| 196 | 202 |
| 197 # This soft-reset causes all of the cherry-picks to show up as staged, | 203 # This soft-reset causes all of the cherry-picks to show up as staged, |
| 198 # which will have the effect of squashing them along with the README update | 204 # which will have the effect of squashing them along with the README update |
| 199 # when committed below. | 205 # when committed below. |
| 200 subprocess.check_call(['git', 'reset', '--soft', original_head]) | 206 subprocess.check_call(['git', 'reset', '--soft', original_head], |
| 207 shell=IS_WINDOWS) |
| 201 | 208 |
| 202 # Write the new README. | 209 # Write the new README. |
| 203 open(readme_path, 'wb').write(readme_content_new) | 210 open(readme_path, 'wb').write(readme_content_new) |
| 204 | 211 |
| 205 # Commit everything. | 212 # Commit everything. |
| 206 subprocess.check_call(['git', 'add', readme_path]) | 213 subprocess.check_call(['git', 'add', readme_path], shell=IS_WINDOWS) |
| 207 subprocess.check_call(['git', 'commit', '--message=' + new_message]) | 214 subprocess.check_call(['git', 'commit', '--message=' + new_message], |
| 215 shell=IS_WINDOWS) |
| 208 | 216 |
| 209 if has_local_modifications: | 217 if has_local_modifications: |
| 210 print >>sys.stderr, ( | 218 print >>sys.stderr, ( |
| 211 'Remember to check the Local Modifications section in ' + | 219 'Remember to check the Local Modifications section in ' + |
| 212 readme_path) | 220 readme_path) |
| 213 | 221 |
| 214 return 0 | 222 return 0 |
| 215 | 223 |
| 216 | 224 |
| 217 if __name__ == '__main__': | 225 if __name__ == '__main__': |
| 218 sys.exit(main(sys.argv[1:])) | 226 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |