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