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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « testing_support/git/__init__.py ('k') | testing_support/git/schema.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import atexit
6 import collections
7 import datetime
8 import os
9 import shutil
10 import subprocess
11 import sys
12 import tempfile
13
14
15 from testing_support.git.schema import GitRepoSchema
16
17
18 class GitRepo(object):
19 """Creates a real git repo for a GitRepoSchema.
20
21 Obtains schema and content information from the GitRepoSchema.
22
23 The format for the commit data supplied by GitRepoSchema.data_for is:
24 {
25 SPECIAL_KEY: special_value,
26 ...
27 "path/to/some/file": { 'data': "some data content for this file",
28 'mode': 0755 },
29 ...
30 }
31
32 The SPECIAL_KEYs are the following attribues of the GitRepo class:
33 * AUTHOR_NAME
34 * AUTHOR_EMAIL
35 * AUTHOR_DATE - must be a datetime.datetime instance
36 * COMMITTER_NAME
37 * COMMITTER_EMAIL
38 * COMMITTER_DATE - must be a datetime.datetime instance
39
40 For file content, if 'data' is None, then this commit will `git rm` that file.
41 """
42 BASE_TEMP_DIR = tempfile.mkdtemp(suffix='base', prefix='git_repo')
43 atexit.register(shutil.rmtree, BASE_TEMP_DIR)
44
45 # Singleton objects to specify specific data in a commit dictionary.
46 AUTHOR_NAME = object()
47 AUTHOR_EMAIL = object()
48 AUTHOR_DATE = object()
49 COMMITTER_NAME = object()
50 COMMITTER_EMAIL = object()
51 COMMITTER_DATE = object()
52
53 DEFAULT_AUTHOR_NAME = 'Author McAuthorly'
54 DEFAULT_AUTHOR_EMAIL = 'author@example.com'
55 DEFAULT_COMMITTER_NAME = 'Charles Committish'
56 DEFAULT_COMMITTER_EMAIL = 'commitish@example.com'
57
58 COMMAND_OUTPUT = collections.namedtuple('COMMAND_OUTPUT', 'retcode stdout')
59
60 def __init__(self, schema):
61 """Makes new GitRepo.
62
63 Automatically creates a temp folder under GitRepo.BASE_TEMP_DIR. It's
64 recommended that you clean this repo up by calling nuke() on it, but if not,
65 GitRepo will automatically clean up all allocated repos at the exit of the
66 program (assuming a normal exit like with sys.exit)
67
68 Args:
69 schema - An instance of GitRepoSchema
70 """
71 self.repo_path = tempfile.mkdtemp(dir=self.BASE_TEMP_DIR)
72 self.commit_map = {}
73 self._date = datetime.datetime(1970, 1, 1)
74
75 self.to_schema_refs = ['--branches']
76
77 self.git('init')
78 self.git('config', 'user.name', 'testcase')
79 self.git('config', 'user.email', 'testcase@example.com')
80 for commit in schema.walk():
81 self._add_schema_commit(commit, schema.data_for(commit.name))
82 self.last_commit = self[commit.name]
83 if schema.master:
84 self.git('update-ref', 'refs/heads/master', self[schema.master])
85
86 def __getitem__(self, commit_name):
87 """Gets the hash of a commit by its schema name.
88
89 >>> r = GitRepo(GitRepoSchema('A B C'))
90 >>> r['B']
91 '7381febe1da03b09da47f009963ab7998a974935'
92 """
93 return self.commit_map[commit_name]
94
95 def _add_schema_commit(self, commit, commit_data):
96 commit_data = commit_data or {}
97
98 if commit.parents:
99 parents = list(commit.parents)
100 self.git('checkout', '--detach', '-q', self[parents[0]])
101 if len(parents) > 1:
102 self.git('merge', '--no-commit', '-q', *[self[x] for x in parents[1:]])
103 else:
104 self.git('checkout', '--orphan', 'root_%s' % commit.name)
105 self.git('rm', '-rf', '.')
106
107 env = self.get_git_commit_env(commit_data)
108
109 for fname, file_data in commit_data.iteritems():
110 deleted = False
111 if 'data' in file_data:
112 data = file_data.get('data')
113 if data is None:
114 deleted = True
115 self.git('rm', fname)
116 else:
117 path = os.path.join(self.repo_path, fname)
118 pardir = os.path.dirname(path)
119 if not os.path.exists(pardir):
120 os.makedirs(pardir)
121 with open(path, 'wb') as f:
122 f.write(data)
123
124 mode = file_data.get('mode')
125 if mode and not deleted:
126 os.chmod(path, mode)
127
128 self.git('add', fname)
129
130 rslt = self.git('commit', '--allow-empty', '-m', commit.name, env=env)
131 assert rslt.retcode == 0, 'Failed to commit %s' % str(commit)
132 self.commit_map[commit.name] = self.git('rev-parse', 'HEAD').stdout.strip()
133 self.git('tag', 'tag_%s' % commit.name, self[commit.name])
134 if commit.is_branch:
135 self.git('branch', '-f', 'branch_%s' % commit.name, self[commit.name])
136
137 def get_git_commit_env(self, commit_data=None):
138 commit_data = commit_data or {}
139 env = {}
140 for prefix in ('AUTHOR', 'COMMITTER'):
141 for suffix in ('NAME', 'EMAIL', 'DATE'):
142 singleton = '%s_%s' % (prefix, suffix)
143 key = getattr(self, singleton)
144 if key in commit_data:
145 val = commit_data[key]
146 else:
147 if suffix == 'DATE':
148 val = self._date
149 self._date += datetime.timedelta(days=1)
150 else:
151 val = getattr(self, 'DEFAULT_%s' % singleton)
152 env['GIT_%s' % singleton] = str(val)
153 return env
154
155
156 def git(self, *args, **kwargs):
157 """Runs a git command specified by |args| in this repo."""
158 assert self.repo_path is not None
159 try:
160 with open(os.devnull, 'wb') as devnull:
161 output = subprocess.check_output(
162 ('git',) + args, cwd=self.repo_path, stderr=devnull, **kwargs)
163 return self.COMMAND_OUTPUT(0, output)
164 except subprocess.CalledProcessError as e:
165 return self.COMMAND_OUTPUT(e.returncode, e.output)
166
167 def git_commit(self, message):
168 return self.git('commit', '-am', message, env=self.get_git_commit_env())
169
170 def nuke(self):
171 """Obliterates the git repo on disk.
172
173 Causes this GitRepo to be unusable.
174 """
175 shutil.rmtree(self.repo_path)
176 self.repo_path = None
177
178 def run(self, fn, *args, **kwargs):
179 """Run a python function with the given args and kwargs with the cwd set to
180 the git repo."""
181 assert self.repo_path is not None
182 curdir = os.getcwd()
183 try:
184 os.chdir(self.repo_path)
185 return fn(*args, **kwargs)
186 finally:
187 os.chdir(curdir)
188
189 def capture_stdio(self, fn, *args, **kwargs):
190 """Run a python function with the given args and kwargs with the cwd set to
191 the git repo.
192
193 Returns the (stdout, stderr) of whatever ran, instead of the what |fn|
194 returned.
195 """
196 stdout = sys.stdout
197 stderr = sys.stderr
198 try:
199 # "multiple statements on a line" pylint: disable=C0321
200 with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
201 sys.stdout = out
202 sys.stderr = err
203 try:
204 self.run(fn, *args, **kwargs)
205 except SystemExit:
206 pass
207 out.seek(0)
208 err.seek(0)
209 return out.read(), err.read()
210 finally:
211 sys.stdout = stdout
212 sys.stderr = stderr
213
214 def open(self, path, mode='rb'):
215 return open(os.path.join(self.repo_path, path), mode)
216
217 def to_schema(self):
218 lines = self.git('rev-list', '--parents', '--reverse', '--topo-order',
219 '--format=%s', *self.to_schema_refs).stdout.splitlines()
220 hash_to_msg = {}
221 ret = GitRepoSchema()
222 current = None
223 parents = []
224 for line in lines:
225 if line.startswith('commit'):
226 assert current is None
227 tokens = line.split()
228 current, parents = tokens[1], tokens[2:]
229 assert all(p in hash_to_msg for p in parents)
230 else:
231 assert current is not None
232 hash_to_msg[current] = line
233 ret.add_partial(line)
234 for parent in parents:
235 ret.add_partial(line, hash_to_msg[parent])
236 current = None
237 parents = []
238 assert current is None
239 return ret
OLDNEW
« 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