comparison mercurial/cmdutil.py @ 33548:4cd4344a53c4

status: add a flag to terse the output (issue4119) This adds an experimental flag -t/--terse which will terse the output. The terse flag will respect other flags which filters the output. The flag takes a string whose value can be a subsequence of "marduic" (the order does not matter here.) Ignored files are not considered while tersing unless -i flag is passed or 'i' is there is the terse flag value. The flag is experimental for testing as there may be cases which will produce strange results with the flag. We can set the terse on by default by simply passing 'u' to the cmdutil.tersestatus(). This patch also adds a test file with tests covering the new feature.
author Pulkit Goyal <7895pulkit@gmail.com>
date Sat, 17 Jun 2017 20:10:22 +0530
parents a3acacbd0ff3
children 5ac845ca059a 6f4bc9688ca9
comparison
equal deleted inserted replaced
33547:a6af8560494e 33548:4cd4344a53c4
397 def recordinwlock(ui, repo, message, match, opts): 397 def recordinwlock(ui, repo, message, match, opts):
398 with repo.wlock(): 398 with repo.wlock():
399 return recordfunc(ui, repo, message, match, opts) 399 return recordfunc(ui, repo, message, match, opts)
400 400
401 return commit(ui, repo, recordinwlock, pats, opts) 401 return commit(ui, repo, recordinwlock, pats, opts)
402
403 def tersestatus(root, statlist, status, ignorefn, ignore):
404 """
405 Returns a list of statuses with directory collapsed if all the files in the
406 directory has the same status.
407 """
408
409 def numfiles(dirname):
410 """
411 Calculates the number of tracked files in a given directory which also
412 includes files which were removed or deleted. Considers ignored files
413 if ignore argument is True or 'i' is present in status argument.
414 """
415 if lencache.get(dirname):
416 return lencache[dirname]
417 if 'i' in status or ignore:
418 def match(localpath):
419 absolutepath = os.path.join(root, localpath)
420 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
421 return True
422 return False
423 else:
424 def match(localpath):
425 # there can be directory whose all the files are ignored and
426 # hence the drectory should also be ignored while counting
427 # number of files or subdirs in it's parent directory. This
428 # checks the same.
429 # XXX: We need a better logic here.
430 if os.path.isdir(os.path.join(root, localpath)):
431 return isignoreddir(localpath)
432 else:
433 # XXX: there can be files which have the ignored pattern but
434 # are not ignored. That leads to bug in counting number of
435 # tracked files in the directory.
436 return ignorefn(localpath)
437 lendir = 0
438 abspath = os.path.join(root, dirname)
439 # There might be cases when a directory does not exists as the whole
440 # directory can be removed and/or deleted.
441 try:
442 for f in os.listdir(abspath):
443 localpath = os.path.join(dirname, f)
444 if not match(localpath):
445 lendir += 1
446 except OSError:
447 pass
448 lendir += len(absentdir.get(dirname, []))
449 lencache[dirname] = lendir
450 return lendir
451
452 def isemptydir(abspath):
453 """
454 Check whether a directory is empty or not, i.e. there is no files in the
455 directory and all its subdirectories.
456 """
457 for f in os.listdir(abspath):
458 fullpath = os.path.join(abspath, f)
459 if os.path.isdir(fullpath):
460 # recursion here
461 ret = isemptydir(fullpath)
462 if not ret:
463 return False
464 else:
465 return False
466 return True
467
468 def isignoreddir(localpath):
469 """
470 This function checks whether the directory contains only ignored files
471 and hence should the directory be considered ignored. Returns True, if
472 that should be ignored otherwise False.
473 """
474 dirpath = os.path.join(root, localpath)
475 for f in os.listdir(dirpath):
476 filepath = os.path.join(dirpath, f)
477 if os.path.isdir(filepath):
478 # recursion here
479 ret = isignoreddir(os.path.join(localpath, f))
480 if not ret:
481 return False
482 else:
483 if not ignorefn(os.path.join(localpath, f)):
484 return False
485 return True
486
487 def absentones(removedfiles, missingfiles):
488 """
489 Returns a dictionary of directories with files in it which are either
490 removed or missing (deleted) in them.
491 """
492 absentdir = {}
493 absentfiles = removedfiles + missingfiles
494 while absentfiles:
495 f = absentfiles.pop()
496 par = os.path.dirname(f)
497 if par == '':
498 continue
499 # we need to store files rather than number of files as some files
500 # or subdirectories in a directory can be counted twice. This is
501 # also we have used sets here.
502 try:
503 absentdir[par].add(f)
504 except KeyError:
505 absentdir[par] = set([f])
506 absentfiles.append(par)
507 return absentdir
508
509 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
510 # get a dictonary of directories and files which are missing as os.listdir()
511 # won't be able to list them.
512 absentdir = absentones(statlist[2], statlist[3])
513 finalrs = [[]] * len(indexes)
514 didsomethingchanged = False
515 # dictionary to store number of files and subdir in a directory so that we
516 # don't compute that again.
517 lencache = {}
518
519 for st in pycompat.bytestr(status):
520
521 try:
522 ind = indexes[st]
523 except KeyError:
524 # TODO: Need a better error message here
525 raise error.Abort("'%s' not recognized" % st)
526
527 sfiles = statlist[ind]
528 if not sfiles:
529 continue
530 pardict = {}
531 for a in sfiles:
532 par = os.path.dirname(a)
533 pardict.setdefault(par, []).append(a)
534
535 rs = []
536 newls = []
537 for par, files in pardict.iteritems():
538 lenpar = numfiles(par)
539 if lenpar == len(files):
540 newls.append(par)
541
542 if not newls:
543 continue
544
545 while newls:
546 newel = newls.pop()
547 if newel == '':
548 continue
549 parn = os.path.dirname(newel)
550 pardict[newel] = []
551 # Adding pycompat.ossep as newel is a directory.
552 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
553 lenpar = numfiles(parn)
554 if lenpar == len(pardict[parn]):
555 newls.append(parn)
556
557 # dict.values() for Py3 compatibility
558 for files in pardict.values():
559 rs.extend(files)
560
561 rs.sort()
562 finalrs[ind] = rs
563 didsomethingchanged = True
564
565 # If nothing is changed, make sure the order of files is preserved.
566 if not didsomethingchanged:
567 return statlist
568
569 for x in xrange(len(indexes)):
570 if not finalrs[x]:
571 finalrs[x] = statlist[x]
572
573 return finalrs
402 574
403 def findpossible(cmd, table, strict=False): 575 def findpossible(cmd, table, strict=False):
404 """ 576 """
405 Return cmd -> (aliases, command table entry) 577 Return cmd -> (aliases, command table entry)
406 for each matching command. 578 for each matching command.