mercurial/cmdutil.py
changeset 17180 ae0629161090
parent 17059 fba17a64fa49
child 17181 6f71167292f2
equal deleted inserted replaced
17179:0849d725e2f9 17180:ae0629161090
     8 from node import hex, nullid, nullrev, short
     8 from node import hex, nullid, nullrev, short
     9 from i18n import _
     9 from i18n import _
    10 import os, sys, errno, re, tempfile
    10 import os, sys, errno, re, tempfile
    11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
    11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
    12 import match as matchmod
    12 import match as matchmod
    13 import subrepo, context, repair, bookmarks
    13 import subrepo, context, repair, bookmarks, graphmod, revset
    14 
    14 
    15 def parsealiases(cmd):
    15 def parsealiases(cmd):
    16     return cmd.lstrip("^").split("|")
    16     return cmd.lstrip("^").split("|")
    17 
    17 
    18 def findpossible(cmd, table, strict=False):
    18 def findpossible(cmd, table, strict=False):
  1189                     fns = fns_generator()
  1189                     fns = fns_generator()
  1190                 prepare(ctx, fns)
  1190                 prepare(ctx, fns)
  1191             for rev in nrevs:
  1191             for rev in nrevs:
  1192                 yield change(rev)
  1192                 yield change(rev)
  1193     return iterate()
  1193     return iterate()
       
  1194 
       
  1195 def _makegraphfilematcher(repo, pats, followfirst):
       
  1196     # When displaying a revision with --patch --follow FILE, we have
       
  1197     # to know which file of the revision must be diffed. With
       
  1198     # --follow, we want the names of the ancestors of FILE in the
       
  1199     # revision, stored in "fcache". "fcache" is populated by
       
  1200     # reproducing the graph traversal already done by --follow revset
       
  1201     # and relating linkrevs to file names (which is not "correct" but
       
  1202     # good enough).
       
  1203     fcache = {}
       
  1204     fcacheready = [False]
       
  1205     pctx = repo['.']
       
  1206     wctx = repo[None]
       
  1207 
       
  1208     def populate():
       
  1209         for fn in pats:
       
  1210             for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
       
  1211                 for c in i:
       
  1212                     fcache.setdefault(c.linkrev(), set()).add(c.path())
       
  1213 
       
  1214     def filematcher(rev):
       
  1215         if not fcacheready[0]:
       
  1216             # Lazy initialization
       
  1217             fcacheready[0] = True
       
  1218             populate()
       
  1219         return scmutil.match(wctx, fcache.get(rev, []), default='path')
       
  1220 
       
  1221     return filematcher
       
  1222 
       
  1223 def _makegraphlogrevset(repo, pats, opts, revs):
       
  1224     """Return (expr, filematcher) where expr is a revset string built
       
  1225     from log options and file patterns or None. If --stat or --patch
       
  1226     are not passed filematcher is None. Otherwise it is a callable
       
  1227     taking a revision number and returning a match objects filtering
       
  1228     the files to be detailed when displaying the revision.
       
  1229     """
       
  1230     opt2revset = {
       
  1231         'no_merges':        ('not merge()', None),
       
  1232         'only_merges':      ('merge()', None),
       
  1233         '_ancestors':       ('ancestors(%(val)s)', None),
       
  1234         '_fancestors':      ('_firstancestors(%(val)s)', None),
       
  1235         '_descendants':     ('descendants(%(val)s)', None),
       
  1236         '_fdescendants':    ('_firstdescendants(%(val)s)', None),
       
  1237         '_matchfiles':      ('_matchfiles(%(val)s)', None),
       
  1238         'date':             ('date(%(val)r)', None),
       
  1239         'branch':           ('branch(%(val)r)', ' or '),
       
  1240         '_patslog':         ('filelog(%(val)r)', ' or '),
       
  1241         '_patsfollow':      ('follow(%(val)r)', ' or '),
       
  1242         '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
       
  1243         'keyword':          ('keyword(%(val)r)', ' or '),
       
  1244         'prune':            ('not (%(val)r or ancestors(%(val)r))', ' and '),
       
  1245         'user':             ('user(%(val)r)', ' or '),
       
  1246         }
       
  1247 
       
  1248     opts = dict(opts)
       
  1249     # follow or not follow?
       
  1250     follow = opts.get('follow') or opts.get('follow_first')
       
  1251     followfirst = opts.get('follow_first') and 1 or 0
       
  1252     # --follow with FILE behaviour depends on revs...
       
  1253     startrev = revs[0]
       
  1254     followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
       
  1255 
       
  1256     # branch and only_branch are really aliases and must be handled at
       
  1257     # the same time
       
  1258     opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
       
  1259     opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
       
  1260     # pats/include/exclude are passed to match.match() directly in
       
  1261     # _matchfile() revset but walkchangerevs() builds its matcher with
       
  1262     # scmutil.match(). The difference is input pats are globbed on
       
  1263     # platforms without shell expansion (windows).
       
  1264     pctx = repo[None]
       
  1265     match, pats = scmutil.matchandpats(pctx, pats, opts)
       
  1266     slowpath = match.anypats() or (match.files() and opts.get('removed'))
       
  1267     if not slowpath:
       
  1268         for f in match.files():
       
  1269             if follow and f not in pctx:
       
  1270                 raise util.Abort(_('cannot follow file not in parent '
       
  1271                                    'revision: "%s"') % f)
       
  1272             filelog = repo.file(f)
       
  1273             if not len(filelog):
       
  1274                 # A zero count may be a directory or deleted file, so
       
  1275                 # try to find matching entries on the slow path.
       
  1276                 if follow:
       
  1277                     raise util.Abort(
       
  1278                         _('cannot follow nonexistent file: "%s"') % f)
       
  1279                 slowpath = True
       
  1280     if slowpath:
       
  1281         # See walkchangerevs() slow path.
       
  1282         #
       
  1283         if follow:
       
  1284             raise util.Abort(_('can only follow copies/renames for explicit '
       
  1285                                'filenames'))
       
  1286         # pats/include/exclude cannot be represented as separate
       
  1287         # revset expressions as their filtering logic applies at file
       
  1288         # level. For instance "-I a -X a" matches a revision touching
       
  1289         # "a" and "b" while "file(a) and not file(b)" does
       
  1290         # not. Besides, filesets are evaluated against the working
       
  1291         # directory.
       
  1292         matchargs = ['r:', 'd:relpath']
       
  1293         for p in pats:
       
  1294             matchargs.append('p:' + p)
       
  1295         for p in opts.get('include', []):
       
  1296             matchargs.append('i:' + p)
       
  1297         for p in opts.get('exclude', []):
       
  1298             matchargs.append('x:' + p)
       
  1299         matchargs = ','.join(('%r' % p) for p in matchargs)
       
  1300         opts['_matchfiles'] = matchargs
       
  1301     else:
       
  1302         if follow:
       
  1303             fpats = ('_patsfollow', '_patsfollowfirst')
       
  1304             fnopats = (('_ancestors', '_fancestors'),
       
  1305                        ('_descendants', '_fdescendants'))
       
  1306             if pats:
       
  1307                 # follow() revset inteprets its file argument as a
       
  1308                 # manifest entry, so use match.files(), not pats.
       
  1309                 opts[fpats[followfirst]] = list(match.files())
       
  1310             else:
       
  1311                 opts[fnopats[followdescendants][followfirst]] = str(startrev)
       
  1312         else:
       
  1313             opts['_patslog'] = list(pats)
       
  1314 
       
  1315     filematcher = None
       
  1316     if opts.get('patch') or opts.get('stat'):
       
  1317         if follow:
       
  1318             filematcher = _makegraphfilematcher(repo, pats, followfirst)
       
  1319         else:
       
  1320             filematcher = lambda rev: match
       
  1321 
       
  1322     expr = []
       
  1323     for op, val in opts.iteritems():
       
  1324         if not val:
       
  1325             continue
       
  1326         if op not in opt2revset:
       
  1327             continue
       
  1328         revop, andor = opt2revset[op]
       
  1329         if '%(val)' not in revop:
       
  1330             expr.append(revop)
       
  1331         else:
       
  1332             if not isinstance(val, list):
       
  1333                 e = revop % {'val': val}
       
  1334             else:
       
  1335                 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
       
  1336             expr.append(e)
       
  1337 
       
  1338     if expr:
       
  1339         expr = '(' + ' and '.join(expr) + ')'
       
  1340     else:
       
  1341         expr = None
       
  1342     return expr, filematcher
       
  1343 
       
  1344 def getgraphlogrevs(repo, pats, opts):
       
  1345     """Return (revs, expr, filematcher) where revs is an iterable of
       
  1346     revision numbers, expr is a revset string built from log options
       
  1347     and file patterns or None, and used to filter 'revs'. If --stat or
       
  1348     --patch are not passed filematcher is None. Otherwise it is a
       
  1349     callable taking a revision number and returning a match objects
       
  1350     filtering the files to be detailed when displaying the revision.
       
  1351     """
       
  1352     def increasingrevs(repo, revs, matcher):
       
  1353         # The sorted input rev sequence is chopped in sub-sequences
       
  1354         # which are sorted in ascending order and passed to the
       
  1355         # matcher. The filtered revs are sorted again as they were in
       
  1356         # the original sub-sequence. This achieve several things:
       
  1357         #
       
  1358         # - getlogrevs() now returns a generator which behaviour is
       
  1359         #   adapted to log need. First results come fast, last ones
       
  1360         #   are batched for performances.
       
  1361         #
       
  1362         # - revset matchers often operate faster on revision in
       
  1363         #   changelog order, because most filters deal with the
       
  1364         #   changelog.
       
  1365         #
       
  1366         # - revset matchers can reorder revisions. "A or B" typically
       
  1367         #   returns returns the revision matching A then the revision
       
  1368         #   matching B. We want to hide this internal implementation
       
  1369         #   detail from the caller, and sorting the filtered revision
       
  1370         #   again achieves this.
       
  1371         for i, window in increasingwindows(0, len(revs), windowsize=1):
       
  1372             orevs = revs[i:i + window]
       
  1373             nrevs = set(matcher(repo, sorted(orevs)))
       
  1374             for rev in orevs:
       
  1375                 if rev in nrevs:
       
  1376                     yield rev
       
  1377 
       
  1378     if not len(repo):
       
  1379         return iter([]), None, None
       
  1380     # Default --rev value depends on --follow but --follow behaviour
       
  1381     # depends on revisions resolved from --rev...
       
  1382     follow = opts.get('follow') or opts.get('follow_first')
       
  1383     if opts.get('rev'):
       
  1384         revs = scmutil.revrange(repo, opts['rev'])
       
  1385     else:
       
  1386         if follow and len(repo) > 0:
       
  1387             revs = scmutil.revrange(repo, ['.:0'])
       
  1388         else:
       
  1389             revs = range(len(repo) - 1, -1, -1)
       
  1390     if not revs:
       
  1391         return iter([]), None, None
       
  1392     expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
       
  1393     if expr:
       
  1394         matcher = revset.match(repo.ui, expr)
       
  1395         revs = increasingrevs(repo, revs, matcher)
       
  1396     if not opts.get('hidden'):
       
  1397         # --hidden is still experimental and not worth a dedicated revset
       
  1398         # yet. Fortunately, filtering revision number is fast.
       
  1399         revs = (r for r in revs if r not in repo.changelog.hiddenrevs)
       
  1400     else:
       
  1401         revs = iter(revs)
       
  1402     return revs, expr, filematcher
       
  1403 
       
  1404 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
       
  1405                  filematcher=None):
       
  1406     seen, state = [], graphmod.asciistate()
       
  1407     for rev, type, ctx, parents in dag:
       
  1408         char = 'o'
       
  1409         if ctx.node() in showparents:
       
  1410             char = '@'
       
  1411         elif ctx.obsolete():
       
  1412             char = 'x'
       
  1413         copies = None
       
  1414         if getrenamed and ctx.rev():
       
  1415             copies = []
       
  1416             for fn in ctx.files():
       
  1417                 rename = getrenamed(fn, ctx.rev())
       
  1418                 if rename:
       
  1419                     copies.append((fn, rename[0]))
       
  1420         revmatchfn = None
       
  1421         if filematcher is not None:
       
  1422             revmatchfn = filematcher(ctx.rev())
       
  1423         displayer.show(ctx, copies=copies, matchfn=revmatchfn)
       
  1424         lines = displayer.hunk.pop(rev).split('\n')
       
  1425         if not lines[-1]:
       
  1426             del lines[-1]
       
  1427         displayer.flush(rev)
       
  1428         edges = edgefn(type, char, lines, seen, rev, parents)
       
  1429         for type, char, lines, coldata in edges:
       
  1430             graphmod.ascii(ui, state, type, char, lines, coldata)
       
  1431     displayer.close()
  1194 
  1432 
  1195 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
  1433 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
  1196     join = lambda f: os.path.join(prefix, f)
  1434     join = lambda f: os.path.join(prefix, f)
  1197     bad = []
  1435     bad = []
  1198     oldbad = match.bad
  1436     oldbad = match.bad