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

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: fixes 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 sys
23 import tempfile
24 import threading
25
26 import subprocess2
27
28
29 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git'
30
31
32 def memoize_one(**kwargs):
33 """Memoizes a single-argument pure function.
34
35 Values of None are not cached.
36
37 Kwargs:
38 threadsafe (bool) - REQUIRED. Specifies whether to use locking around
39 cache manipulation functions. This is a kwarg so that users of memoize_one
40 are forced to explicitly and verbosely pick True or False.
41
42 Adds three methods to the decorated function:
43 * get(key, default=None) - Gets the value for this key from the cache.
44 * set(key, value) - Sets the value for this key from the cache.
45 * clear() - Drops the entire contents of the cache. Useful for unittests.
46 * update(other) - Updates the contents of the cache from another dict.
47 """
48 assert 'threadsafe' in kwargs, 'Must specify threadsafe={True,False}'
49 threadsafe = kwargs['threadsafe']
50
51 if threadsafe:
52 def withlock(lock, f):
53 def inner(*args, **kwargs):
54 with lock:
55 return f(*args, **kwargs)
56 return inner
57 else:
58 def withlock(_lock, f):
59 return f
60
61 def decorator(f):
62 # Instantiate the lock in decorator, in case users of memoize_one do:
63 #
64 # memoizer = memoize_one(threadsafe=True)
65 #
66 # @memoizer
67 # def fn1(val): ...
68 #
69 # @memoizer
70 # def fn2(val): ...
71
72 lock = threading.Lock() if threadsafe else None
73 cache = {}
74 _get = withlock(lock, cache.get)
75 _set = withlock(lock, cache.__setitem__)
76
77 @functools.wraps(f)
78 def inner(arg):
79 ret = _get(arg)
80 if ret is None:
81 ret = f(arg)
82 if ret is not None:
83 _set(arg, ret)
84 return ret
85 inner.get = _get
86 inner.set = _set
87 inner.clear = withlock(lock, cache.clear)
88 inner.update = withlock(lock, cache.update)
89 return inner
90 return decorator
91
92
93 def _ScopedPool_initer(orig, orig_args): # pragma: no cover
94 """Initializer method for ScopedPool's subprocesses.
95
96 This helps ScopedPool handle Ctrl-C's correctly.
97 """
98 signal.signal(signal.SIGINT, signal.SIG_IGN)
99 if orig:
100 orig(*orig_args)
101
102
103 @contextlib.contextmanager
104 def ScopedPool(*args, **kwargs):
105 """Context Manager which returns a multiprocessing.pool instance which
106 correctly deals with thrown exceptions.
107
108 *args - Arguments to multiprocessing.pool
109
110 Kwargs:
111 kind ('threads', 'procs') - The type of underlying coprocess to use.
112 **etc - Arguments to multiprocessing.pool
113 """
114 if kwargs.pop('kind', None) == 'threads':
115 pool = multiprocessing.pool.ThreadPool(*args, **kwargs)
116 else:
117 orig, orig_args = kwargs.get('initializer'), kwargs.get('initargs', ())
118 kwargs['initializer'] = _ScopedPool_initer
119 kwargs['initargs'] = orig, orig_args
120 pool = multiprocessing.pool.Pool(*args, **kwargs)
121
122 try:
123 yield pool
124 pool.close()
125 except:
126 pool.terminate()
127 raise
128 finally:
129 pool.join()
130
131
132 class ProgressPrinter(object):
133 """Threaded single-stat status message printer."""
134 def __init__(self, fmt, enabled=None, stream=sys.stderr, period=0.5):
135 """Create a ProgressPrinter.
136
137 Use it as a context manager which produces a simple 'increment' method:
138
139 with ProgressPrinter('(%%(count)d/%d)' % 1000) as inc:
140 for i in xrange(1000):
141 # do stuff
142 if i % 10 == 0:
143 inc(10)
144
145 Args:
146 fmt - String format with a single '%(count)d' where the counter value
147 should go.
148 enabled (bool) - If this is None, will default to True if
149 logging.getLogger() is set to INFO or more verbose.
150 stream (file-like) - The stream to print status messages to.
151 period (float) - The time in seconds for the printer thread to wait
152 between printing.
153 """
154 self.fmt = fmt
155 if enabled is None: # pragma: no cover
156 self.enabled = logging.getLogger().isEnabledFor(logging.INFO)
157 else:
158 self.enabled = enabled
159
160 self._count = 0
161 self._dead = False
162 self._dead_cond = threading.Condition()
163 self._stream = stream
164 self._thread = threading.Thread(target=self._run)
165 self._period = period
166
167 def _emit(self, s):
168 if self.enabled:
169 self._stream.write('\r' + s)
170 self._stream.flush()
171
172 def _run(self):
173 with self._dead_cond:
174 while not self._dead:
175 self._emit(self.fmt % {'count': self._count})
176 self._dead_cond.wait(self._period)
177 self._emit((self.fmt + '\n') % {'count': self._count})
178
179 def inc(self, amount=1):
180 self._count += amount
181
182 def __enter__(self):
183 self._thread.start()
184 return self.inc
185
186 def __exit__(self, _exc_type, _exc_value, _traceback):
187 self._dead = True
188 with self._dead_cond:
189 self._dead_cond.notifyAll()
190 self._thread.join()
191 del self._thread
192
193
194 def parse_commitrefs(*commitrefs):
195 """Returns binary encoded commit hashes for one or more commitrefs.
196
197 A commitref is anything which can resolve to a commit. Popular examples:
198 * 'HEAD'
199 * 'origin/master'
200 * 'cool_branch~2'
201 """
202 try:
203 return map(binascii.unhexlify, hashes(*commitrefs))
204 except subprocess2.CalledProcessError:
205 raise Exception('one of %s does not seem to be a valid commitref.' %
M-A Ruel 2013/11/15 02:51:29 Why not use a cute exception? Exception is hard to
iannucci 2013/11/15 04:16:05 Done.
206 str(commitrefs))
207
208
209 def run(*cmd, **kwargs):
210 """Runs a git command. Returns stdout as a string.
211
212 If logging is DEBUG, we'll print the command before we run it.
213
214 kwargs
215 autostrip (bool) - Strip the output. Defaults to True.
216 Output string is always strip()'d.
217 """
218 autostrip = kwargs.pop('autostrip', True)
219 cmd = (GIT_EXE,) + cmd
220 logging.debug('running: %s', ' '.join(repr(tok) for tok in cmd))
221 ret = subprocess2.check_output(cmd, stderr=subprocess2.PIPE, **kwargs)
222 if autostrip:
223 ret = (ret or '').strip()
224 return ret
225
226
227 def hashes(*reflike):
228 return run('rev-parse', *reflike).splitlines()
229
230
231 def intern_f(f, kind='blob'):
232 """Interns a file object into the git object store.
233
234 Args:
235 f (file-like object) - The file-like object to intern
236 kind (git object type) - One of 'blob', 'commit', 'tree', 'tag'.
237
238 Returns the git hash of the interned object (hex encoded).
239 """
240 ret = run('hash-object', '-t', kind, '-w', '--stdin', stdin=f)
241 f.close()
242 return ret
243
244
245 def tree(treeref, recurse=False):
246 """Returns a dict representation of a git tree object.
247
248 Args:
249 treeref (str) - a git ref which resolves to a tree (commits count as trees).
250 recurse (bool) - include all of the tree's decendants too. File names will
251 take the form of 'some/path/to/file'.
252
253 Return format:
254 { 'file_name': (mode, type, ref) }
255
256 mode is an integer where:
257 * 0040000 - Directory
258 * 0100644 - Regular non-executable file
259 * 0100664 - Regular non-executable group-writeable file
260 * 0100755 - Regular executable file
261 * 0120000 - Symbolic link
262 * 0160000 - Gitlink
263
264 type is a string where it's one of 'blob', 'commit', 'tree', 'tag'.
265
266 ref is the hex encoded hash of the entry.
267 """
268 ret = {}
269 opts = ['ls-tree', '--full-tree']
270 if recurse:
271 opts += ['-r']
272 opts.append(treeref)
273 try:
274 for line in run(*opts).splitlines():
275 mode, typ, ref, name = line.split(None, 3)
276 ret[name] = (mode, typ, ref)
277 except subprocess2.CalledProcessError:
278 return None
279 return ret
280
281
282 def mktree(treedict):
283 """Makes a git tree object and returns its hash.
284
285 See |tree()| for the values of mode, type, and ref.
286
287 Args:
288 treedict - { name: (mode, type, ref) }
289 """
290 with tempfile.TemporaryFile() as f:
291 for name, (mode, typ, ref) in treedict.iteritems():
292 f.write('%s %s %s\t%s\0' % (mode, typ, ref, name))
293 f.seek(0)
294 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