Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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): |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 25 import setup_color | 25 import setup_color |
| 26 import shutil | 26 import shutil |
| 27 import signal | 27 import signal |
| 28 import sys | 28 import sys |
| 29 import tempfile | 29 import tempfile |
| 30 import textwrap | 30 import textwrap |
| 31 import threading | 31 import threading |
| 32 | 32 |
| 33 import subprocess2 | 33 import subprocess2 |
| 34 | 34 |
| 35 from cStringIO import StringIO | |
| 36 | |
| 37 | |
| 35 ROOT = os.path.abspath(os.path.dirname(__file__)) | 38 ROOT = os.path.abspath(os.path.dirname(__file__)) |
| 36 | |
| 37 IS_WIN = sys.platform == 'win32' | 39 IS_WIN = sys.platform == 'win32' |
| 38 GIT_EXE = ROOT+'\\git.bat' if IS_WIN else 'git' | 40 GIT_EXE = ROOT+'\\git.bat' if IS_WIN else 'git' |
| 39 TEST_MODE = False | 41 TEST_MODE = False |
| 40 | 42 |
| 41 FREEZE = 'FREEZE' | 43 FREEZE = 'FREEZE' |
| 42 FREEZE_SECTIONS = { | 44 FREEZE_SECTIONS = { |
| 43 'indexed': 'soft', | 45 'indexed': 'soft', |
| 44 'unindexed': 'mixed' | 46 'unindexed': 'mixed' |
| 45 } | 47 } |
| 46 FREEZE_MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(FREEZE_SECTIONS))) | 48 FREEZE_MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(FREEZE_SECTIONS))) |
| (...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 274 how many times the decorated |function| is called.""" | 276 how many times the decorated |function| is called.""" |
| 275 def _inner_gen(): | 277 def _inner_gen(): |
| 276 yield function() | 278 yield function() |
| 277 while True: | 279 while True: |
| 278 yield | 280 yield |
| 279 return _inner_gen().next | 281 return _inner_gen().next |
| 280 | 282 |
| 281 | 283 |
| 282 ## Git functions | 284 ## Git functions |
| 283 | 285 |
| 286 def die(message, *args): | |
| 287 print >> sys.stderr, textwrap.dedent(message % args) | |
| 288 sys.exit(1) | |
| 289 | |
| 284 | 290 |
| 285 def blame(filename, revision=None, porcelain=False, *_args): | 291 def blame(filename, revision=None, porcelain=False, *_args): |
| 286 command = ['blame'] | 292 command = ['blame'] |
| 287 if porcelain: | 293 if porcelain: |
| 288 command.append('-p') | 294 command.append('-p') |
| 289 if revision is not None: | 295 if revision is not None: |
| 290 command.append(revision) | 296 command.append(revision) |
| 291 command.extend(['--', filename]) | 297 command.extend(['--', filename]) |
| 292 return run(*command) | 298 return run(*command) |
| 293 | 299 |
| 294 | 300 |
| 295 def branch_config(branch, option, default=None): | 301 def branch_config(branch, option, default=None): |
| 296 return config('branch.%s.%s' % (branch, option), default=default) | 302 return get_config('branch.%s.%s' % (branch, option), default=default) |
| 297 | 303 |
| 298 | 304 |
| 299 def config_regexp(pattern): | 305 def config_regexp(pattern): |
| 300 if IS_WIN: # pragma: no cover | 306 if IS_WIN: # pragma: no cover |
| 301 # this madness is because we call git.bat which calls git.exe which calls | 307 # this madness is because we call git.bat which calls git.exe which calls |
| 302 # bash.exe (or something to that effect). Each layer divides the number of | 308 # bash.exe (or something to that effect). Each layer divides the number of |
| 303 # ^'s by 2. | 309 # ^'s by 2. |
| 304 pattern = pattern.replace('^', '^' * 8) | 310 pattern = pattern.replace('^', '^' * 8) |
| 305 return run('config', '--get-regexp', pattern).splitlines() | 311 return run('config', '--get-regexp', pattern).splitlines() |
| 306 | 312 |
| 307 | 313 |
| 308 def branch_config_map(option): | 314 def branch_config_map(option): |
| 309 """Return {branch: <|option| value>} for all branches.""" | 315 """Return {branch: <|option| value>} for all branches.""" |
| 310 try: | 316 try: |
| 311 reg = re.compile(r'^branch\.(.*)\.%s$' % option) | 317 reg = re.compile(r'^branch\.(.*)\.%s$' % option) |
| 312 lines = config_regexp(reg.pattern) | 318 lines = config_regexp(reg.pattern) |
| 313 return {reg.match(k).group(1): v for k, v in (l.split() for l in lines)} | 319 return {reg.match(k).group(1): v for k, v in (l.split() for l in lines)} |
| 314 except subprocess2.CalledProcessError: | 320 except subprocess2.CalledProcessError: |
| 315 return {} | 321 return {} |
| 316 | 322 |
| 317 | 323 |
| 318 def branches(*args): | 324 def branches(*args): |
| 319 NO_BRANCH = ('* (no branch', '* (detached', '* (HEAD detached') | 325 NO_BRANCH = ('* (no branch', '* (detached', '* (HEAD detached') |
| 320 | 326 |
| 321 key = 'depot-tools.branch-limit' | 327 key = 'depot-tools.branch-limit' |
| 322 limit = 20 | 328 limit = get_config_int(key, 20) |
| 323 try: | |
| 324 limit = int(config(key, limit)) | |
| 325 except ValueError: | |
| 326 pass | |
| 327 | 329 |
| 328 raw_branches = run('branch', *args).splitlines() | 330 raw_branches = run('branch', *args).splitlines() |
| 329 | 331 |
| 330 num = len(raw_branches) | 332 num = len(raw_branches) |
| 333 | |
| 331 if num > limit: | 334 if num > limit: |
| 332 print >> sys.stderr, textwrap.dedent("""\ | 335 die("""\ |
| 333 Your git repo has too many branches (%d/%d) for this tool to work well. | 336 Your git repo has too many branches (%d/%d) for this tool to work well. |
| 334 | 337 |
| 335 You may adjust this limit by running: | 338 You may adjust this limit by running: |
| 336 git config %s <new_limit> | 339 git config %s <new_limit> |
| 337 """ % (num, limit, key)) | 340 """, num, limit, key) |
| 338 sys.exit(1) | |
| 339 | 341 |
| 340 for line in raw_branches: | 342 for line in raw_branches: |
| 341 if line.startswith(NO_BRANCH): | 343 if line.startswith(NO_BRANCH): |
| 342 continue | 344 continue |
| 343 yield line.split()[-1] | 345 yield line.split()[-1] |
| 344 | 346 |
| 345 | 347 |
| 346 def config(option, default=None): | 348 def get_config(option, default=None): |
|
iannucci
2016/06/16 00:24:20
would you mind terribly doing the config and die r
agable
2016/06/16 12:47:32
Done: https://codereview.chromium.org/2075603002
| |
| 347 try: | 349 try: |
| 348 return run('config', '--get', option) or default | 350 return run('config', '--get', option) or default |
| 349 except subprocess2.CalledProcessError: | 351 except subprocess2.CalledProcessError: |
| 350 return default | 352 return default |
| 351 | 353 |
| 352 | 354 |
| 353 def config_list(option): | 355 def get_config_int(option, default=0): |
| 356 assert isinstance(default, int) | |
| 357 try: | |
| 358 return int(get_config(option, default)) | |
| 359 except ValueError: | |
| 360 return default | |
| 361 | |
| 362 | |
| 363 def get_config_list(option): | |
| 354 try: | 364 try: |
| 355 return run('config', '--get-all', option).split() | 365 return run('config', '--get-all', option).split() |
| 356 except subprocess2.CalledProcessError: | 366 except subprocess2.CalledProcessError: |
| 357 return [] | 367 return [] |
| 358 | 368 |
| 359 | 369 |
| 360 def current_branch(): | 370 def current_branch(): |
| 361 try: | 371 try: |
| 362 return run('rev-parse', '--abbrev-ref', 'HEAD') | 372 return run('rev-parse', '--abbrev-ref', 'HEAD') |
| 363 except subprocess2.CalledProcessError: | 373 except subprocess2.CalledProcessError: |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 375 pass | 385 pass |
| 376 | 386 |
| 377 | 387 |
| 378 def diff(oldrev, newrev, *args): | 388 def diff(oldrev, newrev, *args): |
| 379 return run('diff', oldrev, newrev, *args) | 389 return run('diff', oldrev, newrev, *args) |
| 380 | 390 |
| 381 | 391 |
| 382 def freeze(): | 392 def freeze(): |
| 383 took_action = False | 393 took_action = False |
| 384 | 394 |
| 395 stat = status() | |
| 396 if any(is_unmerged(s) for s in stat.itervalues()): | |
| 397 die("Cannot freeze unmerged changes!") | |
| 398 | |
| 399 key = 'depot-tools.freeze-size-limit' | |
| 400 MB = 2**20 | |
| 401 limit_mb = get_config_int(key, 100) | |
| 402 if limit_mb > 0: | |
| 403 untracked_size = sum( | |
|
iannucci
2016/06/16 00:24:20
I would consider doing this as a proper loop: if t
agable
2016/06/16 15:26:55
Done.
| |
| 404 os.stat(f).st_size | |
| 405 for f, s in stat.iteritems() if s.lstat == '?' | |
| 406 ) / MB | |
| 407 if untracked_size > limit_mb: | |
| 408 die("""\ | |
| 409 You appear to have too much untracked+unignored data in your git | |
| 410 checkout: %d/%dMB. | |
| 411 | |
| 412 Run `git status` to see what it is. | |
| 413 | |
| 414 In addition to making many git commands slower, this will prevent | |
| 415 depot_tools from freezing your in-progress changes. | |
| 416 | |
| 417 You should add untracked data that you want to ignore to your repo's | |
| 418 .git/info/excludes | |
| 419 file. See `git help ignore` for the format of this file. | |
| 420 | |
| 421 If this data is indended as part of your commit, you may adjust the | |
| 422 freeze limit by running: | |
| 423 git config %s <new_limit> | |
| 424 Where <new_limit> is an integer threshold in megabytes.""", | |
| 425 untracked_size, limit_mb, key) | |
| 426 | |
| 385 try: | 427 try: |
| 386 run('commit', '--no-verify', '-m', FREEZE + '.indexed') | 428 run('commit', '--no-verify', '-m', FREEZE + '.indexed') |
| 387 took_action = True | 429 took_action = True |
| 388 except subprocess2.CalledProcessError: | 430 except subprocess2.CalledProcessError: |
| 389 pass | 431 pass |
| 390 | 432 |
| 391 try: | 433 try: |
| 392 run('add', '-A') | 434 run('add', '-A') |
| 393 run('commit', '--no-verify', '-m', FREEZE + '.unindexed') | 435 run('commit', '--no-verify', '-m', FREEZE + '.unindexed') |
| 394 took_action = True | 436 took_action = True |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 484 ret = run('hash-object', '-t', kind, '-w', '--stdin', stdin=f) | 526 ret = run('hash-object', '-t', kind, '-w', '--stdin', stdin=f) |
| 485 f.close() | 527 f.close() |
| 486 return ret | 528 return ret |
| 487 | 529 |
| 488 | 530 |
| 489 def is_dormant(branch): | 531 def is_dormant(branch): |
| 490 # TODO(iannucci): Do an oldness check? | 532 # TODO(iannucci): Do an oldness check? |
| 491 return branch_config(branch, 'dormant', 'false') != 'false' | 533 return branch_config(branch, 'dormant', 'false') != 'false' |
| 492 | 534 |
| 493 | 535 |
| 536 def is_unmerged(stat_value): | |
| 537 return ( | |
| 538 'U' in (stat_value.lstat, stat_value.rstat) or | |
| 539 ((stat_value.lstat == stat_value.rstat) and stat_value.lstat in 'AD') | |
| 540 ) | |
| 541 | |
| 542 | |
| 494 def manual_merge_base(branch, base, parent): | 543 def manual_merge_base(branch, base, parent): |
| 495 set_branch_config(branch, 'base', base) | 544 set_branch_config(branch, 'base', base) |
| 496 set_branch_config(branch, 'base-upstream', parent) | 545 set_branch_config(branch, 'base-upstream', parent) |
| 497 | 546 |
| 498 | 547 |
| 499 def mktree(treedict): | 548 def mktree(treedict): |
| 500 """Makes a git tree object and returns its hash. | 549 """Makes a git tree object and returns its hash. |
| 501 | 550 |
| 502 See |tree()| for the values of mode, type, and ref. | 551 See |tree()| for the values of mode, type, and ref. |
| 503 | 552 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 560 del_branch_config(branch, 'base') | 609 del_branch_config(branch, 'base') |
| 561 del_branch_config(branch, 'base-upstream') | 610 del_branch_config(branch, 'base-upstream') |
| 562 | 611 |
| 563 | 612 |
| 564 def repo_root(): | 613 def repo_root(): |
| 565 """Returns the absolute path to the repository root.""" | 614 """Returns the absolute path to the repository root.""" |
| 566 return run('rev-parse', '--show-toplevel') | 615 return run('rev-parse', '--show-toplevel') |
| 567 | 616 |
| 568 | 617 |
| 569 def root(): | 618 def root(): |
| 570 return config('depot-tools.upstream', 'origin/master') | 619 return get_config('depot-tools.upstream', 'origin/master') |
| 571 | 620 |
| 572 | 621 |
| 573 @contextlib.contextmanager | 622 @contextlib.contextmanager |
| 574 def less(): # pragma: no cover | 623 def less(): # pragma: no cover |
| 575 """Runs 'less' as context manager yielding its stdin as a PIPE. | 624 """Runs 'less' as context manager yielding its stdin as a PIPE. |
| 576 | 625 |
| 577 Automatically checks if sys.stdout is a non-TTY stream. If so, it avoids | 626 Automatically checks if sys.stdout is a non-TTY stream. If so, it avoids |
| 578 running less and just yields sys.stdout. | 627 running less and just yields sys.stdout. |
| 579 """ | 628 """ |
| 580 if not setup_color.IS_TTY: | 629 if not setup_color.IS_TTY: |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 693 if dirty: | 742 if dirty: |
| 694 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd | 743 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd |
| 695 print 'Uncommitted files: (git diff-index --name-status HEAD)' | 744 print 'Uncommitted files: (git diff-index --name-status HEAD)' |
| 696 print dirty[:4096] | 745 print dirty[:4096] |
| 697 if len(dirty) > 4096: # pragma: no cover | 746 if len(dirty) > 4096: # pragma: no cover |
| 698 print '... (run "git diff-index --name-status HEAD" to see full output).' | 747 print '... (run "git diff-index --name-status HEAD" to see full output).' |
| 699 return True | 748 return True |
| 700 return False | 749 return False |
| 701 | 750 |
| 702 | 751 |
| 752 def status(): | |
| 753 """Returns a parsed version of git-status. | |
| 754 | |
| 755 Returns a dictionary of {current_name: (lstat, rstat, src)} where: | |
| 756 * lstat is the left status code letter from git-status | |
| 757 * rstat is the left status code letter from git-status | |
| 758 * src is the current name of the file, or the original name of the file | |
| 759 if lstat == 'R' | |
| 760 """ | |
| 761 stat_entry = collections.namedtuple('stat_entry', 'lstat rstat src') | |
| 762 | |
| 763 def tokenizer(stream): | |
| 764 acc = StringIO() | |
| 765 c = None | |
| 766 while c != '': | |
| 767 c = stream.read(1) | |
| 768 if c in (None, '', '\0'): | |
| 769 s = acc.getvalue() | |
| 770 acc = StringIO() | |
| 771 if s: | |
| 772 yield s | |
| 773 else: | |
| 774 acc.write(c) | |
| 775 | |
| 776 def parser(tokens): | |
| 777 END = object() | |
| 778 tok = lambda: next(tokens, END) | |
| 779 while True: | |
| 780 status_dest = tok() | |
| 781 if status_dest is END: | |
| 782 return | |
| 783 stat, dest = status_dest[:2], status_dest[3:] | |
| 784 lstat, rstat = stat | |
| 785 if lstat == 'R': | |
| 786 src = tok() | |
| 787 assert src is not END | |
| 788 else: | |
| 789 src = dest | |
| 790 yield (dest, stat_entry(lstat, rstat, src)) | |
| 791 | |
| 792 return dict(parser(tokenizer(run_stream('status', '-z', bufsize=-1)))) | |
|
iannucci
2016/06/16 00:24:20
why dict-ify this if you're just going to stream i
agable
2016/06/16 15:26:55
Done. Resulted in some more efficient code in stat
| |
| 793 | |
| 794 | |
| 703 def squash_current_branch(header=None, merge_base=None): | 795 def squash_current_branch(header=None, merge_base=None): |
| 704 header = header or 'git squash commit.' | 796 header = header or 'git squash commit.' |
| 705 merge_base = merge_base or get_or_create_merge_base(current_branch()) | 797 merge_base = merge_base or get_or_create_merge_base(current_branch()) |
| 706 log_msg = header + '\n' | 798 log_msg = header + '\n' |
| 707 if log_msg: | 799 if log_msg: |
| 708 log_msg += '\n' | 800 log_msg += '\n' |
| 709 log_msg += run('log', '--reverse', '--format=%H%n%B', '%s..HEAD' % merge_base) | 801 log_msg += run('log', '--reverse', '--format=%H%n%B', '%s..HEAD' % merge_base) |
| 710 run('reset', '--soft', merge_base) | 802 run('reset', '--soft', merge_base) |
| 711 | 803 |
| 712 if not get_dirty_files(): | 804 if not get_dirty_files(): |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 904 ['HEAD']) | 996 ['HEAD']) |
| 905 | 997 |
| 906 | 998 |
| 907 def clone_file(repository, new_workdir, link, operation): | 999 def clone_file(repository, new_workdir, link, operation): |
| 908 if not os.path.exists(os.path.join(repository, link)): | 1000 if not os.path.exists(os.path.join(repository, link)): |
| 909 return | 1001 return |
| 910 link_dir = os.path.dirname(os.path.join(new_workdir, link)) | 1002 link_dir = os.path.dirname(os.path.join(new_workdir, link)) |
| 911 if not os.path.exists(link_dir): | 1003 if not os.path.exists(link_dir): |
| 912 os.makedirs(link_dir) | 1004 os.makedirs(link_dir) |
| 913 operation(os.path.join(repository, link), os.path.join(new_workdir, link)) | 1005 operation(os.path.join(repository, link), os.path.join(new_workdir, link)) |
| OLD | NEW |