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)