| Index: git_common.py
|
| diff --git a/git_common.py b/git_common.py
|
| index 298ecc4ff3a8c9ee8f9263130b3751167b5eb5fa..55fb5c8b59af68e0f7a3041fbd01f6d640116e90 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,9 +225,13 @@ def once(function):
|
|
|
| ## Git functions
|
|
|
| +def die(message, *args):
|
| + 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)
|
| + return get_config('branch.%s.%s' % (branch, option), default=default)
|
|
|
|
|
| def branch_config_map(option):
|
| @@ -242,23 +248,19 @@ 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 = get_config_int(key, 20)
|
|
|
| raw_branches = run('branch', *args).splitlines()
|
|
|
| num = len(raw_branches)
|
| +
|
| if num > limit:
|
| - print >> sys.stderr, textwrap.dedent("""\
|
| - Your git repo has too many branches (%d/%d) for this tool to work well.
|
| + die("""\
|
| + Your git repo has too many branches (%d/%d) for this tool to work well.
|
|
|
| - You may adjust this limit by running:
|
| + 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):
|
| @@ -275,14 +277,22 @@ def run_with_retcode(*cmd, **kwargs):
|
| return cpe.returncode
|
|
|
|
|
| -def config(option, default=None):
|
| +def get_config(option, default=None):
|
| try:
|
| return run('config', '--get', option) or default
|
| except subprocess2.CalledProcessError:
|
| return default
|
|
|
|
|
| -def config_list(option):
|
| +def get_config_int(option, default=0):
|
| + assert isinstance(default, int)
|
| + try:
|
| + return int(get_config(option, default))
|
| + except ValueError:
|
| + return default
|
| +
|
| +
|
| +def get_config_list(option):
|
| try:
|
| return run('config', '--get-all', option).split()
|
| except subprocess2.CalledProcessError:
|
| @@ -310,6 +320,38 @@ def del_config(option, scope='local'):
|
| def freeze():
|
| took_action = False
|
|
|
| + stat = status()
|
| + if any(is_unmerged(s) for s in stat.itervalues()):
|
| + die("Cannot freeze unmerged changes!")
|
| +
|
| + key = 'depot-tools.freeze-size-limit'
|
| + MB = 2**20
|
| + limit_mb = get_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
|
| + if untracked_size > limit_mb:
|
| + die("""\
|
| + You appear to 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
|
| @@ -416,6 +458,13 @@ def is_dormant(branch):
|
| return branch_config(branch, 'dormant', 'false') != 'false'
|
|
|
|
|
| +def is_unmerged(stat_value):
|
| + return (
|
| + 'U' in (stat_value.lstat, stat_value.rstat) or
|
| + ((stat_value.lstat == stat_value.rstat) and stat_value.lstat in 'AD')
|
| + )
|
| +
|
| +
|
| def manual_merge_base(branch, base, parent):
|
| set_branch_config(branch, 'base', base)
|
| set_branch_config(branch, 'base-upstream', parent)
|
| @@ -487,7 +536,7 @@ def remove_merge_base(branch):
|
|
|
|
|
| def root():
|
| - return config('depot-tools.upstream', 'origin/master')
|
| + return get_config('depot-tools.upstream', 'origin/master')
|
|
|
|
|
| def run(*cmd, **kwargs):
|
| @@ -544,6 +593,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 current name of the file, or the original name of the file
|
| + if lstat == 'R'
|
| + """
|
| + 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))))
|
| +
|
| +
|
| 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())
|
|
|