PageRenderTime 67ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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

Large files files are truncated, but you can click here to view the full file