| 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 |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 61 parser.add_argument( | 61 parser.add_argument( |
| 62 '--fetch-ref', | 62 '--fetch-ref', |
| 63 default='HEAD', | 63 default='HEAD', |
| 64 help='The remote ref to fetch', | 64 help='The remote ref to fetch', |
| 65 metavar='REF') | 65 metavar='REF') |
| 66 parser.add_argument( | 66 parser.add_argument( |
| 67 '--readme', | 67 '--readme', |
| 68 help='The README.chromium file describing the imported project', | 68 help='The README.chromium file describing the imported project', |
| 69 metavar='FILE', | 69 metavar='FILE', |
| 70 dest='readme_path') | 70 dest='readme_path') |
| 71 parser.add_argument( |
| 72 '--exclude', |
| 73 default=['codereview.settings'], |
| 74 action='append', |
| 75 help='Files to exclude from the imported copy', |
| 76 metavar='PATH') |
| 71 parsed = parser.parse_args(args) | 77 parsed = parser.parse_args(args) |
| 72 | 78 |
| 73 original_head = ( | 79 original_head = ( |
| 74 subprocess.check_output(['git', 'rev-parse', 'HEAD'], | 80 subprocess.check_output(['git', 'rev-parse', 'HEAD'], |
| 75 shell=IS_WINDOWS).rstrip()) | 81 shell=IS_WINDOWS).rstrip()) |
| 76 | 82 |
| 77 # Read the README, because that’s what it’s for. Extract some things from | 83 # Read the README, because that’s what it’s for. Extract some things from |
| 78 # it, and save it to be able to update it later. | 84 # it, and save it to be able to update it later. |
| 79 readme_path = (parsed.readme_path or | 85 readme_path = (parsed.readme_path or |
| 80 os.path.join(os.path.dirname(__file__ or '.'), | 86 os.path.join(os.path.dirname(__file__ or '.'), |
| (...skipping 19 matching lines...) Expand all Loading... |
| 100 # the update operation. | 106 # the update operation. |
| 101 if not GitMergeBaseIsAncestor(parsed.update_to, 'FETCH_HEAD'): | 107 if not GitMergeBaseIsAncestor(parsed.update_to, 'FETCH_HEAD'): |
| 102 raise Exception('update_to is not an ancestor of FETCH_HEAD', | 108 raise Exception('update_to is not an ancestor of FETCH_HEAD', |
| 103 parsed.update_to, | 109 parsed.update_to, |
| 104 'FETCH_HEAD') | 110 'FETCH_HEAD') |
| 105 if not GitMergeBaseIsAncestor(revision_old, parsed.update_to): | 111 if not GitMergeBaseIsAncestor(revision_old, parsed.update_to): |
| 106 raise Exception('revision_old is not an ancestor of update_to', | 112 raise Exception('revision_old is not an ancestor of update_to', |
| 107 revision_old, | 113 revision_old, |
| 108 parsed.update_to) | 114 parsed.update_to) |
| 109 | 115 |
| 110 update_range = revision_old + '..' + parsed.update_to | 116 # git-filter-branch needs a ref to update. It’s not enough to just tell it |
| 117 # to operate on a range of commits ending at parsed.update_to, because |
| 118 # parsed.update_to is a commit hash that can’t be updated to point to |
| 119 # anything else. |
| 120 subprocess.check_call(['git', 'update-ref', 'UPDATE_TO', parsed.update_to], |
| 121 shell=IS_WINDOWS) |
| 111 | 122 |
| 112 # This cherry-picks each change in the window from the upstream project into | 123 # Filter the range being updated over to exclude files that ought to be |
| 113 # the current branch. | 124 # missing. This points UPDATE_TO to the rewritten (filtered) version. |
| 125 # git-filter-branch insists on running from the top level of the working |
| 126 # tree. |
| 127 toplevel = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], |
| 128 shell=IS_WINDOWS).rstrip() |
| 129 subprocess.check_call( |
| 130 ['git', |
| 131 'filter-branch', |
| 132 '--force', |
| 133 '--index-filter', |
| 134 'git rm --cached --ignore-unmatch ' + ' '.join(parsed.exclude), |
| 135 revision_old + '..UPDATE_TO'], |
| 136 cwd=toplevel, |
| 137 shell=IS_WINDOWS) |
| 138 |
| 139 # git-filter-branch saved a copy of the original UPDATE_TO at |
| 140 # original/UPDATE_TO, but this isn’t useful because it refers to the same |
| 141 # thing as parsed.update_to, which is already known. |
| 142 subprocess.check_call( |
| 143 ['git', 'update-ref', '-d', 'refs/original/UPDATE_TO'], |
| 144 shell=IS_WINDOWS) |
| 145 |
| 146 filtered_update_range = revision_old + '..UPDATE_TO' |
| 147 unfiltered_update_range = revision_old + '..' + parsed.update_to |
| 148 |
| 149 # This cherry-picks each change in the window from the filtered view of the |
| 150 # upstream project into the current branch. |
| 114 assisted_cherry_pick = False | 151 assisted_cherry_pick = False |
| 115 try: | 152 try: |
| 116 if not SubprocessCheckCall0Or1(['git', | 153 if not SubprocessCheckCall0Or1(['git', |
| 117 'cherry-pick', | 154 'cherry-pick', |
| 118 '--keep-redundant-commits', | 155 '--keep-redundant-commits', |
| 119 '--strategy=subtree', | 156 '--strategy=subtree', |
| 120 '-Xsubtree=' + parsed.subtree, | 157 '-Xsubtree=' + parsed.subtree, |
| 121 '-x', | 158 '-x', |
| 122 update_range]): | 159 filtered_update_range]): |
| 123 assisted_cherry_pick = True | 160 assisted_cherry_pick = True |
| 124 print >>sys.stderr, (""" | 161 print >>sys.stderr, (""" |
| 125 Please fix the errors above and run "git cherry-pick --continue". | 162 Please fix the errors above and run "git cherry-pick --continue". |
| 126 Press Enter when "git cherry-pick" completes. | 163 Press Enter when "git cherry-pick" completes. |
| 127 You may use a new shell for this, or ^Z if job control is available. | 164 You may use a new shell for this, or ^Z if job control is available. |
| 128 Press ^C to abort. | 165 Press ^C to abort. |
| 129 """) | 166 """) |
| 130 raw_input() | 167 raw_input() |
| 131 except: | 168 except: |
| 132 # ^C, signal, or something else. | 169 # ^C, signal, or something else. |
| 133 print >>sys.stderr, 'Aborting...' | 170 print >>sys.stderr, 'Aborting...' |
| 134 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS) | 171 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS) |
| 135 raise | 172 raise |
| 136 | 173 |
| 137 # Get an abbreviated hash and subject line for each commit in the window, | 174 # Get an abbreviated hash and subject line for each commit in the window, |
| 138 # sorted in chronological order. | 175 # sorted in chronological order. Use the unfiltered view so that the commit |
| 176 # hashes are recognizable. |
| 139 log_lines = subprocess.check_output(['git', | 177 log_lines = subprocess.check_output(['git', |
| 140 '-c', | 178 '-c', |
| 141 'core.abbrev=12', | 179 'core.abbrev=12', |
| 142 'log', | 180 'log', |
| 143 '--abbrev-commit', | 181 '--abbrev-commit', |
| 144 '--pretty=oneline', | 182 '--pretty=oneline', |
| 145 '--reverse', | 183 '--reverse', |
| 146 update_range], | 184 unfiltered_update_range], |
| 147 shell=IS_WINDOWS).splitlines(False) | 185 shell=IS_WINDOWS).splitlines(False) |
| 148 | 186 |
| 149 if assisted_cherry_pick: | 187 if assisted_cherry_pick: |
| 150 # If the user had to help, count the number of cherry-picked commits, | 188 # If the user had to help, count the number of cherry-picked commits, |
| 151 # expecting it to match. | 189 # expecting it to match. |
| 152 cherry_picked_commits = int(subprocess.check_output( | 190 cherry_picked_commits = int(subprocess.check_output( |
| 153 ['git', 'rev-list', '--count', original_head + '..HEAD'])) | 191 ['git', 'rev-list', '--count', original_head + '..HEAD'], |
| 192 shell=IS_WINDOWS)) |
| 154 if cherry_picked_commits != len(log_lines): | 193 if cherry_picked_commits != len(log_lines): |
| 155 print >>sys.stderr, 'Something smells fishy, aborting anyway...' | 194 print >>sys.stderr, 'Something smells fishy, aborting anyway...' |
| 156 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS) | 195 subprocess.call(['git', 'cherry-pick', '--abort'], shell=IS_WINDOWS) |
| 157 raise Exception('not all commits were cherry-picked', | 196 raise Exception('not all commits were cherry-picked', |
| 158 len(log_lines), | 197 len(log_lines), |
| 159 cherry_picked_commits) | 198 cherry_picked_commits) |
| 160 | 199 |
| 161 # Make a nice commit message. Start with the full commit hash. | 200 # Make a nice commit message. Start with the full commit hash. |
| 162 revision_new = subprocess.check_output( | 201 revision_new = subprocess.check_output( |
| 163 ['git', 'rev-parse', parsed.update_to], shell=IS_WINDOWS).rstrip() | 202 ['git', 'rev-parse', parsed.update_to], shell=IS_WINDOWS).rstrip() |
| (...skipping 21 matching lines...) Expand all Loading... |
| 185 readme_content_old, | 224 readme_content_old, |
| 186 1, | 225 1, |
| 187 re.MULTILINE) | 226 re.MULTILINE) |
| 188 | 227 |
| 189 # If the in-tree copy has no changes relative to the upstream, clear the | 228 # If the in-tree copy has no changes relative to the upstream, clear the |
| 190 # “Local Modifications” section of the README. | 229 # “Local Modifications” section of the README. |
| 191 has_local_modifications = True | 230 has_local_modifications = True |
| 192 if SubprocessCheckCall0Or1(['git', | 231 if SubprocessCheckCall0Or1(['git', |
| 193 'diff-tree', | 232 'diff-tree', |
| 194 '--quiet', | 233 '--quiet', |
| 195 parsed.update_to, | 234 'UPDATE_TO', |
| 196 'HEAD:' + parsed.subtree]): | 235 'HEAD:' + parsed.subtree]): |
| 197 has_local_modifications = False | 236 has_local_modifications = False |
| 198 | 237 |
| 238 if not parsed.exclude: |
| 239 modifications = 'None.\n' |
| 240 elif len(parsed.exclude) == 1: |
| 241 modifications = ( |
| 242 ' - %s has been excluded.\n' % parsed.exclude[0]) |
| 243 else: |
| 244 modifications = ( |
| 245 ' - The following files have been excluded:\n') |
| 246 for excluded in sorted(parsed.exclude): |
| 247 modifications += ' - ' + excluded + '\n' |
| 199 readme_content_new = re.sub(r'\nLocal Modifications:\n.*$', | 248 readme_content_new = re.sub(r'\nLocal Modifications:\n.*$', |
| 200 '\nLocal Modifications:\nNone.', | 249 '\nLocal Modifications:\n' + modifications, |
| 201 readme_content_new, | 250 readme_content_new, |
| 202 1) | 251 1, |
| 252 re.DOTALL) |
| 203 | 253 |
| 204 # This soft-reset causes all of the cherry-picks to show up as staged, | 254 # The UPDATE_TO ref is no longer useful. |
| 205 # which will have the effect of squashing them along with the README update | 255 subprocess.check_call(['git', 'update-ref', '-d', 'UPDATE_TO'], |
| 206 # when committed below. | 256 shell=IS_WINDOWS) |
| 257 |
| 258 # This soft-reset causes all of the cherry-picks to show up as staged, which |
| 259 # will have the effect of squashing them along with the README update when |
| 260 # committed below. |
| 207 subprocess.check_call(['git', 'reset', '--soft', original_head], | 261 subprocess.check_call(['git', 'reset', '--soft', original_head], |
| 208 shell=IS_WINDOWS) | 262 shell=IS_WINDOWS) |
| 209 | 263 |
| 210 # Write the new README. | 264 # Write the new README. |
| 211 open(readme_path, 'wb').write(readme_content_new) | 265 open(readme_path, 'wb').write(readme_content_new) |
| 212 | 266 |
| 213 # Commit everything. | 267 # Commit everything. |
| 214 subprocess.check_call(['git', 'add', readme_path], shell=IS_WINDOWS) | 268 subprocess.check_call(['git', 'add', readme_path], shell=IS_WINDOWS) |
| 215 | 269 |
| 216 try: | 270 try: |
| (...skipping 11 matching lines...) Expand all Loading... |
| 228 if has_local_modifications: | 282 if has_local_modifications: |
| 229 print >>sys.stderr, ( | 283 print >>sys.stderr, ( |
| 230 'Remember to check the Local Modifications section in ' + | 284 'Remember to check the Local Modifications section in ' + |
| 231 readme_path) | 285 readme_path) |
| 232 | 286 |
| 233 return 0 | 287 return 0 |
| 234 | 288 |
| 235 | 289 |
| 236 if __name__ == '__main__': | 290 if __name__ == '__main__': |
| 237 sys.exit(main(sys.argv[1:])) | 291 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |