Chromium Code Reviews| Index: git_common.py |
| diff --git a/git_common.py b/git_common.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b0a2a120fa06651fc3ade06296baace296c8595e |
| --- /dev/null |
| +++ b/git_common.py |
| @@ -0,0 +1,219 @@ |
| +# Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +# Monkeypatch IMapIterator so that Ctrl-C can kill everything properly. |
| +# Derived from https://gist.github.com/aljungberg/626518 |
| +import multiprocessing.pool |
| +from multiprocessing.pool import IMapIterator |
|
M-A Ruel
2013/10/21 17:56:44
Can't this be done later?
iannucci
2013/10/22 07:28:22
'this' ? Parallelizing, or monkey-patching IMapIte
M-A Ruel
2013/10/24 13:23:03
Monkey patching, but I don't mind. Leave it there.
|
| +def wrapper(func): |
| + def wrap(self, timeout=None): |
| + return func(self, timeout=timeout or 1e100) |
| + return wrap |
| +IMapIterator.next = wrapper(IMapIterator.next) |
| +IMapIterator.__next__ = IMapIterator.next |
| + |
| +import contextlib |
| +import functools |
| +import signal |
| +import subprocess |
| +import sys |
| +import tempfile |
| +import threading |
| +import binascii |
|
M-A Ruel
2013/10/21 17:56:44
Order
iannucci
2013/10/22 07:28:22
Done.
|
| + |
| +hexlify = binascii.hexlify |
|
M-A Ruel
2013/10/21 17:56:44
It's not used once. Why?
iannucci
2013/10/22 07:28:22
It's imported (in git_number). Used for dealing wi
M-A Ruel
2013/10/24 13:23:03
Then import it where it's used.
|
| +unhexlify = binascii.unhexlify |
|
M-A Ruel
2013/10/21 17:56:44
It's used once, why?
|
| +pathlify = lambda s: '/'.join('%02x' % ord(b) for b in s) |
| + |
| +VERBOSE_LEVEL = 0 |
| + |
|
M-A Ruel
2013/10/21 17:56:44
2 lines
iannucci
2013/10/22 07:28:22
Done.
|
| +class CalledProcessError(Exception): |
| + def __init__(self, returncode, cmd, output=None, out_err=None): |
| + super(CalledProcessError, self).__init__() |
| + self.returncode = returncode |
| + self.cmd = cmd |
| + self.output = output |
| + self.out_err = out_err |
|
agable
2013/10/21 20:16:42
output and out_err are never used.
iannucci
2013/10/22 07:28:22
True. Done.
|
| + |
| + def __str__(self): |
| + return ( |
| + 'Command "%s" returned non-zero exit status %d' % |
| + (self.cmd, self.returncode)) |
| + |
| + |
| +def memoize_one(f): |
|
agable
2013/10/21 20:16:42
Is there anywhere else in depot_tools this could l
iannucci
2013/10/22 07:28:22
Not sure...
|
| + """ |
|
M-A Ruel
2013/10/21 17:56:44
"""Memoizes a single-argument pure function.
iannucci
2013/10/22 07:28:22
Done.
|
| + Memoizes a single-argument pure function. |
| + |
| + Values of None are not cached. |
| + |
| + Adds a mutable attribute to the decorated function: |
| + * cache (dict) - Maps arg to f(arg) |
| + """ |
| + cache = {} |
| + |
| + @functools.wraps(f) |
| + def inner(arg): |
| + ret = cache.get(arg) |
| + if ret is None: |
| + ret = f(arg) |
| + if ret is not None: |
| + cache[arg] = ret |
| + return ret |
| + inner.cache = cache |
| + inner.default_enabled = False |
| + |
| + return inner |
| + |
| + |
| +def initer(orig, orig_args): |
|
agable
2013/10/21 20:16:42
Define this inside ScopedPool?
iannucci
2013/10/22 07:28:22
Can't. Multprocessing sux and this must be importa
|
| + signal.signal(signal.SIGINT, signal.SIG_IGN) |
| + if orig: |
| + orig(*orig_args) |
| + |
| + |
| +@contextlib.contextmanager |
| +def ScopedPool(*args, **kwargs): |
| + if kwargs.pop('kind', None) == 'threads': |
| + pool = multiprocessing.pool.ThreadPool(*args, **kwargs) |
| + else: |
| + orig, orig_args = kwargs.get('initializer'), kwargs.get('initargs', ()) |
| + kwargs['initializer'] = initer |
| + kwargs['initargs'] = orig, orig_args |
| + pool = multiprocessing.pool.Pool(*args, **kwargs) |
| + |
| + try: |
| + yield pool |
| + pool.close() |
| + except: |
| + pool.terminate() |
| + raise |
| + finally: |
| + pool.join() |
| + |
| + |
| +class StatusPrinter(object): |
|
agable
2013/10/21 20:16:42
This is more like a ProgressPrinter, since it incr
iannucci
2013/10/22 07:28:22
Done.
|
| + """Threaded single-stat status message printer.""" |
| + def __init__(self, fmt): |
| + """ |
| + Create a StatusPrinter. |
|
agable
2013/10/21 20:16:42
Bump up a line.
iannucci
2013/10/22 07:28:22
Done.
|
| + |
| + Call .start() to get it going, or use it as a context manager which produces |
|
agable
2013/10/21 20:16:42
Doesn't actually have a .start() method.
iannucci
2013/10/22 07:28:22
well how about that... Done.
|
| + a simple 'increment' method: |
| + |
| + with StatusPrinter('(%%d/%d)' % 1000) as inc: |
| + for i in xrange(1000): |
| + # do stuff |
| + if i % 10 == 0: |
| + inc(10) |
| + |
| + Args: |
| + fmt - String format with a single '%d' where the counter value should go. |
| + """ |
| + self.fmt = fmt |
| + self._count = 0 |
| + self._dead = False |
| + self._dead_cond = threading.Condition() |
| + self._thread = threading.Thread(target=self._run) |
| + |
| + @staticmethod |
| + def _emit(s): |
| + if VERBOSE_LEVEL > 0: |
| + sys.stderr.write('\r'+s) |
| + sys.stderr.flush() |
| + |
| + def _run(self): |
| + with self._dead_cond: |
| + while not self._dead: |
| + self._emit(self.fmt % self._count) |
| + self._dead_cond.wait(.5) |
| + self._emit((self.fmt+'\n') % self._count) |
| + |
| + def inc(self, amount=1): |
| + self._count += amount |
| + |
| + def __enter__(self): |
| + self._thread.start() |
| + return self.inc |
| + |
| + def __exit__(self, _exc_type, _exc_value, _traceback): |
| + self._dead = True |
| + with self._dead_cond: |
| + self._dead_cond.notifyAll() |
| + self._thread.join() |
| + del self._thread |
| + |
| + |
| +def parse_committish(*committish): |
|
agable
2013/10/21 20:16:42
Docstring. Unclear why a committish would have mul
iannucci
2013/10/22 07:28:22
Added comments to *lify, added docstring and renam
|
| + try: |
| + return map(unhexlify, git_hash(*committish).splitlines()) |
| + except CalledProcessError: |
| + raise Exception('%r does not seem to be a valid commitish.' % committish) |
| + |
| + |
| +def check_output(*popenargs, **kwargs): |
|
agable
2013/10/21 20:16:42
Docstriiing.
iannucci
2013/10/22 07:28:22
Done.
|
| + kwargs.setdefault('stdout', subprocess.PIPE) |
| + kwargs.setdefault('stderr', subprocess.PIPE) |
| + indata = kwargs.pop('indata', None) |
| + if indata is not None: |
| + kwargs['stdin'] = subprocess.PIPE |
| + process = subprocess.Popen(*popenargs, **kwargs) |
| + output, out_err = process.communicate(indata) |
|
agable
2013/10/21 20:16:42
I thought communicate misbehaved if you didn't hav
iannucci
2013/10/22 07:28:22
We do this if indata is not-None.
|
| + retcode = process.poll() |
| + if retcode: |
|
agable
2013/10/21 20:16:42
You need to handle the case where poll() returns N
iannucci
2013/10/22 07:28:22
communicate() guarantees that the process is ended
|
| + cmd = kwargs.get('args') |
| + if cmd is None: |
| + cmd = popenargs[0] |
| + raise CalledProcessError(retcode, cmd, output=output, out_err=out_err) |
| + return output |
| + |
| + |
| +GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git' |
|
M-A Ruel
2013/10/21 17:56:44
Constants should be at the top.
iannucci
2013/10/22 07:28:22
Yep. Done.
|
| + |
| +def run_git(*cmd, **kwargs): |
|
agable
2013/10/21 20:16:42
Doooooooc strriiiiiiing.
iannucci
2013/10/22 07:28:22
Done.
M-A Ruel
2013/10/24 13:23:03
I don't think this particular function needed a do
|
| + cmd = (GIT_EXE,) + cmd |
| + if VERBOSE_LEVEL > 1: |
| + print 'running:', " ".join(repr(tok) for tok in cmd) |
| + ret = check_output(cmd, **kwargs) |
| + ret = (ret or '').strip() |
| + return ret |
| + |
| + |
| +def git_hash(*reflike): |
|
agable
2013/10/21 20:16:42
Like seriously. All functions are worth good docst
iannucci
2013/10/22 07:28:22
really? this is a one-line function...
"""Returns
M-A Ruel
2013/10/24 13:23:03
I think it's more about *what* this call does. It
iannucci
2013/10/25 00:52:41
Hm... at the callsite:
hashes = git_hash('HEAD'
|
| + return run_git('rev-parse', *reflike) |
| + |
| + |
| +def git_intern_f(f, kind='blob'): |
|
M-A Ruel
2013/10/21 17:56:44
This function is worth a docstring.
iannucci
2013/10/22 07:28:22
Done.
|
| + ret = run_git('hash-object', '-t', kind, '-w', '--stdin', stdin=f) |
| + f.close() |
| + return ret |
| + |
| + |
| +def git_tree(treeish, recurse=False): |
| + ret = {} |
| + opts = ['ls-tree', '--full-tree'] |
| + if recurse: |
| + opts += ['-r'] |
| + opts.append(treeish) |
| + try: |
| + for line in run_git(*opts).splitlines(): |
| + if not line: |
| + continue |
| + mode, typ, ref, name = line.split(None, 3) |
| + ret[name] = (mode, typ, ref) |
| + except CalledProcessError: |
| + return None |
| + return ret |
| + |
| + |
| +def git_mktree(treedict): |
| + """ |
| + Args: |
| + treedict - { name: (mode, type, ref) } |
| + """ |
| + with tempfile.TemporaryFile() as f: |
| + for name, (mode, typ, ref) in treedict.iteritems(): |
| + f.write('%s %s %s\t%s\0' % (mode, typ, ref, name)) |
| + f.seek(0) |
| + return run_git('mktree', '-z', stdin=f) |