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

Unified Diff: recipe_engine/fetch.py

Issue 2720853002: Fix GitBackend.checkout (Closed)
Patch Set: tests Created 3 years, 10 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | recipe_engine/package.py » ('j') | recipe_engine/package.py » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: recipe_engine/fetch.py
diff --git a/recipe_engine/fetch.py b/recipe_engine/fetch.py
index 4554f4c8e5f0af0a1a20bc8a3a71f4cabf772e86..f8f17b19acf6810dd01212f7db3d91cfc39e7fe4 100644
--- a/recipe_engine/fetch.py
+++ b/recipe_engine/fetch.py
@@ -7,6 +7,7 @@ import httplib
import json
import logging
import os
+import re
import shutil
import sys
import tarfile
@@ -38,11 +39,6 @@ class Backend(object):
"""Returns repo type (see package_pb2.DepSpec)."""
raise NotImplementedError()
- @staticmethod
- def branch_spec(branch):
- """Returns branch spec for given branch suitable for given git backend."""
- raise NotImplementedError()
-
def checkout(self, repo, revision, checkout_dir, allow_fetch):
"""Checks out given |repo| at |revision| to |checkout_dir|.
@@ -119,53 +115,85 @@ class GitBackend(Backend):
logging.info('Running: %s', args)
return subprocess42.check_output(args)
-
@property
def repo_type(self):
return package_pb2.DepSpec.GIT
- @staticmethod
- def branch_spec(branch):
- return 'origin/%s' % branch
-
@util.exponential_retry(condition=GitError.is_remote_error)
- def checkout(self, repo, revision, checkout_dir, allow_fetch):
- logging.info('Freshening repository %s in %s', repo, checkout_dir)
+ def checkout(self, repo_url, revision, checkout_dir, allow_fetch):
+ """Checks out given |repo| at |revision| to |checkout_dir|.
- git = self.Git()
- if not os.path.isdir(checkout_dir):
- if not allow_fetch:
- raise FetchNotAllowedError(
- 'need to clone %s but fetch not allowed' % repo)
- git('clone', '-q', repo, checkout_dir)
- elif not os.path.isdir(os.path.join(checkout_dir, '.git')):
- raise UncleanFilesystemError(
- '%s exists but is not a git repo' % checkout_dir)
+ Network operations are performed only if |allow_fetch| is True.
+
+ Returns the commit hash, if any.
+ """
+ logging.info('Freshening repository %s in %s', repo_url, checkout_dir)
+
+ if revision == 'origin/master':
+ # Some clients pass 'origin/master' when they actually mean
+ # 'refs/heads/master'.
+ # TODO(nodir): remove this if statement BUG(http://crbug.com/696704).
+ revision = 'refs/heads/master'
Paweł Hajdan Jr. 2017/02/28 08:44:33 Consider a logging.warning call in this case.
+ assert re.match('^([a-z0-9]{40}|refs/.+)$', revision), revision
+
+ is_commit = re.match('^[a-z0-9]{40}$', revision)
git = self.Git(checkout_dir=checkout_dir)
- git('config', 'remote.origin.url', repo)
- try:
- git('rev-parse', '-q', '--verify', '%s^{commit}' % revision)
- except GitError as e:
- logging.warning('Revision %s is not available: %s', revision, e)
+ if not os.path.isdir(checkout_dir):
+ os.makedirs(checkout_dir)
+ else:
+ if not os.path.isdir(os.path.join(checkout_dir, '.git')):
+ raise UncleanFilesystemError(
+ '%s exists but is not a git repo' % checkout_dir)
+
+ if is_commit:
+ # Avoid doing sever roundtrips if we already have the commit.
+ # Note: rev-parse is only safe if revision is a commit. If the revision
+ # is a ref, they may differ locally and remotely.
+ try:
+ git('rev-parse', '-q', '--verify', '%s^{commit}' % revision)
+ except GitError as e:
+ logging.warning('Revision %s needs to be fetched: %s', revision, e)
+ else:
+ git('checkout', '-q', '-f', revision)
+ return revision
+
+ if not allow_fetch:
+ # If revision is a commit hash, it is not present in the existing repo, so
+ # we have to fetch.
+ # If revision is a ref, what we have locally may be stale, so we have to
+ # fetch.
+ # We have to fetch anyway.
+ raise FetchNotAllowedError(
+ 'need to fetch %s but fetch not allowed' % repo_url)
- # Revision does not exist. If we can't fetch, then we fail here.
- if not allow_fetch:
- raise FetchNotAllowedError(
- 'need to fetch %s but fetch not allowed' % repo)
- git('fetch')
+ git('init') # Safe to call on an existing git repo.
- git('reset', '-q', '--hard', revision)
+ # Typically we cannot fetch a commit, so we assume that remote master branch
Paweł Hajdan Jr. 2017/02/28 08:44:33 recipes.cfg has a "branch" field, so that assumpti
+ # contains the requested commit. We will checkout the commit afterwards.
+ # Do not name the git remote to minimize repo state and to avoid making it
+ # work with invalid value of "origin/master".
+ # Do not map the fetched commit to anything to avoid checking out possibly
+ # stale refs.
+ git('fetch', repo_url, 'refs/heads/master' if is_commit else revision)
+
+ # FETCH_HEAD points to the fetched commit.
+
+ git('checkout', '-q', '-f', revision if is_commit else 'FETCH_HEAD')
+ return git('rev-parse', 'HEAD').strip()
@util.exponential_retry(condition=GitError.is_remote_error)
def updates(self, repo, revision, checkout_dir, allow_fetch,
other_revision, paths):
- self.checkout(repo, revision, checkout_dir, allow_fetch)
- git = self.Git(checkout_dir=checkout_dir)
- if allow_fetch:
- git('fetch')
+ assert re.match('^[a-z0-9]{40}$', revision), revision
+ # Since we are asked for revision..other_revision, revision must be among
+ # ancestors of other_revision and by fetching other_revision we ensure that
+ # both revisions will be present in the repo.
+ other_revision = self.checkout(
+ repo, other_revision, checkout_dir, allow_fetch)
+ git = self.Git(checkout_dir=checkout_dir)
args = [
'rev-list',
'--reverse',
@@ -211,10 +239,6 @@ class GitilesBackend(Backend):
def repo_type(self):
return package_pb2.DepSpec.GITILES
- @staticmethod
- def branch_spec(branch):
- return branch
-
def checkout(self, repo, revision, checkout_dir, allow_fetch):
requests_ssl.check_requests_ssl()
logging.info('Freshening repository %s in %s', repo, checkout_dir)
« no previous file with comments | « no previous file | recipe_engine/package.py » ('j') | recipe_engine/package.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698