| OLD | NEW |
| 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Gclient-specific SCM-specific operations.""" | 5 """Gclient-specific SCM-specific operations.""" |
| 6 | 6 |
| 7 import logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import posixpath | 9 import posixpath |
| 10 import re | 10 import re |
| (...skipping 240 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 251 " branch.") | 251 " branch.") |
| 252 elif upstream_branch.startswith('refs/remotes'): | 252 elif upstream_branch.startswith('refs/remotes'): |
| 253 current_type = "branch" | 253 current_type = "branch" |
| 254 else: | 254 else: |
| 255 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch) | 255 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch) |
| 256 | 256 |
| 257 # Update the remotes first so we have all the refs. | 257 # Update the remotes first so we have all the refs. |
| 258 backoff_time = 5 | 258 backoff_time = 5 |
| 259 for _ in range(10): | 259 for _ in range(10): |
| 260 try: | 260 try: |
| 261 remote_output, remote_err = scm.GIT.Capture( | 261 remote_output = scm.GIT.Capture( |
| 262 ['remote'] + verbose + ['update'], | 262 ['remote'] + verbose + ['update'], |
| 263 self.checkout_path, | 263 cwd=self.checkout_path) |
| 264 print_error=False) | |
| 265 break | 264 break |
| 266 except gclient_utils.CheckCallError, e: | 265 except gclient_utils.CheckCallError, e: |
| 267 # Hackish but at that point, git is known to work so just checking for | 266 # Hackish but at that point, git is known to work so just checking for |
| 268 # 502 in stderr should be fine. | 267 # 502 in stderr should be fine. |
| 269 if '502' in e.stderr: | 268 if '502' in e.stderr: |
| 270 options.stdout.write(str(e) + '\n') | 269 options.stdout.write(str(e) + '\n') |
| 271 options.stdout.write('Sleeping %.1f seconds and retrying...\n' % | 270 options.stdout.write('Sleeping %.1f seconds and retrying...\n' % |
| 272 backoff_time) | 271 backoff_time) |
| 273 time.sleep(backoff_time) | 272 time.sleep(backoff_time) |
| 274 backoff_time *= 1.3 | 273 backoff_time *= 1.3 |
| 275 continue | 274 continue |
| 276 raise | 275 raise |
| 277 | 276 |
| 278 if verbose: | 277 if verbose: |
| 279 options.stdout.write(remote_output.strip() + '\n') | 278 options.stdout.write(remote_output) |
| 280 # git remote update prints to stderr when used with --verbose | |
| 281 options.stdout.write(remote_err.strip() + '\n') | |
| 282 | 279 |
| 283 # This is a big hammer, debatable if it should even be here... | 280 # This is a big hammer, debatable if it should even be here... |
| 284 if options.force or options.reset: | 281 if options.force or options.reset: |
| 285 self._Run(['reset', '--hard', 'HEAD'], options) | 282 self._Run(['reset', '--hard', 'HEAD'], options) |
| 286 | 283 |
| 287 if current_type == 'detached': | 284 if current_type == 'detached': |
| 288 # case 0 | 285 # case 0 |
| 289 self._CheckClean(rev_str) | 286 self._CheckClean(rev_str) |
| 290 self._CheckDetachedHead(rev_str, options) | 287 self._CheckDetachedHead(rev_str, options) |
| 291 self._Capture(['checkout', '--quiet', '%s^0' % revision]) | 288 self._Capture(['checkout', '--quiet', '%s^0' % revision]) |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 324 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) + | 321 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) + |
| 325 "OR git checkout -b <some new branch> %s" % new_base) | 322 "OR git checkout -b <some new branch> %s" % new_base) |
| 326 raise gclient_utils.Error(switch_error) | 323 raise gclient_utils.Error(switch_error) |
| 327 else: | 324 else: |
| 328 # case 3 - the default case | 325 # case 3 - the default case |
| 329 files = self._Capture(['diff', upstream_branch, '--name-only']).split() | 326 files = self._Capture(['diff', upstream_branch, '--name-only']).split() |
| 330 if verbose: | 327 if verbose: |
| 331 options.stdout.write('Trying fast-forward merge to branch : %s\n' % | 328 options.stdout.write('Trying fast-forward merge to branch : %s\n' % |
| 332 upstream_branch) | 329 upstream_branch) |
| 333 try: | 330 try: |
| 334 merge_output, merge_err = scm.GIT.Capture(['merge', '--ff-only', | 331 merge_output = scm.GIT.Capture(['merge', '--ff-only', upstream_branch], |
| 335 upstream_branch], | 332 cwd=self.checkout_path) |
| 336 self.checkout_path, | |
| 337 print_error=False) | |
| 338 except gclient_utils.CheckCallError, e: | 333 except gclient_utils.CheckCallError, e: |
| 339 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr): | 334 if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr): |
| 340 if not printed_path: | 335 if not printed_path: |
| 341 options.stdout.write('\n_____ %s%s\n' % (self.relpath, rev_str)) | 336 options.stdout.write('\n_____ %s%s\n' % (self.relpath, rev_str)) |
| 342 printed_path = True | 337 printed_path = True |
| 343 while True: | 338 while True: |
| 344 try: | 339 try: |
| 345 # TODO(maruel): That can't work. | 340 # TODO(maruel): That can't work with --jobs. |
| 346 action = str(raw_input("Cannot fast-forward merge, attempt to " | 341 action = str(raw_input("Cannot fast-forward merge, attempt to " |
| 347 "rebase? (y)es / (q)uit / (s)kip : ")) | 342 "rebase? (y)es / (q)uit / (s)kip : ")) |
| 348 except ValueError: | 343 except ValueError: |
| 349 gclient_utils.Error('Invalid Character') | 344 gclient_utils.Error('Invalid Character') |
| 350 continue | 345 continue |
| 351 if re.match(r'yes|y', action, re.I): | 346 if re.match(r'yes|y', action, re.I): |
| 352 self._AttemptRebase(upstream_branch, files, options, | 347 self._AttemptRebase(upstream_branch, files, options, |
| 353 printed_path=printed_path) | 348 printed_path=printed_path) |
| 354 printed_path = True | 349 printed_path = True |
| 355 break | 350 break |
| (...skipping 19 matching lines...) Expand all Loading... |
| 375 # Some other problem happened with the merge | 370 # Some other problem happened with the merge |
| 376 logging.error("Error during fast-forward merge in %s!" % self.relpath) | 371 logging.error("Error during fast-forward merge in %s!" % self.relpath) |
| 377 options.stdout.write(e.stderr + '\n') | 372 options.stdout.write(e.stderr + '\n') |
| 378 raise | 373 raise |
| 379 else: | 374 else: |
| 380 # Fast-forward merge was successful | 375 # Fast-forward merge was successful |
| 381 if not re.match('Already up-to-date.', merge_output) or verbose: | 376 if not re.match('Already up-to-date.', merge_output) or verbose: |
| 382 if not printed_path: | 377 if not printed_path: |
| 383 options.stdout.write('\n_____ %s%s\n' % (self.relpath, rev_str)) | 378 options.stdout.write('\n_____ %s%s\n' % (self.relpath, rev_str)) |
| 384 printed_path = True | 379 printed_path = True |
| 385 print merge_output.strip() | 380 options.stdout.write(merge_output) |
| 386 if merge_err: | |
| 387 options.stdout.write('Merge produced error output:\n%s\n' % | |
| 388 merge_err.strip()) | |
| 389 if not verbose: | 381 if not verbose: |
| 390 # Make the output a little prettier. It's nice to have some | 382 # Make the output a little prettier. It's nice to have some |
| 391 # whitespace between projects when syncing. | 383 # whitespace between projects when syncing. |
| 392 options.stdout.write('\n') | 384 options.stdout.write('\n') |
| 393 | 385 |
| 394 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) | 386 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) |
| 395 | 387 |
| 396 # If the rebase generated a conflict, abort and ask user to fix | 388 # If the rebase generated a conflict, abort and ask user to fix |
| 397 if self._IsRebasing(): | 389 if self._IsRebasing(): |
| 398 raise gclient_utils.Error('\n____ %s%s\n' | 390 raise gclient_utils.Error('\n____ %s%s\n' |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 523 rebase_cmd = ['rebase'] | 515 rebase_cmd = ['rebase'] |
| 524 if options.verbose: | 516 if options.verbose: |
| 525 rebase_cmd.append('--verbose') | 517 rebase_cmd.append('--verbose') |
| 526 if newbase: | 518 if newbase: |
| 527 rebase_cmd.extend(['--onto', newbase]) | 519 rebase_cmd.extend(['--onto', newbase]) |
| 528 rebase_cmd.append(upstream) | 520 rebase_cmd.append(upstream) |
| 529 if branch: | 521 if branch: |
| 530 rebase_cmd.append(branch) | 522 rebase_cmd.append(branch) |
| 531 | 523 |
| 532 try: | 524 try: |
| 533 rebase_output, rebase_err = scm.GIT.Capture(rebase_cmd, | 525 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path) |
| 534 self.checkout_path, | |
| 535 print_error=False) | |
| 536 except gclient_utils.CheckCallError, e: | 526 except gclient_utils.CheckCallError, e: |
| 537 if re.match(r'cannot rebase: you have unstaged changes', e.stderr) or \ | 527 if (re.match(r'cannot rebase: you have unstaged changes', e.stderr) or |
| 538 re.match(r'cannot rebase: your index contains uncommitted changes', | 528 re.match(r'cannot rebase: your index contains uncommitted changes', |
| 539 e.stderr): | 529 e.stderr)): |
| 540 while True: | 530 while True: |
| 541 rebase_action = str(raw_input("Cannot rebase because of unstaged " | 531 rebase_action = str(raw_input("Cannot rebase because of unstaged " |
| 542 "changes.\n'git reset --hard HEAD' ?\n" | 532 "changes.\n'git reset --hard HEAD' ?\n" |
| 543 "WARNING: destroys any uncommitted " | 533 "WARNING: destroys any uncommitted " |
| 544 "work in your current branch!" | 534 "work in your current branch!" |
| 545 " (y)es / (q)uit / (s)how : ")) | 535 " (y)es / (q)uit / (s)how : ")) |
| 546 if re.match(r'yes|y', rebase_action, re.I): | 536 if re.match(r'yes|y', rebase_action, re.I): |
| 547 self._Run(['reset', '--hard', 'HEAD'], options) | 537 self._Run(['reset', '--hard', 'HEAD'], options) |
| 548 # Should this be recursive? | 538 # Should this be recursive? |
| 549 rebase_output, rebase_err = scm.GIT.Capture(rebase_cmd, | 539 rebase_output = scm.GIT.Capture(rebase_cmd, cwd=self.checkout_path) |
| 550 self.checkout_path) | |
| 551 break | 540 break |
| 552 elif re.match(r'quit|q', rebase_action, re.I): | 541 elif re.match(r'quit|q', rebase_action, re.I): |
| 553 raise gclient_utils.Error("Please merge or rebase manually\n" | 542 raise gclient_utils.Error("Please merge or rebase manually\n" |
| 554 "cd %s && git " % self.checkout_path | 543 "cd %s && git " % self.checkout_path |
| 555 + "%s" % ' '.join(rebase_cmd)) | 544 + "%s" % ' '.join(rebase_cmd)) |
| 556 elif re.match(r'show|s', rebase_action, re.I): | 545 elif re.match(r'show|s', rebase_action, re.I): |
| 557 options.stdout.write('\n%s\n' % e.stderr.strip()) | 546 options.stdout.write('\n%s\n' % e.stderr.strip()) |
| 558 continue | 547 continue |
| 559 else: | 548 else: |
| 560 gclient_utils.Error("Input not recognized") | 549 gclient_utils.Error("Input not recognized") |
| 561 continue | 550 continue |
| 562 elif re.search(r'^CONFLICT', e.stdout, re.M): | 551 elif re.search(r'^CONFLICT', e.stdout, re.M): |
| 563 raise gclient_utils.Error("Conflict while rebasing this branch.\n" | 552 raise gclient_utils.Error("Conflict while rebasing this branch.\n" |
| 564 "Fix the conflict and run gclient again.\n" | 553 "Fix the conflict and run gclient again.\n" |
| 565 "See 'man git-rebase' for details.\n") | 554 "See 'man git-rebase' for details.\n") |
| 566 else: | 555 else: |
| 567 options.stdout.write(e.stdout.strip() + '\n') | 556 options.stdout.write(e.stdout.strip() + '\n') |
| 568 options.stdout.write('Rebase produced error output:\n%s\n' % | 557 options.stdout.write('Rebase produced error output:\n%s\n' % |
| 569 e.stderr.strip()) | 558 e.stderr.strip()) |
| 570 raise gclient_utils.Error("Unrecognized error, please merge or rebase " | 559 raise gclient_utils.Error("Unrecognized error, please merge or rebase " |
| 571 "manually.\ncd %s && git " % | 560 "manually.\ncd %s && git " % |
| 572 self.checkout_path | 561 self.checkout_path |
| 573 + "%s" % ' '.join(rebase_cmd)) | 562 + "%s" % ' '.join(rebase_cmd)) |
| 574 | 563 |
| 575 print rebase_output.strip() | 564 options.stdout.write(rebase_output) |
| 576 if rebase_err: | |
| 577 options.stdout.write('Rebase produced error output:\n%s\n' % | |
| 578 rebase_err.strip()) | |
| 579 if not options.verbose: | 565 if not options.verbose: |
| 580 # Make the output a little prettier. It's nice to have some | 566 # Make the output a little prettier. It's nice to have some |
| 581 # whitespace between projects when syncing. | 567 # whitespace between projects when syncing. |
| 582 options.stdout.write('\n') | 568 options.stdout.write('\n') |
| 583 | 569 |
| 584 @staticmethod | 570 @staticmethod |
| 585 def _CheckMinVersion(min_version): | 571 def _CheckMinVersion(min_version): |
| 586 (ok, current_version) = scm.GIT.AssertVersion(min_version) | 572 (ok, current_version) = scm.GIT.AssertVersion(min_version) |
| 587 if not ok: | 573 if not ok: |
| 588 raise gclient_utils.Error('git version %s < minimum required %s' % | 574 raise gclient_utils.Error('git version %s < minimum required %s' % |
| 589 (current_version, min_version)) | 575 (current_version, min_version)) |
| 590 | 576 |
| 591 def _IsRebasing(self): | 577 def _IsRebasing(self): |
| 592 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't | 578 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't |
| 593 # have a plumbing command to determine whether a rebase is in progress, so | 579 # have a plumbing command to determine whether a rebase is in progress, so |
| 594 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash | 580 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash |
| 595 g = os.path.join(self.checkout_path, '.git') | 581 g = os.path.join(self.checkout_path, '.git') |
| 596 return ( | 582 return ( |
| 597 os.path.isdir(os.path.join(g, "rebase-merge")) or | 583 os.path.isdir(os.path.join(g, "rebase-merge")) or |
| 598 os.path.isdir(os.path.join(g, "rebase-apply"))) | 584 os.path.isdir(os.path.join(g, "rebase-apply"))) |
| 599 | 585 |
| 600 def _CheckClean(self, rev_str): | 586 def _CheckClean(self, rev_str): |
| 601 # Make sure the tree is clean; see git-rebase.sh for reference | 587 # Make sure the tree is clean; see git-rebase.sh for reference |
| 602 try: | 588 try: |
| 603 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'], | 589 scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'], |
| 604 self.checkout_path, print_error=False) | 590 cwd=self.checkout_path) |
| 605 except gclient_utils.CheckCallError: | 591 except gclient_utils.CheckCallError: |
| 606 raise gclient_utils.Error('\n____ %s%s\n' | 592 raise gclient_utils.Error('\n____ %s%s\n' |
| 607 '\tYou have unstaged changes.\n' | 593 '\tYou have unstaged changes.\n' |
| 608 '\tPlease commit, stash, or reset.\n' | 594 '\tPlease commit, stash, or reset.\n' |
| 609 % (self.relpath, rev_str)) | 595 % (self.relpath, rev_str)) |
| 610 try: | 596 try: |
| 611 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r', | 597 scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r', |
| 612 '--ignore-submodules', 'HEAD', '--'], self.checkout_path, | 598 '--ignore-submodules', 'HEAD', '--'], |
| 613 print_error=False) | 599 cwd=self.checkout_path) |
| 614 except gclient_utils.CheckCallError: | 600 except gclient_utils.CheckCallError: |
| 615 raise gclient_utils.Error('\n____ %s%s\n' | 601 raise gclient_utils.Error('\n____ %s%s\n' |
| 616 '\tYour index contains uncommitted changes\n' | 602 '\tYour index contains uncommitted changes\n' |
| 617 '\tPlease commit, stash, or reset.\n' | 603 '\tPlease commit, stash, or reset.\n' |
| 618 % (self.relpath, rev_str)) | 604 % (self.relpath, rev_str)) |
| 619 | 605 |
| 620 def _CheckDetachedHead(self, rev_str, options): | 606 def _CheckDetachedHead(self, rev_str, options): |
| 621 # HEAD is detached. Make sure it is safe to move away from (i.e., it is | 607 # HEAD is detached. Make sure it is safe to move away from (i.e., it is |
| 622 # reference by a commit). If not, error out -- most likely a rebase is | 608 # reference by a commit). If not, error out -- most likely a rebase is |
| 623 # in progress, try to detect so we can give a better error. | 609 # in progress, try to detect so we can give a better error. |
| 624 try: | 610 try: |
| 625 _, _ = scm.GIT.Capture( | 611 scm.GIT.Capture(['name-rev', '--no-undefined', 'HEAD'], |
| 626 ['name-rev', '--no-undefined', 'HEAD'], | 612 cwd=self.checkout_path) |
| 627 self.checkout_path, | |
| 628 print_error=False) | |
| 629 except gclient_utils.CheckCallError: | 613 except gclient_utils.CheckCallError: |
| 630 # Commit is not contained by any rev. See if the user is rebasing: | 614 # Commit is not contained by any rev. See if the user is rebasing: |
| 631 if self._IsRebasing(): | 615 if self._IsRebasing(): |
| 632 # Punt to the user | 616 # Punt to the user |
| 633 raise gclient_utils.Error('\n____ %s%s\n' | 617 raise gclient_utils.Error('\n____ %s%s\n' |
| 634 '\tAlready in a conflict, i.e. (no branch).\n' | 618 '\tAlready in a conflict, i.e. (no branch).\n' |
| 635 '\tFix the conflict and run gclient again.\n' | 619 '\tFix the conflict and run gclient again.\n' |
| 636 '\tOr to abort run:\n\t\tgit-rebase --abort\n' | 620 '\tOr to abort run:\n\t\tgit-rebase --abort\n' |
| 637 '\tSee man git-rebase for details.\n' | 621 '\tSee man git-rebase for details.\n' |
| 638 % (self.relpath, rev_str)) | 622 % (self.relpath, rev_str)) |
| 639 # Let's just save off the commit so we can proceed. | 623 # Let's just save off the commit so we can proceed. |
| 640 name = ('saved-by-gclient-' + | 624 name = ('saved-by-gclient-' + |
| 641 self._Capture(['rev-parse', '--short', 'HEAD'])) | 625 self._Capture(['rev-parse', '--short', 'HEAD'])) |
| 642 self._Capture(['branch', name]) | 626 self._Capture(['branch', name]) |
| 643 options.stdout.write( | 627 options.stdout.write( |
| 644 '\n_____ found an unreferenced commit and saved it as \'%s\'\n' % | 628 '\n_____ found an unreferenced commit and saved it as \'%s\'\n' % |
| 645 name) | 629 name) |
| 646 | 630 |
| 647 def _GetCurrentBranch(self): | 631 def _GetCurrentBranch(self): |
| 648 # Returns name of current branch or None for detached HEAD | 632 # Returns name of current branch or None for detached HEAD |
| 649 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD']) | 633 branch = self._Capture(['rev-parse', '--abbrev-ref=strict', 'HEAD']) |
| 650 if branch == 'HEAD': | 634 if branch == 'HEAD': |
| 651 return None | 635 return None |
| 652 return branch | 636 return branch |
| 653 | 637 |
| 654 def _Capture(self, args): | 638 def _Capture(self, args): |
| 655 return gclient_utils.CheckCall(['git'] + args, | 639 return gclient_utils.CheckCall( |
| 656 cwd=self.checkout_path)[0].strip() | 640 ['git'] + args, cwd=self.checkout_path, print_error=False)[0].strip() |
| 657 | 641 |
| 658 def _Run(self, args, options, **kwargs): | 642 def _Run(self, args, options, **kwargs): |
| 659 kwargs.setdefault('cwd', self.checkout_path) | 643 kwargs.setdefault('cwd', self.checkout_path) |
| 660 gclient_utils.CheckCallAndFilterAndHeader(['git'] + args, | 644 gclient_utils.CheckCallAndFilterAndHeader(['git'] + args, |
| 661 always=options.verbose, stdout=options.stdout, **kwargs) | 645 always=options.verbose, stdout=options.stdout, **kwargs) |
| 662 | 646 |
| 663 | 647 |
| 664 class SVNWrapper(SCMWrapper): | 648 class SVNWrapper(SCMWrapper): |
| 665 """ Wrapper for SVN """ | 649 """ Wrapper for SVN """ |
| 666 | 650 |
| (...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 963 | 947 |
| 964 This method returns a new list to be used as a command.""" | 948 This method returns a new list to be used as a command.""" |
| 965 new_command = command[:] | 949 new_command = command[:] |
| 966 if revision: | 950 if revision: |
| 967 new_command.extend(['--revision', str(revision).strip()]) | 951 new_command.extend(['--revision', str(revision).strip()]) |
| 968 # --force was added to 'svn update' in svn 1.5. | 952 # --force was added to 'svn update' in svn 1.5. |
| 969 if ((options.force or options.manually_grab_svn_rev) and | 953 if ((options.force or options.manually_grab_svn_rev) and |
| 970 scm.SVN.AssertVersion("1.5")[0]): | 954 scm.SVN.AssertVersion("1.5")[0]): |
| 971 new_command.append('--force') | 955 new_command.append('--force') |
| 972 return new_command | 956 return new_command |
| OLD | NEW |