git_utils.py: allow checkouts of local repositories at any commithash along master branch
Needed so that we can create new GM/SKP baselines on a shared
rebaseline_server instance, *efficiently*. (Create checkouts pinned to certain
commithashes as copies of the local Skia checkout, rather than checking out
copies of the main Skia repo across the internet.)
BUG=skia:1918
NOTREECHECKS=true
R=borenet@google.com
Review URL: https://codereview.chromium.org/484143002
diff --git a/py/utils/git_utils.py b/py/utils/git_utils.py
index 52ad83a..fec79d7 100644
--- a/py/utils/git_utils.py
+++ b/py/utils/git_utils.py
@@ -152,25 +152,28 @@
class NewGitCheckout(object):
"""Creates a new local checkout of a Git repository."""
- def __init__(self, repository, refspec=None, subdir=None,
- containing_dir=None):
- """Check out a new local copy of the repository.
+ def __init__(self, repository, refspec=None, commit='HEAD',
+ subdir=None, containing_dir=None):
+ """Set parameters for this local copy of a Git repository.
Because this is a new checkout, rather than a reference to an existing
checkout on disk, it is safe to assume that the calling thread is the
only thread manipulating the checkout.
- You can use the 'with' statement to create this object in such a way that
- it cleans up after itself:
+ You must use the 'with' statement to create this object:
with NewGitCheckout(*args) as checkout:
# use checkout instance
# the checkout is automatically cleaned up here
Args:
- repository: name of the remote repository
- refspec: an arbitrary remote ref (e.g., the name of a branch);
- if None, allow the git command to pick a default
+ repository: URL of the remote repository (e.g.,
+ 'https://skia.googlesource.com/common') or path to a local repository
+ (e.g., '/path/to/repo/.git') to check out a copy of
+ refspec: which refs (e.g., a branch name) to fetch from the repository;
+ if None, git-fetch will choose the default refs to fetch
+ commit: commit hash, branch, or tag within refspec, indicating what point
+ to update the local checkout to
subdir: if specified, the caller only wants access to files within this
subdir in the repository.
For now, we check out the entire repository regardless of this param,
@@ -180,19 +183,14 @@
within this directory; otherwise, a system-dependent default location
will be used, as determined by tempfile.mkdtemp()
"""
- # _git_root points to the tree holding the git checkout in its entirety;
- # _file_root points to the files the caller wants to look at
- self._git_root = tempfile.mkdtemp(dir=containing_dir)
- if subdir:
- self._file_root = os.path.join(self._git_root, subdir)
- else:
- self._file_root = self._git_root
+ self._repository = repository
+ self._refspec = refspec
+ self._commit = commit
+ self._subdir = subdir
+ self._containing_dir = containing_dir
+ self._git_root = None
+ self._file_root = None
- pull_cmd = [GIT, 'pull', repository]
- if refspec:
- pull_cmd.append(refspec)
- self._run_in_git_root(args=[GIT, 'init'])
- self._run_in_git_root(args=pull_cmd)
@property
def root(self):
@@ -209,6 +207,28 @@
args=[GIT, 'rev-parse', 'HEAD']).strip()
def __enter__(self):
+ """Check out a new local copy of the repository.
+
+ Uses the parameters that were passed into the constructor.
+ """
+ # _git_root points to the tree holding the git checkout in its entirety;
+ # _file_root points to the files the caller wants to look at
+ self._git_root = tempfile.mkdtemp(dir=self._containing_dir)
+ if self._subdir:
+ self._file_root = os.path.join(self._git_root, self._subdir)
+ else:
+ self._file_root = self._git_root
+
+ local_branch_name = 'local'
+ self._run_in_git_root(args=[GIT, 'init'])
+ fetch_cmd = [GIT, 'fetch', self._repository]
+ if self._refspec:
+ fetch_cmd.append(self._refspec)
+ self._run_in_git_root(args=fetch_cmd)
+ self._run_in_git_root(args=[GIT, 'merge', 'FETCH_HEAD'])
+ self._run_in_git_root(args=[GIT, 'branch', local_branch_name, self._commit])
+ self._run_in_git_root(args=[GIT, 'checkout', local_branch_name])
+
return self
# pylint: disable=W0622
diff --git a/py/utils/git_utils_manualtest.py b/py/utils/git_utils_manualtest.py
index 720a9e9..a1acaac 100755
--- a/py/utils/git_utils_manualtest.py
+++ b/py/utils/git_utils_manualtest.py
@@ -18,10 +18,12 @@
import git_utils
-# A git repo we can use for tests.
-REPO = 'https://skia.googlesource.com/common'
+# A git repo we can use for tests, with local and remote copies.
+LOCAL_REPO = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), os.pardir, os.pardir, '.git'))
+REMOTE_REPO = 'https://skia.googlesource.com/common'
-# A file in some subdirectory within REPO.
+# A file in some subdirectory within the test repo.
REPO_FILE = os.path.join('py', 'utils', 'git_utils.py')
@@ -29,7 +31,7 @@
def test_defaults(self):
"""Test NewGitCheckout created using default parameters."""
- with git_utils.NewGitCheckout(repository=REPO) as checkout:
+ with git_utils.NewGitCheckout(repository=LOCAL_REPO) as checkout:
filepath = os.path.join(checkout.root, REPO_FILE)
self.assertTrue(
os.path.exists(filepath),
@@ -39,6 +41,18 @@
os.path.exists(filepath),
'file %s should not exist' % filepath)
+ def test_remote(self):
+ """Test NewGitCheckout with a remote repo.
+
+ This makes requests across the network, so we may not want to run it
+ very often...
+ """
+ with git_utils.NewGitCheckout(repository=REMOTE_REPO) as checkout:
+ filepath = os.path.join(checkout.root, REPO_FILE)
+ self.assertTrue(
+ os.path.exists(filepath),
+ 'file %s should exist' % filepath)
+
def test_subdir(self):
"""Create NewGitCheckout with a specific subdirectory."""
subdir = os.path.dirname(REPO_FILE)
@@ -46,7 +60,7 @@
containing_dir = tempfile.mkdtemp()
try:
- with git_utils.NewGitCheckout(repository=REPO, subdir=subdir,
+ with git_utils.NewGitCheckout(repository=LOCAL_REPO, subdir=subdir,
containing_dir=containing_dir) as checkout:
self.assertTrue(
checkout.root.startswith(containing_dir),
@@ -59,8 +73,8 @@
finally:
os.rmdir(containing_dir)
- def test_refspec(self):
- """Create NewGitCheckout with a specific refspec.
+ def test_commit(self):
+ """Create NewGitCheckout with a specific commit.
This test depends on the fact that the whitespace.txt file was added to the
repo in a particular commit.
@@ -72,7 +86,7 @@
hash_with_file = 'c2200447734f13070fb3b2808dea58847241ab0e'
with git_utils.NewGitCheckout(
- repository=REPO, refspec=hash_without_file) as checkout:
+ repository=LOCAL_REPO, commit=hash_without_file) as checkout:
filepath = os.path.join(checkout.root, filename)
self.assertEquals(
hash_without_file, checkout.commithash(),
@@ -82,7 +96,7 @@
'file %s should not exist' % filepath)
with git_utils.NewGitCheckout(
- repository=REPO, refspec=hash_with_file) as checkout:
+ repository=LOCAL_REPO, commit=hash_with_file) as checkout:
filepath = os.path.join(checkout.root, filename)
self.assertEquals(
hash_with_file, checkout.commithash(),