| 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 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 124 """Checks out a clean copy of the tree and removes any local modification. | 124 """Checks out a clean copy of the tree and removes any local modification. |
| 125 | 125 |
| 126 This function shouldn't throw unless the remote repository is inaccessible, | 126 This function shouldn't throw unless the remote repository is inaccessible, |
| 127 there is no free disk space or hard issues like that. | 127 there is no free disk space or hard issues like that. |
| 128 | 128 |
| 129 Args: | 129 Args: |
| 130 revision: The revision it should sync to, SCM specific. | 130 revision: The revision it should sync to, SCM specific. |
| 131 """ | 131 """ |
| 132 raise NotImplementedError() | 132 raise NotImplementedError() |
| 133 | 133 |
| 134 def apply_patch(self, patches, post_processors=None, verbose=False): | 134 def apply_patch(self, patches, post_processors=None, verbose=False, |
| 135 name=None, email=None): |
| 135 """Applies a patch and returns the list of modified files. | 136 """Applies a patch and returns the list of modified files. |
| 136 | 137 |
| 137 This function should throw patch.UnsupportedPatchFormat or | 138 This function should throw patch.UnsupportedPatchFormat or |
| 138 PatchApplicationFailed when relevant. | 139 PatchApplicationFailed when relevant. |
| 139 | 140 |
| 140 Args: | 141 Args: |
| 141 patches: patch.PatchSet object. | 142 patches: patch.PatchSet object. |
| 142 """ | 143 """ |
| 143 raise NotImplementedError() | 144 raise NotImplementedError() |
| 144 | 145 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 158 | 159 |
| 159 class RawCheckout(CheckoutBase): | 160 class RawCheckout(CheckoutBase): |
| 160 """Used to apply a patch locally without any intent to commit it. | 161 """Used to apply a patch locally without any intent to commit it. |
| 161 | 162 |
| 162 To be used by the try server. | 163 To be used by the try server. |
| 163 """ | 164 """ |
| 164 def prepare(self, revision): | 165 def prepare(self, revision): |
| 165 """Stubbed out.""" | 166 """Stubbed out.""" |
| 166 pass | 167 pass |
| 167 | 168 |
| 168 def apply_patch(self, patches, post_processors=None, verbose=False): | 169 def apply_patch(self, patches, post_processors=None, verbose=False, |
| 170 name=None, email=None): |
| 169 """Ignores svn properties.""" | 171 """Ignores svn properties.""" |
| 170 post_processors = post_processors or self.post_processors or [] | 172 post_processors = post_processors or self.post_processors or [] |
| 171 for p in patches: | 173 for p in patches: |
| 172 stdout = [] | 174 stdout = [] |
| 173 try: | 175 try: |
| 174 filepath = os.path.join(self.project_path, p.filename) | 176 filepath = os.path.join(self.project_path, p.filename) |
| 175 if p.is_delete: | 177 if p.is_delete: |
| 176 os.remove(filepath) | 178 os.remove(filepath) |
| 177 stdout.append('Deleted.') | 179 stdout.append('Deleted.') |
| 178 else: | 180 else: |
| (...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 342 assert bool(self.commit_user) >= bool(self.commit_pwd) | 344 assert bool(self.commit_user) >= bool(self.commit_pwd) |
| 343 | 345 |
| 344 def prepare(self, revision): | 346 def prepare(self, revision): |
| 345 # Will checkout if the directory is not present. | 347 # Will checkout if the directory is not present. |
| 346 assert self.svn_url | 348 assert self.svn_url |
| 347 if not os.path.isdir(self.project_path): | 349 if not os.path.isdir(self.project_path): |
| 348 logging.info('Checking out %s in %s' % | 350 logging.info('Checking out %s in %s' % |
| 349 (self.project_name, self.project_path)) | 351 (self.project_name, self.project_path)) |
| 350 return self._revert(revision) | 352 return self._revert(revision) |
| 351 | 353 |
| 352 def apply_patch(self, patches, post_processors=None, verbose=False): | 354 def apply_patch(self, patches, post_processors=None, verbose=False, |
| 355 name=None, email=None): |
| 353 post_processors = post_processors or self.post_processors or [] | 356 post_processors = post_processors or self.post_processors or [] |
| 354 for p in patches: | 357 for p in patches: |
| 355 stdout = [] | 358 stdout = [] |
| 356 try: | 359 try: |
| 357 filepath = os.path.join(self.project_path, p.filename) | 360 filepath = os.path.join(self.project_path, p.filename) |
| 358 # It is important to use credentials=False otherwise credentials could | 361 # It is important to use credentials=False otherwise credentials could |
| 359 # leak in the error message. Credentials are not necessary here for the | 362 # leak in the error message. Credentials are not necessary here for the |
| 360 # following commands anyway. | 363 # following commands anyway. |
| 361 if p.is_delete: | 364 if p.is_delete: |
| 362 stdout.append(self._check_output_svn( | 365 stdout.append(self._check_output_svn( |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 546 ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) | 549 ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) |
| 547 except subprocess.CalledProcessError: | 550 except subprocess.CalledProcessError: |
| 548 return None | 551 return None |
| 549 # Ignore the '----' lines. | 552 # Ignore the '----' lines. |
| 550 return len([l for l in out.splitlines() if l.startswith('r')]) - 1 | 553 return len([l for l in out.splitlines() if l.startswith('r')]) - 1 |
| 551 | 554 |
| 552 | 555 |
| 553 class GitCheckout(CheckoutBase): | 556 class GitCheckout(CheckoutBase): |
| 554 """Manages a git checkout.""" | 557 """Manages a git checkout.""" |
| 555 def __init__(self, root_dir, project_name, remote_branch, git_url, | 558 def __init__(self, root_dir, project_name, remote_branch, git_url, |
| 556 commit_user, post_processors=None): | 559 commit_user, post_processors=None, base_ref=None): |
| 557 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 |
| 558 self.git_url = git_url | 562 self.git_url = git_url |
| 559 self.commit_user = commit_user | 563 self.commit_user = commit_user |
| 560 self.remote_branch = remote_branch | 564 self.remote_branch = remote_branch |
| 561 # 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 |
| 562 # remote branch. | 566 # remote branch. |
| 563 self.working_branch = 'working_branch' | 567 self.working_branch = 'working_branch' |
| 564 # There is no reason to not hardcode origin. | 568 # There is no reason to not hardcode origin. |
| 565 self.remote = 'origin' | 569 self.remote = 'origin' |
| 566 # There is no reason to not hardcode master. | 570 # There is no reason to not hardcode master. |
| 567 self.master_branch = 'master' | 571 self.master_branch = 'master' |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 620 self.remote, self.remote_branch) | 624 self.remote, self.remote_branch) |
| 621 self._check_call_git( | 625 self._check_call_git( |
| 622 ['pull', self.remote, | 626 ['pull', self.remote, |
| 623 '%s:%s' % (self.remote_branch, remote_tracked_path), | 627 '%s:%s' % (self.remote_branch, remote_tracked_path), |
| 624 '--quiet']) | 628 '--quiet']) |
| 625 | 629 |
| 626 def _get_head_commit_hash(self): | 630 def _get_head_commit_hash(self): |
| 627 """Gets the current revision (in unicode) from the local branch.""" | 631 """Gets the current revision (in unicode) from the local branch.""" |
| 628 return unicode(self._check_output_git(['rev-parse', 'HEAD']).strip()) | 632 return unicode(self._check_output_git(['rev-parse', 'HEAD']).strip()) |
| 629 | 633 |
| 630 def apply_patch(self, patches, post_processors=None, verbose=False): | 634 def apply_patch(self, patches, post_processors=None, verbose=False, |
| 635 name=None, email=None): |
| 631 """Applies a patch on 'working_branch' and switches to it. | 636 """Applies a patch on 'working_branch' and switches to it. |
| 632 | 637 |
| 633 The changes remain staged on the current branch. | 638 Also commits the changes on the local branch. |
| 634 | 639 |
| 635 Ignores svn properties and raise an exception on unexpected ones. | 640 Ignores svn properties and raise an exception on unexpected ones. |
| 636 """ | 641 """ |
| 637 post_processors = post_processors or self.post_processors or [] | 642 post_processors = post_processors or self.post_processors or [] |
| 638 # It this throws, the checkout is corrupted. Maybe worth deleting it and | 643 # It this throws, the checkout is corrupted. Maybe worth deleting it and |
| 639 # trying again? | 644 # trying again? |
| 640 if self.remote_branch: | 645 if self.remote_branch: |
| 641 self._check_call_git( | 646 self._check_call_git( |
| 642 ['checkout', '-b', self.working_branch, '-t', self.remote_branch, | 647 ['checkout', '-b', self.working_branch, '-t', self.remote_branch, |
| 643 '--quiet']) | 648 '--quiet']) |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 697 print align_stdout(stdout) | 702 print align_stdout(stdout) |
| 698 except OSError, e: | 703 except OSError, e: |
| 699 raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) | 704 raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e)) |
| 700 except subprocess.CalledProcessError, e: | 705 except subprocess.CalledProcessError, e: |
| 701 raise PatchApplicationFailed( | 706 raise PatchApplicationFailed( |
| 702 p, | 707 p, |
| 703 'While running %s;\n%s%s' % ( | 708 'While running %s;\n%s%s' % ( |
| 704 ' '.join(e.cmd), | 709 ' '.join(e.cmd), |
| 705 align_stdout(stdout), | 710 align_stdout(stdout), |
| 706 align_stdout([getattr(e, 'stdout', '')]))) | 711 align_stdout([getattr(e, 'stdout', '')]))) |
| 712 # Once all the patches are processed and added to the index, commit the |
| 713 # index. |
| 714 cmd = ['commit', '-m', 'Committed patch'] |
| 715 if name and email: |
| 716 cmd = ['-c', 'user.email=%s' % email, '-c', 'user.name=%s' % name] + cmd |
| 717 if verbose: |
| 718 cmd.append('--verbose') |
| 719 self._check_call_git(cmd) |
| 720 if self.base_ref: |
| 721 base_ref = self.base_ref |
| 722 else: |
| 723 base_ref = '%s/%s' % (self.remote, |
| 724 self.remote_branch or self.master_branch) |
| 707 found_files = self._check_output_git( | 725 found_files = self._check_output_git( |
| 708 ['diff', '--ignore-submodules', | 726 ['diff', base_ref, '--ignore-submodules', |
| 709 '--name-only', '--staged']).splitlines(False) | 727 '--name-only']).splitlines(False) |
| 710 assert sorted(patches.filenames) == sorted(found_files), ( | 728 assert sorted(patches.filenames) == sorted(found_files), ( |
| 711 'Found extra %s locally, %s not patched' % ( | 729 'Found extra %s locally, %s not patched' % ( |
| 712 sorted(set(found_files) - set(patches.filenames)), | 730 sorted(set(found_files) - set(patches.filenames)), |
| 713 sorted(set(patches.filenames) - set(found_files)))) | 731 sorted(set(patches.filenames) - set(found_files)))) |
| 714 | 732 |
| 715 def commit(self, commit_message, user): | 733 def commit(self, commit_message, user): |
| 716 """Commits, updates the commit message and pushes.""" | 734 """Commits, updates the commit message and pushes.""" |
| 717 # TODO(hinoka): CQ no longer uses this, I think its deprecated. | |
| 718 # Delete this. | |
| 719 assert self.commit_user | 735 assert self.commit_user |
| 720 assert isinstance(commit_message, unicode) | 736 assert isinstance(commit_message, unicode) |
| 721 current_branch = self._check_output_git( | 737 current_branch = self._check_output_git( |
| 722 ['rev-parse', '--abbrev-ref', 'HEAD']).strip() | 738 ['rev-parse', '--abbrev-ref', 'HEAD']).strip() |
| 723 assert current_branch == self.working_branch | 739 assert current_branch == self.working_branch |
| 724 | 740 |
| 725 commit_cmd = ['commit', '-m', commit_message] | 741 commit_cmd = ['commit', '--amend', '-m', commit_message] |
| 726 if user and user != self.commit_user: | 742 if user and user != self.commit_user: |
| 727 # We do not have the first or last name of the user, grab the username | 743 # We do not have the first or last name of the user, grab the username |
| 728 # from the email and call it the original author's name. | 744 # from the email and call it the original author's name. |
| 729 # TODO(rmistry): Do not need the below if user is already in | 745 # TODO(rmistry): Do not need the below if user is already in |
| 730 # "Name <email>" format. | 746 # "Name <email>" format. |
| 731 name = user.split('@')[0] | 747 name = user.split('@')[0] |
| 732 commit_cmd.extend(['--author', '%s <%s>' % (name, user)]) | 748 commit_cmd.extend(['--author', '%s <%s>' % (name, user)]) |
| 733 self._check_call_git(commit_cmd) | 749 self._check_call_git(commit_cmd) |
| 734 | 750 |
| 735 # Push to the remote repository. | 751 # Push to the remote repository. |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 803 self.checkout = checkout | 819 self.checkout = checkout |
| 804 self.post_processors = (post_processors or []) + ( | 820 self.post_processors = (post_processors or []) + ( |
| 805 self.checkout.post_processors or []) | 821 self.checkout.post_processors or []) |
| 806 | 822 |
| 807 def prepare(self, revision): | 823 def prepare(self, revision): |
| 808 return self.checkout.prepare(revision) | 824 return self.checkout.prepare(revision) |
| 809 | 825 |
| 810 def get_settings(self, key): | 826 def get_settings(self, key): |
| 811 return self.checkout.get_settings(key) | 827 return self.checkout.get_settings(key) |
| 812 | 828 |
| 813 def apply_patch(self, patches, post_processors=None, verbose=False): | 829 def apply_patch(self, patches, post_processors=None, verbose=False, |
| 830 name=None, email=None): |
| 814 return self.checkout.apply_patch( | 831 return self.checkout.apply_patch( |
| 815 patches, post_processors or self.post_processors, verbose) | 832 patches, post_processors or self.post_processors, verbose) |
| 816 | 833 |
| 817 def commit(self, message, user): # pylint: disable=R0201 | 834 def commit(self, message, user): # pylint: disable=R0201 |
| 818 logging.info('Would have committed for %s with message: %s' % ( | 835 logging.info('Would have committed for %s with message: %s' % ( |
| 819 user, message)) | 836 user, message)) |
| 820 return 'FAKE' | 837 return 'FAKE' |
| 821 | 838 |
| 822 def revisions(self, rev1, rev2): | 839 def revisions(self, rev1, rev2): |
| 823 return self.checkout.revisions(rev1, rev2) | 840 return self.checkout.revisions(rev1, rev2) |
| 824 | 841 |
| 825 @property | 842 @property |
| 826 def project_name(self): | 843 def project_name(self): |
| 827 return self.checkout.project_name | 844 return self.checkout.project_name |
| 828 | 845 |
| 829 @property | 846 @property |
| 830 def project_path(self): | 847 def project_path(self): |
| 831 return self.checkout.project_path | 848 return self.checkout.project_path |
| OLD | NEW |