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