PageRenderTime 37ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/mercurial/cmdutil.py

https://bitbucket.org/mirror/mercurial/
Python | 2597 lines | 2389 code | 90 blank | 118 comment | 277 complexity | f61e4de40a70f384fd0447d92e9866dd MD5 | raw file
Possible License(s): GPL-2.0
  1. # cmdutil.py - help for command processing in mercurial
  2. #
  3. # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. from node import hex, nullid, nullrev, short
  8. from i18n import _
  9. import os, sys, errno, re, tempfile
  10. import util, scmutil, templater, patch, error, templatekw, revlog, copies
  11. import match as matchmod
  12. import context, repair, graphmod, revset, phases, obsolete, pathutil
  13. import changelog
  14. import bookmarks
  15. import lock as lockmod
  16. def parsealiases(cmd):
  17. return cmd.lstrip("^").split("|")
  18. def findpossible(cmd, table, strict=False):
  19. """
  20. Return cmd -> (aliases, command table entry)
  21. for each matching command.
  22. Return debug commands (or their aliases) only if no normal command matches.
  23. """
  24. choice = {}
  25. debugchoice = {}
  26. if cmd in table:
  27. # short-circuit exact matches, "log" alias beats "^log|history"
  28. keys = [cmd]
  29. else:
  30. keys = table.keys()
  31. for e in keys:
  32. aliases = parsealiases(e)
  33. found = None
  34. if cmd in aliases:
  35. found = cmd
  36. elif not strict:
  37. for a in aliases:
  38. if a.startswith(cmd):
  39. found = a
  40. break
  41. if found is not None:
  42. if aliases[0].startswith("debug") or found.startswith("debug"):
  43. debugchoice[found] = (aliases, table[e])
  44. else:
  45. choice[found] = (aliases, table[e])
  46. if not choice and debugchoice:
  47. choice = debugchoice
  48. return choice
  49. def findcmd(cmd, table, strict=True):
  50. """Return (aliases, command table entry) for command string."""
  51. choice = findpossible(cmd, table, strict)
  52. if cmd in choice:
  53. return choice[cmd]
  54. if len(choice) > 1:
  55. clist = choice.keys()
  56. clist.sort()
  57. raise error.AmbiguousCommand(cmd, clist)
  58. if choice:
  59. return choice.values()[0]
  60. raise error.UnknownCommand(cmd)
  61. def findrepo(p):
  62. while not os.path.isdir(os.path.join(p, ".hg")):
  63. oldp, p = p, os.path.dirname(p)
  64. if p == oldp:
  65. return None
  66. return p
  67. def bailifchanged(repo):
  68. if repo.dirstate.p2() != nullid:
  69. raise util.Abort(_('outstanding uncommitted merge'))
  70. modified, added, removed, deleted = repo.status()[:4]
  71. if modified or added or removed or deleted:
  72. raise util.Abort(_('uncommitted changes'))
  73. ctx = repo[None]
  74. for s in sorted(ctx.substate):
  75. if ctx.sub(s).dirty():
  76. raise util.Abort(_("uncommitted changes in subrepo %s") % s)
  77. def logmessage(ui, opts):
  78. """ get the log message according to -m and -l option """
  79. message = opts.get('message')
  80. logfile = opts.get('logfile')
  81. if message and logfile:
  82. raise util.Abort(_('options --message and --logfile are mutually '
  83. 'exclusive'))
  84. if not message and logfile:
  85. try:
  86. if logfile == '-':
  87. message = ui.fin.read()
  88. else:
  89. message = '\n'.join(util.readfile(logfile).splitlines())
  90. except IOError, inst:
  91. raise util.Abort(_("can't read commit message '%s': %s") %
  92. (logfile, inst.strerror))
  93. return message
  94. def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
  95. """get appropriate commit message editor according to '--edit' option
  96. 'finishdesc' is a function to be called with edited commit message
  97. (= 'description' of the new changeset) just after editing, but
  98. before checking empty-ness. It should return actual text to be
  99. stored into history. This allows to change description before
  100. storing.
  101. 'extramsg' is a extra message to be shown in the editor instead of
  102. 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
  103. is automatically added.
  104. 'getcommiteditor' returns 'commitforceeditor' regardless of
  105. 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
  106. they are specific for usage in MQ.
  107. """
  108. if edit or finishdesc or extramsg:
  109. return lambda r, c, s: commitforceeditor(r, c, s,
  110. finishdesc=finishdesc,
  111. extramsg=extramsg)
  112. else:
  113. return commiteditor
  114. def loglimit(opts):
  115. """get the log limit according to option -l/--limit"""
  116. limit = opts.get('limit')
  117. if limit:
  118. try:
  119. limit = int(limit)
  120. except ValueError:
  121. raise util.Abort(_('limit must be a positive integer'))
  122. if limit <= 0:
  123. raise util.Abort(_('limit must be positive'))
  124. else:
  125. limit = None
  126. return limit
  127. def makefilename(repo, pat, node, desc=None,
  128. total=None, seqno=None, revwidth=None, pathname=None):
  129. node_expander = {
  130. 'H': lambda: hex(node),
  131. 'R': lambda: str(repo.changelog.rev(node)),
  132. 'h': lambda: short(node),
  133. 'm': lambda: re.sub('[^\w]', '_', str(desc))
  134. }
  135. expander = {
  136. '%': lambda: '%',
  137. 'b': lambda: os.path.basename(repo.root),
  138. }
  139. try:
  140. if node:
  141. expander.update(node_expander)
  142. if node:
  143. expander['r'] = (lambda:
  144. str(repo.changelog.rev(node)).zfill(revwidth or 0))
  145. if total is not None:
  146. expander['N'] = lambda: str(total)
  147. if seqno is not None:
  148. expander['n'] = lambda: str(seqno)
  149. if total is not None and seqno is not None:
  150. expander['n'] = lambda: str(seqno).zfill(len(str(total)))
  151. if pathname is not None:
  152. expander['s'] = lambda: os.path.basename(pathname)
  153. expander['d'] = lambda: os.path.dirname(pathname) or '.'
  154. expander['p'] = lambda: pathname
  155. newname = []
  156. patlen = len(pat)
  157. i = 0
  158. while i < patlen:
  159. c = pat[i]
  160. if c == '%':
  161. i += 1
  162. c = pat[i]
  163. c = expander[c]()
  164. newname.append(c)
  165. i += 1
  166. return ''.join(newname)
  167. except KeyError, inst:
  168. raise util.Abort(_("invalid format spec '%%%s' in output filename") %
  169. inst.args[0])
  170. def makefileobj(repo, pat, node=None, desc=None, total=None,
  171. seqno=None, revwidth=None, mode='wb', modemap=None,
  172. pathname=None):
  173. writable = mode not in ('r', 'rb')
  174. if not pat or pat == '-':
  175. fp = writable and repo.ui.fout or repo.ui.fin
  176. if util.safehasattr(fp, 'fileno'):
  177. return os.fdopen(os.dup(fp.fileno()), mode)
  178. else:
  179. # if this fp can't be duped properly, return
  180. # a dummy object that can be closed
  181. class wrappedfileobj(object):
  182. noop = lambda x: None
  183. def __init__(self, f):
  184. self.f = f
  185. def __getattr__(self, attr):
  186. if attr == 'close':
  187. return self.noop
  188. else:
  189. return getattr(self.f, attr)
  190. return wrappedfileobj(fp)
  191. if util.safehasattr(pat, 'write') and writable:
  192. return pat
  193. if util.safehasattr(pat, 'read') and 'r' in mode:
  194. return pat
  195. fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
  196. if modemap is not None:
  197. mode = modemap.get(fn, mode)
  198. if mode == 'wb':
  199. modemap[fn] = 'ab'
  200. return open(fn, mode)
  201. def openrevlog(repo, cmd, file_, opts):
  202. """opens the changelog, manifest, a filelog or a given revlog"""
  203. cl = opts['changelog']
  204. mf = opts['manifest']
  205. msg = None
  206. if cl and mf:
  207. msg = _('cannot specify --changelog and --manifest at the same time')
  208. elif cl or mf:
  209. if file_:
  210. msg = _('cannot specify filename with --changelog or --manifest')
  211. elif not repo:
  212. msg = _('cannot specify --changelog or --manifest '
  213. 'without a repository')
  214. if msg:
  215. raise util.Abort(msg)
  216. r = None
  217. if repo:
  218. if cl:
  219. r = repo.unfiltered().changelog
  220. elif mf:
  221. r = repo.manifest
  222. elif file_:
  223. filelog = repo.file(file_)
  224. if len(filelog):
  225. r = filelog
  226. if not r:
  227. if not file_:
  228. raise error.CommandError(cmd, _('invalid arguments'))
  229. if not os.path.isfile(file_):
  230. raise util.Abort(_("revlog '%s' not found") % file_)
  231. r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
  232. file_[:-2] + ".i")
  233. return r
  234. def copy(ui, repo, pats, opts, rename=False):
  235. # called with the repo lock held
  236. #
  237. # hgsep => pathname that uses "/" to separate directories
  238. # ossep => pathname that uses os.sep to separate directories
  239. cwd = repo.getcwd()
  240. targets = {}
  241. after = opts.get("after")
  242. dryrun = opts.get("dry_run")
  243. wctx = repo[None]
  244. def walkpat(pat):
  245. srcs = []
  246. badstates = after and '?' or '?r'
  247. m = scmutil.match(repo[None], [pat], opts, globbed=True)
  248. for abs in repo.walk(m):
  249. state = repo.dirstate[abs]
  250. rel = m.rel(abs)
  251. exact = m.exact(abs)
  252. if state in badstates:
  253. if exact and state == '?':
  254. ui.warn(_('%s: not copying - file is not managed\n') % rel)
  255. if exact and state == 'r':
  256. ui.warn(_('%s: not copying - file has been marked for'
  257. ' remove\n') % rel)
  258. continue
  259. # abs: hgsep
  260. # rel: ossep
  261. srcs.append((abs, rel, exact))
  262. return srcs
  263. # abssrc: hgsep
  264. # relsrc: ossep
  265. # otarget: ossep
  266. def copyfile(abssrc, relsrc, otarget, exact):
  267. abstarget = pathutil.canonpath(repo.root, cwd, otarget)
  268. if '/' in abstarget:
  269. # We cannot normalize abstarget itself, this would prevent
  270. # case only renames, like a => A.
  271. abspath, absname = abstarget.rsplit('/', 1)
  272. abstarget = repo.dirstate.normalize(abspath) + '/' + absname
  273. reltarget = repo.pathto(abstarget, cwd)
  274. target = repo.wjoin(abstarget)
  275. src = repo.wjoin(abssrc)
  276. state = repo.dirstate[abstarget]
  277. scmutil.checkportable(ui, abstarget)
  278. # check for collisions
  279. prevsrc = targets.get(abstarget)
  280. if prevsrc is not None:
  281. ui.warn(_('%s: not overwriting - %s collides with %s\n') %
  282. (reltarget, repo.pathto(abssrc, cwd),
  283. repo.pathto(prevsrc, cwd)))
  284. return
  285. # check for overwrites
  286. exists = os.path.lexists(target)
  287. samefile = False
  288. if exists and abssrc != abstarget:
  289. if (repo.dirstate.normalize(abssrc) ==
  290. repo.dirstate.normalize(abstarget)):
  291. if not rename:
  292. ui.warn(_("%s: can't copy - same file\n") % reltarget)
  293. return
  294. exists = False
  295. samefile = True
  296. if not after and exists or after and state in 'mn':
  297. if not opts['force']:
  298. ui.warn(_('%s: not overwriting - file exists\n') %
  299. reltarget)
  300. return
  301. if after:
  302. if not exists:
  303. if rename:
  304. ui.warn(_('%s: not recording move - %s does not exist\n') %
  305. (relsrc, reltarget))
  306. else:
  307. ui.warn(_('%s: not recording copy - %s does not exist\n') %
  308. (relsrc, reltarget))
  309. return
  310. elif not dryrun:
  311. try:
  312. if exists:
  313. os.unlink(target)
  314. targetdir = os.path.dirname(target) or '.'
  315. if not os.path.isdir(targetdir):
  316. os.makedirs(targetdir)
  317. if samefile:
  318. tmp = target + "~hgrename"
  319. os.rename(src, tmp)
  320. os.rename(tmp, target)
  321. else:
  322. util.copyfile(src, target)
  323. srcexists = True
  324. except IOError, inst:
  325. if inst.errno == errno.ENOENT:
  326. ui.warn(_('%s: deleted in working copy\n') % relsrc)
  327. srcexists = False
  328. else:
  329. ui.warn(_('%s: cannot copy - %s\n') %
  330. (relsrc, inst.strerror))
  331. return True # report a failure
  332. if ui.verbose or not exact:
  333. if rename:
  334. ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
  335. else:
  336. ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
  337. targets[abstarget] = abssrc
  338. # fix up dirstate
  339. scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
  340. dryrun=dryrun, cwd=cwd)
  341. if rename and not dryrun:
  342. if not after and srcexists and not samefile:
  343. util.unlinkpath(repo.wjoin(abssrc))
  344. wctx.forget([abssrc])
  345. # pat: ossep
  346. # dest ossep
  347. # srcs: list of (hgsep, hgsep, ossep, bool)
  348. # return: function that takes hgsep and returns ossep
  349. def targetpathfn(pat, dest, srcs):
  350. if os.path.isdir(pat):
  351. abspfx = pathutil.canonpath(repo.root, cwd, pat)
  352. abspfx = util.localpath(abspfx)
  353. if destdirexists:
  354. striplen = len(os.path.split(abspfx)[0])
  355. else:
  356. striplen = len(abspfx)
  357. if striplen:
  358. striplen += len(os.sep)
  359. res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
  360. elif destdirexists:
  361. res = lambda p: os.path.join(dest,
  362. os.path.basename(util.localpath(p)))
  363. else:
  364. res = lambda p: dest
  365. return res
  366. # pat: ossep
  367. # dest ossep
  368. # srcs: list of (hgsep, hgsep, ossep, bool)
  369. # return: function that takes hgsep and returns ossep
  370. def targetpathafterfn(pat, dest, srcs):
  371. if matchmod.patkind(pat):
  372. # a mercurial pattern
  373. res = lambda p: os.path.join(dest,
  374. os.path.basename(util.localpath(p)))
  375. else:
  376. abspfx = pathutil.canonpath(repo.root, cwd, pat)
  377. if len(abspfx) < len(srcs[0][0]):
  378. # A directory. Either the target path contains the last
  379. # component of the source path or it does not.
  380. def evalpath(striplen):
  381. score = 0
  382. for s in srcs:
  383. t = os.path.join(dest, util.localpath(s[0])[striplen:])
  384. if os.path.lexists(t):
  385. score += 1
  386. return score
  387. abspfx = util.localpath(abspfx)
  388. striplen = len(abspfx)
  389. if striplen:
  390. striplen += len(os.sep)
  391. if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
  392. score = evalpath(striplen)
  393. striplen1 = len(os.path.split(abspfx)[0])
  394. if striplen1:
  395. striplen1 += len(os.sep)
  396. if evalpath(striplen1) > score:
  397. striplen = striplen1
  398. res = lambda p: os.path.join(dest,
  399. util.localpath(p)[striplen:])
  400. else:
  401. # a file
  402. if destdirexists:
  403. res = lambda p: os.path.join(dest,
  404. os.path.basename(util.localpath(p)))
  405. else:
  406. res = lambda p: dest
  407. return res
  408. pats = scmutil.expandpats(pats)
  409. if not pats:
  410. raise util.Abort(_('no source or destination specified'))
  411. if len(pats) == 1:
  412. raise util.Abort(_('no destination specified'))
  413. dest = pats.pop()
  414. destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
  415. if not destdirexists:
  416. if len(pats) > 1 or matchmod.patkind(pats[0]):
  417. raise util.Abort(_('with multiple sources, destination must be an '
  418. 'existing directory'))
  419. if util.endswithsep(dest):
  420. raise util.Abort(_('destination %s is not a directory') % dest)
  421. tfn = targetpathfn
  422. if after:
  423. tfn = targetpathafterfn
  424. copylist = []
  425. for pat in pats:
  426. srcs = walkpat(pat)
  427. if not srcs:
  428. continue
  429. copylist.append((tfn(pat, dest, srcs), srcs))
  430. if not copylist:
  431. raise util.Abort(_('no files to copy'))
  432. errors = 0
  433. for targetpath, srcs in copylist:
  434. for abssrc, relsrc, exact in srcs:
  435. if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
  436. errors += 1
  437. if errors:
  438. ui.warn(_('(consider using --after)\n'))
  439. return errors != 0
  440. def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
  441. runargs=None, appendpid=False):
  442. '''Run a command as a service.'''
  443. def writepid(pid):
  444. if opts['pid_file']:
  445. mode = appendpid and 'a' or 'w'
  446. fp = open(opts['pid_file'], mode)
  447. fp.write(str(pid) + '\n')
  448. fp.close()
  449. if opts['daemon'] and not opts['daemon_pipefds']:
  450. # Signal child process startup with file removal
  451. lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
  452. os.close(lockfd)
  453. try:
  454. if not runargs:
  455. runargs = util.hgcmd() + sys.argv[1:]
  456. runargs.append('--daemon-pipefds=%s' % lockpath)
  457. # Don't pass --cwd to the child process, because we've already
  458. # changed directory.
  459. for i in xrange(1, len(runargs)):
  460. if runargs[i].startswith('--cwd='):
  461. del runargs[i]
  462. break
  463. elif runargs[i].startswith('--cwd'):
  464. del runargs[i:i + 2]
  465. break
  466. def condfn():
  467. return not os.path.exists(lockpath)
  468. pid = util.rundetached(runargs, condfn)
  469. if pid < 0:
  470. raise util.Abort(_('child process failed to start'))
  471. writepid(pid)
  472. finally:
  473. try:
  474. os.unlink(lockpath)
  475. except OSError, e:
  476. if e.errno != errno.ENOENT:
  477. raise
  478. if parentfn:
  479. return parentfn(pid)
  480. else:
  481. return
  482. if initfn:
  483. initfn()
  484. if not opts['daemon']:
  485. writepid(os.getpid())
  486. if opts['daemon_pipefds']:
  487. lockpath = opts['daemon_pipefds']
  488. try:
  489. os.setsid()
  490. except AttributeError:
  491. pass
  492. os.unlink(lockpath)
  493. util.hidewindow()
  494. sys.stdout.flush()
  495. sys.stderr.flush()
  496. nullfd = os.open(os.devnull, os.O_RDWR)
  497. logfilefd = nullfd
  498. if logfile:
  499. logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
  500. os.dup2(nullfd, 0)
  501. os.dup2(logfilefd, 1)
  502. os.dup2(logfilefd, 2)
  503. if nullfd not in (0, 1, 2):
  504. os.close(nullfd)
  505. if logfile and logfilefd not in (0, 1, 2):
  506. os.close(logfilefd)
  507. if runfn:
  508. return runfn()
  509. def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
  510. """Utility function used by commands.import to import a single patch
  511. This function is explicitly defined here to help the evolve extension to
  512. wrap this part of the import logic.
  513. The API is currently a bit ugly because it a simple code translation from
  514. the import command. Feel free to make it better.
  515. :hunk: a patch (as a binary string)
  516. :parents: nodes that will be parent of the created commit
  517. :opts: the full dict of option passed to the import command
  518. :msgs: list to save commit message to.
  519. (used in case we need to save it when failing)
  520. :updatefunc: a function that update a repo to a given node
  521. updatefunc(<repo>, <node>)
  522. """
  523. tmpname, message, user, date, branch, nodeid, p1, p2 = \
  524. patch.extract(ui, hunk)
  525. editor = getcommiteditor(**opts)
  526. update = not opts.get('bypass')
  527. strip = opts["strip"]
  528. sim = float(opts.get('similarity') or 0)
  529. if not tmpname:
  530. return (None, None, False)
  531. msg = _('applied to working directory')
  532. rejects = False
  533. try:
  534. cmdline_message = logmessage(ui, opts)
  535. if cmdline_message:
  536. # pickup the cmdline msg
  537. message = cmdline_message
  538. elif message:
  539. # pickup the patch msg
  540. message = message.strip()
  541. else:
  542. # launch the editor
  543. message = None
  544. ui.debug('message:\n%s\n' % message)
  545. if len(parents) == 1:
  546. parents.append(repo[nullid])
  547. if opts.get('exact'):
  548. if not nodeid or not p1:
  549. raise util.Abort(_('not a Mercurial patch'))
  550. p1 = repo[p1]
  551. p2 = repo[p2 or nullid]
  552. elif p2:
  553. try:
  554. p1 = repo[p1]
  555. p2 = repo[p2]
  556. # Without any options, consider p2 only if the
  557. # patch is being applied on top of the recorded
  558. # first parent.
  559. if p1 != parents[0]:
  560. p1 = parents[0]
  561. p2 = repo[nullid]
  562. except error.RepoError:
  563. p1, p2 = parents
  564. else:
  565. p1, p2 = parents
  566. n = None
  567. if update:
  568. if p1 != parents[0]:
  569. updatefunc(repo, p1.node())
  570. if p2 != parents[1]:
  571. repo.setparents(p1.node(), p2.node())
  572. if opts.get('exact') or opts.get('import_branch'):
  573. repo.dirstate.setbranch(branch or 'default')
  574. partial = opts.get('partial', False)
  575. files = set()
  576. try:
  577. patch.patch(ui, repo, tmpname, strip=strip, files=files,
  578. eolmode=None, similarity=sim / 100.0)
  579. except patch.PatchError, e:
  580. if not partial:
  581. raise util.Abort(str(e))
  582. if partial:
  583. rejects = True
  584. files = list(files)
  585. if opts.get('no_commit'):
  586. if message:
  587. msgs.append(message)
  588. else:
  589. if opts.get('exact') or p2:
  590. # If you got here, you either use --force and know what
  591. # you are doing or used --exact or a merge patch while
  592. # being updated to its first parent.
  593. m = None
  594. else:
  595. m = scmutil.matchfiles(repo, files or [])
  596. n = repo.commit(message, opts.get('user') or user,
  597. opts.get('date') or date, match=m,
  598. editor=editor, force=partial)
  599. else:
  600. if opts.get('exact') or opts.get('import_branch'):
  601. branch = branch or 'default'
  602. else:
  603. branch = p1.branch()
  604. store = patch.filestore()
  605. try:
  606. files = set()
  607. try:
  608. patch.patchrepo(ui, repo, p1, store, tmpname, strip,
  609. files, eolmode=None)
  610. except patch.PatchError, e:
  611. raise util.Abort(str(e))
  612. memctx = context.makememctx(repo, (p1.node(), p2.node()),
  613. message,
  614. opts.get('user') or user,
  615. opts.get('date') or date,
  616. branch, files, store,
  617. editor=getcommiteditor())
  618. n = memctx.commit()
  619. finally:
  620. store.close()
  621. if opts.get('exact') and hex(n) != nodeid:
  622. raise util.Abort(_('patch is damaged or loses information'))
  623. if n:
  624. # i18n: refers to a short changeset id
  625. msg = _('created %s') % short(n)
  626. return (msg, n, rejects)
  627. finally:
  628. os.unlink(tmpname)
  629. def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
  630. opts=None):
  631. '''export changesets as hg patches.'''
  632. total = len(revs)
  633. revwidth = max([len(str(rev)) for rev in revs])
  634. filemode = {}
  635. def single(rev, seqno, fp):
  636. ctx = repo[rev]
  637. node = ctx.node()
  638. parents = [p.node() for p in ctx.parents() if p]
  639. branch = ctx.branch()
  640. if switch_parent:
  641. parents.reverse()
  642. prev = (parents and parents[0]) or nullid
  643. shouldclose = False
  644. if not fp and len(template) > 0:
  645. desc_lines = ctx.description().rstrip().split('\n')
  646. desc = desc_lines[0] #Commit always has a first line.
  647. fp = makefileobj(repo, template, node, desc=desc, total=total,
  648. seqno=seqno, revwidth=revwidth, mode='wb',
  649. modemap=filemode)
  650. if fp != template:
  651. shouldclose = True
  652. if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
  653. repo.ui.note("%s\n" % fp.name)
  654. if not fp:
  655. write = repo.ui.write
  656. else:
  657. def write(s, **kw):
  658. fp.write(s)
  659. write("# HG changeset patch\n")
  660. write("# User %s\n" % ctx.user())
  661. write("# Date %d %d\n" % ctx.date())
  662. write("# %s\n" % util.datestr(ctx.date()))
  663. if branch and branch != 'default':
  664. write("# Branch %s\n" % branch)
  665. write("# Node ID %s\n" % hex(node))
  666. write("# Parent %s\n" % hex(prev))
  667. if len(parents) > 1:
  668. write("# Parent %s\n" % hex(parents[1]))
  669. write(ctx.description().rstrip())
  670. write("\n\n")
  671. for chunk, label in patch.diffui(repo, prev, node, opts=opts):
  672. write(chunk, label=label)
  673. if shouldclose:
  674. fp.close()
  675. for seqno, rev in enumerate(revs):
  676. single(rev, seqno + 1, fp)
  677. def diffordiffstat(ui, repo, diffopts, node1, node2, match,
  678. changes=None, stat=False, fp=None, prefix='',
  679. listsubrepos=False):
  680. '''show diff or diffstat.'''
  681. if fp is None:
  682. write = ui.write
  683. else:
  684. def write(s, **kw):
  685. fp.write(s)
  686. if stat:
  687. diffopts = diffopts.copy(context=0)
  688. width = 80
  689. if not ui.plain():
  690. width = ui.termwidth()
  691. chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
  692. prefix=prefix)
  693. for chunk, label in patch.diffstatui(util.iterlines(chunks),
  694. width=width,
  695. git=diffopts.git):
  696. write(chunk, label=label)
  697. else:
  698. for chunk, label in patch.diffui(repo, node1, node2, match,
  699. changes, diffopts, prefix=prefix):
  700. write(chunk, label=label)
  701. if listsubrepos:
  702. ctx1 = repo[node1]
  703. ctx2 = repo[node2]
  704. for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
  705. tempnode2 = node2
  706. try:
  707. if node2 is not None:
  708. tempnode2 = ctx2.substate[subpath][1]
  709. except KeyError:
  710. # A subrepo that existed in node1 was deleted between node1 and
  711. # node2 (inclusive). Thus, ctx2's substate won't contain that
  712. # subpath. The best we can do is to ignore it.
  713. tempnode2 = None
  714. submatch = matchmod.narrowmatcher(subpath, match)
  715. sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
  716. stat=stat, fp=fp, prefix=prefix)
  717. class changeset_printer(object):
  718. '''show changeset information when templating not requested.'''
  719. def __init__(self, ui, repo, patch, diffopts, buffered):
  720. self.ui = ui
  721. self.repo = repo
  722. self.buffered = buffered
  723. self.patch = patch
  724. self.diffopts = diffopts
  725. self.header = {}
  726. self.hunk = {}
  727. self.lastheader = None
  728. self.footer = None
  729. def flush(self, rev):
  730. if rev in self.header:
  731. h = self.header[rev]
  732. if h != self.lastheader:
  733. self.lastheader = h
  734. self.ui.write(h)
  735. del self.header[rev]
  736. if rev in self.hunk:
  737. self.ui.write(self.hunk[rev])
  738. del self.hunk[rev]
  739. return 1
  740. return 0
  741. def close(self):
  742. if self.footer:
  743. self.ui.write(self.footer)
  744. def show(self, ctx, copies=None, matchfn=None, **props):
  745. if self.buffered:
  746. self.ui.pushbuffer()
  747. self._show(ctx, copies, matchfn, props)
  748. self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
  749. else:
  750. self._show(ctx, copies, matchfn, props)
  751. def _show(self, ctx, copies, matchfn, props):
  752. '''show a single changeset or file revision'''
  753. changenode = ctx.node()
  754. rev = ctx.rev()
  755. if self.ui.quiet:
  756. self.ui.write("%d:%s\n" % (rev, short(changenode)),
  757. label='log.node')
  758. return
  759. log = self.repo.changelog
  760. date = util.datestr(ctx.date())
  761. hexfunc = self.ui.debugflag and hex or short
  762. parents = [(p, hexfunc(log.node(p)))
  763. for p in self._meaningful_parentrevs(log, rev)]
  764. # i18n: column positioning for "hg log"
  765. self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
  766. label='log.changeset changeset.%s' % ctx.phasestr())
  767. branch = ctx.branch()
  768. # don't show the default branch name
  769. if branch != 'default':
  770. # i18n: column positioning for "hg log"
  771. self.ui.write(_("branch: %s\n") % branch,
  772. label='log.branch')
  773. for bookmark in self.repo.nodebookmarks(changenode):
  774. # i18n: column positioning for "hg log"
  775. self.ui.write(_("bookmark: %s\n") % bookmark,
  776. label='log.bookmark')
  777. for tag in self.repo.nodetags(changenode):
  778. # i18n: column positioning for "hg log"
  779. self.ui.write(_("tag: %s\n") % tag,
  780. label='log.tag')
  781. if self.ui.debugflag and ctx.phase():
  782. # i18n: column positioning for "hg log"
  783. self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
  784. label='log.phase')
  785. for parent in parents:
  786. # i18n: column positioning for "hg log"
  787. self.ui.write(_("parent: %d:%s\n") % parent,
  788. label='log.parent changeset.%s' % ctx.phasestr())
  789. if self.ui.debugflag:
  790. mnode = ctx.manifestnode()
  791. # i18n: column positioning for "hg log"
  792. self.ui.write(_("manifest: %d:%s\n") %
  793. (self.repo.manifest.rev(mnode), hex(mnode)),
  794. label='ui.debug log.manifest')
  795. # i18n: column positioning for "hg log"
  796. self.ui.write(_("user: %s\n") % ctx.user(),
  797. label='log.user')
  798. # i18n: column positioning for "hg log"
  799. self.ui.write(_("date: %s\n") % date,
  800. label='log.date')
  801. if self.ui.debugflag:
  802. files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
  803. for key, value in zip([# i18n: column positioning for "hg log"
  804. _("files:"),
  805. # i18n: column positioning for "hg log"
  806. _("files+:"),
  807. # i18n: column positioning for "hg log"
  808. _("files-:")], files):
  809. if value:
  810. self.ui.write("%-12s %s\n" % (key, " ".join(value)),
  811. label='ui.debug log.files')
  812. elif ctx.files() and self.ui.verbose:
  813. # i18n: column positioning for "hg log"
  814. self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
  815. label='ui.note log.files')
  816. if copies and self.ui.verbose:
  817. copies = ['%s (%s)' % c for c in copies]
  818. # i18n: column positioning for "hg log"
  819. self.ui.write(_("copies: %s\n") % ' '.join(copies),
  820. label='ui.note log.copies')
  821. extra = ctx.extra()
  822. if extra and self.ui.debugflag:
  823. for key, value in sorted(extra.items()):
  824. # i18n: column positioning for "hg log"
  825. self.ui.write(_("extra: %s=%s\n")
  826. % (key, value.encode('string_escape')),
  827. label='ui.debug log.extra')
  828. description = ctx.description().strip()
  829. if description:
  830. if self.ui.verbose:
  831. self.ui.write(_("description:\n"),
  832. label='ui.note log.description')
  833. self.ui.write(description,
  834. label='ui.note log.description')
  835. self.ui.write("\n\n")
  836. else:
  837. # i18n: column positioning for "hg log"
  838. self.ui.write(_("summary: %s\n") %
  839. description.splitlines()[0],
  840. label='log.summary')
  841. self.ui.write("\n")
  842. self.showpatch(changenode, matchfn)
  843. def showpatch(self, node, matchfn):
  844. if not matchfn:
  845. matchfn = self.patch
  846. if matchfn:
  847. stat = self.diffopts.get('stat')
  848. diff = self.diffopts.get('patch')
  849. diffopts = patch.diffopts(self.ui, self.diffopts)
  850. prev = self.repo.changelog.parents(node)[0]
  851. if stat:
  852. diffordiffstat(self.ui, self.repo, diffopts, prev, node,
  853. match=matchfn, stat=True)
  854. if diff:
  855. if stat:
  856. self.ui.write("\n")
  857. diffordiffstat(self.ui, self.repo, diffopts, prev, node,
  858. match=matchfn, stat=False)
  859. self.ui.write("\n")
  860. def _meaningful_parentrevs(self, log, rev):
  861. """Return list of meaningful (or all if debug) parentrevs for rev.
  862. For merges (two non-nullrev revisions) both parents are meaningful.
  863. Otherwise the first parent revision is considered meaningful if it
  864. is not the preceding revision.
  865. """
  866. parents = log.parentrevs(rev)
  867. if not self.ui.debugflag and parents[1] == nullrev:
  868. if parents[0] >= rev - 1:
  869. parents = []
  870. else:
  871. parents = [parents[0]]
  872. return parents
  873. class changeset_templater(changeset_printer):
  874. '''format changeset information.'''
  875. def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
  876. changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
  877. formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
  878. defaulttempl = {
  879. 'parent': '{rev}:{node|formatnode} ',
  880. 'manifest': '{rev}:{node|formatnode}',
  881. 'file_copy': '{name} ({source})',
  882. 'extra': '{key}={value|stringescape}'
  883. }
  884. # filecopy is preserved for compatibility reasons
  885. defaulttempl['filecopy'] = defaulttempl['file_copy']
  886. self.t = templater.templater(mapfile, {'formatnode': formatnode},
  887. cache=defaulttempl)
  888. if tmpl:
  889. self.t.cache['changeset'] = tmpl
  890. self.cache = {}
  891. def _meaningful_parentrevs(self, ctx):
  892. """Return list of meaningful (or all if debug) parentrevs for rev.
  893. """
  894. parents = ctx.parents()
  895. if len(parents) > 1:
  896. return parents
  897. if self.ui.debugflag:
  898. return [parents[0], self.repo['null']]
  899. if parents[0].rev() >= ctx.rev() - 1:
  900. return []
  901. return parents
  902. def _show(self, ctx, copies, matchfn, props):
  903. '''show a single changeset or file revision'''
  904. showlist = templatekw.showlist
  905. # showparents() behaviour depends on ui trace level which
  906. # causes unexpected behaviours at templating level and makes
  907. # it harder to extract it in a standalone function. Its
  908. # behaviour cannot be changed so leave it here for now.
  909. def showparents(**args):
  910. ctx = args['ctx']
  911. parents = [[('rev', p.rev()), ('node', p.hex())]
  912. for p in self._meaningful_parentrevs(ctx)]
  913. return showlist('parent', parents, **args)
  914. props = props.copy()
  915. props.update(templatekw.keywords)
  916. props['parents'] = showparents
  917. props['templ'] = self.t
  918. props['ctx'] = ctx
  919. props['repo'] = self.repo
  920. props['revcache'] = {'copies': copies}
  921. props['cache'] = self.cache
  922. # find correct templates for current mode
  923. tmplmodes = [
  924. (True, None),
  925. (self.ui.verbose, 'verbose'),
  926. (self.ui.quiet, 'quiet'),
  927. (self.ui.debugflag, 'debug'),
  928. ]
  929. types = {'header': '', 'footer':'', 'changeset': 'changeset'}
  930. for mode, postfix in tmplmodes:
  931. for type in types:
  932. cur = postfix and ('%s_%s' % (type, postfix)) or type
  933. if mode and cur in self.t:
  934. types[type] = cur
  935. try:
  936. # write header
  937. if types['header']:
  938. h = templater.stringify(self.t(types['header'], **props))
  939. if self.buffered:
  940. self.header[ctx.rev()] = h
  941. else:
  942. if self.lastheader != h:
  943. self.lastheader = h
  944. self.ui.write(h)
  945. # write changeset metadata, then patch if requested
  946. key = types['changeset']
  947. self.ui.write(templater.stringify(self.t(key, **props)))
  948. self.showpatch(ctx.node(), matchfn)
  949. if types['footer']:
  950. if not self.footer:
  951. self.footer = templater.stringify(self.t(types['footer'],
  952. **props))
  953. except KeyError, inst:
  954. msg = _("%s: no key named '%s'")
  955. raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
  956. except SyntaxError, inst:
  957. raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
  958. def gettemplate(ui, tmpl, style):
  959. """
  960. Find the template matching the given template spec or style.
  961. """
  962. # ui settings
  963. if not tmpl and not style:
  964. tmpl = ui.config('ui', 'logtemplate')
  965. if tmpl:
  966. try:
  967. tmpl = templater.parsestring(tmpl)
  968. except SyntaxError:
  969. tmpl = templater.parsestring(tmpl, quoted=False)
  970. return tmpl, None
  971. else:
  972. style = util.expandpath(ui.config('ui', 'style', ''))
  973. if style:
  974. mapfile = style
  975. if not os.path.split(mapfile)[0]:
  976. mapname = (templater.templatepath('map-cmdline.' + mapfile)
  977. or templater.templatepath(mapfile))
  978. if mapname:
  979. mapfile = mapname
  980. return None, mapfile
  981. if not tmpl:
  982. return None, None
  983. # looks like a literal template?
  984. if '{' in tmpl:
  985. return tmpl, None
  986. # perhaps a stock style?
  987. if not os.path.split(tmpl)[0]:
  988. mapname = (templater.templatepath('map-cmdline.' + tmpl)
  989. or templater.templatepath(tmpl))
  990. if mapname and os.path.isfile(mapname):
  991. return None, mapname
  992. # perhaps it's a reference to [templates]
  993. t = ui.config('templates', tmpl)
  994. if t:
  995. try:
  996. tmpl = templater.parsestring(t)
  997. except SyntaxError:
  998. tmpl = templater.parsestring(t, quoted=False)
  999. return tmpl, None
  1000. # perhaps it's a path to a map or a template
  1001. if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
  1002. # is it a mapfile for a style?
  1003. if os.path.basename(tmpl).startswith("map-"):
  1004. return None, os.path.realpath(tmpl)
  1005. tmpl = open(tmpl).read()
  1006. return tmpl, None
  1007. # constant string?
  1008. return tmpl, None
  1009. def show_changeset(ui, repo, opts, buffered=False):
  1010. """show one changeset using template or regular display.
  1011. Display format will be the first non-empty hit of:
  1012. 1. option 'template'
  1013. 2. option 'style'
  1014. 3. [ui] setting 'logtemplate'
  1015. 4. [ui] setting 'style'
  1016. If all of these values are either the unset or the empty string,
  1017. regular display via changeset_printer() is done.
  1018. """
  1019. # options
  1020. patch = None
  1021. if opts.get('patch') or opts.get('stat'):
  1022. patch = scmutil.matchall(repo)
  1023. tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
  1024. if not tmpl and not mapfile:
  1025. return changeset_printer(ui, repo, patch, opts, buffered)
  1026. try:
  1027. t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
  1028. except SyntaxError, inst:
  1029. raise util.Abort(inst.args[0])
  1030. return t
  1031. def showmarker(ui, marker):
  1032. """utility function to display obsolescence marker in a readable way
  1033. To be used by debug function."""
  1034. ui.write(hex(marker.precnode()))
  1035. for repl in marker.succnodes():
  1036. ui.write(' ')
  1037. ui.write(hex(repl))
  1038. ui.write(' %X ' % marker._data[2])
  1039. ui.write('{%s}' % (', '.join('%r: %r' % t for t in
  1040. sorted(marker.metadata().items()))))
  1041. ui.write('\n')
  1042. def finddate(ui, repo, date):
  1043. """Find the tipmost changeset that matches the given date spec"""
  1044. df = util.matchdate(date)
  1045. m = scmutil.matchall(repo)
  1046. results = {}
  1047. def prep(ctx, fns):
  1048. d = ctx.date()
  1049. if df(d[0]):
  1050. results[ctx.rev()] = d
  1051. for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
  1052. rev = ctx.rev()
  1053. if rev in results:
  1054. ui.status(_("found revision %s from %s\n") %
  1055. (rev, util.datestr(results[rev])))
  1056. return str(rev)
  1057. raise util.Abort(_("revision matching date not found"))
  1058. def increasingwindows(windowsize=8, sizelimit=512):
  1059. while True:
  1060. yield windowsize
  1061. if windowsize < sizelimit:
  1062. windowsize *= 2
  1063. class FileWalkError(Exception):
  1064. pass
  1065. def walkfilerevs(repo, match, follow, revs, fncache):
  1066. '''Walks the file history for the matched files.
  1067. Returns the changeset revs that are involved in the file history.
  1068. Throws FileWalkError if the file history can't be walked using
  1069. filelogs alone.
  1070. '''
  1071. wanted = set()
  1072. copies = []
  1073. minrev, maxrev = min(revs), max(revs)
  1074. def filerevgen(filelog, last):
  1075. """
  1076. Only files, no patterns. Check the history of each file.
  1077. Examines filelog entries within minrev, maxrev linkrev range
  1078. Returns an iterator yielding (linkrev, parentlinkrevs, copied)
  1079. tuples in backwards order
  1080. """
  1081. cl_count = len(repo)
  1082. revs = []
  1083. for j in xrange(0, last + 1):
  1084. linkrev = filelog.linkrev(j)
  1085. if linkrev < minrev:
  1086. continue
  1087. # only yield rev for which we have the changelog, it can
  1088. # happen while doing "hg log" during a pull or commit
  1089. if linkrev >= cl_count:
  1090. break
  1091. parentlinkrevs = []
  1092. for p in filelog.parentrevs(j):
  1093. if p != nullrev:
  1094. parentlinkrevs.append(filelog.linkrev(p))
  1095. n = filelog.node(j)
  1096. revs.append((linkrev, parentlinkrevs,
  1097. follow and filelog.renamed(n)))
  1098. return reversed(revs)
  1099. def iterfiles():
  1100. pctx = repo['.']
  1101. for filename in match.files():
  1102. if follow:
  1103. if filename not in pctx:
  1104. raise util.Abort(_('cannot follow file not in parent '
  1105. 'revision: "%s"') % filename)
  1106. yield filename, pctx[filename].filenode()
  1107. else:
  1108. yield filename, None
  1109. for filename_node in copies:
  1110. yield filename_node
  1111. for file_, node in iterfiles():
  1112. filelog = repo.file(file_)
  1113. if not len(filelog):
  1114. if node is None:
  1115. # A zero count may be a directory or deleted file, so
  1116. # try to find matching entries on the slow path.
  1117. if follow:
  1118. raise util.Abort(
  1119. _('cannot follow nonexistent file: "%s"') % file_)
  1120. raise FileWalkError("Cannot walk via filelog")
  1121. else:
  1122. continue
  1123. if node is None:
  1124. last = len(filelog) - 1
  1125. else:
  1126. last = filelog.rev(node)
  1127. # keep track of all ancestors of the file
  1128. ancestors = set([filelog.linkrev(last)])
  1129. # iterate from latest to oldest revision
  1130. for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
  1131. if not follow:
  1132. if rev > maxrev:
  1133. continue
  1134. else:
  1135. # Note that last might not be the first interesting
  1136. # rev to us:
  1137. # if the file has been changed after maxrev, we'll
  1138. # have linkrev(last) > maxrev, and we still need
  1139. # to explore the file graph
  1140. if rev not in ancestors:
  1141. continue
  1142. # XXX insert 1327 fix here
  1143. if flparentlinkrevs:
  1144. ancestors.update(flparentlinkrevs)
  1145. fncache.setdefault(rev, []).append(file_)
  1146. wanted.add(rev)
  1147. if copied:
  1148. copies.append(copied)
  1149. return wanted
  1150. def walkchangerevs(repo, match, opts, prepare):
  1151. '''Iterate over files and the revs in which they changed.
  1152. Callers most commonly need to iterate backwards over the history
  1153. in which they are interested. Doing so has awful (quadratic-looking)
  1154. performance, so we use iterators in a "windowed" way.
  1155. We walk a window of revisions in the desired order. Within the
  1156. window, we first walk forwards to gather data, then in the desired
  1157. order (usually backwards) to display it.
  1158. This function returns an iterator yielding contexts. Before
  1159. yielding each context, the iterator will first call the prepare
  1160. function on each context in the window in forward order.'''
  1161. follow = opts.get('follow') or opts.get('follow_first')
  1162. if opts.get('rev'):
  1163. revs = scmutil.revrange(repo, opts.get('rev'))
  1164. elif follow:
  1165. revs = repo.revs('reverse(:.)')
  1166. else:
  1167. revs = revset.spanset(repo)
  1168. revs.reverse()
  1169. if not revs:
  1170. return []
  1171. wanted = set()
  1172. slowpath = match.anypats() or (match.files() and opts.get('removed'))
  1173. fncache = {}
  1174. change = repo.changectx
  1175. # First step is to fill wanted, the set of revisions that we want to yield.
  1176. # When it does not induce extra cost, we also fill fncache for revisions in
  1177. # wanted: a cache of filenames that were changed (ctx.files()) and that
  1178. # match the file filtering conditions.
  1179. if not slowpath and not match.files():
  1180. # No files, no patterns. Display all revs.
  1181. wanted = revs
  1182. if not slowpath and match.files():
  1183. # We only have to read through the filelog to find wanted revisions
  1184. try:
  1185. wanted = walkfilerevs(repo, match, follow, revs, fncache)
  1186. except FileWalkError:
  1187. slowpath = True
  1188. # We decided to fall back to the slowpath because at least one
  1189. # of the paths was not a file. Check to see if at least one of them
  1190. # existed in history, otherwise simply return
  1191. for path in match.files():
  1192. if path == '.' or path in repo.store:
  1193. break
  1194. else:
  1195. return []
  1196. if slowpath:
  1197. # We have to read the changelog to match filenames against
  1198. # changed files
  1199. if follow:
  1200. raise util.Abort(_('can only follow copies/renames for explicit '
  1201. 'filenames'))
  1202. # The slow path checks files modified in every changeset.
  1203. # This is really slow on large repos, so compute the set lazily.
  1204. class lazywantedset(object):
  1205. def __init__(self):
  1206. self.set = set()
  1207. self.revs = set(revs)
  1208. # No need to worry about locality here because it will be accessed
  1209. # in the same order as the increasing window below.
  1210. def __contains__(self, value):
  1211. if value in self.set:
  1212. return True
  1213. elif not value in self.revs:
  1214. return False
  1215. else:
  1216. self.revs.discard(value)
  1217. ctx = change(value)
  1218. matches = filter(match, ctx.files())
  1219. if matches:
  1220. fncache[value] = matches
  1221. self.set.add(value)
  1222. return True
  1223. return False
  1224. def discard(self, value):
  1225. self.revs.discard(value)
  1226. self.set.discard(value)
  1227. wanted = lazywantedset()
  1228. class followfilter(object):
  1229. def __init__(self, onlyfirst=False):
  1230. self.startrev = nullrev
  1231. self.roots = set()
  1232. self.onlyfirst = onlyfirst
  1233. def match(self, rev):
  1234. def realparents(rev):
  1235. if self.onlyfirst:
  1236. return repo.changelog.parentrevs(rev)[0:1]
  1237. else:
  1238. return filter(lambda x: x != nullrev,
  1239. repo.changelog.parentrevs(rev))
  1240. if self.startrev == nullrev:
  1241. self.startrev = rev
  1242. return True
  1243. if rev > self.startrev:
  1244. # forward: all descendants
  1245. if not self.roots:
  1246. self.roots.add(self.startrev)
  1247. for parent in realparents(rev):
  1248. if parent in self.roots:
  1249. self.roots.add(rev)
  1250. return True
  1251. else:
  1252. # backwards: all parents
  1253. if not self.roots:
  1254. self.roots.update(realparents(self.startrev))
  1255. if rev in self.roots:
  1256. self.roots.remove(rev)
  1257. self.roots.update(realparents(rev))
  1258. return True
  1259. return False
  1260. # it might be worthwhile to do this in the iterator if the rev range
  1261. # is descending and the prune args are all within that range
  1262. for rev in opts.get('prune', ()):
  1263. rev = repo[rev].rev()
  1264. ff = followfilter()
  1265. stop = min(revs[0], revs[-1])
  1266. for x in xrange(rev, stop - 1, -1):
  1267. if ff.match(x):
  1268. wanted = wanted - [x]
  1269. # Now that wanted is correctly initialized, we can iterate over the
  1270. # revision range, yielding only revisions in wanted.
  1271. def iterate():
  1272. if follow and not match.files():
  1273. ff = followfilter(onlyfirst=opts.get('follow_first'))
  1274. def want(rev):
  1275. return ff.match(rev) and rev in wanted
  1276. else:
  1277. def want(rev):
  1278. return rev in wanted
  1279. it = iter(revs)
  1280. stopiteration = False
  1281. for windowsize in increasingwindows():
  1282. nrevs = []
  1283. for i in xrange(windowsize):
  1284. try:
  1285. rev = it.next()
  1286. if want(rev):
  1287. nrevs.append(rev)
  1288. except (StopIteration):
  1289. stopiteration = True
  1290. break
  1291. for rev in sorted(nrevs):
  1292. fns = fncache.get(rev)
  1293. ctx = change(rev)
  1294. if not fns:
  1295. def fns_generator():
  1296. for f in ctx.files():
  1297. if match(f):
  1298. yield f
  1299. fns = fns_generator()
  1300. prepare(ctx, fns)
  1301. for rev in nrevs:
  1302. yield change(rev)
  1303. if stopiteration:
  1304. break
  1305. return iterate()
  1306. def _makelogfilematcher(repo, pats, followfirst):
  1307. # When displaying a revision with --patch --follow FILE, we have
  1308. # to know which file of the revision must be diffed. With
  1309. # --follow, we want the names of the ancestors of FILE in the
  1310. # revision, stored in "fcache". "fcache" is populated by
  1311. # reproducing the graph traversal already done by --follow revset
  1312. # and relating linkrevs to file names (which is not "correct" but
  1313. # good enough).
  1314. fcache = {}
  1315. fcacheready = [False]
  1316. pctx = repo['.']
  1317. wctx = repo[None]
  1318. def populate():
  1319. for fn in pats:
  1320. for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
  1321. for c in i:
  1322. fcache.setdefault(c.linkrev(), set()).add(c.path())
  1323. def filematcher(rev):
  1324. if not fcacheready[0]:
  1325. # Lazy initialization
  1326. fcacheready[0] = True
  1327. populate()
  1328. return scmutil.match(wctx, fcache.get(rev, []), default='path')
  1329. return filematcher
  1330. def _makelogrevset(repo, pats, opts, revs):
  1331. """Return (expr, filematcher) where expr is a revset string built
  1332. from log options and file patterns or None. If --stat or --patch
  1333. are not passed filematcher is None. Otherwise it is a callable
  1334. taking a revision number and returning a match objects filtering
  1335. the files to be detailed when displaying the revision.
  1336. """
  1337. opt2revset = {
  1338. 'no_merges': ('not merge()', None),
  1339. 'only_merges': ('merge()', None),
  1340. '_ancestors': ('ancestors(%(val)s)', None),
  1341. '_fancestors': ('_firstancestors(%(val)s)', None),
  1342. '_descendants': ('descendants(%(val)s)', None),
  1343. '_fdescendants': ('_firstdescendants(%(val)s)', None),
  1344. '_matchfiles': ('_matchfiles(%(val)s)', None),
  1345. 'date': ('date(%(val)r)', None),
  1346. 'branch': ('branch(%(val)r)', ' or '),
  1347. '_patslog': ('filelog(%(val)r)', ' or '),
  1348. '_patsfollow': ('follow(%(val)r)', ' or '),
  1349. '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
  1350. 'keyword': ('keyword(%(val)r)', ' or '),
  1351. 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
  1352. 'user': ('user(%(val)r)', ' or '),
  1353. }
  1354. opts = dict(opts)
  1355. # follow or not follow?
  1356. follow = opts.get('follow') or opts.get('follow_first')
  1357. followfirst = opts.get('follow_first') and 1 or 0
  1358. # --follow with FILE behaviour depends on revs...
  1359. it = iter(revs)
  1360. startrev = it.next()
  1361. try:
  1362. followdescendants = startrev < it.next()
  1363. except (StopIteration):
  1364. followdescendants = False
  1365. # branch and only_branch are really aliases and must be handled at
  1366. # the same time
  1367. opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
  1368. opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
  1369. # pats/include/exclude are passed to match.match() directly in
  1370. # _matchfiles() revset but walkchangerevs() builds its matcher with
  1371. # scmutil.match(). The difference is input pats are globbed on
  1372. # platforms without shell expansion (windows).
  1373. pctx = repo[None]
  1374. match, pats = scmutil.matchandpats(pctx, pats, opts)
  1375. slowpath = match.anypats() or (match.files() and opts.get('removed'))
  1376. if not slowpath:
  1377. for f in match.files():
  1378. if follow and f not in pctx:
  1379. raise util.Abort(_('cannot follow file not in parent '
  1380. 'revision: "%s"') % f)
  1381. filelog = repo.file(f)
  1382. if not filelog:
  1383. # A zero count may be a directory or deleted file, so
  1384. # try to find matching entries on the slow path.
  1385. if follow:
  1386. raise util.Abort(
  1387. _('cannot follow nonexistent file: "%s"') % f)
  1388. slowpath = True
  1389. # We decided to fall back to the slowpath because at least one
  1390. # of the paths was not a file. Check to see if at least one of them
  1391. # existed in history - in that case, we'll continue down the
  1392. # slowpath; otherwise, we can turn off the slowpath
  1393. if slowpath:
  1394. for path in match.files():
  1395. if path == '.' or path in repo.store:
  1396. break
  1397. else:
  1398. slowpath = False
  1399. if slowpath:
  1400. # See walkchangerevs() slow path.
  1401. #
  1402. if follow:
  1403. raise util.Abort(_('can only follow copies/renames for explicit '
  1404. 'filenames'))
  1405. # pats/include/exclude cannot be represented as separate
  1406. # revset expressions as their filtering logic applies at file
  1407. # level. For instance "-I a -X a" matches a revision touching
  1408. # "a" and "b" while "file(a) and not file(b)" does
  1409. # not. Besides, filesets are evaluated against the working
  1410. # directory.
  1411. matchargs = ['r:', 'd:relpath']
  1412. for p in pats:
  1413. matchargs.append('p:' + p)
  1414. for p in opts.get('include', []):
  1415. matchargs.append('i:' + p)
  1416. for p in opts.get('exclude', []):
  1417. matchargs.append('x:' + p)
  1418. matchargs = ','.join(('%r' % p) for p in matchargs)
  1419. opts['_matchfiles'] = matchargs
  1420. else:
  1421. if follow:
  1422. fpats = ('_patsfollow', '_patsfollowfirst')
  1423. fnopats = (('_ancestors', '_fancestors'),
  1424. ('_descendants', '_fdescendants'))
  1425. if pats:
  1426. # follow() revset interprets its file argument as a
  1427. # manifest entry, so use match.files(), not pats.
  1428. opts[fpats[followfirst]] = list(match.files())
  1429. else:
  1430. opts[fnopats[followdescendants][followfirst]] = str(startrev)
  1431. else:
  1432. opts['_patslog'] = list(pats)
  1433. filematcher = None
  1434. if opts.get('patch') or opts.get('stat'):
  1435. if follow:
  1436. filematcher = _makelogfilematcher(repo, pats, followfirst)
  1437. else:
  1438. filematcher = lambda rev: match
  1439. expr = []
  1440. for op, val in opts.iteritems():
  1441. if not val:
  1442. continue
  1443. if op not in opt2revset:
  1444. continue
  1445. revop, andor = opt2revset[op]
  1446. if '%(val)' not in revop:
  1447. expr.append(revop)
  1448. else:
  1449. if not isinstance(val, list):
  1450. e = revop % {'val': val}
  1451. else:
  1452. e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
  1453. expr.append(e)
  1454. if expr:
  1455. expr = '(' + ' and '.join(expr) + ')'
  1456. else:
  1457. expr = None
  1458. return expr, filematcher
  1459. def getgraphlogrevs(repo, pats, opts):
  1460. """Return (revs, expr, filematcher) where revs is an iterable of
  1461. revision numbers, expr is a revset string built from log options
  1462. and file patterns or None, and used to filter 'revs'. If --stat or
  1463. --patch are not passed filematcher is None. Otherwise it is a
  1464. callable taking a revision number and returning a match objects
  1465. filtering the files to be detailed when displaying the revision.
  1466. """
  1467. if not len(repo):
  1468. return [], None, None
  1469. limit = loglimit(opts)
  1470. # Default --rev value depends on --follow but --follow behaviour
  1471. # depends on revisions resolved from --rev...
  1472. follow = opts.get('follow') or opts.get('follow_first')
  1473. possiblyunsorted = False # whether revs might need sorting
  1474. if opts.get('rev'):
  1475. revs = scmutil.revrange(repo, opts['rev'])
  1476. # Don't sort here because _makelogrevset might depend on the
  1477. # order of revs
  1478. possiblyunsorted = True
  1479. else:
  1480. if follow and len(repo) > 0:
  1481. revs = repo.revs('reverse(:.)')
  1482. else:
  1483. revs = revset.spanset(repo)
  1484. revs.reverse()
  1485. if not revs:
  1486. return revset.baseset(), None, None
  1487. expr, filematcher = _makelogrevset(repo, pats, opts, revs)
  1488. if possiblyunsorted:
  1489. revs.sort(reverse=True)
  1490. if expr:
  1491. # Revset matchers often operate faster on revisions in changelog
  1492. # order, because most filters deal with the changelog.
  1493. revs.reverse()
  1494. matcher = revset.match(repo.ui, expr)
  1495. # Revset matches can reorder revisions. "A or B" typically returns
  1496. # returns the revision matching A then the revision matching B. Sort
  1497. # again to fix that.
  1498. revs = matcher(repo, revs)
  1499. revs.sort(reverse=True)
  1500. if limit is not None:
  1501. limitedrevs = revset.baseset()
  1502. for idx, rev in enumerate(revs):
  1503. if idx >= limit:
  1504. break
  1505. limitedrevs.append(rev)
  1506. revs = limitedrevs
  1507. return revs, expr, filematcher
  1508. def getlogrevs(repo, pats, opts):
  1509. """Return (revs, expr, filematcher) where revs is an iterable of
  1510. revision numbers, expr is a revset string built from log options
  1511. and file patterns or None, and used to filter 'revs'. If --stat or
  1512. --patch are not passed filematcher is None. Otherwise it is a
  1513. callable taking a revision number and returning a match objects
  1514. filtering the files to be detailed when displaying the revision.
  1515. """
  1516. limit = loglimit(opts)
  1517. # Default --rev value depends on --follow but --follow behaviour
  1518. # depends on revisions resolved from --rev...
  1519. follow = opts.get('follow') or opts.get('follow_first')
  1520. if opts.get('rev'):
  1521. revs = scmutil.revrange(repo, opts['rev'])
  1522. elif follow:
  1523. revs = repo.revs('reverse(:.)')
  1524. else:
  1525. revs = revset.spanset(repo)
  1526. revs.reverse()
  1527. if not revs:
  1528. return revset.baseset([]), None, None
  1529. expr, filematcher = _makelogrevset(repo, pats, opts, revs)
  1530. if expr:
  1531. # Revset matchers often operate faster on revisions in changelog
  1532. # order, because most filters deal with the changelog.
  1533. if not opts.get('rev'):
  1534. revs.reverse()
  1535. matcher = revset.match(repo.ui, expr)
  1536. # Revset matches can reorder revisions. "A or B" typically returns
  1537. # returns the revision matching A then the revision matching B. Sort
  1538. # again to fix that.
  1539. revs = matcher(repo, revs)
  1540. if not opts.get('rev'):
  1541. revs.sort(reverse=True)
  1542. if limit is not None:
  1543. count = 0
  1544. limitedrevs = revset.baseset([])
  1545. it = iter(revs)
  1546. while count < limit:
  1547. try:
  1548. limitedrevs.append(it.next())
  1549. except (StopIteration):
  1550. break
  1551. count += 1
  1552. revs = limitedrevs
  1553. return revs, expr, filematcher
  1554. def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
  1555. filematcher=None):
  1556. seen, state = [], graphmod.asciistate()
  1557. for rev, type, ctx, parents in dag:
  1558. char = 'o'
  1559. if ctx.node() in showparents:
  1560. char = '@'
  1561. elif ctx.obsolete():
  1562. char = 'x'
  1563. copies = None
  1564. if getrenamed and ctx.rev():
  1565. copies = []
  1566. for fn in ctx.files():
  1567. rename = getrenamed(fn, ctx.rev())
  1568. if rename:
  1569. copies.append((fn, rename[0]))
  1570. revmatchfn = None
  1571. if filematcher is not None:
  1572. revmatchfn = filematcher(ctx.rev())
  1573. displayer.show(ctx, copies=copies, matchfn=revmatchfn)
  1574. lines = displayer.hunk.pop(rev).split('\n')
  1575. if not lines[-1]:
  1576. del lines[-1]
  1577. displayer.flush(rev)
  1578. edges = edgefn(type, char, lines, seen, rev, parents)
  1579. for type, char, lines, coldata in edges:
  1580. graphmod.ascii(ui, state, type, char, lines, coldata)
  1581. displayer.close()
  1582. def graphlog(ui, repo, *pats, **opts):
  1583. # Parameters are identical to log command ones
  1584. revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
  1585. revdag = graphmod.dagwalker(repo, revs)
  1586. getrenamed = None
  1587. if opts.get('copies'):
  1588. endrev = None
  1589. if opts.get('rev'):
  1590. endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
  1591. getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
  1592. displayer = show_changeset(ui, repo, opts, buffered=True)
  1593. showparents = [ctx.node() for ctx in repo[None].parents()]
  1594. displaygraph(ui, revdag, displayer, showparents,
  1595. graphmod.asciiedges, getrenamed, filematcher)
  1596. def checkunsupportedgraphflags(pats, opts):
  1597. for op in ["newest_first"]:
  1598. if op in opts and opts[op]:
  1599. raise util.Abort(_("-G/--graph option is incompatible with --%s")
  1600. % op.replace("_", "-"))
  1601. def graphrevs(repo, nodes, opts):
  1602. limit = loglimit(opts)
  1603. nodes.reverse()
  1604. if limit is not None:
  1605. nodes = nodes[:limit]
  1606. return graphmod.nodes(repo, nodes)
  1607. def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
  1608. join = lambda f: os.path.join(prefix, f)
  1609. bad = []
  1610. oldbad = match.bad
  1611. match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
  1612. names = []
  1613. wctx = repo[None]
  1614. cca = None
  1615. abort, warn = scmutil.checkportabilityalert(ui)
  1616. if abort or warn:
  1617. cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
  1618. for f in repo.walk(match):
  1619. exact = match.exact(f)
  1620. if exact or not explicitonly and f not in repo.dirstate:
  1621. if cca:
  1622. cca(f)
  1623. names.append(f)
  1624. if ui.verbose or not exact:
  1625. ui.status(_('adding %s\n') % match.rel(join(f)))
  1626. for subpath in sorted(wctx.substate):
  1627. sub = wctx.sub(subpath)
  1628. try:
  1629. submatch = matchmod.narrowmatcher(subpath, match)
  1630. if listsubrepos:
  1631. bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
  1632. False))
  1633. else:
  1634. bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
  1635. True))
  1636. except error.LookupError:
  1637. ui.status(_("skipping missing subrepository: %s\n")
  1638. % join(subpath))
  1639. if not dryrun:
  1640. rejected = wctx.add(names, prefix)
  1641. bad.extend(f for f in rejected if f in match.files())
  1642. return bad
  1643. def forget(ui, repo, match, prefix, explicitonly):
  1644. join = lambda f: os.path.join(prefix, f)
  1645. bad = []
  1646. oldbad = match.bad
  1647. match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
  1648. wctx = repo[None]
  1649. forgot = []
  1650. s = repo.status(match=match, clean=True)
  1651. forget = sorted(s[0] + s[1] + s[3] + s[6])
  1652. if explicitonly:
  1653. forget = [f for f in forget if match.exact(f)]
  1654. for subpath in sorted(wctx.substate):
  1655. sub = wctx.sub(subpath)
  1656. try:
  1657. submatch = matchmod.narrowmatcher(subpath, match)
  1658. subbad, subforgot = sub.forget(ui, submatch, prefix)
  1659. bad.extend([subpath + '/' + f for f in subbad])
  1660. forgot.extend([subpath + '/' + f for f in subforgot])
  1661. except error.LookupError:
  1662. ui.status(_("skipping missing subrepository: %s\n")
  1663. % join(subpath))
  1664. if not explicitonly:
  1665. for f in match.files():
  1666. if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
  1667. if f not in forgot:
  1668. if os.path.exists(match.rel(join(f))):
  1669. ui.warn(_('not removing %s: '
  1670. 'file is already untracked\n')
  1671. % match.rel(join(f)))
  1672. bad.append(f)
  1673. for f in forget:
  1674. if ui.verbose or not match.exact(f):
  1675. ui.status(_('removing %s\n') % match.rel(join(f)))
  1676. rejected = wctx.forget(forget, prefix)
  1677. bad.extend(f for f in rejected if f in match.files())
  1678. forgot.extend(forget)
  1679. return bad, forgot
  1680. def cat(ui, repo, ctx, matcher, prefix, **opts):
  1681. err = 1
  1682. def write(path):
  1683. fp = makefileobj(repo, opts.get('output'), ctx.node(),
  1684. pathname=os.path.join(prefix, path))
  1685. data = ctx[path].data()
  1686. if opts.get('decode'):
  1687. data = repo.wwritedata(path, data)
  1688. fp.write(data)
  1689. fp.close()
  1690. # Automation often uses hg cat on single files, so special case it
  1691. # for performance to avoid the cost of parsing the manifest.
  1692. if len(matcher.files()) == 1 and not matcher.anypats():
  1693. file = matcher.files()[0]
  1694. mf = repo.manifest
  1695. mfnode = ctx._changeset[0]
  1696. if mf.find(mfnode, file)[0]:
  1697. write(file)
  1698. return 0
  1699. # Don't warn about "missing" files that are really in subrepos
  1700. bad = matcher.bad
  1701. def badfn(path, msg):
  1702. for subpath in ctx.substate:
  1703. if path.startswith(subpath):
  1704. return
  1705. bad(path, msg)
  1706. matcher.bad = badfn
  1707. for abs in ctx.walk(matcher):
  1708. write(abs)
  1709. err = 0
  1710. matcher.bad = bad
  1711. for subpath in sorted(ctx.substate):
  1712. sub = ctx.sub(subpath)
  1713. try:
  1714. submatch = matchmod.narrowmatcher(subpath, matcher)
  1715. if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
  1716. **opts):
  1717. err = 0
  1718. except error.RepoLookupError:
  1719. ui.status(_("skipping missing subrepository: %s\n")
  1720. % os.path.join(prefix, subpath))
  1721. return err
  1722. def duplicatecopies(repo, rev, fromrev, skiprev=None):
  1723. '''reproduce copies from fromrev to rev in the dirstate
  1724. If skiprev is specified, it's a revision that should be used to
  1725. filter copy records. Any copies that occur between fromrev and
  1726. skiprev will not be duplicated, even if they appear in the set of
  1727. copies between fromrev and rev.
  1728. '''
  1729. exclude = {}
  1730. if skiprev is not None:
  1731. exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
  1732. for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
  1733. # copies.pathcopies returns backward renames, so dst might not
  1734. # actually be in the dirstate
  1735. if dst in exclude:
  1736. continue
  1737. if repo.dirstate[dst] in "nma":
  1738. repo.dirstate.copy(src, dst)
  1739. def commit(ui, repo, commitfunc, pats, opts):
  1740. '''commit the specified files or all outstanding changes'''
  1741. date = opts.get('date')
  1742. if date:
  1743. opts['date'] = util.parsedate(date)
  1744. message = logmessage(ui, opts)
  1745. # extract addremove carefully -- this function can be called from a command
  1746. # that doesn't support addremove
  1747. if opts.get('addremove'):
  1748. scmutil.addremove(repo, pats, opts)
  1749. return commitfunc(ui, repo, message,
  1750. scmutil.match(repo[None], pats, opts), opts)
  1751. def amend(ui, repo, commitfunc, old, extra, pats, opts):
  1752. ui.note(_('amending changeset %s\n') % old)
  1753. base = old.p1()
  1754. wlock = lock = newid = None
  1755. try:
  1756. wlock = repo.wlock()
  1757. lock = repo.lock()
  1758. tr = repo.transaction('amend')
  1759. try:
  1760. # See if we got a message from -m or -l, if not, open the editor
  1761. # with the message of the changeset to amend
  1762. message = logmessage(ui, opts)
  1763. # ensure logfile does not conflict with later enforcement of the
  1764. # message. potential logfile content has been processed by
  1765. # `logmessage` anyway.
  1766. opts.pop('logfile')
  1767. # First, do a regular commit to record all changes in the working
  1768. # directory (if there are any)
  1769. ui.callhooks = False
  1770. currentbookmark = repo._bookmarkcurrent
  1771. try:
  1772. repo._bookmarkcurrent = None
  1773. opts['message'] = 'temporary amend commit for %s' % old
  1774. node = commit(ui, repo, commitfunc, pats, opts)
  1775. finally:
  1776. repo._bookmarkcurrent = currentbookmark
  1777. ui.callhooks = True
  1778. ctx = repo[node]
  1779. # Participating changesets:
  1780. #
  1781. # node/ctx o - new (intermediate) commit that contains changes
  1782. # | from working dir to go into amending commit
  1783. # | (or a workingctx if there were no changes)
  1784. # |
  1785. # old o - changeset to amend
  1786. # |
  1787. # base o - parent of amending changeset
  1788. # Update extra dict from amended commit (e.g. to preserve graft
  1789. # source)
  1790. extra.update(old.extra())
  1791. # Also update it from the intermediate commit or from the wctx
  1792. extra.update(ctx.extra())
  1793. if len(old.parents()) > 1:
  1794. # ctx.files() isn't reliable for merges, so fall back to the
  1795. # slower repo.status() method
  1796. files = set([fn for st in repo.status(base, old)[:3]
  1797. for fn in st])
  1798. else:
  1799. files = set(old.files())
  1800. # Second, we use either the commit we just did, or if there were no
  1801. # changes the parent of the working directory as the version of the
  1802. # files in the final amend commit
  1803. if node:
  1804. ui.note(_('copying changeset %s to %s\n') % (ctx, base))
  1805. user = ctx.user()
  1806. date = ctx.date()
  1807. # Recompute copies (avoid recording a -> b -> a)
  1808. copied = copies.pathcopies(base, ctx)
  1809. # Prune files which were reverted by the updates: if old
  1810. # introduced file X and our intermediate commit, node,
  1811. # renamed that file, then those two files are the same and
  1812. # we can discard X from our list of files. Likewise if X
  1813. # was deleted, it's no longer relevant
  1814. files.update(ctx.files())
  1815. def samefile(f):
  1816. if f in ctx.manifest():
  1817. a = ctx.filectx(f)
  1818. if f in base.manifest():
  1819. b = base.filectx(f)
  1820. return (not a.cmp(b)
  1821. and a.flags() == b.flags())
  1822. else:
  1823. return False
  1824. else:
  1825. return f not in base.manifest()
  1826. files = [f for f in files if not samefile(f)]
  1827. def filectxfn(repo, ctx_, path):
  1828. try:
  1829. fctx = ctx[path]
  1830. flags = fctx.flags()
  1831. mctx = context.memfilectx(repo,
  1832. fctx.path(), fctx.data(),
  1833. islink='l' in flags,
  1834. isexec='x' in flags,
  1835. copied=copied.get(path))
  1836. return mctx
  1837. except KeyError:
  1838. raise IOError
  1839. else:
  1840. ui.note(_('copying changeset %s to %s\n') % (old, base))
  1841. # Use version of files as in the old cset
  1842. def filectxfn(repo, ctx_, path):
  1843. try:
  1844. return old.filectx(path)
  1845. except KeyError:
  1846. raise IOError
  1847. user = opts.get('user') or old.user()
  1848. date = opts.get('date') or old.date()
  1849. editor = getcommiteditor(**opts)
  1850. if not message:
  1851. editor = getcommiteditor(edit=True)
  1852. message = old.description()
  1853. pureextra = extra.copy()
  1854. extra['amend_source'] = old.hex()
  1855. new = context.memctx(repo,
  1856. parents=[base.node(), old.p2().node()],
  1857. text=message,
  1858. files=files,
  1859. filectxfn=filectxfn,
  1860. user=user,
  1861. date=date,
  1862. extra=extra,
  1863. editor=editor)
  1864. newdesc = changelog.stripdesc(new.description())
  1865. if ((not node)
  1866. and newdesc == old.description()
  1867. and user == old.user()
  1868. and date == old.date()
  1869. and pureextra == old.extra()):
  1870. # nothing changed. continuing here would create a new node
  1871. # anyway because of the amend_source noise.
  1872. #
  1873. # This not what we expect from amend.
  1874. return old.node()
  1875. ph = repo.ui.config('phases', 'new-commit', phases.draft)
  1876. try:
  1877. if opts.get('secret'):
  1878. commitphase = 'secret'
  1879. else:
  1880. commitphase = old.phase()
  1881. repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
  1882. newid = repo.commitctx(new)
  1883. finally:
  1884. repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
  1885. if newid != old.node():
  1886. # Reroute the working copy parent to the new changeset
  1887. repo.setparents(newid, nullid)
  1888. # Move bookmarks from old parent to amend commit
  1889. bms = repo.nodebookmarks(old.node())
  1890. if bms:
  1891. marks = repo._bookmarks
  1892. for bm in bms:
  1893. marks[bm] = newid
  1894. marks.write()
  1895. #commit the whole amend process
  1896. if obsolete._enabled and newid != old.node():
  1897. # mark the new changeset as successor of the rewritten one
  1898. new = repo[newid]
  1899. obs = [(old, (new,))]
  1900. if node:
  1901. obs.append((ctx, ()))
  1902. obsolete.createmarkers(repo, obs)
  1903. tr.close()
  1904. finally:
  1905. tr.release()
  1906. if (not obsolete._enabled) and newid != old.node():
  1907. # Strip the intermediate commit (if there was one) and the amended
  1908. # commit
  1909. if node:
  1910. ui.note(_('stripping intermediate changeset %s\n') % ctx)
  1911. ui.note(_('stripping amended changeset %s\n') % old)
  1912. repair.strip(ui, repo, old.node(), topic='amend-backup')
  1913. finally:
  1914. if newid is None:
  1915. repo.dirstate.invalidate()
  1916. lockmod.release(lock, wlock)
  1917. return newid
  1918. def commiteditor(repo, ctx, subs):
  1919. if ctx.description():
  1920. return ctx.description()
  1921. return commitforceeditor(repo, ctx, subs)
  1922. def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
  1923. committext = buildcommittext(repo, ctx, subs, extramsg)
  1924. # run editor in the repository root
  1925. olddir = os.getcwd()
  1926. os.chdir(repo.root)
  1927. text = repo.ui.edit(committext, ctx.user(), ctx.extra())
  1928. text = re.sub("(?m)^HG:.*(\n|$)", "", text)
  1929. os.chdir(olddir)
  1930. if finishdesc:
  1931. text = finishdesc(text)
  1932. if not text.strip():
  1933. raise util.Abort(_("empty commit message"))
  1934. return text
  1935. def buildcommittext(repo, ctx, subs, extramsg):
  1936. edittext = []
  1937. modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
  1938. if ctx.description():
  1939. edittext.append(ctx.description())
  1940. edittext.append("")
  1941. edittext.append("") # Empty line between message and comments.
  1942. edittext.append(_("HG: Enter commit message."
  1943. " Lines beginning with 'HG:' are removed."))
  1944. if extramsg:
  1945. edittext.append("HG: %s" % extramsg)
  1946. else:
  1947. edittext.append(_("HG: Leave message empty to abort commit."))
  1948. edittext.append("HG: --")
  1949. edittext.append(_("HG: user: %s") % ctx.user())
  1950. if ctx.p2():
  1951. edittext.append(_("HG: branch merge"))
  1952. if ctx.branch():
  1953. edittext.append(_("HG: branch '%s'") % ctx.branch())
  1954. if bookmarks.iscurrent(repo):
  1955. edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
  1956. edittext.extend([_("HG: subrepo %s") % s for s in subs])
  1957. edittext.extend([_("HG: added %s") % f for f in added])
  1958. edittext.extend([_("HG: changed %s") % f for f in modified])
  1959. edittext.extend([_("HG: removed %s") % f for f in removed])
  1960. if not added and not modified and not removed:
  1961. edittext.append(_("HG: no files changed"))
  1962. edittext.append("")
  1963. return "\n".join(edittext)
  1964. def commitstatus(repo, node, branch, bheads=None, opts={}):
  1965. ctx = repo[node]
  1966. parents = ctx.parents()
  1967. if (not opts.get('amend') and bheads and node not in bheads and not
  1968. [x for x in parents if x.node() in bheads and x.branch() == branch]):
  1969. repo.ui.status(_('created new head\n'))
  1970. # The message is not printed for initial roots. For the other
  1971. # changesets, it is printed in the following situations:
  1972. #
  1973. # Par column: for the 2 parents with ...
  1974. # N: null or no parent
  1975. # B: parent is on another named branch
  1976. # C: parent is a regular non head changeset
  1977. # H: parent was a branch head of the current branch
  1978. # Msg column: whether we print "created new head" message
  1979. # In the following, it is assumed that there already exists some
  1980. # initial branch heads of the current branch, otherwise nothing is
  1981. # printed anyway.
  1982. #
  1983. # Par Msg Comment
  1984. # N N y additional topo root
  1985. #
  1986. # B N y additional branch root
  1987. # C N y additional topo head
  1988. # H N n usual case
  1989. #
  1990. # B B y weird additional branch root
  1991. # C B y branch merge
  1992. # H B n merge with named branch
  1993. #
  1994. # C C y additional head from merge
  1995. # C H n merge with a head
  1996. #
  1997. # H H n head merge: head count decreases
  1998. if not opts.get('close_branch'):
  1999. for r in parents:
  2000. if r.closesbranch() and r.branch() == branch:
  2001. repo.ui.status(_('reopening closed branch head %d\n') % r)
  2002. if repo.ui.debugflag:
  2003. repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
  2004. elif repo.ui.verbose:
  2005. repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
  2006. def revert(ui, repo, ctx, parents, *pats, **opts):
  2007. parent, p2 = parents
  2008. node = ctx.node()
  2009. mf = ctx.manifest()
  2010. if node == p2:
  2011. parent = p2
  2012. if node == parent:
  2013. pmf = mf
  2014. else:
  2015. pmf = None
  2016. # need all matching names in dirstate and manifest of target rev,
  2017. # so have to walk both. do not print errors if files exist in one
  2018. # but not other.
  2019. # `names` is a mapping for all elements in working copy and target revision
  2020. # The mapping is in the form:
  2021. # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
  2022. names = {}
  2023. wlock = repo.wlock()
  2024. try:
  2025. ## filling of the `names` mapping
  2026. # walk dirstate to fill `names`
  2027. m = scmutil.match(repo[None], pats, opts)
  2028. m.bad = lambda x, y: False
  2029. for abs in repo.walk(m):
  2030. names[abs] = m.rel(abs), m.exact(abs)
  2031. # walk target manifest to fill `names`
  2032. def badfn(path, msg):
  2033. if path in names:
  2034. return
  2035. if path in ctx.substate:
  2036. return
  2037. path_ = path + '/'
  2038. for f in names:
  2039. if f.startswith(path_):
  2040. return
  2041. ui.warn("%s: %s\n" % (m.rel(path), msg))
  2042. m = scmutil.match(ctx, pats, opts)
  2043. m.bad = badfn
  2044. for abs in ctx.walk(m):
  2045. if abs not in names:
  2046. names[abs] = m.rel(abs), m.exact(abs)
  2047. # get the list of subrepos that must be reverted
  2048. targetsubs = sorted(s for s in ctx.substate if m(s))
  2049. # Find status of all file in `names`. (Against working directory parent)
  2050. m = scmutil.matchfiles(repo, names)
  2051. changes = repo.status(node1=parent, match=m)[:4]
  2052. modified, added, removed, deleted = map(set, changes)
  2053. # if f is a rename, update `names` to also revert the source
  2054. cwd = repo.getcwd()
  2055. for f in added:
  2056. src = repo.dirstate.copied(f)
  2057. if src and src not in names and repo.dirstate[src] == 'r':
  2058. removed.add(src)
  2059. names[src] = (repo.pathto(src, cwd), True)
  2060. ## computation of the action to performs on `names` content.
  2061. def removeforget(abs):
  2062. if repo.dirstate[abs] == 'a':
  2063. return _('forgetting %s\n')
  2064. return _('removing %s\n')
  2065. # action to be actually performed by revert
  2066. # (<list of file>, message>) tuple
  2067. actions = {'revert': ([], _('reverting %s\n')),
  2068. 'add': ([], _('adding %s\n')),
  2069. 'remove': ([], removeforget),
  2070. 'undelete': ([], _('undeleting %s\n'))}
  2071. disptable = (
  2072. # dispatch table:
  2073. # file state
  2074. # action if in target manifest
  2075. # action if not in target manifest
  2076. # make backup if in target manifest
  2077. # make backup if not in target manifest
  2078. (modified, (actions['revert'], True),
  2079. (actions['remove'], True)),
  2080. (added, (actions['revert'], True),
  2081. (actions['remove'], False)),
  2082. (removed, (actions['undelete'], True),
  2083. (None, False)),
  2084. (deleted, (actions['revert'], False),
  2085. (actions['remove'], False)),
  2086. )
  2087. for abs, (rel, exact) in sorted(names.items()):
  2088. # hash on file in target manifest (or None if missing from target)
  2089. mfentry = mf.get(abs)
  2090. # target file to be touch on disk (relative to cwd)
  2091. target = repo.wjoin(abs)
  2092. def handle(xlist, dobackup):
  2093. xlist[0].append(abs)
  2094. if (dobackup and not opts.get('no_backup') and
  2095. os.path.lexists(target) and
  2096. abs in ctx and repo[None][abs].cmp(ctx[abs])):
  2097. bakname = "%s.orig" % rel
  2098. ui.note(_('saving current version of %s as %s\n') %
  2099. (rel, bakname))
  2100. if not opts.get('dry_run'):
  2101. util.rename(target, bakname)
  2102. if ui.verbose or not exact:
  2103. msg = xlist[1]
  2104. if not isinstance(msg, basestring):
  2105. msg = msg(abs)
  2106. ui.status(msg % rel)
  2107. # search the entry in the dispatch table.
  2108. # if the file is in any of this sets, it was touched in the working
  2109. # directory parent and we are sure it needs to be reverted.
  2110. for table, hit, miss in disptable:
  2111. if abs not in table:
  2112. continue
  2113. # file has changed in dirstate
  2114. if mfentry:
  2115. handle(*hit)
  2116. elif miss[0] is not None:
  2117. handle(*miss)
  2118. break
  2119. else:
  2120. # Not touched in current dirstate.
  2121. # file is unknown in parent, restore older version or ignore.
  2122. if abs not in repo.dirstate:
  2123. if mfentry:
  2124. handle(actions['add'], True)
  2125. elif exact:
  2126. ui.warn(_('file not managed: %s\n') % rel)
  2127. continue
  2128. # parent is target, no changes mean no changes
  2129. if node == parent:
  2130. if exact:
  2131. ui.warn(_('no changes needed to %s\n') % rel)
  2132. continue
  2133. # no change in dirstate but parent and target may differ
  2134. if pmf is None:
  2135. # only need parent manifest in this unlikely case,
  2136. # so do not read by default
  2137. pmf = repo[parent].manifest()
  2138. if abs in pmf and mfentry:
  2139. # if version of file is same in parent and target
  2140. # manifests, do nothing
  2141. if (pmf[abs] != mfentry or
  2142. pmf.flags(abs) != mf.flags(abs)):
  2143. handle(actions['revert'], False)
  2144. else:
  2145. handle(actions['remove'], False)
  2146. if not opts.get('dry_run'):
  2147. _performrevert(repo, parents, ctx, actions)
  2148. if targetsubs:
  2149. # Revert the subrepos on the revert list
  2150. for sub in targetsubs:
  2151. ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
  2152. finally:
  2153. wlock.release()
  2154. def _performrevert(repo, parents, ctx, actions):
  2155. """function that actually perform all the actions computed for revert
  2156. This is an independent function to let extension to plug in and react to
  2157. the imminent revert.
  2158. Make sure you have the working directory locked when calling this function.
  2159. """
  2160. parent, p2 = parents
  2161. node = ctx.node()
  2162. def checkout(f):
  2163. fc = ctx[f]
  2164. repo.wwrite(f, fc.data(), fc.flags())
  2165. audit_path = pathutil.pathauditor(repo.root)
  2166. for f in actions['remove'][0]:
  2167. if repo.dirstate[f] == 'a':
  2168. repo.dirstate.drop(f)
  2169. continue
  2170. audit_path(f)
  2171. try:
  2172. util.unlinkpath(repo.wjoin(f))
  2173. except OSError:
  2174. pass
  2175. repo.dirstate.remove(f)
  2176. normal = None
  2177. if node == parent:
  2178. # We're reverting to our parent. If possible, we'd like status
  2179. # to report the file as clean. We have to use normallookup for
  2180. # merges to avoid losing information about merged/dirty files.
  2181. if p2 != nullid:
  2182. normal = repo.dirstate.normallookup
  2183. else:
  2184. normal = repo.dirstate.normal
  2185. for f in actions['revert'][0]:
  2186. checkout(f)
  2187. if normal:
  2188. normal(f)
  2189. for f in actions['add'][0]:
  2190. checkout(f)
  2191. repo.dirstate.add(f)
  2192. normal = repo.dirstate.normallookup
  2193. if node == parent and p2 == nullid:
  2194. normal = repo.dirstate.normal
  2195. for f in actions['undelete'][0]:
  2196. checkout(f)
  2197. normal(f)
  2198. copied = copies.pathcopies(repo[parent], ctx)
  2199. for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
  2200. if f in copied:
  2201. repo.dirstate.copy(copied[f], f)
  2202. def command(table):
  2203. """Returns a function object to be used as a decorator for making commands.
  2204. This function receives a command table as its argument. The table should
  2205. be a dict.
  2206. The returned function can be used as a decorator for adding commands
  2207. to that command table. This function accepts multiple arguments to define
  2208. a command.
  2209. The first argument is the command name.
  2210. The options argument is an iterable of tuples defining command arguments.
  2211. See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
  2212. The synopsis argument defines a short, one line summary of how to use the
  2213. command. This shows up in the help output.
  2214. The norepo argument defines whether the command does not require a
  2215. local repository. Most commands operate against a repository, thus the
  2216. default is False.
  2217. The optionalrepo argument defines whether the command optionally requires
  2218. a local repository.
  2219. The inferrepo argument defines whether to try to find a repository from the
  2220. command line arguments. If True, arguments will be examined for potential
  2221. repository locations. See ``findrepo()``. If a repository is found, it
  2222. will be used.
  2223. """
  2224. def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
  2225. inferrepo=False):
  2226. def decorator(func):
  2227. if synopsis:
  2228. table[name] = func, list(options), synopsis
  2229. else:
  2230. table[name] = func, list(options)
  2231. if norepo:
  2232. # Avoid import cycle.
  2233. import commands
  2234. commands.norepo += ' %s' % ' '.join(parsealiases(name))
  2235. if optionalrepo:
  2236. import commands
  2237. commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
  2238. if inferrepo:
  2239. import commands
  2240. commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
  2241. return func
  2242. return decorator
  2243. return cmd
  2244. # a list of (ui, repo, otherpeer, opts, missing) functions called by
  2245. # commands.outgoing. "missing" is "missing" of the result of
  2246. # "findcommonoutgoing()"
  2247. outgoinghooks = util.hooks()
  2248. # a list of (ui, repo) functions called by commands.summary
  2249. summaryhooks = util.hooks()
  2250. # a list of (ui, repo, opts, changes) functions called by commands.summary.
  2251. #
  2252. # functions should return tuple of booleans below, if 'changes' is None:
  2253. # (whether-incomings-are-needed, whether-outgoings-are-needed)
  2254. #
  2255. # otherwise, 'changes' is a tuple of tuples below:
  2256. # - (sourceurl, sourcebranch, sourcepeer, incoming)
  2257. # - (desturl, destbranch, destpeer, outgoing)
  2258. summaryremotehooks = util.hooks()
  2259. # A list of state files kept by multistep operations like graft.
  2260. # Since graft cannot be aborted, it is considered 'clearable' by update.
  2261. # note: bisect is intentionally excluded
  2262. # (state file, clearable, allowcommit, error, hint)
  2263. unfinishedstates = [
  2264. ('graftstate', True, False, _('graft in progress'),
  2265. _("use 'hg graft --continue' or 'hg update' to abort")),
  2266. ('updatestate', True, False, _('last update was interrupted'),
  2267. _("use 'hg update' to get a consistent checkout"))
  2268. ]
  2269. def checkunfinished(repo, commit=False):
  2270. '''Look for an unfinished multistep operation, like graft, and abort
  2271. if found. It's probably good to check this right before
  2272. bailifchanged().
  2273. '''
  2274. for f, clearable, allowcommit, msg, hint in unfinishedstates:
  2275. if commit and allowcommit:
  2276. continue
  2277. if repo.vfs.exists(f):
  2278. raise util.Abort(msg, hint=hint)
  2279. def clearunfinished(repo):
  2280. '''Check for unfinished operations (as above), and clear the ones
  2281. that are clearable.
  2282. '''
  2283. for f, clearable, allowcommit, msg, hint in unfinishedstates:
  2284. if not clearable and repo.vfs.exists(f):
  2285. raise util.Abort(msg, hint=hint)
  2286. for f, clearable, allowcommit, msg, hint in unfinishedstates:
  2287. if clearable and repo.vfs.exists(f):
  2288. util.unlink(repo.join(f))