Chromium Code Reviews| Index: git_common.py |
| diff --git a/git_common.py b/git_common.py |
| index 298ecc4ff3a8c9ee8f9263130b3751167b5eb5fa..f813378f868c80d9ce4a1d209fecd8381b7f7c2b 100644 |
| --- a/git_common.py |
| +++ b/git_common.py |
| @@ -30,6 +30,8 @@ import threading |
| import subprocess2 |
| +from cStringIO import StringIO |
| + |
| GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git' |
| TEST_MODE = False |
| @@ -223,6 +225,11 @@ def once(function): |
| ## Git functions |
| +def die_if(cond, message, *args): |
|
agable
2014/06/11 21:30:16
I'd prefer just die(message, *args), and leave the
iannucci
2014/06/28 18:08:55
Done.
|
| + if cond: |
| + print >> sys.stderr, textwrap.dedent(message % args) |
| + sys.exit(1) |
| + |
| def branch_config(branch, option, default=None): |
| return config('branch.%s.%s' % (branch, option), default=default) |
| @@ -242,23 +249,18 @@ def branches(*args): |
| NO_BRANCH = ('* (no branch', '* (detached from ') |
| key = 'depot-tools.branch-limit' |
| - limit = 20 |
| - try: |
| - limit = int(config(key, limit)) |
| - except ValueError: |
| - pass |
| + limit = config_int(key, 20) |
|
agable
2014/06/11 21:30:16
See comment below. This is super weird -- it looks
iannucci
2014/06/28 18:08:55
Done.
|
| raw_branches = run('branch', *args).splitlines() |
| num = len(raw_branches) |
| - if num > limit: |
| - print >> sys.stderr, textwrap.dedent("""\ |
| + |
| + die_if(num > limit, """\ |
| Your git repo has too many branches (%d/%d) for this tool to work well. |
| You may adjust this limit by running: |
| git config %s <new_limit> |
| - """ % (num, limit, key)) |
| - sys.exit(1) |
| + """, num, limit, key) |
| for line in raw_branches: |
| if line.startswith(NO_BRANCH): |
| @@ -282,6 +284,14 @@ def config(option, default=None): |
| return default |
| +def config_int(option, default=0): |
| + assert isinstance(default, int) |
| + try: |
| + return int(config(option, default)) |
| + except ValueError: |
| + return default |
| + |
| + |
| def config_list(option): |
| try: |
| return run('config', '--get-all', option).split() |
| @@ -310,6 +320,35 @@ def del_config(option, scope='local'): |
| def freeze(): |
| took_action = False |
| + stat = status() |
| + die_if(any('U' in (s.lstat, s.rstat) for s in stat.itervalues()), |
| + "Cannot freeze unmerged changes!") |
| + |
| + key = 'depot-tools.freeze-size-limit' |
| + MB = 2**20 |
| + limit_mb = config_int(key, 100) |
| + if limit_mb > 0: |
| + untracked_size = sum( |
| + os.stat(f).st_size |
| + for f, s in stat.iteritems() if s.lstat == '?' |
| + ) / MB |
| + die_if(untracked_size > limit_mb, """\ |
| + You have too much untracked+unignored data in your git checkout: %d/%dMB. |
| + Run `git status` to see what it is. |
| + |
| + In addition to making many git commands slower, this will prevent |
| + depot_tools from freezing your in-progress changes. |
| + |
| + You should add untracked data that you want to ignore to your repo's |
| + .git/info/excludes |
| + file. See `git help ignore` for the format of this file. |
| + |
| + If this data is indended as part of your commit, you may adjust the freeze |
| + limit by running: |
| + git config %s <new_limit> |
| + Where <new_limit> is an integer threshold in megabytes.""", |
| + untracked_size, limit_mb, key) |
| + |
| try: |
| run('commit', '-m', FREEZE + '.indexed') |
| took_action = True |
| @@ -544,6 +583,50 @@ def set_branch_config(branch, option, value, scope='local'): |
| def set_config(option, value, scope='local'): |
| run('config', '--' + scope, option, value) |
| + |
| +def status(): |
| + """Returns a parsed version of git-status. |
| + |
| + Returns a dictionary of {current_name: (lstat, rstat, src)} where: |
| + * lstat is the left status code letter from git-status |
| + * rstat is the left status code letter from git-status |
| + * src is the 'original' name of the file if lstat == 'R', or |
|
agable
2014/06/11 21:30:16
src is the current name of the file, or the 'origi
iannucci
2014/06/28 18:08:55
Done.
|
| + the current_name. |
| + """ |
| + stat_entry = collections.namedtuple('stat_entry', 'lstat rstat src') |
| + |
| + def tokenizer(stream): |
| + acc = StringIO() |
| + c = None |
| + while c != '': |
| + c = stream.read(1) |
| + if c in (None, '', '\0'): |
| + s = acc.getvalue() |
| + acc = StringIO() |
| + if s: |
| + yield s |
| + else: |
| + acc.write(c) |
| + |
| + def parser(tokens): |
| + END = object() |
| + tok = lambda: next(tokens, END) |
| + while True: |
| + status_dest = tok() |
| + if status_dest is END: |
| + return |
| + stat, dest = status_dest[:2], status_dest[3:] |
| + lstat, rstat = stat |
| + if lstat == 'R': |
| + src = tok() |
| + assert src is not END |
| + else: |
| + src = dest |
| + yield (dest, stat_entry(lstat, rstat, src)) |
| + |
| + return dict(parser(tokenizer(run_stream('status', '-z', bufsize=-1)))) |
|
agable
2014/06/11 21:30:16
Why not use --porcelain and then iterate over read
iannucci
2014/06/28 18:08:55
Then I have to tokenzie quotes. --porcelain is way
|
| + |
| + |
| def squash_current_branch(header=None, merge_base=None): |
| header = header or 'git squash commit.' |
| merge_base = merge_base or get_or_create_merge_base(current_branch()) |