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()) |