add NewGitCheckout to git_utils.py
rebaseline_server will use this to check out expectations at specific revisions
BUG=skia:1918
TBR=rmistry
Review URL: https://codereview.chromium.org/464413003
diff --git a/py/utils/git_utils.py b/py/utils/git_utils.py
index 5ac63ec..52ad83a 100644
--- a/py/utils/git_utils.py
+++ b/py/utils/git_utils.py
@@ -5,9 +5,12 @@
"""This module contains functions for using git."""
-
+import os
import re
import shell_utils
+import shutil
+import subprocess
+import tempfile
def _FindGit():
@@ -144,3 +147,79 @@
shell_utils.run([GIT, 'checkout', 'master'])
if self._delete_when_finished:
shell_utils.run([GIT, 'branch', '-D', self._branch_name])
+
+
+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.
+
+ 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:
+
+ 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
+ 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,
+ and just hide the rest of the repository; but later on we may
+ optimize performance by only checking out this part of the repo.
+ containing_dir: if specified, the new checkout will be created somewhere
+ 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
+
+ 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):
+ """Returns the root directory containing the checked-out files.
+
+ If you specified the subdir parameter in the constructor, this directory
+ will point at just the subdir you requested.
+ """
+ return self._file_root
+
+ def commithash(self):
+ """Returns the commithash of the local checkout."""
+ return self._run_in_git_root(
+ args=[GIT, 'rev-parse', 'HEAD']).strip()
+
+ def __enter__(self):
+ return self
+
+ # pylint: disable=W0622
+ def __exit__(self, type, value, traceback):
+ shutil.rmtree(self._git_root)
+
+ def _run_in_git_root(self, args):
+ """Run an external command with cwd set to self._git_root.
+
+ Returns the command's output as a byte string.
+
+ Raises an Exception if the command fails.
+ """
+ return subprocess.check_output(args=args, cwd=self._git_root)
diff --git a/py/utils/git_utils_test.py b/py/utils/git_utils_test.py
new file mode 100755
index 0000000..88a2736
--- /dev/null
+++ b/py/utils/git_utils_test.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+
+"""
+Copyright 2014 Google Inc.
+
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+Test git_utils.py.
+"""
+
+# System-level imports
+import os
+import tempfile
+import unittest
+
+# Imports from within Skia
+import git_utils
+
+
+# A git repo we can use for tests.
+REPO = 'https://skia.googlesource.com/common'
+
+# A file in some subdirectory within REPO.
+REPO_FILE = os.path.join('py', 'utils', 'git_utils.py')
+
+
+class NewGitCheckoutTest(unittest.TestCase):
+
+ def test_defaults(self):
+ """Test NewGitCheckout created using default parameters."""
+ with git_utils.NewGitCheckout(repository=REPO) as checkout:
+ filepath = os.path.join(checkout.root, REPO_FILE)
+ self.assertTrue(
+ os.path.exists(filepath),
+ 'file %s should exist' % filepath)
+ # Confirm that NewGitCheckout cleaned up after itself.
+ self.assertFalse(
+ os.path.exists(filepath),
+ 'file %s should not exist' % filepath)
+
+ def test_subdir(self):
+ """Create NewGitCheckout with a specific subdirectory."""
+ subdir = os.path.dirname(REPO_FILE)
+ file_within_subdir = os.path.basename(REPO_FILE)
+
+ containing_dir = tempfile.mkdtemp()
+ try:
+ with git_utils.NewGitCheckout(repository=REPO, subdir=subdir,
+ containing_dir=containing_dir) as checkout:
+ self.assertTrue(
+ checkout.root.startswith(containing_dir),
+ 'checkout.root %s should be within %s' % (
+ checkout.root, containing_dir))
+ filepath = os.path.join(checkout.root, file_within_subdir)
+ self.assertTrue(
+ os.path.exists(filepath),
+ 'file %s should exist' % filepath)
+ finally:
+ os.rmdir(containing_dir)
+
+ def test_refspec(self):
+ """Create NewGitCheckout with a specific refspec.
+
+ This test depends on the fact that the whitespace.txt file was added to the
+ repo in a particular commit.
+ See https://skia.googlesource.com/common/+/c2200447734f13070fb3b2808dea58847241ab0e
+ ('Initial commit of whitespace.txt')
+ """
+ filename = 'whitespace.txt'
+ hash_without_file = 'f63e1cfff23615157e28942af5f5e8298351cb10'
+ hash_with_file = 'c2200447734f13070fb3b2808dea58847241ab0e'
+
+ with git_utils.NewGitCheckout(
+ repository=REPO, refspec=hash_without_file) as checkout:
+ filepath = os.path.join(checkout.root, filename)
+ self.assertEquals(
+ hash_without_file, checkout.commithash(),
+ '"%s" != "%s"' % (hash_without_file, checkout.commithash()))
+ self.assertFalse(
+ os.path.exists(filepath),
+ 'file %s should not exist' % filepath)
+
+ with git_utils.NewGitCheckout(
+ repository=REPO, refspec=hash_with_file) as checkout:
+ filepath = os.path.join(checkout.root, filename)
+ self.assertEquals(
+ hash_with_file, checkout.commithash(),
+ '"%s" != "%s"' % (hash_with_file, checkout.commithash()))
+ self.assertTrue(
+ os.path.exists(filepath),
+ 'file %s should exist' % filepath)
+
+
+def main(test_case_class):
+ """Run the unit tests within this class."""
+ suite = unittest.TestLoader().loadTestsFromTestCase(NewGitCheckoutTest)
+ results = unittest.TextTestRunner(verbosity=2).run(suite)
+ if not results.wasSuccessful():
+ raise Exception('failed unittest %s' % test_case_class)
+
+if __name__ == '__main__':
+ main(NewGitCheckoutTest)