mercurial/commands.py
changeset 1060 e453d2053b2e
parent 1054 5139ee9bfc38
parent 1059 4eab07ef66e2
child 1061 fed8d078840b
equal deleted inserted replaced
1055:ea465485f54f 1060:e453d2053b2e
    43     return files, matchfn, walk()
    43     return files, matchfn, walk()
    44 
    44 
    45 def walk(repo, pats, opts, head = ''):
    45 def walk(repo, pats, opts, head = ''):
    46     files, matchfn, results = makewalk(repo, pats, opts, head)
    46     files, matchfn, results = makewalk(repo, pats, opts, head)
    47     for r in results: yield r
    47     for r in results: yield r
       
    48 
       
    49 def walkchangerevs(ui, repo, cwd, pats, opts):
       
    50     # This code most commonly needs to iterate backwards over the
       
    51     # history it is interested in.  Doing so has awful
       
    52     # (quadratic-looking) performance, so we use iterators in a
       
    53     # "windowed" way.  Walk forwards through a window of revisions,
       
    54     # yielding them in the desired order, and walk the windows
       
    55     # themselves backwards.
       
    56     cwd = repo.getcwd()
       
    57     if not pats and cwd:
       
    58         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
       
    59         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
       
    60     files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
       
    61                                         pats, opts)
       
    62     revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
       
    63     wanted = {}
       
    64     slowpath = anypats
       
    65     window = 300
       
    66     fncache = {}
       
    67     if not slowpath and not files:
       
    68         # No files, no patterns.  Display all revs.
       
    69         wanted = dict(zip(revs, revs))
       
    70     if not slowpath:
       
    71         # Only files, no patterns.  Check the history of each file.
       
    72         def filerevgen(filelog):
       
    73             for i in xrange(filelog.count() - 1, -1, -window):
       
    74                 revs = []
       
    75                 for j in xrange(max(0, i - window), i + 1):
       
    76                     revs.append(filelog.linkrev(filelog.node(j)))
       
    77                 revs.reverse()
       
    78                 for rev in revs:
       
    79                     yield rev
       
    80 
       
    81         minrev, maxrev = min(revs), max(revs)
       
    82         for file in files:
       
    83             filelog = repo.file(file)
       
    84             # A zero count may be a directory or deleted file, so
       
    85             # try to find matching entries on the slow path.
       
    86             if filelog.count() == 0:
       
    87                 slowpath = True
       
    88                 break
       
    89             for rev in filerevgen(filelog):
       
    90                 if rev <= maxrev:
       
    91                     if rev < minrev: break
       
    92                     fncache.setdefault(rev, [])
       
    93                     fncache[rev].append(file)
       
    94                     wanted[rev] = 1
       
    95     if slowpath:
       
    96         # The slow path checks files modified in every changeset.
       
    97         def changerevgen():
       
    98             for i in xrange(repo.changelog.count() - 1, -1, -window):
       
    99                 for j in xrange(max(0, i - window), i + 1):
       
   100                     yield j, repo.changelog.read(repo.lookup(str(j)))[3]
       
   101 
       
   102         for rev, changefiles in changerevgen():
       
   103             matches = filter(matchfn, changefiles)
       
   104             if matches:
       
   105                 fncache[rev] = matches
       
   106                 wanted[rev] = 1
       
   107 
       
   108     for i in xrange(0, len(revs), window):
       
   109         yield 'window', revs[0] < revs[-1], revs[-1]
       
   110         nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
       
   111                  if rev in wanted]
       
   112         srevs = list(nrevs)
       
   113         srevs.sort()
       
   114         for rev in srevs:
       
   115             fns = fncache.get(rev)
       
   116             if not fns:
       
   117                 fns = repo.changelog.read(repo.lookup(str(rev)))[3]
       
   118                 fns = filter(matchfn, fns)
       
   119             yield 'add', rev, fns
       
   120         for rev in nrevs:
       
   121             yield 'iter', rev, None
    48 
   122 
    49 revrangesep = ':'
   123 revrangesep = ':'
    50 
   124 
    51 def revrange(ui, repo, revs, revlog=None):
   125 def revrange(ui, repo, revs, revlog=None):
    52     if revlog is None:
   126     if revlog is None:
   643 
   717 
   644 def debugwalk(ui, repo, *pats, **opts):
   718 def debugwalk(ui, repo, *pats, **opts):
   645     """show how files match on given patterns"""
   719     """show how files match on given patterns"""
   646     items = list(walk(repo, pats, opts))
   720     items = list(walk(repo, pats, opts))
   647     if not items: return
   721     if not items: return
   648     fmt = '%%s  %%-%ds  %%-%ds  %%s' % (
   722     fmt = '%%s  %%-%ds  %%-%ds  %%s\n' % (
   649         max([len(abs) for (src, abs, rel, exact) in items]),
   723         max([len(abs) for (src, abs, rel, exact) in items]),
   650         max([len(rel) for (src, abs, rel, exact) in items]))
   724         max([len(rel) for (src, abs, rel, exact) in items]))
   651     exactly = {True: 'exact', False: ''}
   725     exactly = {True: 'exact', False: ''}
   652     for src, abs, rel, exact in items:
   726     for src, abs, rel, exact in items:
   653         print fmt % (src, abs, rel, exactly[exact])
   727         ui.write(fmt % (src, abs, rel, exactly[exact]))
   654 
   728 
   655 def diff(ui, repo, *pats, **opts):
   729 def diff(ui, repo, *pats, **opts):
   656     """diff working directory (or selected files)"""
   730     """diff working directory (or selected files)"""
   657     node1, node2 = None, None
   731     node1, node2 = None, None
   658     revs = [repo.lookup(x) for x in opts['rev']]
   732     revs = [repo.lookup(x) for x in opts['rev']]
   716     for src, abs, rel, exact in walk(repo, pats, opts):
   790     for src, abs, rel, exact in walk(repo, pats, opts):
   717         if repo.dirstate.state(abs) == 'a':
   791         if repo.dirstate.state(abs) == 'a':
   718             forget.append(abs)
   792             forget.append(abs)
   719             if not exact: ui.status('forgetting ', rel, '\n')
   793             if not exact: ui.status('forgetting ', rel, '\n')
   720     repo.forget(forget)
   794     repo.forget(forget)
       
   795 
       
   796 def grep(ui, repo, pattern = None, *pats, **opts):
       
   797     """search for a pattern in specified files and revisions"""
       
   798     if pattern is None: pattern = opts['regexp']
       
   799     if not pattern: raise util.Abort('no pattern to search for')
       
   800     reflags = 0
       
   801     if opts['ignore_case']: reflags |= re.I
       
   802     regexp = re.compile(pattern, reflags)
       
   803     sep, end = ':', '\n'
       
   804     if opts['null'] or opts['print0']: sep = end = '\0'
       
   805 
       
   806     fcache = {}
       
   807     def getfile(fn):
       
   808         if fn not in fcache:
       
   809             fcache[fn] = repo.file(fn)
       
   810         return fcache[fn]
       
   811 
       
   812     def matchlines(body):
       
   813         begin = 0
       
   814         linenum = 0
       
   815         while True:
       
   816             match = regexp.search(body, begin)
       
   817             if not match: break
       
   818             mstart, mend = match.span()
       
   819             linenum += body.count('\n', begin, mstart) + 1
       
   820             lstart = body.rfind('\n', begin, mstart) + 1 or begin
       
   821             lend = body.find('\n', mend)
       
   822             yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
       
   823             begin = lend + 1
       
   824 
       
   825     class linestate:
       
   826         def __init__(self, line, linenum, colstart, colend):
       
   827             self.line = line
       
   828             self.linenum = linenum
       
   829             self.colstart = colstart
       
   830             self.colend = colend
       
   831         def __eq__(self, other): return self.line == other.line
       
   832         def __hash__(self): return hash(self.line)
       
   833 
       
   834     matches = {}
       
   835     def grepbody(fn, rev, body):
       
   836         matches[rev].setdefault(fn, {})
       
   837         m = matches[rev][fn]
       
   838         for lnum, cstart, cend, line in matchlines(body):
       
   839             s = linestate(line, lnum, cstart, cend)
       
   840             m[s] = s
       
   841 
       
   842     prev = {}
       
   843     def display(fn, rev, states, prevstates):
       
   844         diff = list(set(states).symmetric_difference(set(prevstates)))
       
   845         diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
       
   846         for l in diff:
       
   847             if incrementing:
       
   848                 change = ((l in prevstates) and '-') or '+'
       
   849                 r = rev
       
   850             else:
       
   851                 change = ((l in states) and '-') or '+'
       
   852                 r = prev[fn]
       
   853             ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
       
   854 
       
   855     fstate = {}
       
   856     for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
       
   857         if st == 'window':
       
   858             incrementing = rev
       
   859             matches.clear()
       
   860         elif st == 'add':
       
   861             change = repo.changelog.read(repo.lookup(str(rev)))
       
   862             mf = repo.manifest.read(change[0])
       
   863             matches[rev] = {}
       
   864             for fn in fns:
       
   865                 fstate.setdefault(fn, {})
       
   866                 try:
       
   867                     grepbody(fn, rev, getfile(fn).read(mf[fn]))
       
   868                 except KeyError:
       
   869                     pass
       
   870         elif st == 'iter':
       
   871             states = matches[rev].items()
       
   872             states.sort()
       
   873             for fn, m in states:
       
   874                 if incrementing or fstate[fn]:
       
   875                     display(fn, rev, m, fstate[fn])
       
   876                 fstate[fn] = m
       
   877                 prev[fn] = rev
       
   878 
       
   879     if not incrementing:
       
   880         fstate = fstate.items()
       
   881         fstate.sort()
       
   882         for fn, state in fstate:
       
   883             display(fn, rev, {}, state)
   721 
   884 
   722 def heads(ui, repo, **opts):
   885 def heads(ui, repo, **opts):
   723     """show current repository heads"""
   886     """show current repository heads"""
   724     heads = repo.changelog.heads()
   887     heads = repo.changelog.heads()
   725     br = None
   888     br = None
   846         else:
  1009         else:
   847             ui.write(rel, end)
  1010             ui.write(rel, end)
   848 
  1011 
   849 def log(ui, repo, *pats, **opts):
  1012 def log(ui, repo, *pats, **opts):
   850     """show revision history of entire repository or files"""
  1013     """show revision history of entire repository or files"""
   851     # This code most commonly needs to iterate backwards over the
  1014     class dui:
   852     # history it is interested in.  This has awful (quadratic-looking)
  1015         # Implement and delegate some ui protocol.  Save hunks of
   853     # performance, so we use iterators that walk forwards through
  1016         # output for later display in the desired order.
   854     # windows of revisions, yielding revisions in reverse order, while
  1017         def __init__(self, ui):
   855     # walking the windows backwards.
  1018             self.ui = ui
       
  1019             self.hunk = {}
       
  1020         def bump(self, rev):
       
  1021             self.rev = rev
       
  1022             self.hunk[rev] = []
       
  1023         def note(self, *args):
       
  1024             if self.verbose: self.write(*args)
       
  1025         def status(self, *args):
       
  1026             if not self.quiet: self.write(*args)
       
  1027         def write(self, *args):
       
  1028             self.hunk[self.rev].append(args)
       
  1029         def __getattr__(self, key):
       
  1030             return getattr(self.ui, key)
   856     cwd = repo.getcwd()
  1031     cwd = repo.getcwd()
   857     if not pats and cwd:
  1032     if not pats and cwd:
   858         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
  1033         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
   859         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
  1034         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
   860     files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
  1035     for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
   861                                         pats, opts)
  1036                                        opts):
   862     revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
  1037         if st == 'window':
   863     wanted = {}
       
   864     slowpath = anypats
       
   865     window = 300
       
   866     if not slowpath and not files:
       
   867         # No files, no patterns.  Display all revs.
       
   868         wanted = dict(zip(revs, revs))
       
   869     if not slowpath:
       
   870         # Only files, no patterns.  Check the history of each file.
       
   871         def filerevgen(filelog):
       
   872             for i in xrange(filelog.count() - 1, -1, -window):
       
   873                 revs = []
       
   874                 for j in xrange(max(0, i - window), i + 1):
       
   875                     revs.append(filelog.linkrev(filelog.node(j)))
       
   876                 revs.reverse()
       
   877                 for rev in revs:
       
   878                     yield rev
       
   879 
       
   880         minrev, maxrev = min(revs), max(revs)
       
   881         for filelog in map(repo.file, files):
       
   882             # A zero count may be a directory or deleted file, so
       
   883             # try to find matching entries on the slow path.
       
   884             if filelog.count() == 0:
       
   885                 slowpath = True
       
   886                 break
       
   887             for rev in filerevgen(filelog):
       
   888                 if rev <= maxrev:
       
   889                     if rev < minrev: break
       
   890                     wanted[rev] = 1
       
   891     if slowpath:
       
   892         # The slow path checks files modified in every changeset.
       
   893         def mfrevgen():
       
   894             for i in xrange(repo.changelog.count() - 1, -1, -window):
       
   895                 for j in xrange(max(0, i - window), i + 1):
       
   896                     yield j, repo.changelog.read(repo.lookup(str(j)))[3]
       
   897 
       
   898         for rev, mf in mfrevgen():
       
   899             if filter(matchfn, mf):
       
   900                 wanted[rev] = 1
       
   901 
       
   902     def changerevgen():
       
   903         class dui:
       
   904             # Implement and delegate some ui protocol.  Save hunks of
       
   905             # output for later display in the desired order.
       
   906             def __init__(self, ui):
       
   907                 self.ui = ui
       
   908                 self.hunk = {}
       
   909             def bump(self, rev):
       
   910                 self.rev = rev
       
   911                 self.hunk[rev] = []
       
   912             def note(self, *args):
       
   913                 if self.verbose: self.write(*args)
       
   914             def status(self, *args):
       
   915                 if not self.quiet: self.write(*args)
       
   916             def write(self, *args):
       
   917                 self.hunk[self.rev].append(args)
       
   918             def __getattr__(self, key):
       
   919                 return getattr(self.ui, key)
       
   920         for i in xrange(0, len(revs), window):
       
   921             nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
       
   922                      if rev in wanted]
       
   923             srevs = list(nrevs)
       
   924             srevs.sort()
       
   925             du = dui(ui)
  1038             du = dui(ui)
   926             for rev in srevs:
  1039         elif st == 'add':
   927                 du.bump(rev)
  1040             du.bump(rev)
   928                 yield rev, du
  1041             show_changeset(du, repo, rev)
   929             for rev in nrevs:
  1042             if opts['patch']:
   930                 for args in du.hunk[rev]:
  1043                 changenode = repo.changelog.node(rev)
   931                     ui.write(*args)
  1044                 prev, other = repo.changelog.parents(changenode)
   932 
  1045                 dodiff(du, du, repo, prev, changenode, fns)
   933     for rev, dui in changerevgen():
  1046                 du.write("\n\n")
   934         show_changeset(dui, repo, rev)
  1047         elif st == 'iter':
   935         if opts['patch']:
  1048             for args in du.hunk[rev]:
   936             changenode = repo.changelog.node(rev)
  1049                 ui.write(*args)
   937             prev, other = repo.changelog.parents(changenode)
       
   938             dodiff(dui, dui, repo, prev, changenode, files)
       
   939             dui.write("\n\n")
       
   940 
  1050 
   941 def manifest(ui, repo, rev=None):
  1051 def manifest(ui, repo, rev=None):
   942     """output the latest or given revision of the project manifest"""
  1052     """output the latest or given revision of the project manifest"""
   943     if rev:
  1053     if rev:
   944         try:
  1054         try:
  1408     "forget":
  1518     "forget":
  1409         (forget,
  1519         (forget,
  1410          [('I', 'include', [], 'include path in search'),
  1520          [('I', 'include', [], 'include path in search'),
  1411           ('X', 'exclude', [], 'exclude path from search')],
  1521           ('X', 'exclude', [], 'exclude path from search')],
  1412          "hg forget [OPTION]... FILE..."),
  1522          "hg forget [OPTION]... FILE..."),
       
  1523     "grep": (grep,
       
  1524              [('0', 'print0', None, 'terminate file names with NUL'),
       
  1525               ('I', 'include', [], 'include path in search'),
       
  1526               ('X', 'exclude', [], 'include path in search'),
       
  1527               ('Z', 'null', None, 'terminate file names with NUL'),
       
  1528               ('a', 'all-revs', '', 'search all revs'),
       
  1529               ('e', 'regexp', '', 'pattern to search for'),
       
  1530               ('f', 'full-path', None, 'print complete paths'),
       
  1531               ('i', 'ignore-case', None, 'ignore case when matching'),
       
  1532               ('l', 'files-with-matches', None, 'print names of files with matches'),
       
  1533               ('n', 'line-number', '', 'print line numbers'),
       
  1534               ('r', 'rev', [], 'search in revision rev'),
       
  1535               ('s', 'no-messages', None, 'do not print error messages'),
       
  1536               ('v', 'invert-match', None, 'select non-matching lines')],
       
  1537              "hg grep [options] [pat] [files]"),
  1413     "heads":
  1538     "heads":
  1414         (heads,
  1539         (heads,
  1415          [('b', 'branches', None, 'find branch info')],
  1540          [('b', 'branches', None, 'find branch info')],
  1416          'hg heads [-b]'),
  1541          'hg heads [-b]'),
  1417     "help": (help_, [], 'hg help [COMMAND]'),
  1542     "help": (help_, [], 'hg help [COMMAND]'),