##// 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 403 return commit(ui, repo, recordinwlock, pats, opts)
404 404
405 def tersestatus(root, statlist, status, ignorefn, ignore):
406 """
407 Returns a list of statuses with directory collapsed if all the files in the
408 directory has the same status.
405
406 # extracted at module level as it's required each time a file will be added
407 # to dirnode class object below
408 pathsep = pycompat.ossep
409
410 class dirnode(object):
409 411 """
410
411 def numfiles(dirname):
412 """
413 Calculates the number of tracked files in a given directory which also
414 includes files which were removed or deleted. Considers ignored files
415 if ignore argument is True or 'i' is present in status argument.
412 represents a directory in user working copy
413
414 stores information which is required for purpose of tersing the status
415
416 path is the path to the directory
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):
418 return lencache[dirname]
419 if 'i' in status or ignore:
420 def match(localpath):
421 absolutepath = os.path.join(root, localpath)
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):
439 adds a file which is present in this directory to its direct parent
440 dirnode object
441
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
490 444 """
491 Returns a dictionary of directories with files in it which are either
492 removed or missing (deleted) in them.
493 """
494 absentdir = {}
495 absentfiles = removedfiles + missingfiles
496 while absentfiles:
497 f = absentfiles.pop()
498 par = os.path.dirname(f)
499 if par == '':
500 continue
501 # we need to store files rather than number of files as some files
502 # or subdirectories in a directory can be counted twice. This is
503 # also we have used sets here.
504 try:
505 absentdir[par].add(f)
506 except KeyError:
507 absentdir[par] = set([f])
508 absentfiles.append(par)
509 return absentdir
510
511 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
512 # get a dictonary of directories and files which are missing as os.listdir()
513 # won't be able to list them.
514 absentdir = absentones(statlist[2], statlist[3])
515 finalrs = [[]] * len(indexes)
516 didsomethingchanged = False
517 # dictionary to store number of files and subdir in a directory so that we
518 # don't compute that again.
519 lencache = {}
520
521 for st in pycompat.bytestr(status):
522
523 try:
524 ind = indexes[st]
525 except KeyError:
526 # TODO: Need a better error message here
527 raise error.Abort("'%s' not recognized" % st)
528
529 sfiles = statlist[ind]
530 if not sfiles:
531 continue
532 pardict = {}
533 for a in sfiles:
534 par = os.path.dirname(a)
535 pardict.setdefault(par, []).append(a)
536
537 rs = []
538 newls = []
539 for par, files in sorted(pardict.iteritems()):
540 lenpar = numfiles(par)
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
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
459 else:
460 self._addfileindir(filename, status)
461
462 if status not in self.statuses:
463 self.statuses.add(status)
464
465 def _addfilestotersed(path, files, tersedict):
466 """ adds files to the their respective status list in the final tersed list
467
468 path is the path of parent directory of the file
469 files is a list of tuple where each tuple is (filename, status)
470 tersedict is a dictonary which contains each status abbreviation as key and
471 list of files and tersed dirs in that status as value
472 """
473 for f, st in files:
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):
520 """
521 terses the status if all the files in a directory shares the same status
522
523 statuslist is scmutil.status() object which contains a list of files for
524 each status.
525 terseargs is string which is passed by the user as the argument to `--terse`
526 flag.
527
528 The function makes a tree of objects of dirnode class, and at each node it
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 569 def _commentlines(raw):
578 570 '''Surround lineswith a comment char and a new line'''
@@ -4805,12 +4805,18 b' def status(ui, repo, *pats, **opts):'
4805 4805 show = states[:5]
4806 4806
4807 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 4808 if terse:
4812 stat = cmdutil.tersestatus(repo.root, stat, terse,
4813 repo.dirstate._ignore, opts.get('ignored'))
4809 # we need to compute clean and unknown to terse
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 4820 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4815 4821
4816 4822 if (opts.get('all') or opts.get('copies')
General Comments 0
You need to be logged in to leave comments. Login now