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