| OLD | NEW |
| 1 # coding=utf8 | 1 # coding=utf8 |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 """Manages a project checkout. | 5 """Manages a project checkout. |
| 6 | 6 |
| 7 Includes support for svn, git-svn and git. | 7 Includes support for svn, git-svn and git. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 import ConfigParser | 10 import ConfigParser |
| (...skipping 548 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 559 out = self._check_output_git(['branch']).splitlines(False) | 559 out = self._check_output_git(['branch']).splitlines(False) |
| 560 branches = [l[2:] for l in out] | 560 branches = [l[2:] for l in out] |
| 561 active = None | 561 active = None |
| 562 for l in out: | 562 for l in out: |
| 563 if l.startswith('*'): | 563 if l.startswith('*'): |
| 564 active = l[2:] | 564 active = l[2:] |
| 565 break | 565 break |
| 566 return branches, active | 566 return branches, active |
| 567 | 567 |
| 568 | 568 |
| 569 class GitSvnCheckoutBase(GitCheckoutBase, SvnMixIn): | |
| 570 """Base class for git-svn checkout. Not to be used as-is.""" | |
| 571 def __init__(self, | |
| 572 root_dir, project_name, remote_branch, | |
| 573 commit_user, commit_pwd, | |
| 574 svn_url, trunk, post_processors=None): | |
| 575 """trunk is optional.""" | |
| 576 GitCheckoutBase.__init__( | |
| 577 self, root_dir, project_name + '.git', remote_branch, post_processors) | |
| 578 SvnMixIn.__init__(self) | |
| 579 self.commit_user = commit_user | |
| 580 self.commit_pwd = commit_pwd | |
| 581 # svn_url in this case is the root of the svn repository. | |
| 582 self.svn_url = svn_url | |
| 583 self.trunk = trunk | |
| 584 assert bool(self.commit_user) >= bool(self.commit_pwd) | |
| 585 assert self.svn_url | |
| 586 assert self.trunk | |
| 587 self._cache_svn_auth() | |
| 588 | |
| 589 def prepare(self, revision): | |
| 590 """Resets the git repository in a clean state.""" | |
| 591 self._check_call_git(['reset', '--hard', '--quiet']) | |
| 592 if revision: | |
| 593 try: | |
| 594 revision = self._check_output_git( | |
| 595 ['svn', 'find-rev', 'r%d' % revision]) | |
| 596 except subprocess.CalledProcessError: | |
| 597 self._check_call_git( | |
| 598 ['fetch', self.remote, self.remote_branch, '--quiet']) | |
| 599 revision = self._check_output_git( | |
| 600 ['svn', 'find-rev', 'r%d' % revision]) | |
| 601 super(GitSvnCheckoutBase, self).prepare(revision) | |
| 602 else: | |
| 603 branches, active = self._branches() | |
| 604 if active != 'master': | |
| 605 if not 'master' in branches: | |
| 606 self._check_call_git( | |
| 607 ['checkout', '--quiet', '-b', 'master', | |
| 608 '%s/%s' % (self.remote, self.remote_branch)]) | |
| 609 else: | |
| 610 self._check_call_git(['checkout', 'master', '--force', '--quiet']) | |
| 611 # git svn rebase --quiet --quiet doesn't work, use two steps to silence | |
| 612 # it. | |
| 613 self._check_call_git_svn(['fetch', '--quiet', '--quiet']) | |
| 614 self._check_call_git( | |
| 615 ['rebase', '--quiet', '--quiet', | |
| 616 '%s/%s' % (self.remote, self.remote_branch)]) | |
| 617 if self.working_branch in branches: | |
| 618 self._call_git(['branch', '-D', self.working_branch]) | |
| 619 return self._get_revision() | |
| 620 | |
| 621 def _git_svn_info(self, key): | |
| 622 """Calls git svn info. This doesn't support nor need --config-dir.""" | |
| 623 return self._parse_svn_info(self._check_output_git(['svn', 'info']), key) | |
| 624 | |
| 625 def commit(self, commit_message, user): | |
| 626 """Commits a patch.""" | |
| 627 logging.info('Committing patch for %s' % user) | |
| 628 # Fix the commit message and author. It returns the git hash, which we | |
| 629 # ignore unless it's None. | |
| 630 if not super(GitSvnCheckoutBase, self).commit(commit_message, user): | |
| 631 return None | |
| 632 # TODO(maruel): git-svn ignores --config-dir as of git-svn version 1.7.4 and | |
| 633 # doesn't support --with-revprop. | |
| 634 # Either learn perl and upstream or suck it. | |
| 635 kwargs = {} | |
| 636 if self.commit_pwd: | |
| 637 kwargs['stdin'] = self.commit_pwd + '\n' | |
| 638 kwargs['stderr'] = subprocess2.STDOUT | |
| 639 self._check_call_git_svn( | |
| 640 ['dcommit', '--rmdir', '--find-copies-harder', | |
| 641 '--username', self.commit_user], | |
| 642 **kwargs) | |
| 643 revision = int(self._git_svn_info('revision')) | |
| 644 return revision | |
| 645 | |
| 646 def _cache_svn_auth(self): | |
| 647 """Caches the svn credentials. It is necessary since git-svn doesn't prompt | |
| 648 for it.""" | |
| 649 if not self.commit_user or not self.commit_pwd: | |
| 650 return | |
| 651 # Use capture to lower noise in logs. | |
| 652 self._check_output_svn(['ls', self.svn_url], cwd=None) | |
| 653 | |
| 654 def _check_call_git_svn(self, args, **kwargs): | |
| 655 """Handles svn authentication while calling git svn.""" | |
| 656 args = ['svn'] + args | |
| 657 if not self.svn_config.default: | |
| 658 args.extend(['--config-dir', self.svn_config.svn_config_dir]) | |
| 659 return self._check_call_git(args, **kwargs) | |
| 660 | |
| 661 def _get_revision(self): | |
| 662 revision = int(self._git_svn_info('revision')) | |
| 663 if revision != self._last_seen_revision: | |
| 664 logging.info('Updated to revision %d' % revision) | |
| 665 self._last_seen_revision = revision | |
| 666 return revision | |
| 667 | |
| 668 | |
| 669 class GitSvnPremadeCheckout(GitSvnCheckoutBase): | |
| 670 """Manages a git-svn clone made out from an initial git-svn seed. | |
| 671 | |
| 672 This class is very similar to GitSvnCheckout but is faster to bootstrap | |
| 673 because it starts right off with an existing git-svn clone. | |
| 674 """ | |
| 675 def __init__(self, | |
| 676 root_dir, project_name, remote_branch, | |
| 677 commit_user, commit_pwd, | |
| 678 svn_url, trunk, git_url, post_processors=None): | |
| 679 super(GitSvnPremadeCheckout, self).__init__( | |
| 680 root_dir, project_name, remote_branch, | |
| 681 commit_user, commit_pwd, | |
| 682 svn_url, trunk, post_processors) | |
| 683 self.git_url = git_url | |
| 684 assert self.git_url | |
| 685 | |
| 686 def prepare(self, revision): | |
| 687 """Creates the initial checkout for the repo.""" | |
| 688 if not os.path.isdir(self.project_path): | |
| 689 logging.info('Checking out %s in %s' % | |
| 690 (self.project_name, self.project_path)) | |
| 691 assert self.remote == 'origin' | |
| 692 # self.project_path doesn't exist yet. | |
| 693 self._check_call_git( | |
| 694 ['clone', self.git_url, self.project_name, '--quiet'], | |
| 695 cwd=self.root_dir, | |
| 696 stderr=subprocess2.STDOUT) | |
| 697 try: | |
| 698 configured_svn_url = self._check_output_git( | |
| 699 ['config', 'svn-remote.svn.url']).strip() | |
| 700 except subprocess.CalledProcessError: | |
| 701 configured_svn_url = '' | |
| 702 | |
| 703 if configured_svn_url.strip() != self.svn_url: | |
| 704 self._check_call_git_svn( | |
| 705 ['init', | |
| 706 '--prefix', self.remote + '/', | |
| 707 '-T', self.trunk, | |
| 708 self.svn_url]) | |
| 709 self._check_call_git_svn(['fetch']) | |
| 710 return super(GitSvnPremadeCheckout, self).prepare(revision) | |
| 711 | |
| 712 | |
| 713 class GitSvnCheckout(GitSvnCheckoutBase): | |
| 714 """Manages a git-svn clone. | |
| 715 | |
| 716 Using git-svn hides some of the complexity of using a svn checkout. | |
| 717 """ | |
| 718 def __init__(self, | |
| 719 root_dir, project_name, | |
| 720 commit_user, commit_pwd, | |
| 721 svn_url, trunk, post_processors=None): | |
| 722 super(GitSvnCheckout, self).__init__( | |
| 723 root_dir, project_name, 'trunk', | |
| 724 commit_user, commit_pwd, | |
| 725 svn_url, trunk, post_processors) | |
| 726 | |
| 727 def prepare(self, revision): | |
| 728 """Creates the initial checkout for the repo.""" | |
| 729 assert not revision, 'Implement revision if necessary' | |
| 730 if not os.path.isdir(self.project_path): | |
| 731 logging.info('Checking out %s in %s' % | |
| 732 (self.project_name, self.project_path)) | |
| 733 # TODO: Create a shallow clone. | |
| 734 # self.project_path doesn't exist yet. | |
| 735 self._check_call_git_svn( | |
| 736 ['clone', | |
| 737 '--prefix', self.remote + '/', | |
| 738 '-T', self.trunk, | |
| 739 self.svn_url, self.project_path, | |
| 740 '--quiet'], | |
| 741 cwd=self.root_dir, | |
| 742 stderr=subprocess2.STDOUT) | |
| 743 return super(GitSvnCheckout, self).prepare(revision) | |
| 744 | |
| 745 | |
| 746 class ReadOnlyCheckout(object): | 569 class ReadOnlyCheckout(object): |
| 747 """Converts a checkout into a read-only one.""" | 570 """Converts a checkout into a read-only one.""" |
| 748 def __init__(self, checkout, post_processors=None): | 571 def __init__(self, checkout, post_processors=None): |
| 749 super(ReadOnlyCheckout, self).__init__() | 572 super(ReadOnlyCheckout, self).__init__() |
| 750 self.checkout = checkout | 573 self.checkout = checkout |
| 751 self.post_processors = (post_processors or []) + ( | 574 self.post_processors = (post_processors or []) + ( |
| 752 self.checkout.post_processors or []) | 575 self.checkout.post_processors or []) |
| 753 | 576 |
| 754 def prepare(self, revision): | 577 def prepare(self, revision): |
| 755 return self.checkout.prepare(revision) | 578 return self.checkout.prepare(revision) |
| (...skipping 10 matching lines...) Expand all Loading... |
| 766 user, message)) | 589 user, message)) |
| 767 return 'FAKE' | 590 return 'FAKE' |
| 768 | 591 |
| 769 @property | 592 @property |
| 770 def project_name(self): | 593 def project_name(self): |
| 771 return self.checkout.project_name | 594 return self.checkout.project_name |
| 772 | 595 |
| 773 @property | 596 @property |
| 774 def project_path(self): | 597 def project_path(self): |
| 775 return self.checkout.project_path | 598 return self.checkout.project_path |
| OLD | NEW |