| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 """git drover: A tool for merging changes to release branches.""" | 5 """git drover: A tool for merging changes to release branches.""" |
| 6 | 6 |
| 7 import argparse | 7 import argparse |
| 8 import cPickle | 8 import cPickle |
| 9 import functools | 9 import functools |
| 10 import logging | 10 import logging |
| (...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 258 cPickle.dump(self, f) | 258 cPickle.dump(self, f) |
| 259 | 259 |
| 260 def _prepare_manual_resolve(self): | 260 def _prepare_manual_resolve(self): |
| 261 """Prepare the workdir for the user to manually resolve the cherry-pick.""" | 261 """Prepare the workdir for the user to manually resolve the cherry-pick.""" |
| 262 # Files that have been deleted between branch and cherry-pick will not have | 262 # Files that have been deleted between branch and cherry-pick will not have |
| 263 # their skip-worktree bit set so set it manually for those files to avoid | 263 # their skip-worktree bit set so set it manually for those files to avoid |
| 264 # git status incorrectly listing them as unstaged deletes. | 264 # git status incorrectly listing them as unstaged deletes. |
| 265 repo_status = self._run_git_command(['status', '--porcelain']).splitlines() | 265 repo_status = self._run_git_command(['status', '--porcelain']).splitlines() |
| 266 extra_files = [f[3:] for f in repo_status if f[:2] == ' D'] | 266 extra_files = [f[3:] for f in repo_status if f[:2] == ' D'] |
| 267 if extra_files: | 267 if extra_files: |
| 268 self._run_git_command(['update-index', '--skip-worktree', '--'] + | 268 self._run_git_command_with_stdin( |
| 269 extra_files) | 269 ['update-index', '--skip-worktree', '--stdin'], |
| 270 stdin='\n'.join(extra_files) + '\n') |
| 270 | 271 |
| 271 def _upload_and_land(self): | 272 def _upload_and_land(self): |
| 272 if self._dry_run: | 273 if self._dry_run: |
| 273 logging.info('--dry_run enabled; not landing.') | 274 logging.info('--dry_run enabled; not landing.') |
| 274 return True | 275 return True |
| 275 | 276 |
| 276 self._run_git_command(['reset', '--hard']) | 277 self._run_git_command(['reset', '--hard']) |
| 277 self._run_git_command(['cl', 'upload'], | 278 self._run_git_command(['cl', 'upload'], |
| 278 error_message='Upload failed', | 279 error_message='Upload failed', |
| 279 interactive=True) | 280 interactive=True) |
| (...skipping 27 matching lines...) Expand all Loading... |
| 307 stderr = None if self._verbose else _DEV_NULL_FILE | 308 stderr = None if self._verbose else _DEV_NULL_FILE |
| 308 | 309 |
| 309 try: | 310 try: |
| 310 return run(['git'] + args, shell=False, cwd=cwd, stderr=stderr) | 311 return run(['git'] + args, shell=False, cwd=cwd, stderr=stderr) |
| 311 except (OSError, subprocess.CalledProcessError) as e: | 312 except (OSError, subprocess.CalledProcessError) as e: |
| 312 if error_message: | 313 if error_message: |
| 313 raise Error(error_message) | 314 raise Error(error_message) |
| 314 else: | 315 else: |
| 315 raise Error('Command %r failed: %s' % (' '.join(args), e)) | 316 raise Error('Command %r failed: %s' % (' '.join(args), e)) |
| 316 | 317 |
| 318 def _run_git_command_with_stdin(self, args, stdin): |
| 319 """Runs a git command with a provided stdin. |
| 320 |
| 321 Args: |
| 322 args: A list of strings containing the args to pass to git. |
| 323 stdin: A string to provide on stdin. |
| 324 |
| 325 Raises: |
| 326 Error: The command failed to complete successfully. |
| 327 """ |
| 328 cwd = self._workdir if self._workdir else self._parent_repo |
| 329 logging.debug('Running git %s (cwd %r)', ' '.join('%s' % arg |
| 330 for arg in args), cwd) |
| 331 |
| 332 # Discard stderr unless verbose is enabled. |
| 333 stderr = None if self._verbose else _DEV_NULL_FILE |
| 334 |
| 335 try: |
| 336 popen = subprocess.Popen(['git'] + args, shell=False, cwd=cwd, |
| 337 stderr=stderr, stdin=subprocess.PIPE) |
| 338 popen.communicate(stdin) |
| 339 if popen.returncode != 0: |
| 340 raise Error('Command %r failed' % ' '.join(args)) |
| 341 except OSError as e: |
| 342 raise Error('Command %r failed: %s' % (' '.join(args), e)) |
| 343 |
| 317 | 344 |
| 318 def cherry_pick_change(branch, revision, parent_repo, dry_run, verbose=False): | 345 def cherry_pick_change(branch, revision, parent_repo, dry_run, verbose=False): |
| 319 """Cherry-picks a change into a branch. | 346 """Cherry-picks a change into a branch. |
| 320 | 347 |
| 321 Args: | 348 Args: |
| 322 branch: A string containing the release branch number to which to | 349 branch: A string containing the release branch number to which to |
| 323 cherry-pick. | 350 cherry-pick. |
| 324 revision: A string containing the revision to cherry-pick. It can be any | 351 revision: A string containing the revision to cherry-pick. It can be any |
| 325 string that git-rev-parse can identify as referring to a single | 352 string that git-rev-parse can identify as referring to a single |
| 326 revision. | 353 revision. |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 415 cherry_pick_change(options.branch, options.cherry_pick, | 442 cherry_pick_change(options.branch, options.cherry_pick, |
| 416 options.parent_checkout, options.dry_run, | 443 options.parent_checkout, options.dry_run, |
| 417 options.verbose) | 444 options.verbose) |
| 418 except Error as e: | 445 except Error as e: |
| 419 print 'Error:', e.message | 446 print 'Error:', e.message |
| 420 sys.exit(128) | 447 sys.exit(128) |
| 421 | 448 |
| 422 | 449 |
| 423 if __name__ == '__main__': | 450 if __name__ == '__main__': |
| 424 main() | 451 main() |
| OLD | NEW |