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 |