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 532 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
543 # the order specified. | 543 # the order specified. |
544 try: | 544 try: |
545 out = self._check_output_svn( | 545 out = self._check_output_svn( |
546 ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) | 546 ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) |
547 except subprocess.CalledProcessError: | 547 except subprocess.CalledProcessError: |
548 return None | 548 return None |
549 # Ignore the '----' lines. | 549 # Ignore the '----' lines. |
550 return len([l for l in out.splitlines() if l.startswith('r')]) - 1 | 550 return len([l for l in out.splitlines() if l.startswith('r')]) - 1 |
551 | 551 |
552 | 552 |
553 class GitCheckoutBase(CheckoutBase): | 553 class GitCheckoutBase(CheckoutBase): |
M-A Ruel
2013/08/23 17:05:10
The reason for this base class was for a git-svn s
rmistry
2013/08/26 13:08:17
Makes sense, Done.
| |
554 """Base class for git checkout. Not to be used as-is.""" | 554 """Base class for git checkout. Not to be used as-is.""" |
555 def __init__(self, root_dir, project_name, remote_branch, | 555 def __init__(self, root_dir, project_name, remote_branch, git_url, |
556 post_processors=None): | 556 post_processors=None): |
557 super(GitCheckoutBase, self).__init__( | 557 super(GitCheckoutBase, self).__init__( |
558 root_dir, project_name, post_processors) | 558 root_dir, project_name, post_processors) |
559 # There is no reason to not hardcode it. | 559 self.git_url = git_url |
560 self.remote_branch = remote_branch | |
561 # The working branch where patches will be applied. It will track the | |
562 # remote branch. | |
563 self.working_branch = 'working_branch' | |
564 # There is no reason to not hardcode origin. | |
560 self.remote = 'origin' | 565 self.remote = 'origin' |
561 self.remote_branch = remote_branch | |
562 self.working_branch = 'working_branch' | |
563 | 566 |
564 def prepare(self, revision): | 567 def prepare(self, revision): |
565 """Resets the git repository in a clean state. | 568 """Resets the git repository in a clean state. |
566 | 569 |
567 Checks it out if not present and deletes the working branch. | 570 Checks it out if not present and deletes the working branch. |
568 """ | 571 """ |
569 assert self.remote_branch | 572 assert self.remote_branch |
570 assert os.path.isdir(self.project_path) | 573 |
571 self._check_call_git(['reset', '--hard', '--quiet']) | 574 if not os.path.isdir(self.project_path): |
575 # Clone the repo if the directory is not present. | |
576 logging.info('Checking out %s in %s' % | |
577 (self.project_name, self.project_path)) | |
578 assert self.git_url | |
M-A Ruel
2013/08/23 17:05:10
This assert should be in the constructor.
rmistry
2013/08/26 13:08:17
Done.
| |
579 self._check_call_git( | |
580 ['clone', self.git_url, '-b', self.remote_branch, self.project_path], | |
581 cwd=None, timeout=FETCH_TIMEOUT) | |
582 else: | |
583 # Throw away all uncommitted changes in the existing checkout. | |
584 self._check_call_git(['checkout', self.remote_branch]) | |
585 self._check_call_git( | |
586 ['reset', '--hard', '--quiet', | |
587 '%s/%s' % (self.remote, self.remote_branch)]) | |
588 | |
572 if revision: | 589 if revision: |
573 try: | 590 try: |
M-A Ruel
2013/08/23 17:05:10
Add a comment:
# Look if the commit hash already e
rmistry
2013/08/26 13:08:17
Done.
| |
574 revision = self._check_output_git(['rev-parse', revision]) | 591 revision = self._check_output_git(['rev-parse', revision]) |
575 except subprocess.CalledProcessError: | 592 except subprocess.CalledProcessError: |
576 self._check_call_git( | 593 self._check_call_git( |
577 ['fetch', self.remote, self.remote_branch, '--quiet']) | 594 ['fetch', self.remote, self.remote_branch, '--quiet']) |
578 revision = self._check_output_git(['rev-parse', revision]) | 595 revision = self._check_output_git(['rev-parse', revision]) |
579 self._check_call_git(['checkout', '--force', '--quiet', revision]) | 596 self._check_call_git(['checkout', '--force', '--quiet', revision]) |
580 else: | 597 else: |
581 branches, active = self._branches() | 598 branches, active = self._branches() |
582 if active != 'master': | 599 if active != 'master': |
583 self._check_call_git(['checkout', '--force', '--quiet', 'master']) | 600 self._check_call_git(['checkout', '--force', '--quiet', 'master']) |
584 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) | 601 self._check_call_git(['pull', '--quiet']) |
M-A Ruel
2013/08/23 17:05:10
Why not keep the explicit remote?
rmistry
2013/08/26 13:08:17
There is difference in behavior here between these
M-A Ruel
2013/08/26 14:01:11
Wouha, thanks, I had never realized that.
Then I'
rmistry
2013/08/26 15:16:17
Done.
Isaac (away)
2013/09/02 23:38:58
Why not "git fetch origin"? git pull is roughly a
rmistry
2013/09/03 12:40:36
The documentation on pull and fetch are a little c
| |
585 if self.working_branch in branches: | 602 if self.working_branch in branches: |
586 self._call_git(['branch', '-D', self.working_branch]) | 603 self._call_git(['branch', '-D', self.working_branch]) |
604 return self._get_revision() | |
605 | |
606 def _get_revision(self): | |
607 """Gets the current revision from the local branch.""" | |
608 return self._check_output_git(['rev-parse', 'HEAD']).strip() | |
587 | 609 |
588 def apply_patch(self, patches, post_processors=None, verbose=False): | 610 def apply_patch(self, patches, post_processors=None, verbose=False): |
589 """Applies a patch on 'working_branch' and switch to it. | 611 """Applies a patch on 'working_branch' and switches to it. |
590 | 612 |
591 Also commits the changes on the local branch. | 613 Also commits the changes on the local branch. |
592 | 614 |
593 Ignores svn properties and raise an exception on unexpected ones. | 615 Ignores svn properties and raise an exception on unexpected ones. |
594 """ | 616 """ |
595 post_processors = post_processors or self.post_processors or [] | 617 post_processors = post_processors or self.post_processors or [] |
596 # It this throws, the checkout is corrupted. Maybe worth deleting it and | 618 # It this throws, the checkout is corrupted. Maybe worth deleting it and |
597 # trying again? | 619 # trying again? |
598 if self.remote_branch: | 620 if self.remote_branch: |
599 self._check_call_git( | 621 self._check_call_git( |
600 ['checkout', '-b', self.working_branch, | 622 ['checkout', '-b', self.working_branch, '-t', self.remote_branch, |
601 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) | 623 '--quiet']) |
624 | |
602 for index, p in enumerate(patches): | 625 for index, p in enumerate(patches): |
603 stdout = [] | 626 stdout = [] |
604 try: | 627 try: |
605 filepath = os.path.join(self.project_path, p.filename) | 628 filepath = os.path.join(self.project_path, p.filename) |
606 if p.is_delete: | 629 if p.is_delete: |
607 if (not os.path.exists(filepath) and | 630 if (not os.path.exists(filepath) and |
608 any(p1.source_filename == p.filename for p1 in patches[0:index])): | 631 any(p1.source_filename == p.filename for p1 in patches[0:index])): |
609 # The file was already deleted if a prior patch with file rename | 632 # The file was already deleted if a prior patch with file rename |
610 # was already processed because 'git apply' did it for us. | 633 # was already processed because 'git apply' did it for us. |
611 pass | 634 pass |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
660 'While running %s;\n%s%s' % ( | 683 'While running %s;\n%s%s' % ( |
661 ' '.join(e.cmd), | 684 ' '.join(e.cmd), |
662 align_stdout(stdout), | 685 align_stdout(stdout), |
663 align_stdout([getattr(e, 'stdout', '')]))) | 686 align_stdout([getattr(e, 'stdout', '')]))) |
664 # Once all the patches are processed and added to the index, commit the | 687 # Once all the patches are processed and added to the index, commit the |
665 # index. | 688 # index. |
666 cmd = ['commit', '-m', 'Committed patch'] | 689 cmd = ['commit', '-m', 'Committed patch'] |
667 if verbose: | 690 if verbose: |
668 cmd.append('--verbose') | 691 cmd.append('--verbose') |
669 self._check_call_git(cmd) | 692 self._check_call_git(cmd) |
670 # TODO(maruel): Weirdly enough they don't match, need to investigate. | 693 found_files = self._check_output_git( |
671 #found_files = self._check_output_git( | 694 ['diff', '%s/%s' % (self.remote, self.remote_branch), |
672 # ['diff', 'master', '--name-only']).splitlines(False) | 695 '--name-only']).splitlines(False) |
673 #assert sorted(patches.filenames) == sorted(found_files), ( | 696 assert sorted(patches.filenames) == sorted(found_files), ( |
674 # sorted(out), sorted(found_files)) | 697 sorted(patches.filenames), sorted(found_files)) |
675 | 698 |
676 def commit(self, commit_message, user): | 699 def commit(self, commit_message, user): |
677 """Updates the commit message. | 700 """Commits and Updates the commit message. |
678 | 701 |
679 Subclass needs to dcommit or push. | 702 Subclass needs to dcommit or push. |
680 """ | 703 """ |
681 assert isinstance(commit_message, unicode) | 704 assert isinstance(commit_message, unicode) |
682 self._check_call_git(['commit', '--amend', '-m', commit_message]) | 705 self._check_call_git(['commit', '--amend', '-m', commit_message]) |
M-A Ruel
2013/08/23 17:08:04
You also want '--author', user. You don't need any
rmistry
2013/08/26 13:08:17
I do not completely understand this. If the CQ is
M-A Ruel
2013/08/26 14:01:11
What tool output this error, git? It's interesting
rmistry
2013/08/26 15:16:17
I see what I was doing wrong. I was doing:
--autho
rmistry
2013/08/27 12:44:49
Went ahead and added functionality for --author @
| |
683 return self._check_output_git(['rev-parse', 'HEAD']).strip() | 706 return self._get_revision() |
684 | 707 |
685 def _check_call_git(self, args, **kwargs): | 708 def _check_call_git(self, args, **kwargs): |
686 kwargs.setdefault('cwd', self.project_path) | 709 kwargs.setdefault('cwd', self.project_path) |
687 kwargs.setdefault('stdout', self.VOID) | 710 kwargs.setdefault('stdout', self.VOID) |
688 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) | 711 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) |
689 return subprocess2.check_call_out(['git'] + args, **kwargs) | 712 return subprocess2.check_call_out(['git'] + args, **kwargs) |
690 | 713 |
691 def _call_git(self, args, **kwargs): | 714 def _call_git(self, args, **kwargs): |
692 """Like check_call but doesn't throw on failure.""" | 715 """Like check_call but doesn't throw on failure.""" |
693 kwargs.setdefault('cwd', self.project_path) | 716 kwargs.setdefault('cwd', self.project_path) |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
726 return len(out.splitlines()) | 749 return len(out.splitlines()) |
727 | 750 |
728 def _fetch_remote(self): | 751 def _fetch_remote(self): |
729 """Fetches the remote without rebasing.""" | 752 """Fetches the remote without rebasing.""" |
730 raise NotImplementedError() | 753 raise NotImplementedError() |
731 | 754 |
732 | 755 |
733 class GitCheckout(GitCheckoutBase): | 756 class GitCheckout(GitCheckoutBase): |
734 """Git checkout implementation.""" | 757 """Git checkout implementation.""" |
735 def _fetch_remote(self): | 758 def _fetch_remote(self): |
736 # git fetch is always verbose even with -q -q so redirect its output. | 759 # git fetch is always verbose even with -q, so redirect its output. |
737 self._check_output_git(['fetch', self.remote, self.remote_branch], | 760 self._check_output_git(['fetch', self.remote, self.remote_branch], |
738 timeout=FETCH_TIMEOUT) | 761 timeout=FETCH_TIMEOUT) |
739 | 762 |
763 def commit(self, commit_message, user): | |
764 """Updates the commit message and pushes.""" | |
765 # Update the commit message and 'git commit' by calling the superclass. | |
766 rev = super(GitCheckout, self).commit(commit_message, user) | |
767 # Push to the remote repository. | |
768 self._check_call_git( | |
769 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch), | |
770 '--force', '--quiet']) | |
771 return rev | |
M-A Ruel
2013/08/23 17:05:10
Also, it should switch back to the master branch a
rmistry
2013/08/26 13:08:17
That is done in prepare step before the patches ar
M-A Ruel
2013/08/26 14:01:11
What I'm saying is that it should be done at the 2
rmistry
2013/08/26 15:16:17
I guess it does not hurt to have it in both places
| |
772 | |
740 | 773 |
741 class ReadOnlyCheckout(object): | 774 class ReadOnlyCheckout(object): |
742 """Converts a checkout into a read-only one.""" | 775 """Converts a checkout into a read-only one.""" |
743 def __init__(self, checkout, post_processors=None): | 776 def __init__(self, checkout, post_processors=None): |
744 super(ReadOnlyCheckout, self).__init__() | 777 super(ReadOnlyCheckout, self).__init__() |
745 self.checkout = checkout | 778 self.checkout = checkout |
746 self.post_processors = (post_processors or []) + ( | 779 self.post_processors = (post_processors or []) + ( |
747 self.checkout.post_processors or []) | 780 self.checkout.post_processors or []) |
748 | 781 |
749 def prepare(self, revision): | 782 def prepare(self, revision): |
(...skipping 14 matching lines...) Expand all Loading... | |
764 def revisions(self, rev1, rev2): | 797 def revisions(self, rev1, rev2): |
765 return self.checkout.revisions(rev1, rev2) | 798 return self.checkout.revisions(rev1, rev2) |
766 | 799 |
767 @property | 800 @property |
768 def project_name(self): | 801 def project_name(self): |
769 return self.checkout.project_name | 802 return self.checkout.project_name |
770 | 803 |
771 @property | 804 @property |
772 def project_path(self): | 805 def project_path(self): |
773 return self.checkout.project_path | 806 return self.checkout.project_path |
OLD | NEW |