Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 # Monkeypatch IMapIterator so that Ctrl-C can kill everything properly. | 5 # Monkeypatch IMapIterator so that Ctrl-C can kill everything properly. |
| 6 # Derived from https://gist.github.com/aljungberg/626518 | 6 # Derived from https://gist.github.com/aljungberg/626518 |
| 7 import multiprocessing.pool | 7 import multiprocessing.pool |
| 8 from multiprocessing.pool import IMapIterator | 8 from multiprocessing.pool import IMapIterator |
| 9 def wrapper(func): | 9 def wrapper(func): |
| 10 def wrap(self, timeout=None): | 10 def wrap(self, timeout=None): |
| 11 return func(self, timeout=timeout or 1e100) | 11 return func(self, timeout=timeout or 1e100) |
| 12 return wrap | 12 return wrap |
| 13 IMapIterator.next = wrapper(IMapIterator.next) | 13 IMapIterator.next = wrapper(IMapIterator.next) |
| 14 IMapIterator.__next__ = IMapIterator.next | 14 IMapIterator.__next__ = IMapIterator.next |
| 15 # TODO(iannucci): Monkeypatch all other 'wait' methods too. | 15 # TODO(iannucci): Monkeypatch all other 'wait' methods too. |
| 16 | 16 |
| 17 | 17 |
| 18 import binascii | 18 import binascii |
| 19 import contextlib | 19 import contextlib |
| 20 import functools | 20 import functools |
| 21 import logging | 21 import logging |
| 22 import signal | 22 import signal |
| 23 import sys | 23 import sys |
| 24 import tempfile | 24 import tempfile |
| 25 import threading | 25 import threading |
| 26 | 26 |
| 27 import subprocess2 | 27 import subprocess2 |
| 28 | 28 |
| 29 | 29 |
| 30 class DEFAULT(object): | |
|
agable
2014/02/28 19:54:58
Why? This is so completely unnecessary. Maybe kind
iannucci
2014/03/06 00:18:39
*sigh* going back to useless-mode. lcd for the win
| |
| 31 def __init__(self, constructor): | |
| 32 self.constructor = constructor | |
| 33 | |
| 34 @staticmethod | |
| 35 def get_from(inst_or_obj): | |
| 36 if isinstance(inst_or_obj, DEFAULT): | |
| 37 return inst_or_obj.constructor() | |
| 38 else: | |
| 39 return inst_or_obj | |
| 40 | |
| 41 | |
| 30 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git' | 42 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git' |
| 31 | 43 |
| 44 NO_BRANCH = ('* (no branch)', '* (detached from ') | |
| 45 | |
| 32 | 46 |
| 33 class BadCommitRefException(Exception): | 47 class BadCommitRefException(Exception): |
| 34 def __init__(self, refs): | 48 def __init__(self, refs): |
| 35 msg = ('one of %s does not seem to be a valid commitref.' % | 49 msg = ('one of %s does not seem to be a valid commitref.' % |
| 36 str(refs)) | 50 str(refs)) |
| 37 super(BadCommitRefException, self).__init__(msg) | 51 super(BadCommitRefException, self).__init__(msg) |
| 38 | 52 |
| 39 | 53 |
| 40 def memoize_one(**kwargs): | 54 def memoize_one(**kwargs): |
| 41 """Memoizes a single-argument pure function. | 55 """Memoizes a single-argument pure function. |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 192 return self.inc | 206 return self.inc |
| 193 | 207 |
| 194 def __exit__(self, _exc_type, _exc_value, _traceback): | 208 def __exit__(self, _exc_type, _exc_value, _traceback): |
| 195 self._dead = True | 209 self._dead = True |
| 196 with self._dead_cond: | 210 with self._dead_cond: |
| 197 self._dead_cond.notifyAll() | 211 self._dead_cond.notifyAll() |
| 198 self._thread.join() | 212 self._thread.join() |
| 199 del self._thread | 213 del self._thread |
| 200 | 214 |
| 201 | 215 |
| 216 def abbrev(ref): | |
| 217 return run('rev-parse', '--abbrev-ref', ref) | |
| 218 | |
| 219 | |
| 220 def branches(*args): | |
| 221 for line in run('branch', *args).splitlines(): | |
| 222 if line.startswith(NO_BRANCH): | |
| 223 continue | |
| 224 yield line.split()[-1] | |
| 225 | |
| 226 | |
| 227 def config_list(option, default=DEFAULT(list)): | |
|
agable
2014/02/28 19:54:58
default=None
if default is None: default=[]
Let's
iannucci
2014/03/06 00:18:39
sometimes I hate python. The =None idiom is so uni
agable
2014/03/06 18:09:07
I know, and I totally agree. But I don't think thi
| |
| 228 try: | |
| 229 return run('config', '--get-all', option).split() | |
| 230 except subprocess2.CalledProcessError: | |
| 231 return DEFAULT.get_from(default) | |
| 232 | |
| 233 | |
| 234 def current_branch(): | |
| 235 return abbrev('HEAD') | |
| 236 | |
| 237 | |
| 202 def parse_commitrefs(*commitrefs): | 238 def parse_commitrefs(*commitrefs): |
| 203 """Returns binary encoded commit hashes for one or more commitrefs. | 239 """Returns binary encoded commit hashes for one or more commitrefs. |
| 204 | 240 |
| 205 A commitref is anything which can resolve to a commit. Popular examples: | 241 A commitref is anything which can resolve to a commit. Popular examples: |
| 206 * 'HEAD' | 242 * 'HEAD' |
| 207 * 'origin/master' | 243 * 'origin/master' |
| 208 * 'cool_branch~2' | 244 * 'cool_branch~2' |
| 209 """ | 245 """ |
| 210 try: | 246 try: |
| 211 return map(binascii.unhexlify, hashes(*commitrefs)) | 247 return map(binascii.unhexlify, hash_multi(*commitrefs)) |
| 212 except subprocess2.CalledProcessError: | 248 except subprocess2.CalledProcessError: |
| 213 raise BadCommitRefException(commitrefs) | 249 raise BadCommitRefException(commitrefs) |
| 214 | 250 |
| 215 | 251 |
| 216 def run(*cmd, **kwargs): | 252 def run(*cmd, **kwargs): |
| 217 """Runs a git command. Returns stdout as a string. | 253 """Runs a git command. Returns stdout as a string. |
| 218 | 254 |
| 219 If logging is DEBUG, we'll print the command before we run it. | 255 If logging is DEBUG, we'll print the command before we run it. |
| 220 | 256 |
| 221 kwargs | 257 kwargs |
| 222 autostrip (bool) - Strip the output. Defaults to True. | 258 autostrip (bool) - Strip the output. Defaults to True. |
| 223 Output string is always strip()'d. | 259 Output string is always strip()'d. |
| 224 """ | 260 """ |
| 225 autostrip = kwargs.pop('autostrip', True) | 261 autostrip = kwargs.pop('autostrip', True) |
| 226 cmd = (GIT_EXE,) + cmd | 262 cmd = (GIT_EXE,) + cmd |
| 227 logging.debug('Running %s', ' '.join(repr(tok) for tok in cmd)) | 263 logging.debug('Running %s', ' '.join(repr(tok) for tok in cmd)) |
| 228 ret = subprocess2.check_output(cmd, stderr=subprocess2.PIPE, **kwargs) | 264 ret = subprocess2.check_output(cmd, stderr=subprocess2.PIPE, **kwargs) |
| 229 if autostrip: | 265 if autostrip: |
| 230 ret = (ret or '').strip() | 266 ret = (ret or '').strip() |
| 231 return ret | 267 return ret |
| 232 | 268 |
| 233 | 269 |
| 234 def hashes(*reflike): | 270 def hash_one(reflike): |
| 271 return run('rev-parse', reflike) | |
| 272 | |
| 273 | |
| 274 def hash_multi(*reflike): | |
|
agable
2014/02/28 19:54:58
This should be "*reflikes" (cf. "*args").
iannucci
2014/03/06 00:18:39
Done.
| |
| 235 return run('rev-parse', *reflike).splitlines() | 275 return run('rev-parse', *reflike).splitlines() |
| 236 | 276 |
| 237 | 277 |
| 238 def intern_f(f, kind='blob'): | 278 def intern_f(f, kind='blob'): |
| 239 """Interns a file object into the git object store. | 279 """Interns a file object into the git object store. |
| 240 | 280 |
| 241 Args: | 281 Args: |
| 242 f (file-like object) - The file-like object to intern | 282 f (file-like object) - The file-like object to intern |
| 243 kind (git object type) - One of 'blob', 'commit', 'tree', 'tag'. | 283 kind (git object type) - One of 'blob', 'commit', 'tree', 'tag'. |
| 244 | 284 |
| 245 Returns the git hash of the interned object (hex encoded). | 285 Returns the git hash of the interned object (hex encoded). |
| 246 """ | 286 """ |
| 247 ret = run('hash-object', '-t', kind, '-w', '--stdin', stdin=f) | 287 ret = run('hash-object', '-t', kind, '-w', '--stdin', stdin=f) |
| 248 f.close() | 288 f.close() |
| 249 return ret | 289 return ret |
| 250 | 290 |
| 251 | 291 |
| 292 def tags(*args): | |
| 293 for line in run('tag', *args).splitlines(): | |
| 294 if line.startswith(NO_BRANCH): | |
| 295 continue | |
| 296 yield line.split()[-1] | |
| 297 | |
| 298 | |
| 252 def tree(treeref, recurse=False): | 299 def tree(treeref, recurse=False): |
| 253 """Returns a dict representation of a git tree object. | 300 """Returns a dict representation of a git tree object. |
| 254 | 301 |
| 255 Args: | 302 Args: |
| 256 treeref (str) - a git ref which resolves to a tree (commits count as trees). | 303 treeref (str) - a git ref which resolves to a tree (commits count as trees). |
| 257 recurse (bool) - include all of the tree's decendants too. File names will | 304 recurse (bool) - include all of the tree's decendants too. File names will |
| 258 take the form of 'some/path/to/file'. | 305 take the form of 'some/path/to/file'. |
| 259 | 306 |
| 260 Return format: | 307 Return format: |
| 261 { 'file_name': (mode, type, ref) } | 308 { 'file_name': (mode, type, ref) } |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 279 opts.append(treeref) | 326 opts.append(treeref) |
| 280 try: | 327 try: |
| 281 for line in run(*opts).splitlines(): | 328 for line in run(*opts).splitlines(): |
| 282 mode, typ, ref, name = line.split(None, 3) | 329 mode, typ, ref, name = line.split(None, 3) |
| 283 ret[name] = (mode, typ, ref) | 330 ret[name] = (mode, typ, ref) |
| 284 except subprocess2.CalledProcessError: | 331 except subprocess2.CalledProcessError: |
| 285 return None | 332 return None |
| 286 return ret | 333 return ret |
| 287 | 334 |
| 288 | 335 |
| 336 def upstream(branch): | |
| 337 try: | |
| 338 return run('rev-parse', '--abbrev-ref', '--symbolic-full-name', | |
| 339 branch+'@{upstream}') | |
| 340 except subprocess2.CalledProcessError: | |
| 341 return None | |
| 342 | |
| 343 | |
| 289 def mktree(treedict): | 344 def mktree(treedict): |
| 290 """Makes a git tree object and returns its hash. | 345 """Makes a git tree object and returns its hash. |
| 291 | 346 |
| 292 See |tree()| for the values of mode, type, and ref. | 347 See |tree()| for the values of mode, type, and ref. |
| 293 | 348 |
| 294 Args: | 349 Args: |
| 295 treedict - { name: (mode, type, ref) } | 350 treedict - { name: (mode, type, ref) } |
| 296 """ | 351 """ |
| 297 with tempfile.TemporaryFile() as f: | 352 with tempfile.TemporaryFile() as f: |
| 298 for name, (mode, typ, ref) in treedict.iteritems(): | 353 for name, (mode, typ, ref) in treedict.iteritems(): |
| 299 f.write('%s %s %s\t%s\0' % (mode, typ, ref, name)) | 354 f.write('%s %s %s\t%s\0' % (mode, typ, ref, name)) |
| 300 f.seek(0) | 355 f.seek(0) |
| 301 return run('mktree', '-z', stdin=f) | 356 return run('mktree', '-z', stdin=f) |
| OLD | NEW |