mercurial/commands.py
changeset 1057 2fd15d743b3b
parent 1042 23f9d71ab9ae
child 1058 402279974aea
equal deleted inserted replaced
1056:34be48b4ca85 1057:2fd15d743b3b
    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:
   714         if repo.dirstate.state(abs) == 'a':
   788         if repo.dirstate.state(abs) == 'a':
   715             forget.append(abs)
   789             forget.append(abs)
   716             if not exact: ui.status('forgetting ', rel, '\n')
   790             if not exact: ui.status('forgetting ', rel, '\n')
   717     repo.forget(forget)
   791     repo.forget(forget)
   718 
   792 
       
   793 def grep(ui, repo, pattern = None, *pats, **opts):
       
   794     if pattern is None: pattern = opts['regexp']
       
   795     if not pattern: raise util.Abort('no pattern to search for')
       
   796     reflags = 0
       
   797     if opts['ignore_case']: reflags |= re.I
       
   798     regexp = re.compile(pattern, reflags)
       
   799     sep, end = ':', '\n'
       
   800     if opts['null'] or opts['print0']: sep = end = '\0'
       
   801 
       
   802     fcache = {}
       
   803     def getfile(fn):
       
   804         if fn not in fcache:
       
   805             fcache[fn] = repo.file(fn)
       
   806         return fcache[fn]
       
   807 
       
   808     def matchlines(body):
       
   809         for match in regexp.finditer(body):
       
   810             start, end = match.span()
       
   811             lnum = body.count('\n', 0, start) + 1
       
   812             lstart = body.rfind('\n', 0, start) + 1
       
   813             lend = body.find('\n', end)
       
   814             yield lnum, start - lstart, end - lstart, body[lstart:lend]
       
   815 
       
   816     class linestate:
       
   817         def __init__(self, line, linenum, colstart, colend):
       
   818             self.line = line
       
   819             self.linenum = linenum
       
   820             self.colstart = colstart
       
   821             self.colend = colend
       
   822         def __eq__(self, other): return self.line == other.line
       
   823         def __hash__(self): return hash(self.line)
       
   824 
       
   825     matches = {}
       
   826     def grepbody(fn, rev, body):
       
   827         matches[rev].setdefault(fn, {})
       
   828         m = matches[rev][fn]
       
   829         for lnum, cstart, cend, line in matchlines(body):
       
   830             s = linestate(line, lnum, cstart, cend)
       
   831             m[s] = s
       
   832 
       
   833     prev = {}
       
   834     def display(fn, rev, states, prevstates):
       
   835         diff = list(set(states).symmetric_difference(set(prevstates)))
       
   836         diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
       
   837         for l in diff:
       
   838             if incrementing:
       
   839                 change = ((l in prevstates) and '-') or '+'
       
   840                 r = rev
       
   841             else:
       
   842                 change = ((l in states) and '-') or '+'
       
   843                 r = prev[fn]
       
   844             ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
       
   845 
       
   846     fstate = {}
       
   847     for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
       
   848         if st == 'window':
       
   849             incrementing = rev
       
   850             matches.clear()
       
   851         elif st == 'add':
       
   852             change = repo.changelog.read(repo.lookup(str(rev)))
       
   853             mf = repo.manifest.read(change[0])
       
   854             matches[rev] = {}
       
   855             for fn in fns:
       
   856                 fstate.setdefault(fn, {})
       
   857                 try:
       
   858                     grepbody(fn, rev, getfile(fn).read(mf[fn]))
       
   859                 except KeyError:
       
   860                     pass
       
   861         elif st == 'iter':
       
   862             states = matches[rev].items()
       
   863             states.sort()
       
   864             for fn, m in states:
       
   865                 if incrementing or fstate[fn]:
       
   866                     display(fn, rev, m, fstate[fn])
       
   867                 fstate[fn] = m
       
   868                 prev[fn] = rev
       
   869 
       
   870     if not incrementing:
       
   871         fstate = fstate.items()
       
   872         fstate.sort()
       
   873         for fn, state in fstate:
       
   874             display(fn, rev, {}, state)
       
   875 
   719 def heads(ui, repo, **opts):
   876 def heads(ui, repo, **opts):
   720     """show current repository heads"""
   877     """show current repository heads"""
   721     heads = repo.changelog.heads()
   878     heads = repo.changelog.heads()
   722     br = None
   879     br = None
   723     if opts['branches']:
   880     if opts['branches']:
   843         else:
  1000         else:
   844             ui.write(rel, end)
  1001             ui.write(rel, end)
   845 
  1002 
   846 def log(ui, repo, *pats, **opts):
  1003 def log(ui, repo, *pats, **opts):
   847     """show revision history of entire repository or files"""
  1004     """show revision history of entire repository or files"""
   848     # This code most commonly needs to iterate backwards over the
  1005     class dui:
   849     # history it is interested in.  This has awful (quadratic-looking)
  1006         # Implement and delegate some ui protocol.  Save hunks of
   850     # performance, so we use iterators that walk forwards through
  1007         # output for later display in the desired order.
   851     # windows of revisions, yielding revisions in reverse order, while
  1008         def __init__(self, ui):
   852     # walking the windows backwards.
  1009             self.ui = ui
       
  1010             self.hunk = {}
       
  1011         def bump(self, rev):
       
  1012             self.rev = rev
       
  1013             self.hunk[rev] = []
       
  1014         def note(self, *args):
       
  1015             if self.verbose: self.write(*args)
       
  1016         def status(self, *args):
       
  1017             if not self.quiet: self.write(*args)
       
  1018         def write(self, *args):
       
  1019             self.hunk[self.rev].append(args)
       
  1020         def __getattr__(self, key):
       
  1021             return getattr(self.ui, key)
   853     cwd = repo.getcwd()
  1022     cwd = repo.getcwd()
   854     if not pats and cwd:
  1023     if not pats and cwd:
   855         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
  1024         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
   856         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
  1025         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
   857     files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
  1026     for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
   858                                         pats, opts)
  1027                                        opts):
   859     revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
  1028         if st == 'window':
   860     wanted = {}
       
   861     slowpath = anypats
       
   862     window = 300
       
   863     if not slowpath and not files:
       
   864         # No files, no patterns.  Display all revs.
       
   865         wanted = dict(zip(revs, revs))
       
   866     if not slowpath:
       
   867         # Only files, no patterns.  Check the history of each file.
       
   868         def filerevgen(filelog):
       
   869             for i in xrange(filelog.count() - 1, -1, -window):
       
   870                 print "filelog"
       
   871                 revs = []
       
   872                 for j in xrange(max(0, i - window), i + 1):
       
   873                     revs.append(filelog.linkrev(filelog.node(j)))
       
   874                 revs.reverse()
       
   875                 for rev in revs:
       
   876                     yield rev
       
   877 
       
   878         minrev, maxrev = min(revs), max(revs)
       
   879         for filelog in map(repo.file, files):
       
   880             # A zero count may be a directory or deleted file, so
       
   881             # try to find matching entries on the slow path.
       
   882             if filelog.count() == 0:
       
   883                 slowpath = True
       
   884                 break
       
   885             for rev in filerevgen(filelog):
       
   886                 if rev <= maxrev:
       
   887                     if rev < minrev: break
       
   888                     wanted[rev] = 1
       
   889     if slowpath:
       
   890         # The slow path checks files modified in every changeset.
       
   891         def mfrevgen():
       
   892             for i in xrange(repo.changelog.count() - 1, -1, -window):
       
   893                 for j in xrange(max(0, i - window), i + 1):
       
   894                     yield j, repo.changelog.read(repo.lookup(str(j)))[3]
       
   895 
       
   896         for rev, mf in mfrevgen():
       
   897             if filter(matchfn, mf):
       
   898                 wanted[rev] = 1
       
   899 
       
   900     def changerevgen():
       
   901         class dui:
       
   902             # Implement and delegate some ui protocol.  Save hunks of
       
   903             # output for later display in the desired order.
       
   904             def __init__(self, ui):
       
   905                 self.ui = ui
       
   906                 self.hunk = {}
       
   907             def bump(self, rev):
       
   908                 self.rev = rev
       
   909                 self.hunk[rev] = []
       
   910             def note(self, *args):
       
   911                 if self.verbose: self.write(*args)
       
   912             def status(self, *args):
       
   913                 if not self.quiet: self.write(*args)
       
   914             def write(self, *args):
       
   915                 self.hunk[self.rev].append(args)
       
   916             def __getattr__(self, key):
       
   917                 return getattr(self.ui, key)
       
   918         for i in xrange(0, len(revs), window):
       
   919             nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
       
   920                      if rev in wanted]
       
   921             srevs = list(nrevs)
       
   922             srevs.sort()
       
   923             du = dui(ui)
  1029             du = dui(ui)
   924             for rev in srevs:
  1030         elif st == 'add':
   925                 du.bump(rev)
  1031             du.bump(rev)
   926                 yield rev, du
  1032             show_changeset(du, repo, rev)
   927             for rev in nrevs:
  1033             if opts['patch']:
   928                 for args in du.hunk[rev]:
  1034                 changenode = repo.changelog.node(rev)
   929                     ui.write(*args)
  1035                 prev, other = repo.changelog.parents(changenode)
   930 
  1036                 dodiff(du, du, repo, prev, changenode, fns)
   931     for rev, dui in changerevgen():
  1037                 du.write("\n\n")
   932         show_changeset(dui, repo, rev)
  1038         elif st == 'iter':
   933         if opts['patch']:
  1039             for args in du.hunk[rev]:
   934             changenode = repo.changelog.node(rev)
  1040                 ui.write(*args)
   935             prev, other = repo.changelog.parents(changenode)
       
   936             dodiff(dui, dui, repo, prev, changenode, files)
       
   937             dui.write("\n\n")
       
   938 
  1041 
   939 def manifest(ui, repo, rev=None):
  1042 def manifest(ui, repo, rev=None):
   940     """output the latest or given revision of the project manifest"""
  1043     """output the latest or given revision of the project manifest"""
   941     if rev:
  1044     if rev:
   942         try:
  1045         try:
  1406     "forget":
  1509     "forget":
  1407         (forget,
  1510         (forget,
  1408          [('I', 'include', [], 'include path in search'),
  1511          [('I', 'include', [], 'include path in search'),
  1409           ('X', 'exclude', [], 'exclude path from search')],
  1512           ('X', 'exclude', [], 'exclude path from search')],
  1410          "hg forget [OPTION]... FILE..."),
  1513          "hg forget [OPTION]... FILE..."),
       
  1514     "grep": (grep,
       
  1515              [('0', 'print0', None, 'terminate file names with NUL'),
       
  1516               ('I', 'include', [], 'include path in search'),
       
  1517               ('X', 'exclude', [], 'include path in search'),
       
  1518               ('Z', 'null', None, 'terminate file names with NUL'),
       
  1519               ('a', 'all-revs', '', 'search all revs'),
       
  1520               ('e', 'regexp', '', 'pattern to search for'),
       
  1521               ('f', 'full-path', None, 'print complete paths'),
       
  1522               ('i', 'ignore-case', None, 'ignore case when matching'),
       
  1523               ('l', 'files-with-matches', None, 'print names of files with matches'),
       
  1524               ('n', 'line-number', '', 'print line numbers'),
       
  1525               ('r', 'rev', [], 'search in revision rev'),
       
  1526               ('s', 'no-messages', None, 'do not print error messages'),
       
  1527               ('v', 'invert-match', None, 'select non-matching lines')],
       
  1528              "hg grep [options] [pat] [files]"),
  1411     "heads":
  1529     "heads":
  1412         (heads,
  1530         (heads,
  1413          [('b', 'branches', None, 'find branch info')],
  1531          [('b', 'branches', None, 'find branch info')],
  1414          'hg [-b] heads'),
  1532          'hg [-b] heads'),
  1415     "help": (help_, [], 'hg help [COMMAND]'),
  1533     "help": (help_, [], 'hg help [COMMAND]'),