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

Side by Side Diff: git_common.py

Issue 26109002: Add git-number script to calculate generation numbers for commits. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: address comments Created 7 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « git-number ('k') | git_number.py » ('j') | git_number.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2013 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 # Monkeypatch IMapIterator so that Ctrl-C can kill everything properly.
6 # Derived from https://gist.github.com/aljungberg/626518
7 import multiprocessing.pool
8 from multiprocessing.pool import IMapIterator
9 def wrapper(func):
10 def wrap(self, timeout=None):
11 return func(self, timeout=timeout or 1e100)
12 return wrap
13 IMapIterator.next = wrapper(IMapIterator.next)
14 IMapIterator.__next__ = IMapIterator.next
15
16
17 import binascii
18 import contextlib
19 import functools
20 import logging
21 import signal
22 import subprocess
23 import sys
24 import tempfile
25 import threading
26
27
28 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git'
29
30
31 class CalledProcessError(Exception):
32 def __init__(self, returncode, cmd):
33 super(CalledProcessError, self).__init__()
34 self.returncode = returncode
35 self.cmd = cmd
36
37 def __str__(self):
38 return (
39 'Command "%s" returned non-zero exit status %d' %
40 (self.cmd, self.returncode))
41
42
43 def memoize_one(f):
44 """Memoizes a single-argument pure function.
45
46 Values of None are not cached.
47
48 Adds a mutable attribute to the decorated function:
49 * cache (dict) - Maps arg to f(arg)
50
51 To clear the cache (e.g. between unit tests), just do:
52 |your_function|.cache.clear()
53 """
54 cache = {}
M-A Ruel 2013/11/08 19:32:23 I'd prefer if this decorator was thread safe. This
iannucci 2013/11/11 22:59:24 I made a threadsafe=bool option mandatory for this
55
56 @functools.wraps(f)
57 def inner(arg):
58 ret = cache.get(arg)
59 if ret is None:
60 ret = f(arg)
61 if ret is not None:
62 cache[arg] = ret
63 return ret
64 inner.cache = cache
65
66 return inner
67
68
69 def _ScopedPool_initer(orig, orig_args): # pragma: no cover
70 """Initializer method for ScopedPool's subprocesses.
71
72 This helps ScopedPool handle Ctrl-C's correctly.
73 """
74 signal.signal(signal.SIGINT, signal.SIG_IGN)
75 if orig:
76 orig(*orig_args)
77
78
79 @contextlib.contextmanager
80 def ScopedPool(*args, **kwargs):
81 if kwargs.pop('kind', None) == 'threads':
82 pool = multiprocessing.pool.ThreadPool(*args, **kwargs)
83 else:
84 orig, orig_args = kwargs.get('initializer'), kwargs.get('initargs', ())
85 kwargs['initializer'] = _ScopedPool_initer
86 kwargs['initargs'] = orig, orig_args
87 pool = multiprocessing.pool.Pool(*args, **kwargs)
88
89 try:
90 yield pool
91 pool.close()
92 except:
93 pool.terminate()
94 raise
95 finally:
96 pool.join()
97
98
99 class ProgressPrinter(object):
100 """Threaded single-stat status message printer."""
101 def __init__(self, fmt, enabled=None, stream=sys.stderr, period=0.5):
102 """Create a ProgressPrinter.
103
104 Use it as a context manager which produces a simple 'increment' method:
105
106 with ProgressPrinter('(%%(count)d/%d)' % 1000) as inc:
107 for i in xrange(1000):
108 # do stuff
109 if i % 10 == 0:
110 inc(10)
111
112 Args:
113 fmt - String format with a single '%(count)d' where the counter value
114 should go.
115 enabled (bool) - If this is None, will default to True if
116 logging.getLogger() is set to INFO or more verbose.
117 stream (file-like) - The stream to print status messages to.
118 period (float) - The time in seconds for the printer thread to wait
119 between printing.
120 """
121 self.fmt = fmt
122 if enabled is None: # pragma: no cover
123 self.enabled = logging.getLogger().isEnabledFor(logging.INFO)
124 else:
125 self.enabled = enabled
126
127 self._count = 0
128 self._dead = False
129 self._dead_cond = threading.Condition()
130 self._stream = stream
131 self._thread = threading.Thread(target=self._run)
132 self._period = period
133
134 def _emit(self, s):
135 if self.enabled:
136 self._stream.write('\r'+s)
137 self._stream.flush()
138
139 def _run(self):
140 with self._dead_cond:
141 while not self._dead:
142 self._emit(self.fmt % {'count': self._count})
143 self._dead_cond.wait(self._period)
144 self._emit((self.fmt+'\n') % {'count': self._count})
145
146 def inc(self, amount=1):
147 self._count += amount
148
149 def __enter__(self):
150 self._thread.start()
151 return self.inc
152
153 def __exit__(self, _exc_type, _exc_value, _traceback):
154 self._dead = True
155 with self._dead_cond:
156 self._dead_cond.notifyAll()
157 self._thread.join()
158 del self._thread
159
160
161 def parse_committishes(*committishes):
162 """This takes one or more committishes, and returns the binary-encoded git
M-A Ruel 2013/11/08 19:32:23 """Returns binary-encoded git ref (hash) for one o
iannucci 2013/11/11 22:59:24 Yeah I agree, the terminology in the git docs is N
163 hashes for them.
164
165 A committish is anything which can resolve to a commit. Popular examples:
166 * "HEAD"
167 * "origin/master"
168 * "cool_branch~2"
169
170 etc.
171 """
172 try:
173 return map(binascii.unhexlify, hashes(*committishes))
174 except CalledProcessError:
175 raise Exception('one of %s does not seem to be a valid commitish.' %
176 str(committishes))
177
178
179 def _check_output(*popenargs, **kwargs):
180 """Run a Popen command, and return the stdout as a string.
M-A Ruel 2013/11/08 19:32:23 Runs and what about check_output? But it doesn't
iannucci 2013/11/11 22:59:24 Done
181
182 Throws CalledProcessError if the command returns non-zero.
183
184 kwargs:
185 indata (str) - Data to provide to the command on stdin. Mutually exclusive
186 with the Popen kwarg 'stdin'.
187
188 Other than that, popenargs is *args to Popen, and **kwargs is... **kwargs to
189 Popen.
190 """
191 kwargs.setdefault('stdout', subprocess.PIPE)
192 kwargs.setdefault('stderr', subprocess.PIPE)
193 indata = kwargs.pop('indata', None)
194 if indata is not None:
195 kwargs['stdin'] = subprocess.PIPE
196 process = subprocess.Popen(*popenargs, **kwargs)
197 output, _ = process.communicate(indata)
198 if process.returncode:
199 cmd = kwargs.get('args')
200 if cmd is None:
201 cmd = popenargs[0]
202 raise CalledProcessError(process.returncode, cmd)
203 return output
204
205
206 def run(*cmd, **kwargs):
207 """Runs a git command. Returns stdout as a string.
208
209 If logging is DEBUG, we'll print the command before we run it.
210
211 Output string is always strip()'d.
212 """
213 cmd = (GIT_EXE,) + cmd
214 logging.debug('running: %s', " ".join(repr(tok) for tok in cmd))
215 ret = _check_output(cmd, **kwargs)
216 ret = (ret or '').strip()
217 return ret
218
219
220 def hashes(*reflike):
221 return run('rev-parse', *reflike).splitlines()
222
223
224 def intern_f(f, kind='blob'):
225 """Interns a file object into the git object store.
226
227 Args:
228 f (file-like object) - The file-like object to intern
229 kind (git object type) - One of 'blob', 'commit', 'tree', 'tag'.
230
231 Returns the git hash of the interned object (hex encoded).
232 """
233 ret = run('hash-object', '-t', kind, '-w', '--stdin', stdin=f)
234 f.close()
235 return ret
236
237
238 def tree(treeish, recurse=False):
239 """
240 Args:
241 treeish - a git name which resolves to a tree (or to a commit).
242 recurse - include just this tree, or all of its decendants too.
243
244 Returns a dict formatted like:
245 { 'file_name': (mode, type, ref) }
246
247 mode is an integer where:
248 * 0040000 - Directory
249 * 0100644 - Regular non-executable file
250 * 0100664 - Regular non-executable group-writeable file
251 * 0100755 - Regular executable file
252 * 0120000 - Symbolic link
253 * 0160000 - Gitlink
254
255 type is a string where it's one of 'blob', 'commit', 'tree', 'tag'.
256
257 ref is the hex encoded hash of the entry.
258 """
259 ret = {}
260 opts = ['ls-tree', '--full-tree']
261 if recurse:
262 opts += ['-r']
263 opts.append(treeish)
264 try:
265 for line in run(*opts).splitlines():
266 mode, typ, ref, name = line.split(None, 3)
267 ret[name] = (mode, typ, ref)
268 except CalledProcessError:
269 return None
270 return ret
271
272
273 def mktree(treedict):
274 """Make a git tree object and return its hash.
275
276 See tree for the values of mode, type, and ref.
277
278 Args:
279 treedict - { name: (mode, type, ref) }
280 """
281 with tempfile.TemporaryFile() as f:
282 for name, (mode, typ, ref) in treedict.iteritems():
283 f.write('%s %s %s\t%s\0' % (mode, typ, ref, name))
284 f.seek(0)
285 return run('mktree', '-z', stdin=f)
OLDNEW
« no previous file with comments | « git-number ('k') | git_number.py » ('j') | git_number.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698