##// END OF EJS Templates
tersestatus: re-implement the functionality to terse the status...
Pulkit Goyal -
r34683:7e3001b7 default
parent child Browse files
Show More
@@ -402,177 +402,169 def dorecord(ui, repo, commitfunc, cmdsu
402
402
403 return commit(ui, repo, recordinwlock, pats, opts)
403 return commit(ui, repo, recordinwlock, pats, opts)
404
404
405 def tersestatus(root, statlist, status, ignorefn, ignore):
405
406 """
406 # extracted at module level as it's required each time a file will be added
407 Returns a list of statuses with directory collapsed if all the files in the
407 # to dirnode class object below
408 directory has the same status.
408 pathsep = pycompat.ossep
409
410 class dirnode(object):
409 """
411 """
410
412 represents a directory in user working copy
411 def numfiles(dirname):
413
412 """
414 stores information which is required for purpose of tersing the status
413 Calculates the number of tracked files in a given directory which also
415
414 includes files which were removed or deleted. Considers ignored files
416 path is the path to the directory
415 if ignore argument is True or 'i' is present in status argument.
417
418 statuses is a set of statuses of all files in this directory (this includes
419 all the files in all the subdirectories too)
420
421 files is a list of files which are direct child of this directory
422
423 subdirs is a dictionary of sub-directory name as the key and it's own
424 dirnode object as the value
416 """
425 """
417 if lencache.get(dirname):
426
418 return lencache[dirname]
427 def __init__(self, dirpath):
419 if 'i' in status or ignore:
428 self.path = dirpath
420 def match(localpath):
429 self.statuses = set([])
421 absolutepath = os.path.join(root, localpath)
430 self.files = []
422 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
431 self.subdirs = {}
423 return True
432
424 return False
433 def _addfileindir(self, filename, status):
425 else:
434 """ adds a file in this directory as the direct child """
426 def match(localpath):
435 self.files.append((filename, status))
427 # there can be directory whose all the files are ignored and
436
428 # hence the drectory should also be ignored while counting
437 def addfile(self, filename, status):
429 # number of files or subdirs in it's parent directory. This
438 """
430 # checks the same.
439 adds a file which is present in this directory to its direct parent
431 # XXX: We need a better logic here.
440 dirnode object
432 if os.path.isdir(os.path.join(root, localpath)):
441
433 return isignoreddir(localpath)
442 if the file is not direct child of this directory, we traverse to the
443 directory of which this file is a direct child of and add the file there
444 """
445
446 # the filename contains a path separator, it means it's not the direct
447 # child of this directory
448 if pathsep in filename:
449 subdir, filep = filename.split(pathsep, 1)
450
451 # does the dirnode object for subdir exists
452 if subdir not in self.subdirs:
453 subdirpath = os.path.join(self.path, subdir)
454 self.subdirs[subdir] = dirnode(subdirpath)
455
456 # try adding the file in subdir
457 self.subdirs[subdir].addfile(filep, status)
458
434 else:
459 else:
435 # XXX: there can be files which have the ignored pattern but
460 self._addfileindir(filename, status)
436 # are not ignored. That leads to bug in counting number of
461
437 # tracked files in the directory.
462 if status not in self.statuses:
438 return ignorefn(localpath)
463 self.statuses.add(status)
439 lendir = 0
464
440 abspath = os.path.join(root, dirname)
465 def _addfilestotersed(path, files, tersedict):
441 # There might be cases when a directory does not exists as the whole
466 """ adds files to the their respective status list in the final tersed list
442 # directory can be removed and/or deleted.
467
443 try:
468 path is the path of parent directory of the file
444 for f in os.listdir(abspath):
469 files is a list of tuple where each tuple is (filename, status)
445 localpath = os.path.join(dirname, f)
470 tersedict is a dictonary which contains each status abbreviation as key and
446 if not match(localpath):
471 list of files and tersed dirs in that status as value
447 lendir += 1
448 except OSError:
449 pass
450 lendir += len(absentdir.get(dirname, []))
451 lencache[dirname] = lendir
452 return lendir
453
454 def isemptydir(abspath):
455 """
456 Check whether a directory is empty or not, i.e. there is no files in the
457 directory and all its subdirectories.
458 """
459 for f in os.listdir(abspath):
460 fullpath = os.path.join(abspath, f)
461 if os.path.isdir(fullpath):
462 # recursion here
463 ret = isemptydir(fullpath)
464 if not ret:
465 return False
466 else:
467 return False
468 return True
469
470 def isignoreddir(localpath):
471 """Return True if `localpath` directory is ignored or contains only
472 ignored files and should hence be considered ignored.
473 """
474 dirpath = os.path.join(root, localpath)
475 if ignorefn(dirpath):
476 return True
477 for f in os.listdir(dirpath):
478 filepath = os.path.join(dirpath, f)
479 if os.path.isdir(filepath):
480 # recursion here
481 ret = isignoreddir(os.path.join(localpath, f))
482 if not ret:
483 return False
484 else:
485 if not ignorefn(os.path.join(localpath, f)):
486 return False
487 return True
488
489 def absentones(removedfiles, missingfiles):
490 """
472 """
491 Returns a dictionary of directories with files in it which are either
473 for f, st in files:
492 removed or missing (deleted) in them.
474 tersedict[st].append(os.path.join(path, f))
475
476 def _processtersestatus(subdir, tersedict, terseargs):
477 """a recursive function which process status for a certain directory.
478
479 subdir is an oject of dirnode class defined below. each object of dirnode
480 class has a set of statuses which files in that directory has. This ease our
481 check whether we can terse that directory or not.
482
483 tersedict is a dictonary which contains each status abbreviation as key and
484 list of files and tersed dirs in that status as value. In each function call
485 we are passing the same dict and adding files and dirs to it.
486
487 terseargs is the string of arguments passed by the user with `--terse` flag.
488
489 Following are the cases which can happen:
490
491 1) All the files in the directory (including all the files in its
492 subdirectories) share the same status and the user has asked us to terse
493 that status. -> we add the directory name to status list and return
494
495 2) If '1)' does not happen, we do following:
496
497 a) Add all the files which are in this directory (only the ones in
498 this directory, not the subdirs) to their respective status list
499
500 b) Recurse the function on all the subdirectories of this directory
501 """
502
503 if len(subdir.statuses) == 1:
504 onlyst = subdir.statuses.pop()
505
506 # Making sure we terse only when the status abbreviation is passed as
507 # terse argument
508 if onlyst in terseargs:
509 tersedict[onlyst].append(subdir.path + pycompat.ossep)
510 return
511
512 # add the files to status list
513 _addfilestotersed(subdir.path, subdir.files, tersedict)
514
515 #recurse on the subdirs
516 for dirobj in subdir.subdirs.values():
517 _processtersestatus(dirobj, tersedict, terseargs)
518
519 def tersedir(statuslist, terseargs):
493 """
520 """
494 absentdir = {}
521 terses the status if all the files in a directory shares the same status
495 absentfiles = removedfiles + missingfiles
522
496 while absentfiles:
523 statuslist is scmutil.status() object which contains a list of files for
497 f = absentfiles.pop()
524 each status.
498 par = os.path.dirname(f)
525 terseargs is string which is passed by the user as the argument to `--terse`
499 if par == '':
526 flag.
500 continue
527
501 # we need to store files rather than number of files as some files
528 The function makes a tree of objects of dirnode class, and at each node it
502 # or subdirectories in a directory can be counted twice. This is
529 stores the information required to know whether we can terse a certain
503 # also we have used sets here.
530 directory or not.
504 try:
531
505 absentdir[par].add(f)
532 tersedict (defined in the function) is a dictionary which has one word key
506 except KeyError:
533 for each status and a list of files and dir in that status as the respective
507 absentdir[par] = set([f])
534 value. The dictionary is passed to other helper functions which builds it.
508 absentfiles.append(par)
535 """
509 return absentdir
536 # the order matters here as that is used to produce final list
510
537 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
511 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
538
512 # get a dictonary of directories and files which are missing as os.listdir()
539 # checking the argument validity
513 # won't be able to list them.
540 for s in terseargs:
514 absentdir = absentones(statlist[2], statlist[3])
541 if s not in allst:
515 finalrs = [[]] * len(indexes)
542 raise error.Abort(_("'%s' not recognized") % s)
516 didsomethingchanged = False
543
517 # dictionary to store number of files and subdir in a directory so that we
544 # creating a dirnode object for the root of the repo
518 # don't compute that again.
545 rootobj = dirnode('')
519 lencache = {}
546 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
520
547 'ignored', 'removed')
521 for st in pycompat.bytestr(status):
548
522
549 tersedict = {}
523 try:
550 for attrname in pstatus:
524 ind = indexes[st]
551 for f in getattr(statuslist, attrname):
525 except KeyError:
552 rootobj.addfile(f, attrname[0])
526 # TODO: Need a better error message here
553 tersedict[attrname[0]] = []
527 raise error.Abort("'%s' not recognized" % st)
554
528
555 # we won't be tersing the root dir, so add files in it
529 sfiles = statlist[ind]
556 _addfilestotersed(rootobj.path, rootobj.files, tersedict)
530 if not sfiles:
557
531 continue
558 # process each sub-directory and build tersedict
532 pardict = {}
559 for subdir in rootobj.subdirs.values():
533 for a in sfiles:
560 _processtersestatus(subdir, tersedict, terseargs)
534 par = os.path.dirname(a)
561
535 pardict.setdefault(par, []).append(a)
562 tersedlist = []
536
563 for st in allst:
537 rs = []
564 tersedict[st].sort()
538 newls = []
565 tersedlist.append(tersedict[st])
539 for par, files in sorted(pardict.iteritems()):
566
540 lenpar = numfiles(par)
567 return tersedlist
541 if lenpar == len(files):
542 newls.append(par)
543
544 if not newls:
545 continue
546
547 while newls:
548 newel = newls.pop()
549 if newel == '':
550 continue
551 parn = os.path.dirname(newel)
552 pardict[newel] = []
553 # Adding pycompat.ossep as newel is a directory.
554 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
555 lenpar = numfiles(parn)
556 if lenpar == len(pardict[parn]):
557 newls.append(parn)
558
559 # dict.values() for Py3 compatibility
560 for files in pardict.values():
561 rs.extend(files)
562
563 rs.sort()
564 finalrs[ind] = rs
565 didsomethingchanged = True
566
567 # If nothing is changed, make sure the order of files is preserved.
568 if not didsomethingchanged:
569 return statlist
570
571 for x in xrange(len(indexes)):
572 if not finalrs[x]:
573 finalrs[x] = statlist[x]
574
575 return finalrs
576
568
577 def _commentlines(raw):
569 def _commentlines(raw):
578 '''Surround lineswith a comment char and a new line'''
570 '''Surround lineswith a comment char and a new line'''
@@ -4805,12 +4805,18 def status(ui, repo, *pats, **opts):
4805 show = states[:5]
4805 show = states[:5]
4806
4806
4807 m = scmutil.match(repo[node2], pats, opts)
4807 m = scmutil.match(repo[node2], pats, opts)
4808 if terse:
4809 # we need to compute clean and unknown to terse
4808 stat = repo.status(node1, node2, m,
4810 stat = repo.status(node1, node2, m,
4809 'ignored' in show, 'clean' in show, 'unknown' in show,
4811 'ignored' in show or 'i' in terse,
4810 opts.get('subrepos'))
4812 True, True, opts.get('subrepos'))
4811 if terse:
4813
4812 stat = cmdutil.tersestatus(repo.root, stat, terse,
4814 stat = cmdutil.tersedir(stat, terse)
4813 repo.dirstate._ignore, opts.get('ignored'))
4815 else:
4816 stat = repo.status(node1, node2, m,
4817 'ignored' in show, 'clean' in show,
4818 'unknown' in show, opts.get('subrepos'))
4819
4814 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4820 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4815
4821
4816 if (opts.get('all') or opts.get('copies')
4822 if (opts.get('all') or opts.get('copies')
General Comments 0
You need to be logged in to leave comments. Login now