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

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
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('Checking out %s in %s' %
M-A Ruel 2013/09/03 13:10:16 logging.info('Checking out %s in %s', self.project
rmistry 2013/09/03 15:46:28 Done.
579 (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_revision()
609
610 def _sync_remote_branch(self):
611 """Sync the remote branch."""
M-A Ruel 2013/09/03 13:10:16 Syncs
rmistry 2013/09/03 15:46:28 Done.
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_revision(self):
M-A Ruel 2013/09/03 13:10:16 maybe _get_head_commit_hash(self): ? since 'revisi
rmistry 2013/09/03 15:46:28 Makes sense. Done.
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
M-A Ruel 2013/09/03 13:10:16 Probably worth verifying that working_branch is th
rmistry 2013/09/03 15:46:28 Added an assert to verify that working_branch is t
721 commit_cmd = ['commit', '--amend', '-m', commit_message]
M-A Ruel 2013/09/03 13:10:16 You want commit_message.encode('utf-8') otherwise
rmistry 2013/09/03 15:46:28 I added 'é' to the commit message in this CL: http
722 if user and user != self.commit_user:
723 # We do not have the first or last name of the user, grab the username
724 # from the email and call it the original author's name.
725 name = user.split('@')[0]
726 commit_cmd.extend(['--author', '%s <%s>' % (name, user)])
M-A Ruel 2013/09/03 13:10:16 Maybe later: try to detect if "user" is in git-com
rmistry 2013/09/03 15:46:28 Added a TODO here.
727 self._check_call_git(commit_cmd)
678 728
679 Subclass needs to dcommit or push. 729 # Push to the remote repository.
680 """ 730 self._check_call_git(
681 assert isinstance(commit_message, unicode) 731 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch),
682 self._check_call_git(['commit', '--amend', '-m', commit_message]) 732 '--force', '--quiet'])
683 return self._check_output_git(['rev-parse', 'HEAD']).strip() 733 # Get the revision after the push.
734 revision = self._get_revision()
735 # Switch back to the remote_branch and sync it.
736 self._check_call_git(['checkout', self.remote_branch])
737 self._sync_remote_branch()
738 # Delete the working branch since we are done with it.
739 self._check_call_git(['branch', '-D', self.working_branch])
740
741 return revision
684 742
685 def _check_call_git(self, args, **kwargs): 743 def _check_call_git(self, args, **kwargs):
686 kwargs.setdefault('cwd', self.project_path) 744 kwargs.setdefault('cwd', self.project_path)
687 kwargs.setdefault('stdout', self.VOID) 745 kwargs.setdefault('stdout', self.VOID)
688 kwargs.setdefault('timeout', GLOBAL_TIMEOUT) 746 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
689 return subprocess2.check_call_out(['git'] + args, **kwargs) 747 return subprocess2.check_call_out(['git'] + args, **kwargs)
690 748
691 def _call_git(self, args, **kwargs): 749 def _call_git(self, args, **kwargs):
692 """Like check_call but doesn't throw on failure.""" 750 """Like check_call but doesn't throw on failure."""
693 kwargs.setdefault('cwd', self.project_path) 751 kwargs.setdefault('cwd', self.project_path)
(...skipping 26 matching lines...) Expand all
720 # Revision range is ]rev1, rev2] and ordering matters. 778 # Revision range is ]rev1, rev2] and ordering matters.
721 try: 779 try:
722 out = self._check_output_git( 780 out = self._check_output_git(
723 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)]) 781 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)])
724 except subprocess.CalledProcessError: 782 except subprocess.CalledProcessError:
725 return None 783 return None
726 return len(out.splitlines()) 784 return len(out.splitlines())
727 785
728 def _fetch_remote(self): 786 def _fetch_remote(self):
729 """Fetches the remote without rebasing.""" 787 """Fetches the remote without rebasing."""
730 raise NotImplementedError() 788 # 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], 789 self._check_output_git(['fetch', self.remote, self.remote_branch],
738 timeout=FETCH_TIMEOUT) 790 timeout=FETCH_TIMEOUT)
739 791
740 792
741 class ReadOnlyCheckout(object): 793 class ReadOnlyCheckout(object):
742 """Converts a checkout into a read-only one.""" 794 """Converts a checkout into a read-only one."""
743 def __init__(self, checkout, post_processors=None): 795 def __init__(self, checkout, post_processors=None):
744 super(ReadOnlyCheckout, self).__init__() 796 super(ReadOnlyCheckout, self).__init__()
745 self.checkout = checkout 797 self.checkout = checkout
746 self.post_processors = (post_processors or []) + ( 798 self.post_processors = (post_processors or []) + (
(...skipping 17 matching lines...) Expand all
764 def revisions(self, rev1, rev2): 816 def revisions(self, rev1, rev2):
765 return self.checkout.revisions(rev1, rev2) 817 return self.checkout.revisions(rev1, rev2)
766 818
767 @property 819 @property
768 def project_name(self): 820 def project_name(self):
769 return self.checkout.project_name 821 return self.checkout.project_name
770 822
771 @property 823 @property
772 def project_path(self): 824 def project_path(self):
773 return self.checkout.project_path 825 return self.checkout.project_path
OLDNEW
« no previous file with comments | « apply_issue.py ('k') | tests/checkout_test.py » ('j') | tests/checkout_test.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698