PageRenderTime 68ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/hgext/mq.py

https://bitbucket.org/mirror/mercurial/
Python | 3462 lines | 3340 code | 48 blank | 74 comment | 230 complexity | 35a077832e2917af08572e7323cb1c6d 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. # mq.py - patch queues for mercurial
  2. #
  3. # Copyright 2005, 2006 Chris Mason <mason@suse.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. '''manage a stack of patches
  8. This extension lets you work with a stack of patches in a Mercurial
  9. repository. It manages two stacks of patches - all known patches, and
  10. applied patches (subset of known patches).
  11. Known patches are represented as patch files in the .hg/patches
  12. directory. Applied patches are both patch files and changesets.
  13. Common tasks (use :hg:`help command` for more details)::
  14. create new patch qnew
  15. import existing patch qimport
  16. print patch series qseries
  17. print applied patches qapplied
  18. add known patch to applied stack qpush
  19. remove patch from applied stack qpop
  20. refresh contents of top applied patch qrefresh
  21. By default, mq will automatically use git patches when required to
  22. avoid losing file mode changes, copy records, binary files or empty
  23. files creations or deletions. This behaviour can be configured with::
  24. [mq]
  25. git = auto/keep/yes/no
  26. If set to 'keep', mq will obey the [diff] section configuration while
  27. preserving existing git patches upon qrefresh. If set to 'yes' or
  28. 'no', mq will override the [diff] section and always generate git or
  29. regular patches, possibly losing data in the second case.
  30. It may be desirable for mq changesets to be kept in the secret phase (see
  31. :hg:`help phases`), which can be enabled with the following setting::
  32. [mq]
  33. secret = True
  34. You will by default be managing a patch queue named "patches". You can
  35. create other, independent patch queues with the :hg:`qqueue` command.
  36. If the working directory contains uncommitted files, qpush, qpop and
  37. qgoto abort immediately. If -f/--force is used, the changes are
  38. discarded. Setting::
  39. [mq]
  40. keepchanges = True
  41. make them behave as if --keep-changes were passed, and non-conflicting
  42. local changes will be tolerated and preserved. If incompatible options
  43. such as -f/--force or --exact are passed, this setting is ignored.
  44. This extension used to provide a strip command. This command now lives
  45. in the strip extension.
  46. '''
  47. from mercurial.i18n import _
  48. from mercurial.node import bin, hex, short, nullid, nullrev
  49. from mercurial.lock import release
  50. from mercurial import commands, cmdutil, hg, scmutil, util, revset
  51. from mercurial import extensions, error, phases
  52. from mercurial import patch as patchmod
  53. from mercurial import localrepo
  54. from mercurial import subrepo
  55. import os, re, errno, shutil
  56. seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
  57. cmdtable = {}
  58. command = cmdutil.command(cmdtable)
  59. testedwith = 'internal'
  60. # force load strip extension formerly included in mq and import some utility
  61. try:
  62. stripext = extensions.find('strip')
  63. except KeyError:
  64. # note: load is lazy so we could avoid the try-except,
  65. # but I (marmoute) prefer this explicit code.
  66. class dummyui(object):
  67. def debug(self, msg):
  68. pass
  69. stripext = extensions.load(dummyui(), 'strip', '')
  70. strip = stripext.strip
  71. checksubstate = stripext.checksubstate
  72. checklocalchanges = stripext.checklocalchanges
  73. # Patch names looks like unix-file names.
  74. # They must be joinable with queue directory and result in the patch path.
  75. normname = util.normpath
  76. class statusentry(object):
  77. def __init__(self, node, name):
  78. self.node, self.name = node, name
  79. def __repr__(self):
  80. return hex(self.node) + ':' + self.name
  81. class patchheader(object):
  82. def __init__(self, pf, plainmode=False):
  83. def eatdiff(lines):
  84. while lines:
  85. l = lines[-1]
  86. if (l.startswith("diff -") or
  87. l.startswith("Index:") or
  88. l.startswith("===========")):
  89. del lines[-1]
  90. else:
  91. break
  92. def eatempty(lines):
  93. while lines:
  94. if not lines[-1].strip():
  95. del lines[-1]
  96. else:
  97. break
  98. message = []
  99. comments = []
  100. user = None
  101. date = None
  102. parent = None
  103. format = None
  104. subject = None
  105. branch = None
  106. nodeid = None
  107. diffstart = 0
  108. for line in file(pf):
  109. line = line.rstrip()
  110. if (line.startswith('diff --git')
  111. or (diffstart and line.startswith('+++ '))):
  112. diffstart = 2
  113. break
  114. diffstart = 0 # reset
  115. if line.startswith("--- "):
  116. diffstart = 1
  117. continue
  118. elif format == "hgpatch":
  119. # parse values when importing the result of an hg export
  120. if line.startswith("# User "):
  121. user = line[7:]
  122. elif line.startswith("# Date "):
  123. date = line[7:]
  124. elif line.startswith("# Parent "):
  125. parent = line[9:].lstrip()
  126. elif line.startswith("# Branch "):
  127. branch = line[9:]
  128. elif line.startswith("# Node ID "):
  129. nodeid = line[10:]
  130. elif not line.startswith("# ") and line:
  131. message.append(line)
  132. format = None
  133. elif line == '# HG changeset patch':
  134. message = []
  135. format = "hgpatch"
  136. elif (format != "tagdone" and (line.startswith("Subject: ") or
  137. line.startswith("subject: "))):
  138. subject = line[9:]
  139. format = "tag"
  140. elif (format != "tagdone" and (line.startswith("From: ") or
  141. line.startswith("from: "))):
  142. user = line[6:]
  143. format = "tag"
  144. elif (format != "tagdone" and (line.startswith("Date: ") or
  145. line.startswith("date: "))):
  146. date = line[6:]
  147. format = "tag"
  148. elif format == "tag" and line == "":
  149. # when looking for tags (subject: from: etc) they
  150. # end once you find a blank line in the source
  151. format = "tagdone"
  152. elif message or line:
  153. message.append(line)
  154. comments.append(line)
  155. eatdiff(message)
  156. eatdiff(comments)
  157. # Remember the exact starting line of the patch diffs before consuming
  158. # empty lines, for external use by TortoiseHg and others
  159. self.diffstartline = len(comments)
  160. eatempty(message)
  161. eatempty(comments)
  162. # make sure message isn't empty
  163. if format and format.startswith("tag") and subject:
  164. message.insert(0, "")
  165. message.insert(0, subject)
  166. self.message = message
  167. self.comments = comments
  168. self.user = user
  169. self.date = date
  170. self.parent = parent
  171. # nodeid and branch are for external use by TortoiseHg and others
  172. self.nodeid = nodeid
  173. self.branch = branch
  174. self.haspatch = diffstart > 1
  175. self.plainmode = plainmode
  176. def setuser(self, user):
  177. if not self.updateheader(['From: ', '# User '], user):
  178. try:
  179. patchheaderat = self.comments.index('# HG changeset patch')
  180. self.comments.insert(patchheaderat + 1, '# User ' + user)
  181. except ValueError:
  182. if self.plainmode or self._hasheader(['Date: ']):
  183. self.comments = ['From: ' + user] + self.comments
  184. else:
  185. tmp = ['# HG changeset patch', '# User ' + user, '']
  186. self.comments = tmp + self.comments
  187. self.user = user
  188. def setdate(self, date):
  189. if not self.updateheader(['Date: ', '# Date '], date):
  190. try:
  191. patchheaderat = self.comments.index('# HG changeset patch')
  192. self.comments.insert(patchheaderat + 1, '# Date ' + date)
  193. except ValueError:
  194. if self.plainmode or self._hasheader(['From: ']):
  195. self.comments = ['Date: ' + date] + self.comments
  196. else:
  197. tmp = ['# HG changeset patch', '# Date ' + date, '']
  198. self.comments = tmp + self.comments
  199. self.date = date
  200. def setparent(self, parent):
  201. if not self.updateheader(['# Parent '], parent):
  202. try:
  203. patchheaderat = self.comments.index('# HG changeset patch')
  204. self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
  205. except ValueError:
  206. pass
  207. self.parent = parent
  208. def setmessage(self, message):
  209. if self.comments:
  210. self._delmsg()
  211. self.message = [message]
  212. self.comments += self.message
  213. def updateheader(self, prefixes, new):
  214. '''Update all references to a field in the patch header.
  215. Return whether the field is present.'''
  216. res = False
  217. for prefix in prefixes:
  218. for i in xrange(len(self.comments)):
  219. if self.comments[i].startswith(prefix):
  220. self.comments[i] = prefix + new
  221. res = True
  222. break
  223. return res
  224. def _hasheader(self, prefixes):
  225. '''Check if a header starts with any of the given prefixes.'''
  226. for prefix in prefixes:
  227. for comment in self.comments:
  228. if comment.startswith(prefix):
  229. return True
  230. return False
  231. def __str__(self):
  232. if not self.comments:
  233. return ''
  234. return '\n'.join(self.comments) + '\n\n'
  235. def _delmsg(self):
  236. '''Remove existing message, keeping the rest of the comments fields.
  237. If comments contains 'subject: ', message will prepend
  238. the field and a blank line.'''
  239. if self.message:
  240. subj = 'subject: ' + self.message[0].lower()
  241. for i in xrange(len(self.comments)):
  242. if subj == self.comments[i].lower():
  243. del self.comments[i]
  244. self.message = self.message[2:]
  245. break
  246. ci = 0
  247. for mi in self.message:
  248. while mi != self.comments[ci]:
  249. ci += 1
  250. del self.comments[ci]
  251. def newcommit(repo, phase, *args, **kwargs):
  252. """helper dedicated to ensure a commit respect mq.secret setting
  253. It should be used instead of repo.commit inside the mq source for operation
  254. creating new changeset.
  255. """
  256. repo = repo.unfiltered()
  257. if phase is None:
  258. if repo.ui.configbool('mq', 'secret', False):
  259. phase = phases.secret
  260. if phase is not None:
  261. backup = repo.ui.backupconfig('phases', 'new-commit')
  262. try:
  263. if phase is not None:
  264. repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
  265. return repo.commit(*args, **kwargs)
  266. finally:
  267. if phase is not None:
  268. repo.ui.restoreconfig(backup)
  269. class AbortNoCleanup(error.Abort):
  270. pass
  271. class queue(object):
  272. def __init__(self, ui, baseui, path, patchdir=None):
  273. self.basepath = path
  274. try:
  275. fh = open(os.path.join(path, 'patches.queue'))
  276. cur = fh.read().rstrip()
  277. fh.close()
  278. if not cur:
  279. curpath = os.path.join(path, 'patches')
  280. else:
  281. curpath = os.path.join(path, 'patches-' + cur)
  282. except IOError:
  283. curpath = os.path.join(path, 'patches')
  284. self.path = patchdir or curpath
  285. self.opener = scmutil.opener(self.path)
  286. self.ui = ui
  287. self.baseui = baseui
  288. self.applieddirty = False
  289. self.seriesdirty = False
  290. self.added = []
  291. self.seriespath = "series"
  292. self.statuspath = "status"
  293. self.guardspath = "guards"
  294. self.activeguards = None
  295. self.guardsdirty = False
  296. # Handle mq.git as a bool with extended values
  297. try:
  298. gitmode = ui.configbool('mq', 'git', None)
  299. if gitmode is None:
  300. raise error.ConfigError
  301. self.gitmode = gitmode and 'yes' or 'no'
  302. except error.ConfigError:
  303. self.gitmode = ui.config('mq', 'git', 'auto').lower()
  304. self.plainmode = ui.configbool('mq', 'plain', False)
  305. self.checkapplied = True
  306. @util.propertycache
  307. def applied(self):
  308. def parselines(lines):
  309. for l in lines:
  310. entry = l.split(':', 1)
  311. if len(entry) > 1:
  312. n, name = entry
  313. yield statusentry(bin(n), name)
  314. elif l.strip():
  315. self.ui.warn(_('malformated mq status line: %s\n') % entry)
  316. # else we ignore empty lines
  317. try:
  318. lines = self.opener.read(self.statuspath).splitlines()
  319. return list(parselines(lines))
  320. except IOError, e:
  321. if e.errno == errno.ENOENT:
  322. return []
  323. raise
  324. @util.propertycache
  325. def fullseries(self):
  326. try:
  327. return self.opener.read(self.seriespath).splitlines()
  328. except IOError, e:
  329. if e.errno == errno.ENOENT:
  330. return []
  331. raise
  332. @util.propertycache
  333. def series(self):
  334. self.parseseries()
  335. return self.series
  336. @util.propertycache
  337. def seriesguards(self):
  338. self.parseseries()
  339. return self.seriesguards
  340. def invalidate(self):
  341. for a in 'applied fullseries series seriesguards'.split():
  342. if a in self.__dict__:
  343. delattr(self, a)
  344. self.applieddirty = False
  345. self.seriesdirty = False
  346. self.guardsdirty = False
  347. self.activeguards = None
  348. def diffopts(self, opts={}, patchfn=None):
  349. diffopts = patchmod.diffopts(self.ui, opts)
  350. if self.gitmode == 'auto':
  351. diffopts.upgrade = True
  352. elif self.gitmode == 'keep':
  353. pass
  354. elif self.gitmode in ('yes', 'no'):
  355. diffopts.git = self.gitmode == 'yes'
  356. else:
  357. raise util.Abort(_('mq.git option can be auto/keep/yes/no'
  358. ' got %s') % self.gitmode)
  359. if patchfn:
  360. diffopts = self.patchopts(diffopts, patchfn)
  361. return diffopts
  362. def patchopts(self, diffopts, *patches):
  363. """Return a copy of input diff options with git set to true if
  364. referenced patch is a git patch and should be preserved as such.
  365. """
  366. diffopts = diffopts.copy()
  367. if not diffopts.git and self.gitmode == 'keep':
  368. for patchfn in patches:
  369. patchf = self.opener(patchfn, 'r')
  370. # if the patch was a git patch, refresh it as a git patch
  371. for line in patchf:
  372. if line.startswith('diff --git'):
  373. diffopts.git = True
  374. break
  375. patchf.close()
  376. return diffopts
  377. def join(self, *p):
  378. return os.path.join(self.path, *p)
  379. def findseries(self, patch):
  380. def matchpatch(l):
  381. l = l.split('#', 1)[0]
  382. return l.strip() == patch
  383. for index, l in enumerate(self.fullseries):
  384. if matchpatch(l):
  385. return index
  386. return None
  387. guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
  388. def parseseries(self):
  389. self.series = []
  390. self.seriesguards = []
  391. for l in self.fullseries:
  392. h = l.find('#')
  393. if h == -1:
  394. patch = l
  395. comment = ''
  396. elif h == 0:
  397. continue
  398. else:
  399. patch = l[:h]
  400. comment = l[h:]
  401. patch = patch.strip()
  402. if patch:
  403. if patch in self.series:
  404. raise util.Abort(_('%s appears more than once in %s') %
  405. (patch, self.join(self.seriespath)))
  406. self.series.append(patch)
  407. self.seriesguards.append(self.guard_re.findall(comment))
  408. def checkguard(self, guard):
  409. if not guard:
  410. return _('guard cannot be an empty string')
  411. bad_chars = '# \t\r\n\f'
  412. first = guard[0]
  413. if first in '-+':
  414. return (_('guard %r starts with invalid character: %r') %
  415. (guard, first))
  416. for c in bad_chars:
  417. if c in guard:
  418. return _('invalid character in guard %r: %r') % (guard, c)
  419. def setactive(self, guards):
  420. for guard in guards:
  421. bad = self.checkguard(guard)
  422. if bad:
  423. raise util.Abort(bad)
  424. guards = sorted(set(guards))
  425. self.ui.debug('active guards: %s\n' % ' '.join(guards))
  426. self.activeguards = guards
  427. self.guardsdirty = True
  428. def active(self):
  429. if self.activeguards is None:
  430. self.activeguards = []
  431. try:
  432. guards = self.opener.read(self.guardspath).split()
  433. except IOError, err:
  434. if err.errno != errno.ENOENT:
  435. raise
  436. guards = []
  437. for i, guard in enumerate(guards):
  438. bad = self.checkguard(guard)
  439. if bad:
  440. self.ui.warn('%s:%d: %s\n' %
  441. (self.join(self.guardspath), i + 1, bad))
  442. else:
  443. self.activeguards.append(guard)
  444. return self.activeguards
  445. def setguards(self, idx, guards):
  446. for g in guards:
  447. if len(g) < 2:
  448. raise util.Abort(_('guard %r too short') % g)
  449. if g[0] not in '-+':
  450. raise util.Abort(_('guard %r starts with invalid char') % g)
  451. bad = self.checkguard(g[1:])
  452. if bad:
  453. raise util.Abort(bad)
  454. drop = self.guard_re.sub('', self.fullseries[idx])
  455. self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
  456. self.parseseries()
  457. self.seriesdirty = True
  458. def pushable(self, idx):
  459. if isinstance(idx, str):
  460. idx = self.series.index(idx)
  461. patchguards = self.seriesguards[idx]
  462. if not patchguards:
  463. return True, None
  464. guards = self.active()
  465. exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
  466. if exactneg:
  467. return False, repr(exactneg[0])
  468. pos = [g for g in patchguards if g[0] == '+']
  469. exactpos = [g for g in pos if g[1:] in guards]
  470. if pos:
  471. if exactpos:
  472. return True, repr(exactpos[0])
  473. return False, ' '.join(map(repr, pos))
  474. return True, ''
  475. def explainpushable(self, idx, all_patches=False):
  476. write = all_patches and self.ui.write or self.ui.warn
  477. if all_patches or self.ui.verbose:
  478. if isinstance(idx, str):
  479. idx = self.series.index(idx)
  480. pushable, why = self.pushable(idx)
  481. if all_patches and pushable:
  482. if why is None:
  483. write(_('allowing %s - no guards in effect\n') %
  484. self.series[idx])
  485. else:
  486. if not why:
  487. write(_('allowing %s - no matching negative guards\n') %
  488. self.series[idx])
  489. else:
  490. write(_('allowing %s - guarded by %s\n') %
  491. (self.series[idx], why))
  492. if not pushable:
  493. if why:
  494. write(_('skipping %s - guarded by %s\n') %
  495. (self.series[idx], why))
  496. else:
  497. write(_('skipping %s - no matching guards\n') %
  498. self.series[idx])
  499. def savedirty(self):
  500. def writelist(items, path):
  501. fp = self.opener(path, 'w')
  502. for i in items:
  503. fp.write("%s\n" % i)
  504. fp.close()
  505. if self.applieddirty:
  506. writelist(map(str, self.applied), self.statuspath)
  507. self.applieddirty = False
  508. if self.seriesdirty:
  509. writelist(self.fullseries, self.seriespath)
  510. self.seriesdirty = False
  511. if self.guardsdirty:
  512. writelist(self.activeguards, self.guardspath)
  513. self.guardsdirty = False
  514. if self.added:
  515. qrepo = self.qrepo()
  516. if qrepo:
  517. qrepo[None].add(f for f in self.added if f not in qrepo[None])
  518. self.added = []
  519. def removeundo(self, repo):
  520. undo = repo.sjoin('undo')
  521. if not os.path.exists(undo):
  522. return
  523. try:
  524. os.unlink(undo)
  525. except OSError, inst:
  526. self.ui.warn(_('error removing undo: %s\n') % str(inst))
  527. def backup(self, repo, files, copy=False):
  528. # backup local changes in --force case
  529. for f in sorted(files):
  530. absf = repo.wjoin(f)
  531. if os.path.lexists(absf):
  532. self.ui.note(_('saving current version of %s as %s\n') %
  533. (f, f + '.orig'))
  534. if copy:
  535. util.copyfile(absf, absf + '.orig')
  536. else:
  537. util.rename(absf, absf + '.orig')
  538. def printdiff(self, repo, diffopts, node1, node2=None, files=None,
  539. fp=None, changes=None, opts={}):
  540. stat = opts.get('stat')
  541. m = scmutil.match(repo[node1], files, opts)
  542. cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
  543. changes, stat, fp)
  544. def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
  545. # first try just applying the patch
  546. (err, n) = self.apply(repo, [patch], update_status=False,
  547. strict=True, merge=rev)
  548. if err == 0:
  549. return (err, n)
  550. if n is None:
  551. raise util.Abort(_("apply failed for patch %s") % patch)
  552. self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
  553. # apply failed, strip away that rev and merge.
  554. hg.clean(repo, head)
  555. strip(self.ui, repo, [n], update=False, backup='strip')
  556. ctx = repo[rev]
  557. ret = hg.merge(repo, rev)
  558. if ret:
  559. raise util.Abort(_("update returned %d") % ret)
  560. n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
  561. if n is None:
  562. raise util.Abort(_("repo commit failed"))
  563. try:
  564. ph = patchheader(mergeq.join(patch), self.plainmode)
  565. except Exception:
  566. raise util.Abort(_("unable to read %s") % patch)
  567. diffopts = self.patchopts(diffopts, patch)
  568. patchf = self.opener(patch, "w")
  569. comments = str(ph)
  570. if comments:
  571. patchf.write(comments)
  572. self.printdiff(repo, diffopts, head, n, fp=patchf)
  573. patchf.close()
  574. self.removeundo(repo)
  575. return (0, n)
  576. def qparents(self, repo, rev=None):
  577. """return the mq handled parent or p1
  578. In some case where mq get himself in being the parent of a merge the
  579. appropriate parent may be p2.
  580. (eg: an in progress merge started with mq disabled)
  581. If no parent are managed by mq, p1 is returned.
  582. """
  583. if rev is None:
  584. (p1, p2) = repo.dirstate.parents()
  585. if p2 == nullid:
  586. return p1
  587. if not self.applied:
  588. return None
  589. return self.applied[-1].node
  590. p1, p2 = repo.changelog.parents(rev)
  591. if p2 != nullid and p2 in [x.node for x in self.applied]:
  592. return p2
  593. return p1
  594. def mergepatch(self, repo, mergeq, series, diffopts):
  595. if not self.applied:
  596. # each of the patches merged in will have two parents. This
  597. # can confuse the qrefresh, qdiff, and strip code because it
  598. # needs to know which parent is actually in the patch queue.
  599. # so, we insert a merge marker with only one parent. This way
  600. # the first patch in the queue is never a merge patch
  601. #
  602. pname = ".hg.patches.merge.marker"
  603. n = newcommit(repo, None, '[mq]: merge marker', force=True)
  604. self.removeundo(repo)
  605. self.applied.append(statusentry(n, pname))
  606. self.applieddirty = True
  607. head = self.qparents(repo)
  608. for patch in series:
  609. patch = mergeq.lookup(patch, strict=True)
  610. if not patch:
  611. self.ui.warn(_("patch %s does not exist\n") % patch)
  612. return (1, None)
  613. pushable, reason = self.pushable(patch)
  614. if not pushable:
  615. self.explainpushable(patch, all_patches=True)
  616. continue
  617. info = mergeq.isapplied(patch)
  618. if not info:
  619. self.ui.warn(_("patch %s is not applied\n") % patch)
  620. return (1, None)
  621. rev = info[1]
  622. err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
  623. if head:
  624. self.applied.append(statusentry(head, patch))
  625. self.applieddirty = True
  626. if err:
  627. return (err, head)
  628. self.savedirty()
  629. return (0, head)
  630. def patch(self, repo, patchfile):
  631. '''Apply patchfile to the working directory.
  632. patchfile: name of patch file'''
  633. files = set()
  634. try:
  635. fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
  636. files=files, eolmode=None)
  637. return (True, list(files), fuzz)
  638. except Exception, inst:
  639. self.ui.note(str(inst) + '\n')
  640. if not self.ui.verbose:
  641. self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
  642. self.ui.traceback()
  643. return (False, list(files), False)
  644. def apply(self, repo, series, list=False, update_status=True,
  645. strict=False, patchdir=None, merge=None, all_files=None,
  646. tobackup=None, keepchanges=False):
  647. wlock = lock = tr = None
  648. try:
  649. wlock = repo.wlock()
  650. lock = repo.lock()
  651. tr = repo.transaction("qpush")
  652. try:
  653. ret = self._apply(repo, series, list, update_status,
  654. strict, patchdir, merge, all_files=all_files,
  655. tobackup=tobackup, keepchanges=keepchanges)
  656. tr.close()
  657. self.savedirty()
  658. return ret
  659. except AbortNoCleanup:
  660. tr.close()
  661. self.savedirty()
  662. return 2, repo.dirstate.p1()
  663. except: # re-raises
  664. try:
  665. tr.abort()
  666. finally:
  667. repo.invalidate()
  668. repo.dirstate.invalidate()
  669. self.invalidate()
  670. raise
  671. finally:
  672. release(tr, lock, wlock)
  673. self.removeundo(repo)
  674. def _apply(self, repo, series, list=False, update_status=True,
  675. strict=False, patchdir=None, merge=None, all_files=None,
  676. tobackup=None, keepchanges=False):
  677. """returns (error, hash)
  678. error = 1 for unable to read, 2 for patch failed, 3 for patch
  679. fuzz. tobackup is None or a set of files to backup before they
  680. are modified by a patch.
  681. """
  682. # TODO unify with commands.py
  683. if not patchdir:
  684. patchdir = self.path
  685. err = 0
  686. n = None
  687. for patchname in series:
  688. pushable, reason = self.pushable(patchname)
  689. if not pushable:
  690. self.explainpushable(patchname, all_patches=True)
  691. continue
  692. self.ui.status(_("applying %s\n") % patchname)
  693. pf = os.path.join(patchdir, patchname)
  694. try:
  695. ph = patchheader(self.join(patchname), self.plainmode)
  696. except IOError:
  697. self.ui.warn(_("unable to read %s\n") % patchname)
  698. err = 1
  699. break
  700. message = ph.message
  701. if not message:
  702. # The commit message should not be translated
  703. message = "imported patch %s\n" % patchname
  704. else:
  705. if list:
  706. # The commit message should not be translated
  707. message.append("\nimported patch %s" % patchname)
  708. message = '\n'.join(message)
  709. if ph.haspatch:
  710. if tobackup:
  711. touched = patchmod.changedfiles(self.ui, repo, pf)
  712. touched = set(touched) & tobackup
  713. if touched and keepchanges:
  714. raise AbortNoCleanup(
  715. _("local changes found, refresh first"))
  716. self.backup(repo, touched, copy=True)
  717. tobackup = tobackup - touched
  718. (patcherr, files, fuzz) = self.patch(repo, pf)
  719. if all_files is not None:
  720. all_files.update(files)
  721. patcherr = not patcherr
  722. else:
  723. self.ui.warn(_("patch %s is empty\n") % patchname)
  724. patcherr, files, fuzz = 0, [], 0
  725. if merge and files:
  726. # Mark as removed/merged and update dirstate parent info
  727. removed = []
  728. merged = []
  729. for f in files:
  730. if os.path.lexists(repo.wjoin(f)):
  731. merged.append(f)
  732. else:
  733. removed.append(f)
  734. for f in removed:
  735. repo.dirstate.remove(f)
  736. for f in merged:
  737. repo.dirstate.merge(f)
  738. p1, p2 = repo.dirstate.parents()
  739. repo.setparents(p1, merge)
  740. if all_files and '.hgsubstate' in all_files:
  741. wctx = repo[None]
  742. pctx = repo['.']
  743. overwrite = False
  744. mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
  745. overwrite)
  746. files += mergedsubstate.keys()
  747. match = scmutil.matchfiles(repo, files or [])
  748. oldtip = repo['tip']
  749. n = newcommit(repo, None, message, ph.user, ph.date, match=match,
  750. force=True)
  751. if repo['tip'] == oldtip:
  752. raise util.Abort(_("qpush exactly duplicates child changeset"))
  753. if n is None:
  754. raise util.Abort(_("repository commit failed"))
  755. if update_status:
  756. self.applied.append(statusentry(n, patchname))
  757. if patcherr:
  758. self.ui.warn(_("patch failed, rejects left in working dir\n"))
  759. err = 2
  760. break
  761. if fuzz and strict:
  762. self.ui.warn(_("fuzz found when applying patch, stopping\n"))
  763. err = 3
  764. break
  765. return (err, n)
  766. def _cleanup(self, patches, numrevs, keep=False):
  767. if not keep:
  768. r = self.qrepo()
  769. if r:
  770. r[None].forget(patches)
  771. for p in patches:
  772. try:
  773. os.unlink(self.join(p))
  774. except OSError, inst:
  775. if inst.errno != errno.ENOENT:
  776. raise
  777. qfinished = []
  778. if numrevs:
  779. qfinished = self.applied[:numrevs]
  780. del self.applied[:numrevs]
  781. self.applieddirty = True
  782. unknown = []
  783. for (i, p) in sorted([(self.findseries(p), p) for p in patches],
  784. reverse=True):
  785. if i is not None:
  786. del self.fullseries[i]
  787. else:
  788. unknown.append(p)
  789. if unknown:
  790. if numrevs:
  791. rev = dict((entry.name, entry.node) for entry in qfinished)
  792. for p in unknown:
  793. msg = _('revision %s refers to unknown patches: %s\n')
  794. self.ui.warn(msg % (short(rev[p]), p))
  795. else:
  796. msg = _('unknown patches: %s\n')
  797. raise util.Abort(''.join(msg % p for p in unknown))
  798. self.parseseries()
  799. self.seriesdirty = True
  800. return [entry.node for entry in qfinished]
  801. def _revpatches(self, repo, revs):
  802. firstrev = repo[self.applied[0].node].rev()
  803. patches = []
  804. for i, rev in enumerate(revs):
  805. if rev < firstrev:
  806. raise util.Abort(_('revision %d is not managed') % rev)
  807. ctx = repo[rev]
  808. base = self.applied[i].node
  809. if ctx.node() != base:
  810. msg = _('cannot delete revision %d above applied patches')
  811. raise util.Abort(msg % rev)
  812. patch = self.applied[i].name
  813. for fmt in ('[mq]: %s', 'imported patch %s'):
  814. if ctx.description() == fmt % patch:
  815. msg = _('patch %s finalized without changeset message\n')
  816. repo.ui.status(msg % patch)
  817. break
  818. patches.append(patch)
  819. return patches
  820. def finish(self, repo, revs):
  821. # Manually trigger phase computation to ensure phasedefaults is
  822. # executed before we remove the patches.
  823. repo._phasecache
  824. patches = self._revpatches(repo, sorted(revs))
  825. qfinished = self._cleanup(patches, len(patches))
  826. if qfinished and repo.ui.configbool('mq', 'secret', False):
  827. # only use this logic when the secret option is added
  828. oldqbase = repo[qfinished[0]]
  829. tphase = repo.ui.config('phases', 'new-commit', phases.draft)
  830. if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
  831. phases.advanceboundary(repo, tphase, qfinished)
  832. def delete(self, repo, patches, opts):
  833. if not patches and not opts.get('rev'):
  834. raise util.Abort(_('qdelete requires at least one revision or '
  835. 'patch name'))
  836. realpatches = []
  837. for patch in patches:
  838. patch = self.lookup(patch, strict=True)
  839. info = self.isapplied(patch)
  840. if info:
  841. raise util.Abort(_("cannot delete applied patch %s") % patch)
  842. if patch not in self.series:
  843. raise util.Abort(_("patch %s not in series file") % patch)
  844. if patch not in realpatches:
  845. realpatches.append(patch)
  846. numrevs = 0
  847. if opts.get('rev'):
  848. if not self.applied:
  849. raise util.Abort(_('no patches applied'))
  850. revs = scmutil.revrange(repo, opts.get('rev'))
  851. if len(revs) > 1 and revs[0] > revs[1]:
  852. revs.reverse()
  853. revpatches = self._revpatches(repo, revs)
  854. realpatches += revpatches
  855. numrevs = len(revpatches)
  856. self._cleanup(realpatches, numrevs, opts.get('keep'))
  857. def checktoppatch(self, repo):
  858. '''check that working directory is at qtip'''
  859. if self.applied:
  860. top = self.applied[-1].node
  861. patch = self.applied[-1].name
  862. if repo.dirstate.p1() != top:
  863. raise util.Abort(_("working directory revision is not qtip"))
  864. return top, patch
  865. return None, None
  866. def putsubstate2changes(self, substatestate, changes):
  867. for files in changes[:3]:
  868. if '.hgsubstate' in files:
  869. return # already listed up
  870. # not yet listed up
  871. if substatestate in 'a?':
  872. changes[1].append('.hgsubstate')
  873. elif substatestate in 'r':
  874. changes[2].append('.hgsubstate')
  875. else: # modified
  876. changes[0].append('.hgsubstate')
  877. def checklocalchanges(self, repo, force=False, refresh=True):
  878. excsuffix = ''
  879. if refresh:
  880. excsuffix = ', refresh first'
  881. # plain versions for i18n tool to detect them
  882. _("local changes found, refresh first")
  883. _("local changed subrepos found, refresh first")
  884. return checklocalchanges(repo, force, excsuffix)
  885. _reserved = ('series', 'status', 'guards', '.', '..')
  886. def checkreservedname(self, name):
  887. if name in self._reserved:
  888. raise util.Abort(_('"%s" cannot be used as the name of a patch')
  889. % name)
  890. for prefix in ('.hg', '.mq'):
  891. if name.startswith(prefix):
  892. raise util.Abort(_('patch name cannot begin with "%s"')
  893. % prefix)
  894. for c in ('#', ':'):
  895. if c in name:
  896. raise util.Abort(_('"%s" cannot be used in the name of a patch')
  897. % c)
  898. def checkpatchname(self, name, force=False):
  899. self.checkreservedname(name)
  900. if not force and os.path.exists(self.join(name)):
  901. if os.path.isdir(self.join(name)):
  902. raise util.Abort(_('"%s" already exists as a directory')
  903. % name)
  904. else:
  905. raise util.Abort(_('patch "%s" already exists') % name)
  906. def checkkeepchanges(self, keepchanges, force):
  907. if force and keepchanges:
  908. raise util.Abort(_('cannot use both --force and --keep-changes'))
  909. def new(self, repo, patchfn, *pats, **opts):
  910. """options:
  911. msg: a string or a no-argument function returning a string
  912. """
  913. msg = opts.get('msg')
  914. edit = opts.get('edit')
  915. user = opts.get('user')
  916. date = opts.get('date')
  917. if date:
  918. date = util.parsedate(date)
  919. diffopts = self.diffopts({'git': opts.get('git')})
  920. if opts.get('checkname', True):
  921. self.checkpatchname(patchfn)
  922. inclsubs = checksubstate(repo)
  923. if inclsubs:
  924. substatestate = repo.dirstate['.hgsubstate']
  925. if opts.get('include') or opts.get('exclude') or pats:
  926. match = scmutil.match(repo[None], pats, opts)
  927. # detect missing files in pats
  928. def badfn(f, msg):
  929. if f != '.hgsubstate': # .hgsubstate is auto-created
  930. raise util.Abort('%s: %s' % (f, msg))
  931. match.bad = badfn
  932. changes = repo.status(match=match)
  933. else:
  934. changes = self.checklocalchanges(repo, force=True)
  935. commitfiles = list(inclsubs)
  936. for files in changes[:3]:
  937. commitfiles.extend(files)
  938. match = scmutil.matchfiles(repo, commitfiles)
  939. if len(repo[None].parents()) > 1:
  940. raise util.Abort(_('cannot manage merge changesets'))
  941. self.checktoppatch(repo)
  942. insert = self.fullseriesend()
  943. wlock = repo.wlock()
  944. try:
  945. try:
  946. # if patch file write fails, abort early
  947. p = self.opener(patchfn, "w")
  948. except IOError, e:
  949. raise util.Abort(_('cannot write patch "%s": %s')
  950. % (patchfn, e.strerror))
  951. try:
  952. if self.plainmode:
  953. if user:
  954. p.write("From: " + user + "\n")
  955. if not date:
  956. p.write("\n")
  957. if date:
  958. p.write("Date: %d %d\n\n" % date)
  959. else:
  960. p.write("# HG changeset patch\n")
  961. p.write("# Parent "
  962. + hex(repo[None].p1().node()) + "\n")
  963. if user:
  964. p.write("# User " + user + "\n")
  965. if date:
  966. p.write("# Date %s %s\n\n" % date)
  967. defaultmsg = "[mq]: %s" % patchfn
  968. editor = cmdutil.getcommiteditor()
  969. if edit:
  970. def finishdesc(desc):
  971. if desc.rstrip():
  972. return desc
  973. else:
  974. return defaultmsg
  975. # i18n: this message is shown in editor with "HG: " prefix
  976. extramsg = _('Leave message empty to use default message.')
  977. editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
  978. extramsg=extramsg)
  979. commitmsg = msg
  980. else:
  981. commitmsg = msg or defaultmsg
  982. n = newcommit(repo, None, commitmsg, user, date, match=match,
  983. force=True, editor=editor)
  984. if n is None:
  985. raise util.Abort(_("repo commit failed"))
  986. try:
  987. self.fullseries[insert:insert] = [patchfn]
  988. self.applied.append(statusentry(n, patchfn))
  989. self.parseseries()
  990. self.seriesdirty = True
  991. self.applieddirty = True
  992. nctx = repo[n]
  993. if nctx.description() != defaultmsg.rstrip():
  994. msg = nctx.description() + "\n\n"
  995. p.write(msg)
  996. if commitfiles:
  997. parent = self.qparents(repo, n)
  998. if inclsubs:
  999. self.putsubstate2changes(substatestate, changes)
  1000. chunks = patchmod.diff(repo, node1=parent, node2=n,
  1001. changes=changes, opts=diffopts)
  1002. for chunk in chunks:
  1003. p.write(chunk)
  1004. p.close()
  1005. r = self.qrepo()
  1006. if r:
  1007. r[None].add([patchfn])
  1008. except: # re-raises
  1009. repo.rollback()
  1010. raise
  1011. except Exception:
  1012. patchpath = self.join(patchfn)
  1013. try:
  1014. os.unlink(patchpath)
  1015. except OSError:
  1016. self.ui.warn(_('error unlinking %s\n') % patchpath)
  1017. raise
  1018. self.removeundo(repo)
  1019. finally:
  1020. release(wlock)
  1021. def isapplied(self, patch):
  1022. """returns (index, rev, patch)"""
  1023. for i, a in enumerate(self.applied):
  1024. if a.name == patch:
  1025. return (i, a.node, a.name)
  1026. return None
  1027. # if the exact patch name does not exist, we try a few
  1028. # variations. If strict is passed, we try only #1
  1029. #
  1030. # 1) a number (as string) to indicate an offset in the series file
  1031. # 2) a unique substring of the patch name was given
  1032. # 3) patchname[-+]num to indicate an offset in the series file
  1033. def lookup(self, patch, strict=False):
  1034. def partialname(s):
  1035. if s in self.series:
  1036. return s
  1037. matches = [x for x in self.series if s in x]
  1038. if len(matches) > 1:
  1039. self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
  1040. for m in matches:
  1041. self.ui.warn(' %s\n' % m)
  1042. return None
  1043. if matches:
  1044. return matches[0]
  1045. if self.series and self.applied:
  1046. if s == 'qtip':
  1047. return self.series[self.seriesend(True) - 1]
  1048. if s == 'qbase':
  1049. return self.series[0]
  1050. return None
  1051. if patch in self.series:
  1052. return patch
  1053. if not os.path.isfile(self.join(patch)):
  1054. try:
  1055. sno = int(patch)
  1056. except (ValueError, OverflowError):
  1057. pass
  1058. else:
  1059. if -len(self.series) <= sno < len(self.series):
  1060. return self.series[sno]
  1061. if not strict:
  1062. res = partialname(patch)
  1063. if res:
  1064. return res
  1065. minus = patch.rfind('-')
  1066. if minus >= 0:
  1067. res = partialname(patch[:minus])
  1068. if res:
  1069. i = self.series.index(res)
  1070. try:
  1071. off = int(patch[minus + 1:] or 1)
  1072. except (ValueError, OverflowError):
  1073. pass
  1074. else:
  1075. if i - off >= 0:
  1076. return self.series[i - off]
  1077. plus = patch.rfind('+')
  1078. if plus >= 0:
  1079. res = partialname(patch[:plus])
  1080. if res:
  1081. i = self.series.index(res)
  1082. try:
  1083. off = int(patch[plus + 1:] or 1)
  1084. except (ValueError, OverflowError):
  1085. pass
  1086. else:
  1087. if i + off < len(self.series):
  1088. return self.series[i + off]
  1089. raise util.Abort(_("patch %s not in series") % patch)
  1090. def push(self, repo, patch=None, force=False, list=False, mergeq=None,
  1091. all=False, move=False, exact=False, nobackup=False,
  1092. keepchanges=False):
  1093. self.checkkeepchanges(keepchanges, force)
  1094. diffopts = self.diffopts()
  1095. wlock = repo.wlock()
  1096. try:
  1097. heads = []
  1098. for hs in repo.branchmap().itervalues():
  1099. heads.extend(hs)
  1100. if not heads:
  1101. heads = [nullid]
  1102. if repo.dirstate.p1() not in heads and not exact:
  1103. self.ui.status(_("(working directory not at a head)\n"))
  1104. if not self.series:
  1105. self.ui.warn(_('no patches in series\n'))
  1106. return 0
  1107. # Suppose our series file is: A B C and the current 'top'
  1108. # patch is B. qpush C should be performed (moving forward)
  1109. # qpush B is a NOP (no change) qpush A is an error (can't
  1110. # go backwards with qpush)
  1111. if patch:
  1112. patch = self.lookup(patch)
  1113. info = self.isapplied(patch)
  1114. if info and info[0] >= len(self.applied) - 1:
  1115. self.ui.warn(
  1116. _('qpush: %s is already at the top\n') % patch)
  1117. return 0
  1118. pushable, reason = self.pushable(patch)
  1119. if pushable:
  1120. if self.series.index(patch) < self.seriesend():
  1121. raise util.Abort(
  1122. _("cannot push to a previous patch: %s") % patch)
  1123. else:
  1124. if reason:
  1125. reason = _('guarded by %s') % reason
  1126. else:
  1127. reason = _('no matching guards')
  1128. self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
  1129. return 1
  1130. elif all:
  1131. patch = self.series[-1]
  1132. if self.isapplied(patch):
  1133. self.ui.warn(_('all patches are currently applied\n'))
  1134. return 0
  1135. # Following the above example, starting at 'top' of B:
  1136. # qpush should be performed (pushes C), but a subsequent
  1137. # qpush without an argument is an error (nothing to
  1138. # apply). This allows a loop of "...while hg qpush..." to
  1139. # work as it detects an error when done
  1140. start = self.seriesend()
  1141. if start == len(self.series):
  1142. self.ui.warn(_('patch series already fully applied\n'))
  1143. return 1
  1144. if not force and not keepchanges:
  1145. self.checklocalchanges(repo, refresh=self.applied)
  1146. if exact:
  1147. if keepchanges:
  1148. raise util.Abort(
  1149. _("cannot use --exact and --keep-changes together"))
  1150. if move:
  1151. raise util.Abort(_('cannot use --exact and --move '
  1152. 'together'))
  1153. if self.applied:
  1154. raise util.Abort(_('cannot push --exact with applied '
  1155. 'patches'))
  1156. root = self.series[start]
  1157. target = patchheader(self.join(root), self.plainmode).parent
  1158. if not target:
  1159. raise util.Abort(
  1160. _("%s does not have a parent recorded") % root)
  1161. if not repo[target] == repo['.']:
  1162. hg.update(repo, target)
  1163. if move:
  1164. if not patch:
  1165. raise util.Abort(_("please specify the patch to move"))
  1166. for fullstart, rpn in enumerate(self.fullseries):
  1167. # strip markers for patch guards
  1168. if self.guard_re.split(rpn, 1)[0] == self.series[start]:
  1169. break
  1170. for i, rpn in enumerate(self.fullseries[fullstart:]):
  1171. # strip markers for patch guards
  1172. if self.guard_re.split(rpn, 1)[0] == patch:
  1173. break
  1174. index = fullstart + i
  1175. assert index < len(self.fullseries)
  1176. fullpatch = self.fullseries[index]
  1177. del self.fullseries[index]
  1178. self.fullseries.insert(fullstart, fullpatch)
  1179. self.parseseries()
  1180. self.seriesdirty = True
  1181. self.applieddirty = True
  1182. if start > 0:
  1183. self.checktoppatch(repo)
  1184. if not patch:

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