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 12 matching lines...) Expand all Loading... | |
23 import os | 23 import os |
24 import re | 24 import re |
25 import signal | 25 import signal |
26 import sys | 26 import sys |
27 import tempfile | 27 import tempfile |
28 import textwrap | 28 import textwrap |
29 import threading | 29 import threading |
30 | 30 |
31 import subprocess2 | 31 import subprocess2 |
32 | 32 |
33 from cStringIO import StringIO | |
34 | |
33 | 35 |
34 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git' | 36 GIT_EXE = 'git.bat' if sys.platform.startswith('win') else 'git' |
35 TEST_MODE = False | 37 TEST_MODE = False |
36 | 38 |
37 FREEZE = 'FREEZE' | 39 FREEZE = 'FREEZE' |
38 FREEZE_SECTIONS = { | 40 FREEZE_SECTIONS = { |
39 'indexed': 'soft', | 41 'indexed': 'soft', |
40 'unindexed': 'mixed' | 42 'unindexed': 'mixed' |
41 } | 43 } |
42 FREEZE_MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(FREEZE_SECTIONS))) | 44 FREEZE_MATCHER = re.compile(r'%s.(%s)' % (FREEZE, '|'.join(FREEZE_SECTIONS))) |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
216 how many times the decorated |function| is called.""" | 218 how many times the decorated |function| is called.""" |
217 def _inner_gen(): | 219 def _inner_gen(): |
218 yield function() | 220 yield function() |
219 while True: | 221 while True: |
220 yield | 222 yield |
221 return _inner_gen().next | 223 return _inner_gen().next |
222 | 224 |
223 | 225 |
224 ## Git functions | 226 ## Git functions |
225 | 227 |
228 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.
| |
229 if cond: | |
230 print >> sys.stderr, textwrap.dedent(message % args) | |
231 sys.exit(1) | |
232 | |
226 | 233 |
227 def branch_config(branch, option, default=None): | 234 def branch_config(branch, option, default=None): |
228 return config('branch.%s.%s' % (branch, option), default=default) | 235 return config('branch.%s.%s' % (branch, option), default=default) |
229 | 236 |
230 | 237 |
231 def branch_config_map(option): | 238 def branch_config_map(option): |
232 """Return {branch: <|option| value>} for all branches.""" | 239 """Return {branch: <|option| value>} for all branches.""" |
233 try: | 240 try: |
234 reg = re.compile(r'^branch\.(.*)\.%s$' % option) | 241 reg = re.compile(r'^branch\.(.*)\.%s$' % option) |
235 lines = run('config', '--get-regexp', reg.pattern).splitlines() | 242 lines = run('config', '--get-regexp', reg.pattern).splitlines() |
236 return {reg.match(k).group(1): v for k, v in (l.split() for l in lines)} | 243 return {reg.match(k).group(1): v for k, v in (l.split() for l in lines)} |
237 except subprocess2.CalledProcessError: | 244 except subprocess2.CalledProcessError: |
238 return {} | 245 return {} |
239 | 246 |
240 | 247 |
241 def branches(*args): | 248 def branches(*args): |
242 NO_BRANCH = ('* (no branch', '* (detached from ') | 249 NO_BRANCH = ('* (no branch', '* (detached from ') |
243 | 250 |
244 key = 'depot-tools.branch-limit' | 251 key = 'depot-tools.branch-limit' |
245 limit = 20 | 252 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.
| |
246 try: | |
247 limit = int(config(key, limit)) | |
248 except ValueError: | |
249 pass | |
250 | 253 |
251 raw_branches = run('branch', *args).splitlines() | 254 raw_branches = run('branch', *args).splitlines() |
252 | 255 |
253 num = len(raw_branches) | 256 num = len(raw_branches) |
254 if num > limit: | 257 |
255 print >> sys.stderr, textwrap.dedent("""\ | 258 die_if(num > limit, """\ |
256 Your git repo has too many branches (%d/%d) for this tool to work well. | 259 Your git repo has too many branches (%d/%d) for this tool to work well. |
257 | 260 |
258 You may adjust this limit by running: | 261 You may adjust this limit by running: |
259 git config %s <new_limit> | 262 git config %s <new_limit> |
260 """ % (num, limit, key)) | 263 """, num, limit, key) |
261 sys.exit(1) | |
262 | 264 |
263 for line in raw_branches: | 265 for line in raw_branches: |
264 if line.startswith(NO_BRANCH): | 266 if line.startswith(NO_BRANCH): |
265 continue | 267 continue |
266 yield line.split()[-1] | 268 yield line.split()[-1] |
267 | 269 |
268 | 270 |
269 def run_with_retcode(*cmd, **kwargs): | 271 def run_with_retcode(*cmd, **kwargs): |
270 """Run a command but only return the status code.""" | 272 """Run a command but only return the status code.""" |
271 try: | 273 try: |
272 run(*cmd, **kwargs) | 274 run(*cmd, **kwargs) |
273 return 0 | 275 return 0 |
274 except subprocess2.CalledProcessError as cpe: | 276 except subprocess2.CalledProcessError as cpe: |
275 return cpe.returncode | 277 return cpe.returncode |
276 | 278 |
277 | 279 |
278 def config(option, default=None): | 280 def config(option, default=None): |
agable
2014/06/11 21:30:17
I find this API weird. "git config key value" *set
iannucci
2014/06/28 18:08:55
Done.
| |
279 try: | 281 try: |
280 return run('config', '--get', option) or default | 282 return run('config', '--get', option) or default |
281 except subprocess2.CalledProcessError: | 283 except subprocess2.CalledProcessError: |
282 return default | 284 return default |
283 | 285 |
284 | 286 |
287 def config_int(option, default=0): | |
288 assert isinstance(default, int) | |
289 try: | |
290 return int(config(option, default)) | |
291 except ValueError: | |
292 return default | |
293 | |
294 | |
285 def config_list(option): | 295 def config_list(option): |
286 try: | 296 try: |
287 return run('config', '--get-all', option).split() | 297 return run('config', '--get-all', option).split() |
288 except subprocess2.CalledProcessError: | 298 except subprocess2.CalledProcessError: |
289 return [] | 299 return [] |
290 | 300 |
291 | 301 |
292 def current_branch(): | 302 def current_branch(): |
293 try: | 303 try: |
294 return run('rev-parse', '--abbrev-ref', 'HEAD') | 304 return run('rev-parse', '--abbrev-ref', 'HEAD') |
295 except subprocess2.CalledProcessError: | 305 except subprocess2.CalledProcessError: |
296 return None | 306 return None |
297 | 307 |
298 | 308 |
299 def del_branch_config(branch, option, scope='local'): | 309 def del_branch_config(branch, option, scope='local'): |
300 del_config('branch.%s.%s' % (branch, option), scope=scope) | 310 del_config('branch.%s.%s' % (branch, option), scope=scope) |
301 | 311 |
302 | 312 |
303 def del_config(option, scope='local'): | 313 def del_config(option, scope='local'): |
304 try: | 314 try: |
305 run('config', '--' + scope, '--unset', option) | 315 run('config', '--' + scope, '--unset', option) |
306 except subprocess2.CalledProcessError: | 316 except subprocess2.CalledProcessError: |
307 pass | 317 pass |
308 | 318 |
309 | 319 |
310 def freeze(): | 320 def freeze(): |
311 took_action = False | 321 took_action = False |
312 | 322 |
323 stat = status() | |
324 die_if(any('U' in (s.lstat, s.rstat) for s in stat.itervalues()), | |
325 "Cannot freeze unmerged changes!") | |
326 | |
327 key = 'depot-tools.freeze-size-limit' | |
328 MB = 2**20 | |
329 limit_mb = config_int(key, 100) | |
330 if limit_mb > 0: | |
331 untracked_size = sum( | |
332 os.stat(f).st_size | |
333 for f, s in stat.iteritems() if s.lstat == '?' | |
334 ) / MB | |
335 die_if(untracked_size > limit_mb, """\ | |
336 You have too much untracked+unignored data in your git checkout: %d/%dMB. | |
337 Run `git status` to see what it is. | |
338 | |
339 In addition to making many git commands slower, this will prevent | |
340 depot_tools from freezing your in-progress changes. | |
341 | |
342 You should add untracked data that you want to ignore to your repo's | |
343 .git/info/excludes | |
344 file. See `git help ignore` for the format of this file. | |
345 | |
346 If this data is indended as part of your commit, you may adjust the freeze | |
347 limit by running: | |
348 git config %s <new_limit> | |
349 Where <new_limit> is an integer threshold in megabytes.""", | |
350 untracked_size, limit_mb, key) | |
351 | |
313 try: | 352 try: |
314 run('commit', '-m', FREEZE + '.indexed') | 353 run('commit', '-m', FREEZE + '.indexed') |
315 took_action = True | 354 took_action = True |
316 except subprocess2.CalledProcessError: | 355 except subprocess2.CalledProcessError: |
317 pass | 356 pass |
318 | 357 |
319 try: | 358 try: |
320 run('add', '-A') | 359 run('add', '-A') |
321 run('commit', '-m', FREEZE + '.unindexed') | 360 run('commit', '-m', FREEZE + '.unindexed') |
322 took_action = True | 361 took_action = True |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
537 return ret, err | 576 return ret, err |
538 | 577 |
539 | 578 |
540 def set_branch_config(branch, option, value, scope='local'): | 579 def set_branch_config(branch, option, value, scope='local'): |
541 set_config('branch.%s.%s' % (branch, option), value, scope=scope) | 580 set_config('branch.%s.%s' % (branch, option), value, scope=scope) |
542 | 581 |
543 | 582 |
544 def set_config(option, value, scope='local'): | 583 def set_config(option, value, scope='local'): |
545 run('config', '--' + scope, option, value) | 584 run('config', '--' + scope, option, value) |
546 | 585 |
586 | |
587 def status(): | |
588 """Returns a parsed version of git-status. | |
589 | |
590 Returns a dictionary of {current_name: (lstat, rstat, src)} where: | |
591 * lstat is the left status code letter from git-status | |
592 * rstat is the left status code letter from git-status | |
593 * 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.
| |
594 the current_name. | |
595 """ | |
596 stat_entry = collections.namedtuple('stat_entry', 'lstat rstat src') | |
597 | |
598 def tokenizer(stream): | |
599 acc = StringIO() | |
600 c = None | |
601 while c != '': | |
602 c = stream.read(1) | |
603 if c in (None, '', '\0'): | |
604 s = acc.getvalue() | |
605 acc = StringIO() | |
606 if s: | |
607 yield s | |
608 else: | |
609 acc.write(c) | |
610 | |
611 def parser(tokens): | |
612 END = object() | |
613 tok = lambda: next(tokens, END) | |
614 while True: | |
615 status_dest = tok() | |
616 if status_dest is END: | |
617 return | |
618 stat, dest = status_dest[:2], status_dest[3:] | |
619 lstat, rstat = stat | |
620 if lstat == 'R': | |
621 src = tok() | |
622 assert src is not END | |
623 else: | |
624 src = dest | |
625 yield (dest, stat_entry(lstat, rstat, src)) | |
626 | |
627 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
| |
628 | |
629 | |
547 def squash_current_branch(header=None, merge_base=None): | 630 def squash_current_branch(header=None, merge_base=None): |
548 header = header or 'git squash commit.' | 631 header = header or 'git squash commit.' |
549 merge_base = merge_base or get_or_create_merge_base(current_branch()) | 632 merge_base = merge_base or get_or_create_merge_base(current_branch()) |
550 log_msg = header + '\n' | 633 log_msg = header + '\n' |
551 if log_msg: | 634 if log_msg: |
552 log_msg += '\n' | 635 log_msg += '\n' |
553 log_msg += run('log', '--reverse', '--format=%H%n%B', '%s..HEAD' % merge_base) | 636 log_msg += run('log', '--reverse', '--format=%H%n%B', '%s..HEAD' % merge_base) |
554 run('reset', '--soft', merge_base) | 637 run('reset', '--soft', merge_base) |
555 run('commit', '-a', '-F', '-', indata=log_msg) | 638 run('commit', '-a', '-F', '-', indata=log_msg) |
556 | 639 |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
659 return None | 742 return None |
660 return ret | 743 return ret |
661 | 744 |
662 | 745 |
663 def upstream(branch): | 746 def upstream(branch): |
664 try: | 747 try: |
665 return run('rev-parse', '--abbrev-ref', '--symbolic-full-name', | 748 return run('rev-parse', '--abbrev-ref', '--symbolic-full-name', |
666 branch+'@{upstream}') | 749 branch+'@{upstream}') |
667 except subprocess2.CalledProcessError: | 750 except subprocess2.CalledProcessError: |
668 return None | 751 return None |
OLD | NEW |