vcssync: context manager to create a temporary Git checkout (
bug 1357597); r?glob
This seems like another useful primitive to establish.
MozReview-Commit-ID: JtLaZB4jpvd
--- a/vcssync/mozvcssync/gitutil.py
+++ b/vcssync/mozvcssync/gitutil.py
@@ -5,17 +5,19 @@
"""Utility functions for performing various Git functionality."""
from __future__ import absolute_import, unicode_literals
import contextlib
import logging
import os
import pipes
+import shutil
import subprocess
+import tempfile
import dulwich.repo
logger = logging.getLogger(__name__)
class GitCommand(object):
@@ -114,16 +116,71 @@ def update_git_refs(repo, reason, *actio
p.stdin.close()
res = p.wait()
# TODO could use a more rich exception type that captures output.
if res:
raise Exception('failed to update git refs')
@contextlib.contextmanager
+def temporary_git_checkout(repo, revision_or_ref, branch=None,
+ sparse_content=None):
+ """Create a temporary Git checkout from a local source repository.
+
+ A temporary directory will be created. It will be initialized into a Git
+ repository shared from the specified source ``repo`` (which must be local).
+ A checkout of the specified ``revision_or_ref`` will be made in the repo.
+
+ If ``branch`` is defined, a new branch will be created of that name.
+
+ If ``sparse_content`` is defined, its content will be written to
+ ``.git/info/sparse-checkout`` and a sparse checkout will be performed.
+
+ The context manager yields a string with the path to the temporary checkout.
+
+ When the context manager exits, the temporary checkout will be deleted.
+ """
+ source = repo.path if isinstance(repo, dulwich.repo.Repo) else repo
+
+ temp_dir = tempfile.mkdtemp(prefix='git-temp-checkout-')
+ try:
+ logger.warn('creating temporary Git checkout in %s' % temp_dir)
+
+ # Initialize the new Git repo with shared storage.
+ subprocess.check_call(
+ [b'git', b'clone', b'--shared', b'--no-checkout',
+ source, temp_dir],
+ cwd='/')
+
+ if sparse_content:
+ subprocess.check_call(
+ [b'git', b'config', b'--local', b'core.sparseCheckout',
+ b'true'],
+ cwd=temp_dir)
+
+ sparse_path = os.path.join(temp_dir, '.git', 'info',
+ 'sparse-checkout')
+ with open(sparse_path, 'wb') as fh:
+ fh.write(sparse_content)
+
+ # Do the checkout. This should be the most expensive part.
+ args = [b'git', b'checkout']
+ if branch:
+ args.extend([b'-b', branch])
+ args.append(revision_or_ref)
+ subprocess.check_call(args, cwd=temp_dir)
+
+ yield temp_dir
+
+ finally:
+ logger.warn('removing temporary Git repo %s' % temp_dir)
+ shutil.rmtree(temp_dir)
+
+
+@contextlib.contextmanager
def temporary_git_refs_mutations(repo, reflog_message=b'restore repo state'):
"""Temporarily mutate Git refs during an active context manager.
When entered, the context manager takes a "snapshot" of the state of the
Git refs. When it exits, the Git refs are returned to their previous state.
The reflog records mutations that occurred. So any changes made when the
context manager is active can still be accessed until the reflog expires.