Chromium Code Reviews| 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 commit_user, post_processors=None, base_ref=None): | 559 commit_user, post_processors=None, base_ref=None): |
| 560 super(GitCheckout, self).__init__(root_dir, project_name, post_processors) | 560 super(GitCheckout, self).__init__(root_dir, project_name, post_processors) |
| 561 self.base_ref = base_ref | 561 self.base_ref = base_ref |
| 562 self.git_url = git_url | 562 self.git_url = git_url |
| 563 self.commit_user = commit_user | 563 self.commit_user = commit_user |
| 564 self.remote_branch = remote_branch | 564 self.remote_branch = remote_branch |
| 565 # The working branch where patches will be applied. It will track the | 565 # The working branch where patches will be applied. It will track the |
| 566 # remote branch. | 566 # remote branch. |
| 567 self.working_branch = 'working_branch' | 567 self.working_branch = 'working_branch' |
| 568 # There is no reason to not hardcode origin. | 568 # There is no reason to not hardcode origin. |
| 569 self.remote = 'origin' | 569 self.pull_remote = 'origin' |
| 570 # There is no reason to not hardcode master. | 570 self.push_remote = 'upstream' |
| 571 self.master_branch = 'master' | |
| 572 | 571 |
| 573 def prepare(self, revision): | 572 def prepare(self, revision): |
| 574 """Resets the git repository in a clean state. | 573 """Resets the git repository in a clean state. |
| 575 | 574 |
| 576 Checks it out if not present and deletes the working branch. | 575 Checks it out if not present and deletes the working branch. |
| 577 """ | 576 """ |
| 577 assert self.git_url | |
| 578 assert self.remote_branch | 578 assert self.remote_branch |
| 579 assert self.git_url | 579 |
| 580 self._check_call_git( | |
| 581 ['cache', 'populate', self.git_url], timeout=FETCH_TIMEOUT, cwd=None) | |
| 582 cache_path = self._check_output_git( | |
| 583 ['cache', 'exists', self.git_url], cwd=None).strip() | |
| 580 | 584 |
| 581 if not os.path.isdir(self.project_path): | 585 if not os.path.isdir(self.project_path): |
| 582 # Clone the repo if the directory is not present. | 586 # Clone the repo if the directory is not present. |
| 583 logging.info( | |
| 584 'Checking out %s in %s', self.project_name, self.project_path) | |
| 585 self._check_call_git( | 587 self._check_call_git( |
| 586 ['clone', self.git_url, '-b', self.remote_branch, self.project_path], | 588 ['clone', '--shared', cache_path, self.project_path], |
| 587 cwd=None, timeout=FETCH_TIMEOUT) | 589 cwd=None, timeout=FETCH_TIMEOUT) |
| 588 else: | 590 self._call_git( |
| 589 # Throw away all uncommitted changes in the existing checkout. | 591 ['config', 'remote.%s.url' % self.push_remote, self.git_url], |
| 590 self._check_call_git(['checkout', self.remote_branch]) | 592 cwd=self.project_path) |
| 591 self._check_call_git( | |
| 592 ['reset', '--hard', '--quiet', | |
| 593 '%s/%s' % (self.remote, self.remote_branch)]) | |
| 594 | 593 |
| 595 if revision: | 594 if not revision: |
| 596 try: | 595 revision = self.remote_branch |
| 597 # Look if the commit hash already exist. If so, we can skip a | |
| 598 # 'git fetch' call. | |
| 599 revision = self._check_output_git(['rev-parse', revision]) | |
| 600 except subprocess.CalledProcessError: | |
| 601 self._check_call_git( | |
| 602 ['fetch', self.remote, self.remote_branch, '--quiet']) | |
| 603 revision = self._check_output_git(['rev-parse', revision]) | |
| 604 self._check_call_git(['checkout', '--force', '--quiet', revision]) | |
| 605 else: | |
| 606 branches, active = self._branches() | |
| 607 if active != self.master_branch: | |
| 608 self._check_call_git( | |
| 609 ['checkout', '--force', '--quiet', self.master_branch]) | |
| 610 self._sync_remote_branch() | |
| 611 | 596 |
| 612 if self.working_branch in branches: | 597 if not re.match(r'[0-9a-f]{40}$', revision): |
|
iannucci
2014/03/08 10:15:06
add a (?i) to make this case insensitive
What abo
agable
2014/03/11 17:23:30
Done
| |
| 613 self._call_git(['branch', '-D', self.working_branch]) | 598 self._check_call_git(['fetch', self.pull_remote, revision]) |
| 599 revision = self.pull_remote + '/' + revision | |
| 600 | |
| 601 self._check_call_git(['checkout', '--force', '--quiet', revision]) | |
| 602 self._call_git(['clean', '-fdx']) | |
| 603 | |
| 604 branches, _ = self._branches() | |
| 605 if self.working_branch in branches: | |
| 606 self._call_git(['branch', '-D', self.working_branch]) | |
| 607 | |
| 614 return self._get_head_commit_hash() | 608 return self._get_head_commit_hash() |
| 615 | 609 |
| 616 def _sync_remote_branch(self): | |
| 617 """Syncs the remote branch.""" | |
| 618 # We do a 'git pull origin master:refs/remotes/origin/master' instead of | |
| 619 # 'git pull origin master' because from the manpage for git-pull: | |
| 620 # A parameter <ref> without a colon is equivalent to <ref>: when | |
| 621 # pulling/fetching, so it merges <ref> into the current branch without | |
| 622 # storing the remote branch anywhere locally. | |
| 623 remote_tracked_path = 'refs/remotes/%s/%s' % ( | |
| 624 self.remote, self.remote_branch) | |
| 625 self._check_call_git( | |
| 626 ['pull', self.remote, | |
| 627 '%s:%s' % (self.remote_branch, remote_tracked_path), | |
| 628 '--quiet']) | |
| 629 | |
| 630 def _get_head_commit_hash(self): | 610 def _get_head_commit_hash(self): |
| 631 """Gets the current revision (in unicode) from the local branch.""" | 611 """Gets the current revision (in unicode) from the local branch.""" |
| 632 return unicode(self._check_output_git(['rev-parse', 'HEAD']).strip()) | 612 return unicode(self._check_output_git(['rev-parse', 'HEAD']).strip()) |
| 633 | 613 |
| 634 def apply_patch(self, patches, post_processors=None, verbose=False, | 614 def apply_patch(self, patches, post_processors=None, verbose=False, |
| 635 name=None, email=None): | 615 name=None, email=None): |
| 636 """Applies a patch on 'working_branch' and switches to it. | 616 """Applies a patch on 'working_branch' and switches to it. |
| 637 | 617 |
| 638 Also commits the changes on the local branch. | 618 Also commits the changes on the local branch. |
| 639 | 619 |
| 640 Ignores svn properties and raise an exception on unexpected ones. | 620 Ignores svn properties and raise an exception on unexpected ones. |
| 641 """ | 621 """ |
| 642 post_processors = post_processors or self.post_processors or [] | 622 post_processors = post_processors or self.post_processors or [] |
| 643 # It this throws, the checkout is corrupted. Maybe worth deleting it and | 623 # It this throws, the checkout is corrupted. Maybe worth deleting it and |
| 644 # trying again? | 624 # trying again? |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 713 # index. | 693 # index. |
| 714 cmd = ['commit', '-m', 'Committed patch'] | 694 cmd = ['commit', '-m', 'Committed patch'] |
| 715 if name and email: | 695 if name and email: |
| 716 cmd = ['-c', 'user.email=%s' % email, '-c', 'user.name=%s' % name] + cmd | 696 cmd = ['-c', 'user.email=%s' % email, '-c', 'user.name=%s' % name] + cmd |
| 717 if verbose: | 697 if verbose: |
| 718 cmd.append('--verbose') | 698 cmd.append('--verbose') |
| 719 self._check_call_git(cmd) | 699 self._check_call_git(cmd) |
| 720 if self.base_ref: | 700 if self.base_ref: |
| 721 base_ref = self.base_ref | 701 base_ref = self.base_ref |
| 722 else: | 702 else: |
| 723 base_ref = '%s/%s' % (self.remote, | 703 base_ref = '%s/%s' % (self.pull_remote, self.remote_branch) |
| 724 self.remote_branch or self.master_branch) | |
| 725 found_files = self._check_output_git( | 704 found_files = self._check_output_git( |
| 726 ['diff', base_ref, | 705 ['diff', base_ref, |
| 727 '--name-only']).splitlines(False) | 706 '--name-only']).splitlines(False) |
| 728 assert sorted(patches.filenames) == sorted(found_files), ( | 707 assert sorted(patches.filenames) == sorted(found_files), ( |
| 729 sorted(patches.filenames), sorted(found_files)) | 708 sorted(patches.filenames), sorted(found_files)) |
| 730 | 709 |
| 731 def commit(self, commit_message, user): | 710 def commit(self, commit_message, user): |
| 732 """Commits, updates the commit message and pushes.""" | 711 """Commits, updates the commit message and pushes.""" |
| 733 assert self.commit_user | 712 assert self.commit_user |
| 734 assert isinstance(commit_message, unicode) | 713 assert isinstance(commit_message, unicode) |
| 735 current_branch = self._check_output_git( | 714 current_branch = self._check_output_git( |
| 736 ['rev-parse', '--abbrev-ref', 'HEAD']).strip() | 715 ['rev-parse', '--abbrev-ref', 'HEAD']).strip() |
| 737 assert current_branch == self.working_branch | 716 assert current_branch == self.working_branch |
| 738 | 717 |
| 739 commit_cmd = ['commit', '--amend', '-m', commit_message] | 718 commit_cmd = ['commit', '--amend', '-m', commit_message] |
| 740 if user and user != self.commit_user: | 719 if user and user != self.commit_user: |
| 741 # We do not have the first or last name of the user, grab the username | 720 # We do not have the first or last name of the user, grab the username |
| 742 # from the email and call it the original author's name. | 721 # from the email and call it the original author's name. |
| 743 # TODO(rmistry): Do not need the below if user is already in | 722 # TODO(rmistry): Do not need the below if user is already in |
| 744 # "Name <email>" format. | 723 # "Name <email>" format. |
| 745 name = user.split('@')[0] | 724 name = user.split('@')[0] |
| 746 commit_cmd.extend(['--author', '%s <%s>' % (name, user)]) | 725 commit_cmd.extend(['--author', '%s <%s>' % (name, user)]) |
| 747 self._check_call_git(commit_cmd) | 726 self._check_call_git(commit_cmd) |
| 748 | 727 |
| 749 # Push to the remote repository. | 728 # Push to the remote repository. |
| 750 self._check_call_git( | 729 self._check_call_git( |
| 751 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch), | 730 ['push', self.push_remote, |
| 731 '%s:%s' % (self.working_branch, self.remote_branch), | |
| 752 '--force', '--quiet']) | 732 '--force', '--quiet']) |
| 753 # Get the revision after the push. | 733 # Get the revision after the push. |
| 754 revision = self._get_head_commit_hash() | 734 revision = self._get_head_commit_hash() |
| 755 # Switch back to the remote_branch and sync it. | 735 # Switch back to the remote_branch. |
| 756 self._check_call_git(['checkout', self.remote_branch]) | 736 self._check_call_git(['fetch', self.pull_remote, self.remote_branch]) |
| 757 self._sync_remote_branch() | 737 self._check_call_git(['checkout', '--force', '--quiet', |
| 738 '%s/%s' % (self.pull_remote, self.remote_branch)]) | |
| 758 # Delete the working branch since we are done with it. | 739 # Delete the working branch since we are done with it. |
| 759 self._check_call_git(['branch', '-D', self.working_branch]) | 740 self._check_call_git(['branch', '-D', self.working_branch]) |
| 760 | 741 |
| 761 return revision | 742 return revision |
| 762 | 743 |
| 763 def _check_call_git(self, args, **kwargs): | 744 def _check_call_git(self, args, **kwargs): |
| 764 kwargs.setdefault('cwd', self.project_path) | 745 kwargs.setdefault('cwd', self.project_path) |
| 765 kwargs.setdefault('stdout', self.VOID) | 746 kwargs.setdefault('stdout', self.VOID) |
| 766 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) | 747 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
| 767 return subprocess2.check_call_out(['git'] + args, **kwargs) | 748 return subprocess2.check_call_out(['git'] + args, **kwargs) |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 787 for l in out: | 768 for l in out: |
| 788 if l.startswith('*'): | 769 if l.startswith('*'): |
| 789 active = l[2:] | 770 active = l[2:] |
| 790 break | 771 break |
| 791 return branches, active | 772 return branches, active |
| 792 | 773 |
| 793 def revisions(self, rev1, rev2): | 774 def revisions(self, rev1, rev2): |
| 794 """Returns the number of actual commits between both hash.""" | 775 """Returns the number of actual commits between both hash.""" |
| 795 self._fetch_remote() | 776 self._fetch_remote() |
| 796 | 777 |
| 797 rev2 = rev2 or '%s/%s' % (self.remote, self.remote_branch) | 778 rev2 = rev2 or '%s/%s' % (self.pull_remote, self.remote_branch) |
| 798 # Revision range is ]rev1, rev2] and ordering matters. | 779 # Revision range is ]rev1, rev2] and ordering matters. |
| 799 try: | 780 try: |
| 800 out = self._check_output_git( | 781 out = self._check_output_git( |
| 801 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)]) | 782 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)]) |
| 802 except subprocess.CalledProcessError: | 783 except subprocess.CalledProcessError: |
| 803 return None | 784 return None |
| 804 return len(out.splitlines()) | 785 return len(out.splitlines()) |
| 805 | 786 |
| 806 def _fetch_remote(self): | 787 def _fetch_remote(self): |
| 807 """Fetches the remote without rebasing.""" | 788 """Fetches the remote without rebasing.""" |
| 808 # git fetch is always verbose even with -q, so redirect its output. | 789 # git fetch is always verbose even with -q, so redirect its output. |
| 809 self._check_output_git(['fetch', self.remote, self.remote_branch], | 790 self._check_output_git(['fetch', self.pull_remote, self.remote_branch], |
| 810 timeout=FETCH_TIMEOUT) | 791 timeout=FETCH_TIMEOUT) |
| 811 | 792 |
| 812 | 793 |
| 813 class ReadOnlyCheckout(object): | 794 class ReadOnlyCheckout(object): |
| 814 """Converts a checkout into a read-only one.""" | 795 """Converts a checkout into a read-only one.""" |
| 815 def __init__(self, checkout, post_processors=None): | 796 def __init__(self, checkout, post_processors=None): |
| 816 super(ReadOnlyCheckout, self).__init__() | 797 super(ReadOnlyCheckout, self).__init__() |
| 817 self.checkout = checkout | 798 self.checkout = checkout |
| 818 self.post_processors = (post_processors or []) + ( | 799 self.post_processors = (post_processors or []) + ( |
| 819 self.checkout.post_processors or []) | 800 self.checkout.post_processors or []) |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 837 def revisions(self, rev1, rev2): | 818 def revisions(self, rev1, rev2): |
| 838 return self.checkout.revisions(rev1, rev2) | 819 return self.checkout.revisions(rev1, rev2) |
| 839 | 820 |
| 840 @property | 821 @property |
| 841 def project_name(self): | 822 def project_name(self): |
| 842 return self.checkout.project_name | 823 return self.checkout.project_name |
| 843 | 824 |
| 844 @property | 825 @property |
| 845 def project_path(self): | 826 def project_path(self): |
| 846 return self.checkout.project_path | 827 return self.checkout.project_path |
| OLD | NEW |