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

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 (reupload!) Created 7 years, 2 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 | 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 signal
21 import subprocess
22 import sys
23 import tempfile
24 import threading
25
26
27 # The following are importable functions from converting to/from hex/binary
28 # encodings for git hashes.
29 hexlify = binascii.hexlify # "\xDE\xAD" -> "DEAD"
30 unhexlify = binascii.unhexlify # "DEAD" -> "\xDE\xAD"
31
32 # This converts a binary git hash prefix into a posix path, one folder per byte
33 # e.g. "\xDE\xAD" -> "de/ad"
34 pathlify = lambda s: '/'.join('%02x' % ord(b) for b in s)
35
36
37 VERBOSE_LEVEL = 0
38
39 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git'
40
41
42 class CalledProcessError(Exception):
43 def __init__(self, returncode, cmd):
44 super(CalledProcessError, self).__init__()
45 self.returncode = returncode
46 self.cmd = cmd
47
48 def __str__(self):
49 return (
50 'Command "%s" returned non-zero exit status %d' %
51 (self.cmd, self.returncode))
52
53
54 def memoize_one(f):
55 """Memoizes a single-argument pure function.
56
57 Values of None are not cached.
58
59 Adds a mutable attribute to the decorated function:
60 * cache (dict) - Maps arg to f(arg)
61 """
62 cache = {}
63
64 @functools.wraps(f)
65 def inner(arg):
66 ret = cache.get(arg)
67 if ret is None:
68 ret = f(arg)
69 if ret is not None:
70 cache[arg] = ret
71 return ret
72 inner.cache = cache
73 inner.default_enabled = False
74
75 return inner
76
77
78 def ScopedPool_initer(orig, orig_args):
79 """Initializer method for ScopedPool's subprocesses.
80
81 This helps ScopedPool handle Ctrl-C's correctly.
82 """
83 signal.signal(signal.SIGINT, signal.SIG_IGN)
84 if orig:
85 orig(*orig_args)
86
87
88 @contextlib.contextmanager
89 def ScopedPool(*args, **kwargs):
90 if kwargs.pop('kind', None) == 'threads':
91 pool = multiprocessing.pool.ThreadPool(*args, **kwargs)
92 else:
93 orig, orig_args = kwargs.get('initializer'), kwargs.get('initargs', ())
94 kwargs['initializer'] = ScopedPool_initer
95 kwargs['initargs'] = orig, orig_args
96 pool = multiprocessing.pool.Pool(*args, **kwargs)
97
98 try:
99 yield pool
100 pool.close()
101 except:
102 pool.terminate()
103 raise
104 finally:
105 pool.join()
106
107
108 class ProgressPrinter(object):
109 """Threaded single-stat status message printer."""
110 def __init__(self, fmt):
111 """Create a ProgressPrinter.
112
113 Use it as a context manager which produces a simple 'increment' method:
114
115 with ProgressPrinter('(%%(count)d/%d)' % 1000) as inc:
116 for i in xrange(1000):
117 # do stuff
118 if i % 10 == 0:
119 inc(10)
120
121 Args:
122 fmt - String format with a single '%(count)d' where the counter value
123 should go.
124 """
125 self.fmt = fmt
126 self._count = 0
127 self._dead = False
128 self._dead_cond = threading.Condition()
129 self._thread = threading.Thread(target=self._run)
130
131 @staticmethod
132 def _emit(s):
133 if VERBOSE_LEVEL > 0:
M-A Ruel 2013/10/24 13:23:03 Use an instance member instead to set it.
iannucci 2013/10/25 00:52:41 Done.
134 sys.stderr.write('\r'+s)
135 sys.stderr.flush()
136
137 def _run(self):
138 with self._dead_cond:
139 while not self._dead:
140 self._emit(self.fmt % {'count': self._count})
141 self._dead_cond.wait(.5)
142 self._emit((self.fmt+'\n') % {'count': self._count})
143
144 def inc(self, amount=1):
145 self._count += amount
146
147 def __enter__(self):
148 self._thread.start()
149 return self.inc
150
151 def __exit__(self, _exc_type, _exc_value, _traceback):
152 self._dead = True
153 with self._dead_cond:
154 self._dead_cond.notifyAll()
155 self._thread.join()
156 del self._thread
157
158
159 def parse_committishes(*committish):
160 """This takes one or more committishes, and returns the binary-encoded git
161 hashes for them.
162
163 A committish is anything which can resolve to a commit. Popular examples:
164 * "HEAD"
165 * "origin/master"
166 * "cool_branch~2"
167
168 etc.
169 """
170 try:
171 return map(unhexlify, git_hash(*committish).splitlines())
172 except CalledProcessError:
173 raise Exception('%r does not seem to be a valid commitish.' % committish)
174
175
176 def check_output(*popenargs, **kwargs):
177 """Run a Popen command, and return the stdout as a string.
178
179 Throws CalledProcessError if the command returns non-zero.
180
181 kwargs:
182 indata (str) - Data to provide to the command on stdin. Mutually exclusive
183 with the Popen kwarg 'stdin'.
184
185 Other than that, popenargs is *args to Popen, and **kwargs is... **kwargs to
186 Popen.
187 """
188 kwargs.setdefault('stdout', subprocess.PIPE)
189 kwargs.setdefault('stderr', subprocess.PIPE)
190 indata = kwargs.pop('indata', None)
191 if indata is not None:
192 kwargs['stdin'] = subprocess.PIPE
193 process = subprocess.Popen(*popenargs, **kwargs)
194 output, _ = process.communicate(indata)
195 if process.returncode:
196 cmd = kwargs.get('args')
197 if cmd is None:
198 cmd = popenargs[0]
199 raise CalledProcessError(process.returncode, cmd)
200 return output
201
202
203 def run_git(*cmd, **kwargs):
204 """Runs a git command. Returns stdout as a string.
205
206 If VERBOSE_LEVEL is > 1, we'll print the command before we run it.
207
208 Output string is always strip()'d.
209 """
210 cmd = (GIT_EXE,) + cmd
211 if VERBOSE_LEVEL > 1:
M-A Ruel 2013/10/24 13:23:03 Why not using logging.info() ? Or at worst, use lo
iannucci 2013/10/25 00:52:41 because I'm lazy and the logging module annoys me?
212 print 'running:', " ".join(repr(tok) for tok in cmd)
213 ret = check_output(cmd, **kwargs)
214 ret = (ret or '').strip()
215 return ret
216
217
218 def git_hash(*reflike):
219 return run_git('rev-parse', *reflike)
220
221
222 def git_intern_f(f, kind='blob'):
223 """Interns a file object into the git object store.
224
225 Args:
226 f (file-like object) - The file-like object to intern
227 kind (git object type) - One of 'blob', 'commit', 'tree', 'tag'.
228
229 Returns the git hash of the interned object (hex encoded).
230 """
231 ret = run_git('hash-object', '-t', kind, '-w', '--stdin', stdin=f)
232 f.close()
233 return ret
234
235
236 def git_tree(treeish, recurse=False):
237 """
238 Args:
239 treeish - a git name which resolves to a tree (or to a commit).
240 recurse - include just this tree, or all of its decendants too.
241
242 Returns a dict formatted like:
243 { 'file_name': (mode, type, ref) }
244
245 mode is an integer where:
246 * 0040000 - Directory
247 * 0100644 - Regular non-executable file
248 * 0100664 - Regular non-executable group-writeable file
249 * 0100755 - Regular executable file
250 * 0120000 - Symbolic link
251 * 0160000 - Gitlink
252
253 type is a string where it's one of 'blob', 'commit', 'tree', 'tag'.
254
255 ref is the hex encoded hash of the entry.
256 """
257 ret = {}
258 opts = ['ls-tree', '--full-tree']
259 if recurse:
260 opts += ['-r']
261 opts.append(treeish)
262 try:
263 for line in run_git(*opts).splitlines():
264 if not line:
265 continue
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 git_mktree(treedict):
274 """Make a git tree object and return its hash.
275
276 See git_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_git('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