mercurial/commands.py
changeset 1146 9061f79c6c6f
parent 1145 bd917e1a26dd
child 1147 d32b91ebad5d
equal deleted inserted replaced
1145:bd917e1a26dd 1146:9061f79c6c6f
    47     files, matchfn, results = makewalk(repo, pats, opts, head)
    47     files, matchfn, results = makewalk(repo, pats, opts, head)
    48     for r in results:
    48     for r in results:
    49         yield r
    49         yield r
    50 
    50 
    51 def walkchangerevs(ui, repo, cwd, pats, opts):
    51 def walkchangerevs(ui, repo, cwd, pats, opts):
    52     # This code most commonly needs to iterate backwards over the
    52     '''Iterate over files and the revs they changed in.
    53     # history it is interested in.  Doing so has awful
    53 
    54     # (quadratic-looking) performance, so we use iterators in a
    54     Callers most commonly need to iterate backwards over the history
    55     # "windowed" way.  Walk forwards through a window of revisions,
    55     it is interested in.  Doing so has awful (quadratic-looking)
    56     # yielding them in the desired order, and walk the windows
    56     performance, so we use iterators in a "windowed" way.
    57     # themselves backwards.
    57 
       
    58     We walk a window of revisions in the desired order.  Within the
       
    59     window, we first walk forwards to gather data, then in the desired
       
    60     order (usually backwards) to display it.
       
    61 
       
    62     This function returns an (iterator, getchange) pair.  The
       
    63     getchange function returns the changelog entry for a numeric
       
    64     revision.  The iterator yields 3-tuples.  They will be of one of
       
    65     the following forms:
       
    66 
       
    67     "window", incrementing, lastrev: stepping through a window,
       
    68     positive if walking forwards through revs, last rev in the
       
    69     sequence iterated over - use to reset state for the current window
       
    70 
       
    71     "add", rev, fns: out-of-order traversal of the given file names
       
    72     fns, which changed during revision rev - use to gather data for
       
    73     possible display
       
    74 
       
    75     "iter", rev, None: in-order traversal of the revs earlier iterated
       
    76     over with "add" - use to display data'''
    58     cwd = repo.getcwd()
    77     cwd = repo.getcwd()
    59     if not pats and cwd:
    78     if not pats and cwd:
    60         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
    79         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
    61         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
    80         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
    62     files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
    81     files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
    64     revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
    83     revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
    65     wanted = {}
    84     wanted = {}
    66     slowpath = anypats
    85     slowpath = anypats
    67     window = 300
    86     window = 300
    68     fncache = {}
    87     fncache = {}
       
    88 
       
    89     chcache = {}
       
    90     def getchange(rev):
       
    91         ch = chcache.get(rev)
       
    92         if ch is None:
       
    93             chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
       
    94         return ch
       
    95 
    69     if not slowpath and not files:
    96     if not slowpath and not files:
    70         # No files, no patterns.  Display all revs.
    97         # No files, no patterns.  Display all revs.
    71         wanted = dict(zip(revs, revs))
    98         wanted = dict(zip(revs, revs))
    72     if not slowpath:
    99     if not slowpath:
    73         # Only files, no patterns.  Check the history of each file.
   100         # Only files, no patterns.  Check the history of each file.
    98     if slowpath:
   125     if slowpath:
    99         # The slow path checks files modified in every changeset.
   126         # The slow path checks files modified in every changeset.
   100         def changerevgen():
   127         def changerevgen():
   101             for i in xrange(repo.changelog.count() - 1, -1, -window):
   128             for i in xrange(repo.changelog.count() - 1, -1, -window):
   102                 for j in xrange(max(0, i - window), i + 1):
   129                 for j in xrange(max(0, i - window), i + 1):
   103                     yield j, repo.changelog.read(repo.lookup(str(j)))[3]
   130                     yield j, getchange(j)[3]
   104 
   131 
   105         for rev, changefiles in changerevgen():
   132         for rev, changefiles in changerevgen():
   106             matches = filter(matchfn, changefiles)
   133             matches = filter(matchfn, changefiles)
   107             if matches:
   134             if matches:
   108                 fncache[rev] = matches
   135                 fncache[rev] = matches
   109                 wanted[rev] = 1
   136                 wanted[rev] = 1
   110 
   137 
   111     for i in xrange(0, len(revs), window):
   138     def iterate():
   112         yield 'window', revs[0] < revs[-1], revs[-1]
   139         for i in xrange(0, len(revs), window):
   113         nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
   140             yield 'window', revs[0] < revs[-1], revs[-1]
   114                  if rev in wanted]
   141             nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
   115         srevs = list(nrevs)
   142                      if rev in wanted]
   116         srevs.sort()
   143             srevs = list(nrevs)
   117         for rev in srevs:
   144             srevs.sort()
   118             fns = fncache.get(rev)
   145             for rev in srevs:
   119             if not fns:
   146                 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
   120                 fns = repo.changelog.read(repo.lookup(str(rev)))[3]
   147                 yield 'add', rev, fns
   121                 fns = filter(matchfn, fns)
   148             for rev in nrevs:
   122             yield 'add', rev, fns
   149                 yield 'iter', rev, None
   123         for rev in nrevs:
   150     return iterate(), getchange
   124             yield 'iter', rev, None
       
   125 
   151 
   126 revrangesep = ':'
   152 revrangesep = ':'
   127 
   153 
   128 def revrange(ui, repo, revs, revlog=None):
   154 def revrange(ui, repo, revs, revlog=None):
   129     """Yield revision as strings from a list of revision specifications."""
   155     """Yield revision as strings from a list of revision specifications."""
   148                 try:
   174                 try:
   149                     num = revlog.rev(revlog.lookup(val))
   175                     num = revlog.rev(revlog.lookup(val))
   150                 except KeyError:
   176                 except KeyError:
   151                     raise util.Abort('invalid revision identifier %s', val)
   177                     raise util.Abort('invalid revision identifier %s', val)
   152         return num
   178         return num
       
   179     seen = {}
   153     for spec in revs:
   180     for spec in revs:
   154         if spec.find(revrangesep) >= 0:
   181         if spec.find(revrangesep) >= 0:
   155             start, end = spec.split(revrangesep, 1)
   182             start, end = spec.split(revrangesep, 1)
   156             start = fix(start, 0)
   183             start = fix(start, 0)
   157             end = fix(end, revcount - 1)
   184             end = fix(end, revcount - 1)
   158             step = start > end and -1 or 1
   185             step = start > end and -1 or 1
   159             for rev in xrange(start, end+step, step):
   186             for rev in xrange(start, end+step, step):
       
   187                 if rev in seen: continue
       
   188                 seen[rev] = 1
   160                 yield str(rev)
   189                 yield str(rev)
   161         else:
   190         else:
   162             yield str(fix(spec, None))
   191             rev = fix(spec, None)
       
   192             if rev in seen: continue
       
   193             seen[rev] = 1
       
   194             yield str(rev)
   163 
   195 
   164 def make_filename(repo, r, pat, node=None,
   196 def make_filename(repo, r, pat, node=None,
   165                   total=None, seqno=None, revwidth=None):
   197                   total=None, seqno=None, revwidth=None):
   166     node_expander = {
   198     node_expander = {
   167         'H': lambda: hex(node),
   199         'H': lambda: hex(node),
   263     for f in d:
   295     for f in d:
   264         to = repo.file(f).read(mmap[f])
   296         to = repo.file(f).read(mmap[f])
   265         tn = None
   297         tn = None
   266         fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
   298         fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
   267 
   299 
       
   300 def trimuser(ui, rev, name, revcache):
       
   301     """trim the name of the user who committed a change"""
       
   302     try:
       
   303         return revcache[rev]
       
   304     except KeyError:
       
   305         if not ui.verbose:
       
   306             f = name.find('@')
       
   307             if f >= 0:
       
   308                 name = name[:f]
       
   309             f = name.find('<')
       
   310             if f >= 0:
       
   311                 name = name[f+1:]
       
   312         revcache[rev] = name
       
   313         return name
       
   314 
   268 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
   315 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
   269     """show a single changeset or file revision"""
   316     """show a single changeset or file revision"""
   270     log = repo.changelog
   317     log = repo.changelog
   271     if changenode is None:
   318     if changenode is None:
   272         changenode = log.node(rev)
   319         changenode = log.node(rev)
   465 def annotate(ui, repo, *pats, **opts):
   512 def annotate(ui, repo, *pats, **opts):
   466     """show changeset information per file line"""
   513     """show changeset information per file line"""
   467     def getnode(rev):
   514     def getnode(rev):
   468         return short(repo.changelog.node(rev))
   515         return short(repo.changelog.node(rev))
   469 
   516 
       
   517     ucache = {}
   470     def getname(rev):
   518     def getname(rev):
   471         try:
   519         cl = repo.changelog.read(repo.changelog.node(rev))
   472             return bcache[rev]
   520         return trimuser(ui, rev, cl[1], ucache)
   473         except KeyError:
       
   474             cl = repo.changelog.read(repo.changelog.node(rev))
       
   475             name = cl[1]
       
   476             f = name.find('@')
       
   477             if f >= 0:
       
   478                 name = name[:f]
       
   479             f = name.find('<')
       
   480             if f >= 0:
       
   481                 name = name[f+1:]
       
   482             bcache[rev] = name
       
   483             return name
       
   484 
   521 
   485     if not pats:
   522     if not pats:
   486         raise util.Abort('at least one file name or pattern required')
   523         raise util.Abort('at least one file name or pattern required')
   487 
   524 
   488     bcache = {}
       
   489     opmap = [['user', getname], ['number', str], ['changeset', getnode]]
   525     opmap = [['user', getname], ['number', str], ['changeset', getnode]]
   490     if not opts['user'] and not opts['changeset']:
   526     if not opts['user'] and not opts['changeset']:
   491         opts['number'] = 1
   527         opts['number'] = 1
   492 
   528 
   493     if opts['rev']:
   529     if opts['rev']:
   824     """search for a pattern in specified files and revisions"""
   860     """search for a pattern in specified files and revisions"""
   825     reflags = 0
   861     reflags = 0
   826     if opts['ignore_case']:
   862     if opts['ignore_case']:
   827         reflags |= re.I
   863         reflags |= re.I
   828     regexp = re.compile(pattern, reflags)
   864     regexp = re.compile(pattern, reflags)
   829     sep, end = ':', '\n'
   865     sep, eol = ':', '\n'
   830     if opts['print0']:
   866     if opts['print0']:
   831         sep = end = '\0'
   867         sep = eol = '\0'
   832 
   868 
   833     fcache = {}
   869     fcache = {}
   834     def getfile(fn):
   870     def getfile(fn):
   835         if fn not in fcache:
   871         if fn not in fcache:
   836             fcache[fn] = repo.file(fn)
   872             fcache[fn] = repo.file(fn)
   868         for lnum, cstart, cend, line in matchlines(body):
   904         for lnum, cstart, cend, line in matchlines(body):
   869             s = linestate(line, lnum, cstart, cend)
   905             s = linestate(line, lnum, cstart, cend)
   870             m[s] = s
   906             m[s] = s
   871 
   907 
   872     prev = {}
   908     prev = {}
       
   909     ucache = {}
   873     def display(fn, rev, states, prevstates):
   910     def display(fn, rev, states, prevstates):
   874         diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
   911         diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
   875         diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
   912         diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
   876         counts = {'-': 0, '+': 0}
   913         counts = {'-': 0, '+': 0}
       
   914         filerevmatches = {}
   877         for l in diff:
   915         for l in diff:
   878             if incrementing or not opts['every_match']:
   916             if incrementing or not opts['every_match']:
   879                 change = ((l in prevstates) and '-') or '+'
   917                 change = ((l in prevstates) and '-') or '+'
   880                 r = rev
   918                 r = rev
   881             else:
   919             else:
   882                 change = ((l in states) and '-') or '+'
   920                 change = ((l in states) and '-') or '+'
   883                 r = prev[fn]
   921                 r = prev[fn]
   884             ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
   922             cols = [fn, str(rev)]
       
   923             if opts['line_number']: cols.append(str(l.linenum))
       
   924             if opts['every_match']: cols.append(change)
       
   925             if opts['user']: cols.append(trimuser(ui, rev, getchange(rev)[1],
       
   926                                                   ucache))
       
   927             if opts['files_with_matches']:
       
   928                 c = (fn, rev)
       
   929                 if c in filerevmatches: continue
       
   930                 filerevmatches[c] = 1
       
   931             else:
       
   932                 cols.append(l.line)
       
   933             ui.write(sep.join(cols), eol)
   885             counts[change] += 1
   934             counts[change] += 1
   886         return counts['+'], counts['-']
   935         return counts['+'], counts['-']
   887 
   936 
   888     fstate = {}
   937     fstate = {}
   889     skip = {}
   938     skip = {}
   890     for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
   939     changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
       
   940     count = 0
       
   941     for st, rev, fns in changeiter:
   891         if st == 'window':
   942         if st == 'window':
   892             incrementing = rev
   943             incrementing = rev
   893             matches.clear()
   944             matches.clear()
   894         elif st == 'add':
   945         elif st == 'add':
   895             change = repo.changelog.read(repo.lookup(str(rev)))
   946             change = repo.changelog.read(repo.lookup(str(rev)))
   907             states.sort()
   958             states.sort()
   908             for fn, m in states:
   959             for fn, m in states:
   909                 if fn in skip: continue
   960                 if fn in skip: continue
   910                 if incrementing or not opts['every_match'] or fstate[fn]:
   961                 if incrementing or not opts['every_match'] or fstate[fn]:
   911                     pos, neg = display(fn, rev, m, fstate[fn])
   962                     pos, neg = display(fn, rev, m, fstate[fn])
       
   963                     count += pos + neg
   912                     if pos and not opts['every_match']:
   964                     if pos and not opts['every_match']:
   913                         skip[fn] = True
   965                         skip[fn] = True
   914                 fstate[fn] = m
   966                 fstate[fn] = m
   915                 prev[fn] = rev
   967                 prev[fn] = rev
   916 
   968 
   918         fstate = fstate.items()
   970         fstate = fstate.items()
   919         fstate.sort()
   971         fstate.sort()
   920         for fn, state in fstate:
   972         for fn, state in fstate:
   921             if fn in skip: continue
   973             if fn in skip: continue
   922             display(fn, rev, {}, state)
   974             display(fn, rev, {}, state)
       
   975     return (count == 0 and 1) or 0
   923 
   976 
   924 def heads(ui, repo, **opts):
   977 def heads(ui, repo, **opts):
   925     """show current repository heads"""
   978     """show current repository heads"""
   926     heads = repo.changelog.heads()
   979     heads = repo.changelog.heads()
   927     br = None
   980     br = None
  1071             return getattr(self.ui, key)
  1124             return getattr(self.ui, key)
  1072     cwd = repo.getcwd()
  1125     cwd = repo.getcwd()
  1073     if not pats and cwd:
  1126     if not pats and cwd:
  1074         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
  1127         opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
  1075         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
  1128         opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
  1076     for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
  1129     changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
  1077                                        opts):
  1130                                            pats, opts)
       
  1131     for st, rev, fns in changeiter:
  1078         if st == 'window':
  1132         if st == 'window':
  1079             du = dui(ui)
  1133             du = dui(ui)
  1080         elif st == 'add':
  1134         elif st == 'add':
  1081             du.bump(rev)
  1135             du.bump(rev)
  1082             show_changeset(du, repo, rev)
  1136             show_changeset(du, repo, rev)
  1569          [('I', 'include', [], 'include path in search'),
  1623          [('I', 'include', [], 'include path in search'),
  1570           ('X', 'exclude', [], 'exclude path from search')],
  1624           ('X', 'exclude', [], 'exclude path from search')],
  1571          "hg forget [OPTION]... FILE..."),
  1625          "hg forget [OPTION]... FILE..."),
  1572     "grep":
  1626     "grep":
  1573         (grep,
  1627         (grep,
  1574          [('0', 'print0', None, 'end filenames with NUL'),
  1628          [('0', 'print0', None, 'end fields with NUL'),
  1575           ('I', 'include', [], 'include path in search'),
  1629           ('I', 'include', [], 'include path in search'),
  1576           ('X', 'exclude', [], 'include path in search'),
  1630           ('X', 'exclude', [], 'include path in search'),
  1577           ('e', 'every-match', None, 'print every match in file history'),
  1631           ('e', 'every-match', None, 'print every rev with matches'),
  1578           ('i', 'ignore-case', None, 'ignore case when matching'),
  1632           ('i', 'ignore-case', None, 'ignore case when matching'),
  1579           ('l', 'files-with-matches', None, 'print names of files with matches'),
  1633           ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
  1580           ('n', 'line-number', '', 'print line numbers'),
  1634           ('n', 'line-number', None, 'print line numbers'),
  1581           ('r', 'rev', [], 'search in revision rev')],
  1635           ('r', 'rev', [], 'search in revision rev'),
       
  1636           ('u', 'user', None, 'print user who made change')],
  1582          "hg grep [OPTION]... PATTERN [FILE]..."),
  1637          "hg grep [OPTION]... PATTERN [FILE]..."),
  1583     "heads":
  1638     "heads":
  1584         (heads,
  1639         (heads,
  1585          [('b', 'branches', None, 'find branch info')],
  1640          [('b', 'branches', None, 'find branch info')],
  1586          'hg heads [-b]'),
  1641          'hg heads [-b]'),