Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: checkout.py

Issue 22794015: Completing implementation of GitCheckout in depot_tools (Closed) Base URL: http://src.chromium.org/svn/trunk/tools/depot_tools/
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « apply_issue.py ('k') | tests/checkout_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 GitCheckout(CheckoutBase):
554 """Base class for git checkout. Not to be used as-is.""" 554 """Manages a git checkout."""
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 commit_user, post_processors=None):
557 super(GitCheckoutBase, self).__init__( 557 assert git_url
558 root_dir, project_name, post_processors) 558 assert commit_user
559 # There is no reason to not hardcode it. 559 super(GitCheckout, self).__init__(root_dir, project_name, post_processors)
560 self.git_url = git_url
561 self.commit_user = commit_user
562 self.remote_branch = remote_branch
563 # The working branch where patches will be applied. It will track the
564 # remote branch.
565 self.working_branch = 'working_branch'
566 # There is no reason to not hardcode origin.
560 self.remote = 'origin' 567 self.remote = 'origin'
561 self.remote_branch = remote_branch
562 self.working_branch = 'working_branch'
563 568
564 def prepare(self, revision): 569 def prepare(self, revision):
565 """Resets the git repository in a clean state. 570 """Resets the git repository in a clean state.
566 571
567 Checks it out if not present and deletes the working branch. 572 Checks it out if not present and deletes the working branch.
568 """ 573 """
569 assert self.remote_branch 574 assert self.remote_branch
570 assert os.path.isdir(self.project_path) 575
571 self._check_call_git(['reset', '--hard', '--quiet']) 576 if not os.path.isdir(self.project_path):
577 # Clone the repo if the directory is not present.
578 logging.info(
579 'Checking out %s in %s', self.project_name, self.project_path)
580 self._check_call_git(
581 ['clone', self.git_url, '-b', self.remote_branch, self.project_path],
582 cwd=None, timeout=FETCH_TIMEOUT)
583 else:
584 # Throw away all uncommitted changes in the existing checkout.
585 self._check_call_git(['checkout', self.remote_branch])
586 self._check_call_git(
587 ['reset', '--hard', '--quiet',
588 '%s/%s' % (self.remote, self.remote_branch)])
589
572 if revision: 590 if revision:
573 try: 591 try:
592 # Look if the commit hash already exist. If so, we can skip a
593 # 'git fetch' call.
574 revision = self._check_output_git(['rev-parse', revision]) 594 revision = self._check_output_git(['rev-parse', revision])
575 except subprocess.CalledProcessError: 595 except subprocess.CalledProcessError:
576 self._check_call_git( 596 self._check_call_git(
577 ['fetch', self.remote, self.remote_branch, '--quiet']) 597 ['fetch', self.remote, self.remote_branch, '--quiet'])
578 revision = self._check_output_git(['rev-parse', revision]) 598 revision = self._check_output_git(['rev-parse', revision])
579 self._check_call_git(['checkout', '--force', '--quiet', revision]) 599 self._check_call_git(['checkout', '--force', '--quiet', revision])
580 else: 600 else:
581 branches, active = self._branches() 601 branches, active = self._branches()
582 if active != 'master': 602 if active != 'master':
583 self._check_call_git(['checkout', '--force', '--quiet', 'master']) 603 self._check_call_git(['checkout', '--force', '--quiet', 'master'])
584 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) 604 self._sync_remote_branch()
605
585 if self.working_branch in branches: 606 if self.working_branch in branches:
586 self._call_git(['branch', '-D', self.working_branch]) 607 self._call_git(['branch', '-D', self.working_branch])
608 return self._get_head_commit_hash()
609
610 def _sync_remote_branch(self):
611 """Syncs the remote branch."""
612 # We do a 'git pull origin master:refs/remotes/origin/master' instead of
613 # 'git pull origin master' because from the manpage for git-pull:
614 # A parameter <ref> without a colon is equivalent to <ref>: when
615 # pulling/fetching, so it merges <ref> into the current branch without
616 # storing the remote branch anywhere locally.
617 remote_tracked_path = 'refs/remotes/%s/%s' % (
618 self.remote, self.remote_branch)
619 self._check_call_git(
620 ['pull', self.remote,
621 '%s:%s' % (self.remote_branch, remote_tracked_path),
622 '--quiet'])
623
624 def _get_head_commit_hash(self):
625 """Gets the current revision from the local branch."""
626 return self._check_output_git(['rev-parse', 'HEAD']).strip()
587 627
588 def apply_patch(self, patches, post_processors=None, verbose=False): 628 def apply_patch(self, patches, post_processors=None, verbose=False):
589 """Applies a patch on 'working_branch' and switch to it. 629 """Applies a patch on 'working_branch' and switches to it.
590 630
591 Also commits the changes on the local branch. 631 Also commits the changes on the local branch.
592 632
593 Ignores svn properties and raise an exception on unexpected ones. 633 Ignores svn properties and raise an exception on unexpected ones.
594 """ 634 """
595 post_processors = post_processors or self.post_processors or [] 635 post_processors = post_processors or self.post_processors or []
596 # It this throws, the checkout is corrupted. Maybe worth deleting it and 636 # It this throws, the checkout is corrupted. Maybe worth deleting it and
597 # trying again? 637 # trying again?
598 if self.remote_branch: 638 if self.remote_branch:
599 self._check_call_git( 639 self._check_call_git(
600 ['checkout', '-b', self.working_branch, 640 ['checkout', '-b', self.working_branch, '-t', self.remote_branch,
601 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) 641 '--quiet'])
642
602 for index, p in enumerate(patches): 643 for index, p in enumerate(patches):
603 stdout = [] 644 stdout = []
604 try: 645 try:
605 filepath = os.path.join(self.project_path, p.filename) 646 filepath = os.path.join(self.project_path, p.filename)
606 if p.is_delete: 647 if p.is_delete:
607 if (not os.path.exists(filepath) and 648 if (not os.path.exists(filepath) and
608 any(p1.source_filename == p.filename for p1 in patches[0:index])): 649 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 650 # The file was already deleted if a prior patch with file rename
610 # was already processed because 'git apply' did it for us. 651 # was already processed because 'git apply' did it for us.
611 pass 652 pass
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
660 'While running %s;\n%s%s' % ( 701 'While running %s;\n%s%s' % (
661 ' '.join(e.cmd), 702 ' '.join(e.cmd),
662 align_stdout(stdout), 703 align_stdout(stdout),
663 align_stdout([getattr(e, 'stdout', '')]))) 704 align_stdout([getattr(e, 'stdout', '')])))
664 # Once all the patches are processed and added to the index, commit the 705 # Once all the patches are processed and added to the index, commit the
665 # index. 706 # index.
666 cmd = ['commit', '-m', 'Committed patch'] 707 cmd = ['commit', '-m', 'Committed patch']
667 if verbose: 708 if verbose:
668 cmd.append('--verbose') 709 cmd.append('--verbose')
669 self._check_call_git(cmd) 710 self._check_call_git(cmd)
670 # TODO(maruel): Weirdly enough they don't match, need to investigate. 711 found_files = self._check_output_git(
671 #found_files = self._check_output_git( 712 ['diff', '%s/%s' % (self.remote, self.remote_branch),
672 # ['diff', 'master', '--name-only']).splitlines(False) 713 '--name-only']).splitlines(False)
673 #assert sorted(patches.filenames) == sorted(found_files), ( 714 assert sorted(patches.filenames) == sorted(found_files), (
674 # sorted(out), sorted(found_files)) 715 sorted(patches.filenames), sorted(found_files))
675 716
676 def commit(self, commit_message, user): 717 def commit(self, commit_message, user):
677 """Updates the commit message. 718 """Commits, updates the commit message and pushes."""
719 assert isinstance(commit_message, unicode)
720 current_branch = self._check_output_git(
721 ['rev-parse', '--abbrev-ref', 'HEAD']).strip()
722 assert current_branch == self.working_branch
723
724 commit_cmd = ['commit', '--amend', '-m', commit_message]
725 if user and user != self.commit_user:
726 # We do not have the first or last name of the user, grab the username
727 # from the email and call it the original author's name.
728 # TODO(rmistry): Do not need the below if user is already in
729 # "Name <email>" format.
730 name = user.split('@')[0]
731 commit_cmd.extend(['--author', '%s <%s>' % (name, user)])
732 self._check_call_git(commit_cmd)
678 733
679 Subclass needs to dcommit or push. 734 # Push to the remote repository.
680 """ 735 self._check_call_git(
681 assert isinstance(commit_message, unicode) 736 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch),
682 self._check_call_git(['commit', '--amend', '-m', commit_message]) 737 '--force', '--quiet'])
683 return self._check_output_git(['rev-parse', 'HEAD']).strip() 738 # Get the revision after the push.
739 revision = self._get_head_commit_hash()
740 # Switch back to the remote_branch and sync it.
741 self._check_call_git(['checkout', self.remote_branch])
742 self._sync_remote_branch()
743 # Delete the working branch since we are done with it.
744 self._check_call_git(['branch', '-D', self.working_branch])
745
746 return revision
684 747
685 def _check_call_git(self, args, **kwargs): 748 def _check_call_git(self, args, **kwargs):
686 kwargs.setdefault('cwd', self.project_path) 749 kwargs.setdefault('cwd', self.project_path)
687 kwargs.setdefault('stdout', self.VOID) 750 kwargs.setdefault('stdout', self.VOID)
688 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) 751 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
689 return subprocess2.check_call_out(['git'] + args, **kwargs) 752 return subprocess2.check_call_out(['git'] + args, **kwargs)
690 753
691 def _call_git(self, args, **kwargs): 754 def _call_git(self, args, **kwargs):
692 """Like check_call but doesn't throw on failure.""" 755 """Like check_call but doesn't throw on failure."""
693 kwargs.setdefault('cwd', self.project_path) 756 kwargs.setdefault('cwd', self.project_path)
(...skipping 26 matching lines...) Expand all
720 # Revision range is ]rev1, rev2] and ordering matters. 783 # Revision range is ]rev1, rev2] and ordering matters.
721 try: 784 try:
722 out = self._check_output_git( 785 out = self._check_output_git(
723 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)]) 786 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)])
724 except subprocess.CalledProcessError: 787 except subprocess.CalledProcessError:
725 return None 788 return None
726 return len(out.splitlines()) 789 return len(out.splitlines())
727 790
728 def _fetch_remote(self): 791 def _fetch_remote(self):
729 """Fetches the remote without rebasing.""" 792 """Fetches the remote without rebasing."""
730 raise NotImplementedError() 793 # git fetch is always verbose even with -q, so redirect its output.
731
732
733 class GitCheckout(GitCheckoutBase):
734 """Git checkout implementation."""
735 def _fetch_remote(self):
736 # git fetch is always verbose even with -q -q so redirect its output.
737 self._check_output_git(['fetch', self.remote, self.remote_branch], 794 self._check_output_git(['fetch', self.remote, self.remote_branch],
738 timeout=FETCH_TIMEOUT) 795 timeout=FETCH_TIMEOUT)
739 796
740 797
741 class ReadOnlyCheckout(object): 798 class ReadOnlyCheckout(object):
742 """Converts a checkout into a read-only one.""" 799 """Converts a checkout into a read-only one."""
743 def __init__(self, checkout, post_processors=None): 800 def __init__(self, checkout, post_processors=None):
744 super(ReadOnlyCheckout, self).__init__() 801 super(ReadOnlyCheckout, self).__init__()
745 self.checkout = checkout 802 self.checkout = checkout
746 self.post_processors = (post_processors or []) + ( 803 self.post_processors = (post_processors or []) + (
(...skipping 17 matching lines...) Expand all
764 def revisions(self, rev1, rev2): 821 def revisions(self, rev1, rev2):
765 return self.checkout.revisions(rev1, rev2) 822 return self.checkout.revisions(rev1, rev2)
766 823
767 @property 824 @property
768 def project_name(self): 825 def project_name(self):
769 return self.checkout.project_name 826 return self.checkout.project_name
770 827
771 @property 828 @property
772 def project_path(self): 829 def project_path(self):
773 return self.checkout.project_path 830 return self.checkout.project_path
OLDNEW
« no previous file with comments | « apply_issue.py ('k') | tests/checkout_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698