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 |