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

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