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

Unified Diff: testing_support/git/repo.py

Issue 418643004: Copy git tooling into testing_support. (Closed) Base URL: https://chromium.googlesource.com/infra/testing/testing_support@master
Patch Set: fix scripts Created 6 years, 5 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 | « testing_support/git/__init__.py ('k') | testing_support/git/schema.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: testing_support/git/repo.py
diff --git a/testing_support/git/repo.py b/testing_support/git/repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..37b52a9a05d87e08c571291c3264624551049798
--- /dev/null
+++ b/testing_support/git/repo.py
@@ -0,0 +1,239 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import atexit
+import collections
+import datetime
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+
+from testing_support.git.schema import GitRepoSchema
+
+
+class GitRepo(object):
+ """Creates a real git repo for a GitRepoSchema.
+
+ Obtains schema and content information from the GitRepoSchema.
+
+ The format for the commit data supplied by GitRepoSchema.data_for is:
+ {
+ SPECIAL_KEY: special_value,
+ ...
+ "path/to/some/file": { 'data': "some data content for this file",
+ 'mode': 0755 },
+ ...
+ }
+
+ The SPECIAL_KEYs are the following attribues of the GitRepo class:
+ * AUTHOR_NAME
+ * AUTHOR_EMAIL
+ * AUTHOR_DATE - must be a datetime.datetime instance
+ * COMMITTER_NAME
+ * COMMITTER_EMAIL
+ * COMMITTER_DATE - must be a datetime.datetime instance
+
+ For file content, if 'data' is None, then this commit will `git rm` that file.
+ """
+ BASE_TEMP_DIR = tempfile.mkdtemp(suffix='base', prefix='git_repo')
+ atexit.register(shutil.rmtree, BASE_TEMP_DIR)
+
+ # Singleton objects to specify specific data in a commit dictionary.
+ AUTHOR_NAME = object()
+ AUTHOR_EMAIL = object()
+ AUTHOR_DATE = object()
+ COMMITTER_NAME = object()
+ COMMITTER_EMAIL = object()
+ COMMITTER_DATE = object()
+
+ DEFAULT_AUTHOR_NAME = 'Author McAuthorly'
+ DEFAULT_AUTHOR_EMAIL = 'author@example.com'
+ DEFAULT_COMMITTER_NAME = 'Charles Committish'
+ DEFAULT_COMMITTER_EMAIL = 'commitish@example.com'
+
+ COMMAND_OUTPUT = collections.namedtuple('COMMAND_OUTPUT', 'retcode stdout')
+
+ def __init__(self, schema):
+ """Makes new GitRepo.
+
+ Automatically creates a temp folder under GitRepo.BASE_TEMP_DIR. It's
+ recommended that you clean this repo up by calling nuke() on it, but if not,
+ GitRepo will automatically clean up all allocated repos at the exit of the
+ program (assuming a normal exit like with sys.exit)
+
+ Args:
+ schema - An instance of GitRepoSchema
+ """
+ self.repo_path = tempfile.mkdtemp(dir=self.BASE_TEMP_DIR)
+ self.commit_map = {}
+ self._date = datetime.datetime(1970, 1, 1)
+
+ self.to_schema_refs = ['--branches']
+
+ self.git('init')
+ self.git('config', 'user.name', 'testcase')
+ self.git('config', 'user.email', 'testcase@example.com')
+ for commit in schema.walk():
+ self._add_schema_commit(commit, schema.data_for(commit.name))
+ self.last_commit = self[commit.name]
+ if schema.master:
+ self.git('update-ref', 'refs/heads/master', self[schema.master])
+
+ def __getitem__(self, commit_name):
+ """Gets the hash of a commit by its schema name.
+
+ >>> r = GitRepo(GitRepoSchema('A B C'))
+ >>> r['B']
+ '7381febe1da03b09da47f009963ab7998a974935'
+ """
+ return self.commit_map[commit_name]
+
+ def _add_schema_commit(self, commit, commit_data):
+ commit_data = commit_data or {}
+
+ if commit.parents:
+ parents = list(commit.parents)
+ self.git('checkout', '--detach', '-q', self[parents[0]])
+ if len(parents) > 1:
+ self.git('merge', '--no-commit', '-q', *[self[x] for x in parents[1:]])
+ else:
+ self.git('checkout', '--orphan', 'root_%s' % commit.name)
+ self.git('rm', '-rf', '.')
+
+ env = self.get_git_commit_env(commit_data)
+
+ for fname, file_data in commit_data.iteritems():
+ deleted = False
+ if 'data' in file_data:
+ data = file_data.get('data')
+ if data is None:
+ deleted = True
+ self.git('rm', fname)
+ else:
+ path = os.path.join(self.repo_path, fname)
+ pardir = os.path.dirname(path)
+ if not os.path.exists(pardir):
+ os.makedirs(pardir)
+ with open(path, 'wb') as f:
+ f.write(data)
+
+ mode = file_data.get('mode')
+ if mode and not deleted:
+ os.chmod(path, mode)
+
+ self.git('add', fname)
+
+ rslt = self.git('commit', '--allow-empty', '-m', commit.name, env=env)
+ assert rslt.retcode == 0, 'Failed to commit %s' % str(commit)
+ self.commit_map[commit.name] = self.git('rev-parse', 'HEAD').stdout.strip()
+ self.git('tag', 'tag_%s' % commit.name, self[commit.name])
+ if commit.is_branch:
+ self.git('branch', '-f', 'branch_%s' % commit.name, self[commit.name])
+
+ def get_git_commit_env(self, commit_data=None):
+ commit_data = commit_data or {}
+ env = {}
+ for prefix in ('AUTHOR', 'COMMITTER'):
+ for suffix in ('NAME', 'EMAIL', 'DATE'):
+ singleton = '%s_%s' % (prefix, suffix)
+ key = getattr(self, singleton)
+ if key in commit_data:
+ val = commit_data[key]
+ else:
+ if suffix == 'DATE':
+ val = self._date
+ self._date += datetime.timedelta(days=1)
+ else:
+ val = getattr(self, 'DEFAULT_%s' % singleton)
+ env['GIT_%s' % singleton] = str(val)
+ return env
+
+
+ def git(self, *args, **kwargs):
+ """Runs a git command specified by |args| in this repo."""
+ assert self.repo_path is not None
+ try:
+ with open(os.devnull, 'wb') as devnull:
+ output = subprocess.check_output(
+ ('git',) + args, cwd=self.repo_path, stderr=devnull, **kwargs)
+ return self.COMMAND_OUTPUT(0, output)
+ except subprocess.CalledProcessError as e:
+ return self.COMMAND_OUTPUT(e.returncode, e.output)
+
+ def git_commit(self, message):
+ return self.git('commit', '-am', message, env=self.get_git_commit_env())
+
+ def nuke(self):
+ """Obliterates the git repo on disk.
+
+ Causes this GitRepo to be unusable.
+ """
+ shutil.rmtree(self.repo_path)
+ self.repo_path = None
+
+ def run(self, fn, *args, **kwargs):
+ """Run a python function with the given args and kwargs with the cwd set to
+ the git repo."""
+ assert self.repo_path is not None
+ curdir = os.getcwd()
+ try:
+ os.chdir(self.repo_path)
+ return fn(*args, **kwargs)
+ finally:
+ os.chdir(curdir)
+
+ def capture_stdio(self, fn, *args, **kwargs):
+ """Run a python function with the given args and kwargs with the cwd set to
+ the git repo.
+
+ Returns the (stdout, stderr) of whatever ran, instead of the what |fn|
+ returned.
+ """
+ stdout = sys.stdout
+ stderr = sys.stderr
+ try:
+ # "multiple statements on a line" pylint: disable=C0321
+ with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
+ sys.stdout = out
+ sys.stderr = err
+ try:
+ self.run(fn, *args, **kwargs)
+ except SystemExit:
+ pass
+ out.seek(0)
+ err.seek(0)
+ return out.read(), err.read()
+ finally:
+ sys.stdout = stdout
+ sys.stderr = stderr
+
+ def open(self, path, mode='rb'):
+ return open(os.path.join(self.repo_path, path), mode)
+
+ def to_schema(self):
+ lines = self.git('rev-list', '--parents', '--reverse', '--topo-order',
+ '--format=%s', *self.to_schema_refs).stdout.splitlines()
+ hash_to_msg = {}
+ ret = GitRepoSchema()
+ current = None
+ parents = []
+ for line in lines:
+ if line.startswith('commit'):
+ assert current is None
+ tokens = line.split()
+ current, parents = tokens[1], tokens[2:]
+ assert all(p in hash_to_msg for p in parents)
+ else:
+ assert current is not None
+ hash_to_msg[current] = line
+ ret.add_partial(line)
+ for parent in parents:
+ ret.add_partial(line, hash_to_msg[parent])
+ current = None
+ parents = []
+ assert current is None
+ return ret
« no previous file with comments | « testing_support/git/__init__.py ('k') | testing_support/git/schema.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698