diff options
author | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
commit | 458120dd40db6b4df55a4e96b650e16798ef06a0 (patch) | |
tree | 8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/hg/hgext/mq.py | |
parent | 3a742c699f6806c1145aea5149bf15de15a0afd7 (diff) |
add hg and python
Diffstat (limited to 'sys/src/cmd/hg/hgext/mq.py')
-rw-r--r-- | sys/src/cmd/hg/hgext/mq.py | 2653 |
1 files changed, 2653 insertions, 0 deletions
diff --git a/sys/src/cmd/hg/hgext/mq.py b/sys/src/cmd/hg/hgext/mq.py new file mode 100644 index 000000000..a2be932c4 --- /dev/null +++ b/sys/src/cmd/hg/hgext/mq.py @@ -0,0 +1,2653 @@ +# mq.py - patch queues for mercurial +# +# Copyright 2005, 2006 Chris Mason <mason@suse.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2, incorporated herein by reference. + +'''manage a stack of patches + +This extension lets you work with a stack of patches in a Mercurial +repository. It manages two stacks of patches - all known patches, and +applied patches (subset of known patches). + +Known patches are represented as patch files in the .hg/patches +directory. Applied patches are both patch files and changesets. + +Common tasks (use "hg help command" for more details):: + + prepare repository to work with patches qinit + create new patch qnew + import existing patch qimport + + print patch series qseries + print applied patches qapplied + print name of top applied patch qtop + + add known patch to applied stack qpush + remove patch from applied stack qpop + refresh contents of top applied patch qrefresh +''' + +from mercurial.i18n import _ +from mercurial.node import bin, hex, short, nullid, nullrev +from mercurial.lock import release +from mercurial import commands, cmdutil, hg, patch, util +from mercurial import repair, extensions, url, error +import os, sys, re, errno + +commands.norepo += " qclone" + +# Patch names looks like unix-file names. +# They must be joinable with queue directory and result in the patch path. +normname = util.normpath + +class statusentry(object): + def __init__(self, rev, name=None): + if not name: + fields = rev.split(':', 1) + if len(fields) == 2: + self.rev, self.name = fields + else: + self.rev, self.name = None, None + else: + self.rev, self.name = rev, name + + def __str__(self): + return self.rev + ':' + self.name + +class patchheader(object): + def __init__(self, pf): + def eatdiff(lines): + while lines: + l = lines[-1] + if (l.startswith("diff -") or + l.startswith("Index:") or + l.startswith("===========")): + del lines[-1] + else: + break + def eatempty(lines): + while lines: + l = lines[-1] + if re.match('\s*$', l): + del lines[-1] + else: + break + + message = [] + comments = [] + user = None + date = None + format = None + subject = None + diffstart = 0 + + for line in file(pf): + line = line.rstrip() + if line.startswith('diff --git'): + diffstart = 2 + break + if diffstart: + if line.startswith('+++ '): + diffstart = 2 + break + if line.startswith("--- "): + diffstart = 1 + continue + elif format == "hgpatch": + # parse values when importing the result of an hg export + if line.startswith("# User "): + user = line[7:] + elif line.startswith("# Date "): + date = line[7:] + elif not line.startswith("# ") and line: + message.append(line) + format = None + elif line == '# HG changeset patch': + message = [] + format = "hgpatch" + elif (format != "tagdone" and (line.startswith("Subject: ") or + line.startswith("subject: "))): + subject = line[9:] + format = "tag" + elif (format != "tagdone" and (line.startswith("From: ") or + line.startswith("from: "))): + user = line[6:] + format = "tag" + elif format == "tag" and line == "": + # when looking for tags (subject: from: etc) they + # end once you find a blank line in the source + format = "tagdone" + elif message or line: + message.append(line) + comments.append(line) + + eatdiff(message) + eatdiff(comments) + eatempty(message) + eatempty(comments) + + # make sure message isn't empty + if format and format.startswith("tag") and subject: + message.insert(0, "") + message.insert(0, subject) + + self.message = message + self.comments = comments + self.user = user + self.date = date + self.haspatch = diffstart > 1 + + def setuser(self, user): + if not self.updateheader(['From: ', '# User '], user): + try: + patchheaderat = self.comments.index('# HG changeset patch') + self.comments.insert(patchheaderat + 1, '# User ' + user) + except ValueError: + if self._hasheader(['Date: ']): + self.comments = ['From: ' + user] + self.comments + else: + tmp = ['# HG changeset patch', '# User ' + user, ''] + self.comments = tmp + self.comments + self.user = user + + def setdate(self, date): + if not self.updateheader(['Date: ', '# Date '], date): + try: + patchheaderat = self.comments.index('# HG changeset patch') + self.comments.insert(patchheaderat + 1, '# Date ' + date) + except ValueError: + if self._hasheader(['From: ']): + self.comments = ['Date: ' + date] + self.comments + else: + tmp = ['# HG changeset patch', '# Date ' + date, ''] + self.comments = tmp + self.comments + self.date = date + + def setmessage(self, message): + if self.comments: + self._delmsg() + self.message = [message] + self.comments += self.message + + def updateheader(self, prefixes, new): + '''Update all references to a field in the patch header. + Return whether the field is present.''' + res = False + for prefix in prefixes: + for i in xrange(len(self.comments)): + if self.comments[i].startswith(prefix): + self.comments[i] = prefix + new + res = True + break + return res + + def _hasheader(self, prefixes): + '''Check if a header starts with any of the given prefixes.''' + for prefix in prefixes: + for comment in self.comments: + if comment.startswith(prefix): + return True + return False + + def __str__(self): + if not self.comments: + return '' + return '\n'.join(self.comments) + '\n\n' + + def _delmsg(self): + '''Remove existing message, keeping the rest of the comments fields. + If comments contains 'subject: ', message will prepend + the field and a blank line.''' + if self.message: + subj = 'subject: ' + self.message[0].lower() + for i in xrange(len(self.comments)): + if subj == self.comments[i].lower(): + del self.comments[i] + self.message = self.message[2:] + break + ci = 0 + for mi in self.message: + while mi != self.comments[ci]: + ci += 1 + del self.comments[ci] + +class queue(object): + def __init__(self, ui, path, patchdir=None): + self.basepath = path + self.path = patchdir or os.path.join(path, "patches") + self.opener = util.opener(self.path) + self.ui = ui + self.applied_dirty = 0 + self.series_dirty = 0 + self.series_path = "series" + self.status_path = "status" + self.guards_path = "guards" + self.active_guards = None + self.guards_dirty = False + self._diffopts = None + + @util.propertycache + def applied(self): + if os.path.exists(self.join(self.status_path)): + lines = self.opener(self.status_path).read().splitlines() + return [statusentry(l) for l in lines] + return [] + + @util.propertycache + def full_series(self): + if os.path.exists(self.join(self.series_path)): + return self.opener(self.series_path).read().splitlines() + return [] + + @util.propertycache + def series(self): + self.parse_series() + return self.series + + @util.propertycache + def series_guards(self): + self.parse_series() + return self.series_guards + + def invalidate(self): + for a in 'applied full_series series series_guards'.split(): + if a in self.__dict__: + delattr(self, a) + self.applied_dirty = 0 + self.series_dirty = 0 + self.guards_dirty = False + self.active_guards = None + + def diffopts(self): + if self._diffopts is None: + self._diffopts = patch.diffopts(self.ui) + return self._diffopts + + def join(self, *p): + return os.path.join(self.path, *p) + + def find_series(self, patch): + pre = re.compile("(\s*)([^#]+)") + index = 0 + for l in self.full_series: + m = pre.match(l) + if m: + s = m.group(2) + s = s.rstrip() + if s == patch: + return index + index += 1 + return None + + guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)') + + def parse_series(self): + self.series = [] + self.series_guards = [] + for l in self.full_series: + h = l.find('#') + if h == -1: + patch = l + comment = '' + elif h == 0: + continue + else: + patch = l[:h] + comment = l[h:] + patch = patch.strip() + if patch: + if patch in self.series: + raise util.Abort(_('%s appears more than once in %s') % + (patch, self.join(self.series_path))) + self.series.append(patch) + self.series_guards.append(self.guard_re.findall(comment)) + + def check_guard(self, guard): + if not guard: + return _('guard cannot be an empty string') + bad_chars = '# \t\r\n\f' + first = guard[0] + if first in '-+': + return (_('guard %r starts with invalid character: %r') % + (guard, first)) + for c in bad_chars: + if c in guard: + return _('invalid character in guard %r: %r') % (guard, c) + + def set_active(self, guards): + for guard in guards: + bad = self.check_guard(guard) + if bad: + raise util.Abort(bad) + guards = sorted(set(guards)) + self.ui.debug(_('active guards: %s\n') % ' '.join(guards)) + self.active_guards = guards + self.guards_dirty = True + + def active(self): + if self.active_guards is None: + self.active_guards = [] + try: + guards = self.opener(self.guards_path).read().split() + except IOError, err: + if err.errno != errno.ENOENT: raise + guards = [] + for i, guard in enumerate(guards): + bad = self.check_guard(guard) + if bad: + self.ui.warn('%s:%d: %s\n' % + (self.join(self.guards_path), i + 1, bad)) + else: + self.active_guards.append(guard) + return self.active_guards + + def set_guards(self, idx, guards): + for g in guards: + if len(g) < 2: + raise util.Abort(_('guard %r too short') % g) + if g[0] not in '-+': + raise util.Abort(_('guard %r starts with invalid char') % g) + bad = self.check_guard(g[1:]) + if bad: + raise util.Abort(bad) + drop = self.guard_re.sub('', self.full_series[idx]) + self.full_series[idx] = drop + ''.join([' #' + g for g in guards]) + self.parse_series() + self.series_dirty = True + + def pushable(self, idx): + if isinstance(idx, str): + idx = self.series.index(idx) + patchguards = self.series_guards[idx] + if not patchguards: + return True, None + guards = self.active() + exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards] + if exactneg: + return False, exactneg[0] + pos = [g for g in patchguards if g[0] == '+'] + exactpos = [g for g in pos if g[1:] in guards] + if pos: + if exactpos: + return True, exactpos[0] + return False, pos + return True, '' + + def explain_pushable(self, idx, all_patches=False): + write = all_patches and self.ui.write or self.ui.warn + if all_patches or self.ui.verbose: + if isinstance(idx, str): + idx = self.series.index(idx) + pushable, why = self.pushable(idx) + if all_patches and pushable: + if why is None: + write(_('allowing %s - no guards in effect\n') % + self.series[idx]) + else: + if not why: + write(_('allowing %s - no matching negative guards\n') % + self.series[idx]) + else: + write(_('allowing %s - guarded by %r\n') % + (self.series[idx], why)) + if not pushable: + if why: + write(_('skipping %s - guarded by %r\n') % + (self.series[idx], why)) + else: + write(_('skipping %s - no matching guards\n') % + self.series[idx]) + + def save_dirty(self): + def write_list(items, path): + fp = self.opener(path, 'w') + for i in items: + fp.write("%s\n" % i) + fp.close() + if self.applied_dirty: write_list(map(str, self.applied), self.status_path) + if self.series_dirty: write_list(self.full_series, self.series_path) + if self.guards_dirty: write_list(self.active_guards, self.guards_path) + + def removeundo(self, repo): + undo = repo.sjoin('undo') + if not os.path.exists(undo): + return + try: + os.unlink(undo) + except OSError, inst: + self.ui.warn(_('error removing undo: %s\n') % str(inst)) + + def printdiff(self, repo, node1, node2=None, files=None, + fp=None, changes=None, opts={}): + m = cmdutil.match(repo, files, opts) + chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts()) + write = fp is None and repo.ui.write or fp.write + for chunk in chunks: + write(chunk) + + def mergeone(self, repo, mergeq, head, patch, rev): + # first try just applying the patch + (err, n) = self.apply(repo, [ patch ], update_status=False, + strict=True, merge=rev) + + if err == 0: + return (err, n) + + if n is None: + raise util.Abort(_("apply failed for patch %s") % patch) + + self.ui.warn(_("patch didn't work out, merging %s\n") % patch) + + # apply failed, strip away that rev and merge. + hg.clean(repo, head) + self.strip(repo, n, update=False, backup='strip') + + ctx = repo[rev] + ret = hg.merge(repo, rev) + if ret: + raise util.Abort(_("update returned %d") % ret) + n = repo.commit(ctx.description(), ctx.user(), force=True) + if n is None: + raise util.Abort(_("repo commit failed")) + try: + ph = patchheader(mergeq.join(patch)) + except: + raise util.Abort(_("unable to read %s") % patch) + + patchf = self.opener(patch, "w") + comments = str(ph) + if comments: + patchf.write(comments) + self.printdiff(repo, head, n, fp=patchf) + patchf.close() + self.removeundo(repo) + return (0, n) + + def qparents(self, repo, rev=None): + if rev is None: + (p1, p2) = repo.dirstate.parents() + if p2 == nullid: + return p1 + if len(self.applied) == 0: + return None + return bin(self.applied[-1].rev) + pp = repo.changelog.parents(rev) + if pp[1] != nullid: + arevs = [ x.rev for x in self.applied ] + p0 = hex(pp[0]) + p1 = hex(pp[1]) + if p0 in arevs: + return pp[0] + if p1 in arevs: + return pp[1] + return pp[0] + + def mergepatch(self, repo, mergeq, series): + if len(self.applied) == 0: + # each of the patches merged in will have two parents. This + # can confuse the qrefresh, qdiff, and strip code because it + # needs to know which parent is actually in the patch queue. + # so, we insert a merge marker with only one parent. This way + # the first patch in the queue is never a merge patch + # + pname = ".hg.patches.merge.marker" + n = repo.commit('[mq]: merge marker', force=True) + self.removeundo(repo) + self.applied.append(statusentry(hex(n), pname)) + self.applied_dirty = 1 + + head = self.qparents(repo) + + for patch in series: + patch = mergeq.lookup(patch, strict=True) + if not patch: + self.ui.warn(_("patch %s does not exist\n") % patch) + return (1, None) + pushable, reason = self.pushable(patch) + if not pushable: + self.explain_pushable(patch, all_patches=True) + continue + info = mergeq.isapplied(patch) + if not info: + self.ui.warn(_("patch %s is not applied\n") % patch) + return (1, None) + rev = bin(info[1]) + (err, head) = self.mergeone(repo, mergeq, head, patch, rev) + if head: + self.applied.append(statusentry(hex(head), patch)) + self.applied_dirty = 1 + if err: + return (err, head) + self.save_dirty() + return (0, head) + + def patch(self, repo, patchfile): + '''Apply patchfile to the working directory. + patchfile: name of patch file''' + files = {} + try: + fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root, + files=files, eolmode=None) + except Exception, inst: + self.ui.note(str(inst) + '\n') + if not self.ui.verbose: + self.ui.warn(_("patch failed, unable to continue (try -v)\n")) + return (False, files, False) + + return (True, files, fuzz) + + def apply(self, repo, series, list=False, update_status=True, + strict=False, patchdir=None, merge=None, all_files={}): + wlock = lock = tr = None + try: + wlock = repo.wlock() + lock = repo.lock() + tr = repo.transaction() + try: + ret = self._apply(repo, series, list, update_status, + strict, patchdir, merge, all_files=all_files) + tr.close() + self.save_dirty() + return ret + except: + try: + tr.abort() + finally: + repo.invalidate() + repo.dirstate.invalidate() + raise + finally: + del tr + release(lock, wlock) + self.removeundo(repo) + + def _apply(self, repo, series, list=False, update_status=True, + strict=False, patchdir=None, merge=None, all_files={}): + '''returns (error, hash) + error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz''' + # TODO unify with commands.py + if not patchdir: + patchdir = self.path + err = 0 + n = None + for patchname in series: + pushable, reason = self.pushable(patchname) + if not pushable: + self.explain_pushable(patchname, all_patches=True) + continue + self.ui.status(_("applying %s\n") % patchname) + pf = os.path.join(patchdir, patchname) + + try: + ph = patchheader(self.join(patchname)) + except: + self.ui.warn(_("unable to read %s\n") % patchname) + err = 1 + break + + message = ph.message + if not message: + message = _("imported patch %s\n") % patchname + else: + if list: + message.append(_("\nimported patch %s") % patchname) + message = '\n'.join(message) + + if ph.haspatch: + (patcherr, files, fuzz) = self.patch(repo, pf) + all_files.update(files) + patcherr = not patcherr + else: + self.ui.warn(_("patch %s is empty\n") % patchname) + patcherr, files, fuzz = 0, [], 0 + + if merge and files: + # Mark as removed/merged and update dirstate parent info + removed = [] + merged = [] + for f in files: + if os.path.exists(repo.wjoin(f)): + merged.append(f) + else: + removed.append(f) + for f in removed: + repo.dirstate.remove(f) + for f in merged: + repo.dirstate.merge(f) + p1, p2 = repo.dirstate.parents() + repo.dirstate.setparents(p1, merge) + + files = patch.updatedir(self.ui, repo, files) + match = cmdutil.matchfiles(repo, files or []) + n = repo.commit(message, ph.user, ph.date, match=match, force=True) + + if n is None: + raise util.Abort(_("repo commit failed")) + + if update_status: + self.applied.append(statusentry(hex(n), patchname)) + + if patcherr: + self.ui.warn(_("patch failed, rejects left in working dir\n")) + err = 2 + break + + if fuzz and strict: + self.ui.warn(_("fuzz found when applying patch, stopping\n")) + err = 3 + break + return (err, n) + + def _cleanup(self, patches, numrevs, keep=False): + if not keep: + r = self.qrepo() + if r: + r.remove(patches, True) + else: + for p in patches: + os.unlink(self.join(p)) + + if numrevs: + del self.applied[:numrevs] + self.applied_dirty = 1 + + for i in sorted([self.find_series(p) for p in patches], reverse=True): + del self.full_series[i] + self.parse_series() + self.series_dirty = 1 + + def _revpatches(self, repo, revs): + firstrev = repo[self.applied[0].rev].rev() + patches = [] + for i, rev in enumerate(revs): + + if rev < firstrev: + raise util.Abort(_('revision %d is not managed') % rev) + + ctx = repo[rev] + base = bin(self.applied[i].rev) + if ctx.node() != base: + msg = _('cannot delete revision %d above applied patches') + raise util.Abort(msg % rev) + + patch = self.applied[i].name + for fmt in ('[mq]: %s', 'imported patch %s'): + if ctx.description() == fmt % patch: + msg = _('patch %s finalized without changeset message\n') + repo.ui.status(msg % patch) + break + + patches.append(patch) + return patches + + def finish(self, repo, revs): + patches = self._revpatches(repo, sorted(revs)) + self._cleanup(patches, len(patches)) + + def delete(self, repo, patches, opts): + if not patches and not opts.get('rev'): + raise util.Abort(_('qdelete requires at least one revision or ' + 'patch name')) + + realpatches = [] + for patch in patches: + patch = self.lookup(patch, strict=True) + info = self.isapplied(patch) + if info: + raise util.Abort(_("cannot delete applied patch %s") % patch) + if patch not in self.series: + raise util.Abort(_("patch %s not in series file") % patch) + realpatches.append(patch) + + numrevs = 0 + if opts.get('rev'): + if not self.applied: + raise util.Abort(_('no patches applied')) + revs = cmdutil.revrange(repo, opts['rev']) + if len(revs) > 1 and revs[0] > revs[1]: + revs.reverse() + revpatches = self._revpatches(repo, revs) + realpatches += revpatches + numrevs = len(revpatches) + + self._cleanup(realpatches, numrevs, opts.get('keep')) + + def check_toppatch(self, repo): + if len(self.applied) > 0: + top = bin(self.applied[-1].rev) + pp = repo.dirstate.parents() + if top not in pp: + raise util.Abort(_("working directory revision is not qtip")) + return top + return None + def check_localchanges(self, repo, force=False, refresh=True): + m, a, r, d = repo.status()[:4] + if m or a or r or d: + if not force: + if refresh: + raise util.Abort(_("local changes found, refresh first")) + else: + raise util.Abort(_("local changes found")) + return m, a, r, d + + _reserved = ('series', 'status', 'guards') + def check_reserved_name(self, name): + if (name in self._reserved or name.startswith('.hg') + or name.startswith('.mq')): + raise util.Abort(_('"%s" cannot be used as the name of a patch') + % name) + + def new(self, repo, patchfn, *pats, **opts): + """options: + msg: a string or a no-argument function returning a string + """ + msg = opts.get('msg') + force = opts.get('force') + user = opts.get('user') + date = opts.get('date') + if date: + date = util.parsedate(date) + self.check_reserved_name(patchfn) + if os.path.exists(self.join(patchfn)): + raise util.Abort(_('patch "%s" already exists') % patchfn) + if opts.get('include') or opts.get('exclude') or pats: + match = cmdutil.match(repo, pats, opts) + # detect missing files in pats + def badfn(f, msg): + raise util.Abort('%s: %s' % (f, msg)) + match.bad = badfn + m, a, r, d = repo.status(match=match)[:4] + else: + m, a, r, d = self.check_localchanges(repo, force) + match = cmdutil.matchfiles(repo, m + a + r) + commitfiles = m + a + r + self.check_toppatch(repo) + insert = self.full_series_end() + wlock = repo.wlock() + try: + # if patch file write fails, abort early + p = self.opener(patchfn, "w") + try: + if date: + p.write("# HG changeset patch\n") + if user: + p.write("# User " + user + "\n") + p.write("# Date %d %d\n\n" % date) + elif user: + p.write("From: " + user + "\n\n") + + if hasattr(msg, '__call__'): + msg = msg() + commitmsg = msg and msg or ("[mq]: %s" % patchfn) + n = repo.commit(commitmsg, user, date, match=match, force=True) + if n is None: + raise util.Abort(_("repo commit failed")) + try: + self.full_series[insert:insert] = [patchfn] + self.applied.append(statusentry(hex(n), patchfn)) + self.parse_series() + self.series_dirty = 1 + self.applied_dirty = 1 + if msg: + msg = msg + "\n\n" + p.write(msg) + if commitfiles: + diffopts = self.diffopts() + if opts.get('git'): diffopts.git = True + parent = self.qparents(repo, n) + chunks = patch.diff(repo, node1=parent, node2=n, + match=match, opts=diffopts) + for chunk in chunks: + p.write(chunk) + p.close() + wlock.release() + wlock = None + r = self.qrepo() + if r: r.add([patchfn]) + except: + repo.rollback() + raise + except Exception: + patchpath = self.join(patchfn) + try: + os.unlink(patchpath) + except: + self.ui.warn(_('error unlinking %s\n') % patchpath) + raise + self.removeundo(repo) + finally: + release(wlock) + + def strip(self, repo, rev, update=True, backup="all", force=None): + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + + if update: + self.check_localchanges(repo, force=force, refresh=False) + urev = self.qparents(repo, rev) + hg.clean(repo, urev) + repo.dirstate.write() + + self.removeundo(repo) + repair.strip(self.ui, repo, rev, backup) + # strip may have unbundled a set of backed up revisions after + # the actual strip + self.removeundo(repo) + finally: + release(lock, wlock) + + def isapplied(self, patch): + """returns (index, rev, patch)""" + for i, a in enumerate(self.applied): + if a.name == patch: + return (i, a.rev, a.name) + return None + + # if the exact patch name does not exist, we try a few + # variations. If strict is passed, we try only #1 + # + # 1) a number to indicate an offset in the series file + # 2) a unique substring of the patch name was given + # 3) patchname[-+]num to indicate an offset in the series file + def lookup(self, patch, strict=False): + patch = patch and str(patch) + + def partial_name(s): + if s in self.series: + return s + matches = [x for x in self.series if s in x] + if len(matches) > 1: + self.ui.warn(_('patch name "%s" is ambiguous:\n') % s) + for m in matches: + self.ui.warn(' %s\n' % m) + return None + if matches: + return matches[0] + if len(self.series) > 0 and len(self.applied) > 0: + if s == 'qtip': + return self.series[self.series_end(True)-1] + if s == 'qbase': + return self.series[0] + return None + + if patch is None: + return None + if patch in self.series: + return patch + + if not os.path.isfile(self.join(patch)): + try: + sno = int(patch) + except(ValueError, OverflowError): + pass + else: + if -len(self.series) <= sno < len(self.series): + return self.series[sno] + + if not strict: + res = partial_name(patch) + if res: + return res + minus = patch.rfind('-') + if minus >= 0: + res = partial_name(patch[:minus]) + if res: + i = self.series.index(res) + try: + off = int(patch[minus+1:] or 1) + except(ValueError, OverflowError): + pass + else: + if i - off >= 0: + return self.series[i - off] + plus = patch.rfind('+') + if plus >= 0: + res = partial_name(patch[:plus]) + if res: + i = self.series.index(res) + try: + off = int(patch[plus+1:] or 1) + except(ValueError, OverflowError): + pass + else: + if i + off < len(self.series): + return self.series[i + off] + raise util.Abort(_("patch %s not in series") % patch) + + def push(self, repo, patch=None, force=False, list=False, + mergeq=None, all=False): + wlock = repo.wlock() + try: + if repo.dirstate.parents()[0] not in repo.heads(): + self.ui.status(_("(working directory not at a head)\n")) + + if not self.series: + self.ui.warn(_('no patches in series\n')) + return 0 + + patch = self.lookup(patch) + # Suppose our series file is: A B C and the current 'top' + # patch is B. qpush C should be performed (moving forward) + # qpush B is a NOP (no change) qpush A is an error (can't + # go backwards with qpush) + if patch: + info = self.isapplied(patch) + if info: + if info[0] < len(self.applied) - 1: + raise util.Abort( + _("cannot push to a previous patch: %s") % patch) + self.ui.warn( + _('qpush: %s is already at the top\n') % patch) + return + pushable, reason = self.pushable(patch) + if not pushable: + if reason: + reason = _('guarded by %r') % reason + else: + reason = _('no matching guards') + self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason)) + return 1 + elif all: + patch = self.series[-1] + if self.isapplied(patch): + self.ui.warn(_('all patches are currently applied\n')) + return 0 + + # Following the above example, starting at 'top' of B: + # qpush should be performed (pushes C), but a subsequent + # qpush without an argument is an error (nothing to + # apply). This allows a loop of "...while hg qpush..." to + # work as it detects an error when done + start = self.series_end() + if start == len(self.series): + self.ui.warn(_('patch series already fully applied\n')) + return 1 + if not force: + self.check_localchanges(repo) + + self.applied_dirty = 1 + if start > 0: + self.check_toppatch(repo) + if not patch: + patch = self.series[start] + end = start + 1 + else: + end = self.series.index(patch, start) + 1 + + s = self.series[start:end] + all_files = {} + try: + if mergeq: + ret = self.mergepatch(repo, mergeq, s) + else: + ret = self.apply(repo, s, list, all_files=all_files) + except: + self.ui.warn(_('cleaning up working directory...')) + node = repo.dirstate.parents()[0] + hg.revert(repo, node, None) + unknown = repo.status(unknown=True)[4] + # only remove unknown files that we know we touched or + # created while patching + for f in unknown: + if f in all_files: + util.unlink(repo.wjoin(f)) + self.ui.warn(_('done\n')) + raise + + top = self.applied[-1].name + if ret[0] and ret[0] > 1: + msg = _("errors during apply, please fix and refresh %s\n") + self.ui.write(msg % top) + else: + self.ui.write(_("now at: %s\n") % top) + return ret[0] + + finally: + wlock.release() + + def pop(self, repo, patch=None, force=False, update=True, all=False): + def getfile(f, rev, flags): + t = repo.file(f).read(rev) + repo.wwrite(f, t, flags) + + wlock = repo.wlock() + try: + if patch: + # index, rev, patch + info = self.isapplied(patch) + if not info: + patch = self.lookup(patch) + info = self.isapplied(patch) + if not info: + raise util.Abort(_("patch %s is not applied") % patch) + + if len(self.applied) == 0: + # Allow qpop -a to work repeatedly, + # but not qpop without an argument + self.ui.warn(_("no patches applied\n")) + return not all + + if all: + start = 0 + elif patch: + start = info[0] + 1 + else: + start = len(self.applied) - 1 + + if start >= len(self.applied): + self.ui.warn(_("qpop: %s is already at the top\n") % patch) + return + + if not update: + parents = repo.dirstate.parents() + rr = [ bin(x.rev) for x in self.applied ] + for p in parents: + if p in rr: + self.ui.warn(_("qpop: forcing dirstate update\n")) + update = True + else: + parents = [p.hex() for p in repo[None].parents()] + needupdate = False + for entry in self.applied[start:]: + if entry.rev in parents: + needupdate = True + break + update = needupdate + + if not force and update: + self.check_localchanges(repo) + + self.applied_dirty = 1 + end = len(self.applied) + rev = bin(self.applied[start].rev) + if update: + top = self.check_toppatch(repo) + + try: + heads = repo.changelog.heads(rev) + except error.LookupError: + node = short(rev) + raise util.Abort(_('trying to pop unknown node %s') % node) + + if heads != [bin(self.applied[-1].rev)]: + raise util.Abort(_("popping would remove a revision not " + "managed by this patch queue")) + + # we know there are no local changes, so we can make a simplified + # form of hg.update. + if update: + qp = self.qparents(repo, rev) + changes = repo.changelog.read(qp) + mmap = repo.manifest.read(changes[0]) + m, a, r, d = repo.status(qp, top)[:4] + if d: + raise util.Abort(_("deletions found between repo revs")) + for f in m: + getfile(f, mmap[f], mmap.flags(f)) + for f in r: + getfile(f, mmap[f], mmap.flags(f)) + for f in m + r: + repo.dirstate.normal(f) + for f in a: + try: + os.unlink(repo.wjoin(f)) + except OSError, e: + if e.errno != errno.ENOENT: + raise + try: os.removedirs(os.path.dirname(repo.wjoin(f))) + except: pass + repo.dirstate.forget(f) + repo.dirstate.setparents(qp, nullid) + for patch in reversed(self.applied[start:end]): + self.ui.status(_("popping %s\n") % patch.name) + del self.applied[start:end] + self.strip(repo, rev, update=False, backup='strip') + if len(self.applied): + self.ui.write(_("now at: %s\n") % self.applied[-1].name) + else: + self.ui.write(_("patch queue now empty\n")) + finally: + wlock.release() + + def diff(self, repo, pats, opts): + top = self.check_toppatch(repo) + if not top: + self.ui.write(_("no patches applied\n")) + return + qp = self.qparents(repo, top) + self._diffopts = patch.diffopts(self.ui, opts) + self.printdiff(repo, qp, files=pats, opts=opts) + + def refresh(self, repo, pats=None, **opts): + if len(self.applied) == 0: + self.ui.write(_("no patches applied\n")) + return 1 + msg = opts.get('msg', '').rstrip() + newuser = opts.get('user') + newdate = opts.get('date') + if newdate: + newdate = '%d %d' % util.parsedate(newdate) + wlock = repo.wlock() + try: + self.check_toppatch(repo) + (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name) + top = bin(top) + if repo.changelog.heads(top) != [top]: + raise util.Abort(_("cannot refresh a revision with children")) + cparents = repo.changelog.parents(top) + patchparent = self.qparents(repo, top) + ph = patchheader(self.join(patchfn)) + + patchf = self.opener(patchfn, 'r') + + # if the patch was a git patch, refresh it as a git patch + for line in patchf: + if line.startswith('diff --git'): + self.diffopts().git = True + break + + if msg: + ph.setmessage(msg) + if newuser: + ph.setuser(newuser) + if newdate: + ph.setdate(newdate) + + # only commit new patch when write is complete + patchf = self.opener(patchfn, 'w', atomictemp=True) + + patchf.seek(0) + patchf.truncate() + + comments = str(ph) + if comments: + patchf.write(comments) + + if opts.get('git'): + self.diffopts().git = True + tip = repo.changelog.tip() + if top == tip: + # if the top of our patch queue is also the tip, there is an + # optimization here. We update the dirstate in place and strip + # off the tip commit. Then just commit the current directory + # tree. We can also send repo.commit the list of files + # changed to speed up the diff + # + # in short mode, we only diff the files included in the + # patch already plus specified files + # + # this should really read: + # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4] + # but we do it backwards to take advantage of manifest/chlog + # caching against the next repo.status call + # + mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4] + changes = repo.changelog.read(tip) + man = repo.manifest.read(changes[0]) + aaa = aa[:] + matchfn = cmdutil.match(repo, pats, opts) + if opts.get('short'): + # if amending a patch, we start with existing + # files plus specified files - unfiltered + match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files()) + # filter with inc/exl options + matchfn = cmdutil.match(repo, opts=opts) + else: + match = cmdutil.matchall(repo) + m, a, r, d = repo.status(match=match)[:4] + + # we might end up with files that were added between + # tip and the dirstate parent, but then changed in the + # local dirstate. in this case, we want them to only + # show up in the added section + for x in m: + if x not in aa: + mm.append(x) + # we might end up with files added by the local dirstate that + # were deleted by the patch. In this case, they should only + # show up in the changed section. + for x in a: + if x in dd: + del dd[dd.index(x)] + mm.append(x) + else: + aa.append(x) + # make sure any files deleted in the local dirstate + # are not in the add or change column of the patch + forget = [] + for x in d + r: + if x in aa: + del aa[aa.index(x)] + forget.append(x) + continue + elif x in mm: + del mm[mm.index(x)] + dd.append(x) + + m = list(set(mm)) + r = list(set(dd)) + a = list(set(aa)) + c = [filter(matchfn, l) for l in (m, a, r)] + match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2])) + chunks = patch.diff(repo, patchparent, match=match, + changes=c, opts=self.diffopts()) + for chunk in chunks: + patchf.write(chunk) + + try: + if self.diffopts().git: + copies = {} + for dst in a: + src = repo.dirstate.copied(dst) + # during qfold, the source file for copies may + # be removed. Treat this as a simple add. + if src is not None and src in repo.dirstate: + copies.setdefault(src, []).append(dst) + repo.dirstate.add(dst) + # remember the copies between patchparent and tip + for dst in aaa: + f = repo.file(dst) + src = f.renamed(man[dst]) + if src: + copies.setdefault(src[0], []).extend(copies.get(dst, [])) + if dst in a: + copies[src[0]].append(dst) + # we can't copy a file created by the patch itself + if dst in copies: + del copies[dst] + for src, dsts in copies.iteritems(): + for dst in dsts: + repo.dirstate.copy(src, dst) + else: + for dst in a: + repo.dirstate.add(dst) + # Drop useless copy information + for f in list(repo.dirstate.copies()): + repo.dirstate.copy(None, f) + for f in r: + repo.dirstate.remove(f) + # if the patch excludes a modified file, mark that + # file with mtime=0 so status can see it. + mm = [] + for i in xrange(len(m)-1, -1, -1): + if not matchfn(m[i]): + mm.append(m[i]) + del m[i] + for f in m: + repo.dirstate.normal(f) + for f in mm: + repo.dirstate.normallookup(f) + for f in forget: + repo.dirstate.forget(f) + + if not msg: + if not ph.message: + message = "[mq]: %s\n" % patchfn + else: + message = "\n".join(ph.message) + else: + message = msg + + user = ph.user or changes[1] + + # assumes strip can roll itself back if interrupted + repo.dirstate.setparents(*cparents) + self.applied.pop() + self.applied_dirty = 1 + self.strip(repo, top, update=False, + backup='strip') + except: + repo.dirstate.invalidate() + raise + + try: + # might be nice to attempt to roll back strip after this + patchf.rename() + n = repo.commit(message, user, ph.date, match=match, + force=True) + self.applied.append(statusentry(hex(n), patchfn)) + except: + ctx = repo[cparents[0]] + repo.dirstate.rebuild(ctx.node(), ctx.manifest()) + self.save_dirty() + self.ui.warn(_('refresh interrupted while patch was popped! ' + '(revert --all, qpush to recover)\n')) + raise + else: + self.printdiff(repo, patchparent, fp=patchf) + patchf.rename() + added = repo.status()[1] + for a in added: + f = repo.wjoin(a) + try: + os.unlink(f) + except OSError, e: + if e.errno != errno.ENOENT: + raise + try: os.removedirs(os.path.dirname(f)) + except: pass + # forget the file copies in the dirstate + # push should readd the files later on + repo.dirstate.forget(a) + self.pop(repo, force=True) + self.push(repo, force=True) + finally: + wlock.release() + self.removeundo(repo) + + def init(self, repo, create=False): + if not create and os.path.isdir(self.path): + raise util.Abort(_("patch queue directory already exists")) + try: + os.mkdir(self.path) + except OSError, inst: + if inst.errno != errno.EEXIST or not create: + raise + if create: + return self.qrepo(create=True) + + def unapplied(self, repo, patch=None): + if patch and patch not in self.series: + raise util.Abort(_("patch %s is not in series file") % patch) + if not patch: + start = self.series_end() + else: + start = self.series.index(patch) + 1 + unapplied = [] + for i in xrange(start, len(self.series)): + pushable, reason = self.pushable(i) + if pushable: + unapplied.append((i, self.series[i])) + self.explain_pushable(i) + return unapplied + + def qseries(self, repo, missing=None, start=0, length=None, status=None, + summary=False): + def displayname(pfx, patchname): + if summary: + ph = patchheader(self.join(patchname)) + msg = ph.message + msg = msg and ': ' + msg[0] or ': ' + else: + msg = '' + msg = "%s%s%s" % (pfx, patchname, msg) + if self.ui.interactive(): + msg = util.ellipsis(msg, util.termwidth()) + self.ui.write(msg + '\n') + + applied = set([p.name for p in self.applied]) + if length is None: + length = len(self.series) - start + if not missing: + if self.ui.verbose: + idxwidth = len(str(start+length - 1)) + for i in xrange(start, start+length): + patch = self.series[i] + if patch in applied: + stat = 'A' + elif self.pushable(i)[0]: + stat = 'U' + else: + stat = 'G' + pfx = '' + if self.ui.verbose: + pfx = '%*d %s ' % (idxwidth, i, stat) + elif status and status != stat: + continue + displayname(pfx, patch) + else: + msng_list = [] + for root, dirs, files in os.walk(self.path): + d = root[len(self.path) + 1:] + for f in files: + fl = os.path.join(d, f) + if (fl not in self.series and + fl not in (self.status_path, self.series_path, + self.guards_path) + and not fl.startswith('.')): + msng_list.append(fl) + for x in sorted(msng_list): + pfx = self.ui.verbose and ('D ') or '' + displayname(pfx, x) + + def issaveline(self, l): + if l.name == '.hg.patches.save.line': + return True + + def qrepo(self, create=False): + if create or os.path.isdir(self.join(".hg")): + return hg.repository(self.ui, path=self.path, create=create) + + def restore(self, repo, rev, delete=None, qupdate=None): + c = repo.changelog.read(rev) + desc = c[4].strip() + lines = desc.splitlines() + i = 0 + datastart = None + series = [] + applied = [] + qpp = None + for i, line in enumerate(lines): + if line == 'Patch Data:': + datastart = i + 1 + elif line.startswith('Dirstate:'): + l = line.rstrip() + l = l[10:].split(' ') + qpp = [ bin(x) for x in l ] + elif datastart != None: + l = line.rstrip() + se = statusentry(l) + file_ = se.name + if se.rev: + applied.append(se) + else: + series.append(file_) + if datastart is None: + self.ui.warn(_("No saved patch data found\n")) + return 1 + self.ui.warn(_("restoring status: %s\n") % lines[0]) + self.full_series = series + self.applied = applied + self.parse_series() + self.series_dirty = 1 + self.applied_dirty = 1 + heads = repo.changelog.heads() + if delete: + if rev not in heads: + self.ui.warn(_("save entry has children, leaving it alone\n")) + else: + self.ui.warn(_("removing save entry %s\n") % short(rev)) + pp = repo.dirstate.parents() + if rev in pp: + update = True + else: + update = False + self.strip(repo, rev, update=update, backup='strip') + if qpp: + self.ui.warn(_("saved queue repository parents: %s %s\n") % + (short(qpp[0]), short(qpp[1]))) + if qupdate: + self.ui.status(_("queue directory updating\n")) + r = self.qrepo() + if not r: + self.ui.warn(_("Unable to load queue repository\n")) + return 1 + hg.clean(r, qpp[0]) + + def save(self, repo, msg=None): + if len(self.applied) == 0: + self.ui.warn(_("save: no patches applied, exiting\n")) + return 1 + if self.issaveline(self.applied[-1]): + self.ui.warn(_("status is already saved\n")) + return 1 + + ar = [ ':' + x for x in self.full_series ] + if not msg: + msg = _("hg patches saved state") + else: + msg = "hg patches: " + msg.rstrip('\r\n') + r = self.qrepo() + if r: + pp = r.dirstate.parents() + msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1])) + msg += "\n\nPatch Data:\n" + text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and + "\n".join(ar) + '\n' or "") + n = repo.commit(text, force=True) + if not n: + self.ui.warn(_("repo commit failed\n")) + return 1 + self.applied.append(statusentry(hex(n),'.hg.patches.save.line')) + self.applied_dirty = 1 + self.removeundo(repo) + + def full_series_end(self): + if len(self.applied) > 0: + p = self.applied[-1].name + end = self.find_series(p) + if end is None: + return len(self.full_series) + return end + 1 + return 0 + + def series_end(self, all_patches=False): + """If all_patches is False, return the index of the next pushable patch + in the series, or the series length. If all_patches is True, return the + index of the first patch past the last applied one. + """ + end = 0 + def next(start): + if all_patches: + return start + i = start + while i < len(self.series): + p, reason = self.pushable(i) + if p: + break + self.explain_pushable(i) + i += 1 + return i + if len(self.applied) > 0: + p = self.applied[-1].name + try: + end = self.series.index(p) + except ValueError: + return 0 + return next(end + 1) + return next(end) + + def appliedname(self, index): + pname = self.applied[index].name + if not self.ui.verbose: + p = pname + else: + p = str(self.series.index(pname)) + " " + pname + return p + + def qimport(self, repo, files, patchname=None, rev=None, existing=None, + force=None, git=False): + def checkseries(patchname): + if patchname in self.series: + raise util.Abort(_('patch %s is already in the series file') + % patchname) + def checkfile(patchname): + if not force and os.path.exists(self.join(patchname)): + raise util.Abort(_('patch "%s" already exists') + % patchname) + + if rev: + if files: + raise util.Abort(_('option "-r" not valid when importing ' + 'files')) + rev = cmdutil.revrange(repo, rev) + rev.sort(reverse=True) + if (len(files) > 1 or len(rev) > 1) and patchname: + raise util.Abort(_('option "-n" not valid when importing multiple ' + 'patches')) + i = 0 + added = [] + if rev: + # If mq patches are applied, we can only import revisions + # that form a linear path to qbase. + # Otherwise, they should form a linear path to a head. + heads = repo.changelog.heads(repo.changelog.node(rev[-1])) + if len(heads) > 1: + raise util.Abort(_('revision %d is the root of more than one ' + 'branch') % rev[-1]) + if self.applied: + base = hex(repo.changelog.node(rev[0])) + if base in [n.rev for n in self.applied]: + raise util.Abort(_('revision %d is already managed') + % rev[0]) + if heads != [bin(self.applied[-1].rev)]: + raise util.Abort(_('revision %d is not the parent of ' + 'the queue') % rev[0]) + base = repo.changelog.rev(bin(self.applied[0].rev)) + lastparent = repo.changelog.parentrevs(base)[0] + else: + if heads != [repo.changelog.node(rev[0])]: + raise util.Abort(_('revision %d has unmanaged children') + % rev[0]) + lastparent = None + + if git: + self.diffopts().git = True + + for r in rev: + p1, p2 = repo.changelog.parentrevs(r) + n = repo.changelog.node(r) + if p2 != nullrev: + raise util.Abort(_('cannot import merge revision %d') % r) + if lastparent and lastparent != r: + raise util.Abort(_('revision %d is not the parent of %d') + % (r, lastparent)) + lastparent = p1 + + if not patchname: + patchname = normname('%d.diff' % r) + self.check_reserved_name(patchname) + checkseries(patchname) + checkfile(patchname) + self.full_series.insert(0, patchname) + + patchf = self.opener(patchname, "w") + patch.export(repo, [n], fp=patchf, opts=self.diffopts()) + patchf.close() + + se = statusentry(hex(n), patchname) + self.applied.insert(0, se) + + added.append(patchname) + patchname = None + self.parse_series() + self.applied_dirty = 1 + + for filename in files: + if existing: + if filename == '-': + raise util.Abort(_('-e is incompatible with import from -')) + if not patchname: + patchname = normname(filename) + self.check_reserved_name(patchname) + if not os.path.isfile(self.join(patchname)): + raise util.Abort(_("patch %s does not exist") % patchname) + else: + try: + if filename == '-': + if not patchname: + raise util.Abort(_('need --name to import a patch from -')) + text = sys.stdin.read() + else: + text = url.open(self.ui, filename).read() + except (OSError, IOError): + raise util.Abort(_("unable to read %s") % filename) + if not patchname: + patchname = normname(os.path.basename(filename)) + self.check_reserved_name(patchname) + checkfile(patchname) + patchf = self.opener(patchname, "w") + patchf.write(text) + if not force: + checkseries(patchname) + if patchname not in self.series: + index = self.full_series_end() + i + self.full_series[index:index] = [patchname] + self.parse_series() + self.ui.warn(_("adding %s to series file\n") % patchname) + i += 1 + added.append(patchname) + patchname = None + self.series_dirty = 1 + qrepo = self.qrepo() + if qrepo: + qrepo.add(added) + +def delete(ui, repo, *patches, **opts): + """remove patches from queue + + The patches must not be applied, and at least one patch is required. With + -k/--keep, the patch files are preserved in the patch directory. + + To stop managing a patch and move it into permanent history, + use the qfinish command.""" + q = repo.mq + q.delete(repo, patches, opts) + q.save_dirty() + return 0 + +def applied(ui, repo, patch=None, **opts): + """print the patches already applied""" + q = repo.mq + if patch: + if patch not in q.series: + raise util.Abort(_("patch %s is not in series file") % patch) + end = q.series.index(patch) + 1 + else: + end = q.series_end(True) + return q.qseries(repo, length=end, status='A', summary=opts.get('summary')) + +def unapplied(ui, repo, patch=None, **opts): + """print the patches not yet applied""" + q = repo.mq + if patch: + if patch not in q.series: + raise util.Abort(_("patch %s is not in series file") % patch) + start = q.series.index(patch) + 1 + else: + start = q.series_end(True) + q.qseries(repo, start=start, status='U', summary=opts.get('summary')) + +def qimport(ui, repo, *filename, **opts): + """import a patch + + The patch is inserted into the series after the last applied + patch. If no patches have been applied, qimport prepends the patch + to the series. + + The patch will have the same name as its source file unless you + give it a new one with -n/--name. + + You can register an existing patch inside the patch directory with + the -e/--existing flag. + + With -f/--force, an existing patch of the same name will be + overwritten. + + An existing changeset may be placed under mq control with -r/--rev + (e.g. qimport --rev tip -n patch will place tip under mq control). + With -g/--git, patches imported with --rev will use the git diff + format. See the diffs help topic for information on why this is + important for preserving rename/copy information and permission + changes. + + To import a patch from standard input, pass - as the patch file. + When importing from standard input, a patch name must be specified + using the --name flag. + """ + q = repo.mq + q.qimport(repo, filename, patchname=opts['name'], + existing=opts['existing'], force=opts['force'], rev=opts['rev'], + git=opts['git']) + q.save_dirty() + + if opts.get('push') and not opts.get('rev'): + return q.push(repo, None) + return 0 + +def init(ui, repo, **opts): + """init a new queue repository + + The queue repository is unversioned by default. If + -c/--create-repo is specified, qinit will create a separate nested + repository for patches (qinit -c may also be run later to convert + an unversioned patch repository into a versioned one). You can use + qcommit to commit changes to this queue repository.""" + q = repo.mq + r = q.init(repo, create=opts['create_repo']) + q.save_dirty() + if r: + if not os.path.exists(r.wjoin('.hgignore')): + fp = r.wopener('.hgignore', 'w') + fp.write('^\\.hg\n') + fp.write('^\\.mq\n') + fp.write('syntax: glob\n') + fp.write('status\n') + fp.write('guards\n') + fp.close() + if not os.path.exists(r.wjoin('series')): + r.wopener('series', 'w').close() + r.add(['.hgignore', 'series']) + commands.add(ui, r) + return 0 + +def clone(ui, source, dest=None, **opts): + '''clone main and patch repository at same time + + If source is local, destination will have no patches applied. If + source is remote, this command can not check if patches are + applied in source, so cannot guarantee that patches are not + applied in destination. If you clone remote repository, be sure + before that it has no patches applied. + + Source patch repository is looked for in <src>/.hg/patches by + default. Use -p <url> to change. + + The patch directory must be a nested Mercurial repository, as + would be created by qinit -c. + ''' + def patchdir(repo): + url = repo.url() + if url.endswith('/'): + url = url[:-1] + return url + '/.hg/patches' + if dest is None: + dest = hg.defaultdest(source) + sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source)) + if opts['patches']: + patchespath = ui.expandpath(opts['patches']) + else: + patchespath = patchdir(sr) + try: + hg.repository(ui, patchespath) + except error.RepoError: + raise util.Abort(_('versioned patch repository not found' + ' (see qinit -c)')) + qbase, destrev = None, None + if sr.local(): + if sr.mq.applied: + qbase = bin(sr.mq.applied[0].rev) + if not hg.islocal(dest): + heads = set(sr.heads()) + destrev = list(heads.difference(sr.heads(qbase))) + destrev.append(sr.changelog.parents(qbase)[0]) + elif sr.capable('lookup'): + try: + qbase = sr.lookup('qbase') + except error.RepoError: + pass + ui.note(_('cloning main repository\n')) + sr, dr = hg.clone(ui, sr.url(), dest, + pull=opts['pull'], + rev=destrev, + update=False, + stream=opts['uncompressed']) + ui.note(_('cloning patch repository\n')) + hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr), + pull=opts['pull'], update=not opts['noupdate'], + stream=opts['uncompressed']) + if dr.local(): + if qbase: + ui.note(_('stripping applied patches from destination ' + 'repository\n')) + dr.mq.strip(dr, qbase, update=False, backup=None) + if not opts['noupdate']: + ui.note(_('updating destination repository\n')) + hg.update(dr, dr.changelog.tip()) + +def commit(ui, repo, *pats, **opts): + """commit changes in the queue repository""" + q = repo.mq + r = q.qrepo() + if not r: raise util.Abort('no queue repository') + commands.commit(r.ui, r, *pats, **opts) + +def series(ui, repo, **opts): + """print the entire series file""" + repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary']) + return 0 + +def top(ui, repo, **opts): + """print the name of the current patch""" + q = repo.mq + t = q.applied and q.series_end(True) or 0 + if t: + return q.qseries(repo, start=t-1, length=1, status='A', + summary=opts.get('summary')) + else: + ui.write(_("no patches applied\n")) + return 1 + +def next(ui, repo, **opts): + """print the name of the next patch""" + q = repo.mq + end = q.series_end() + if end == len(q.series): + ui.write(_("all patches applied\n")) + return 1 + return q.qseries(repo, start=end, length=1, summary=opts.get('summary')) + +def prev(ui, repo, **opts): + """print the name of the previous patch""" + q = repo.mq + l = len(q.applied) + if l == 1: + ui.write(_("only one patch applied\n")) + return 1 + if not l: + ui.write(_("no patches applied\n")) + return 1 + return q.qseries(repo, start=l-2, length=1, status='A', + summary=opts.get('summary')) + +def setupheaderopts(ui, opts): + def do(opt, val): + if not opts[opt] and opts['current' + opt]: + opts[opt] = val + do('user', ui.username()) + do('date', "%d %d" % util.makedate()) + +def new(ui, repo, patch, *args, **opts): + """create a new patch + + qnew creates a new patch on top of the currently-applied patch (if + any). It will refuse to run if there are any outstanding changes + unless -f/--force is specified, in which case the patch will be + initialized with them. You may also use -I/--include, + -X/--exclude, and/or a list of files after the patch name to add + only changes to matching files to the new patch, leaving the rest + as uncommitted modifications. + + -u/--user and -d/--date can be used to set the (given) user and + date, respectively. -U/--currentuser and -D/--currentdate set user + to current user and date to current date. + + -e/--edit, -m/--message or -l/--logfile set the patch header as + well as the commit message. If none is specified, the header is + empty and the commit message is '[mq]: PATCH'. + + Use the -g/--git option to keep the patch in the git extended diff + format. Read the diffs help topic for more information on why this + is important for preserving permission changes and copy/rename + information. + """ + msg = cmdutil.logmessage(opts) + def getmsg(): return ui.edit(msg, ui.username()) + q = repo.mq + opts['msg'] = msg + if opts.get('edit'): + opts['msg'] = getmsg + else: + opts['msg'] = msg + setupheaderopts(ui, opts) + q.new(repo, patch, *args, **opts) + q.save_dirty() + return 0 + +def refresh(ui, repo, *pats, **opts): + """update the current patch + + If any file patterns are provided, the refreshed patch will + contain only the modifications that match those patterns; the + remaining modifications will remain in the working directory. + + If -s/--short is specified, files currently included in the patch + will be refreshed just like matched files and remain in the patch. + + hg add/remove/copy/rename work as usual, though you might want to + use git-style patches (-g/--git or [diff] git=1) to track copies + and renames. See the diffs help topic for more information on the + git diff format. + """ + q = repo.mq + message = cmdutil.logmessage(opts) + if opts['edit']: + if not q.applied: + ui.write(_("no patches applied\n")) + return 1 + if message: + raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) + patch = q.applied[-1].name + ph = patchheader(q.join(patch)) + message = ui.edit('\n'.join(ph.message), ph.user or ui.username()) + setupheaderopts(ui, opts) + ret = q.refresh(repo, pats, msg=message, **opts) + q.save_dirty() + return ret + +def diff(ui, repo, *pats, **opts): + """diff of the current patch and subsequent modifications + + Shows a diff which includes the current patch as well as any + changes which have been made in the working directory since the + last refresh (thus showing what the current patch would become + after a qrefresh). + + Use 'hg diff' if you only want to see the changes made since the + last qrefresh, or 'hg export qtip' if you want to see changes made + by the current patch without including changes made since the + qrefresh. + """ + repo.mq.diff(repo, pats, opts) + return 0 + +def fold(ui, repo, *files, **opts): + """fold the named patches into the current patch + + Patches must not yet be applied. Each patch will be successively + applied to the current patch in the order given. If all the + patches apply successfully, the current patch will be refreshed + with the new cumulative patch, and the folded patches will be + deleted. With -k/--keep, the folded patch files will not be + removed afterwards. + + The header for each folded patch will be concatenated with the + current patch header, separated by a line of '* * *'.""" + + q = repo.mq + + if not files: + raise util.Abort(_('qfold requires at least one patch name')) + if not q.check_toppatch(repo): + raise util.Abort(_('No patches applied')) + q.check_localchanges(repo) + + message = cmdutil.logmessage(opts) + if opts['edit']: + if message: + raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) + + parent = q.lookup('qtip') + patches = [] + messages = [] + for f in files: + p = q.lookup(f) + if p in patches or p == parent: + ui.warn(_('Skipping already folded patch %s') % p) + if q.isapplied(p): + raise util.Abort(_('qfold cannot fold already applied patch %s') % p) + patches.append(p) + + for p in patches: + if not message: + ph = patchheader(q.join(p)) + if ph.message: + messages.append(ph.message) + pf = q.join(p) + (patchsuccess, files, fuzz) = q.patch(repo, pf) + if not patchsuccess: + raise util.Abort(_('Error folding patch %s') % p) + patch.updatedir(ui, repo, files) + + if not message: + ph = patchheader(q.join(parent)) + message, user = ph.message, ph.user + for msg in messages: + message.append('* * *') + message.extend(msg) + message = '\n'.join(message) + + if opts['edit']: + message = ui.edit(message, user or ui.username()) + + q.refresh(repo, msg=message) + q.delete(repo, patches, opts) + q.save_dirty() + +def goto(ui, repo, patch, **opts): + '''push or pop patches until named patch is at top of stack''' + q = repo.mq + patch = q.lookup(patch) + if q.isapplied(patch): + ret = q.pop(repo, patch, force=opts['force']) + else: + ret = q.push(repo, patch, force=opts['force']) + q.save_dirty() + return ret + +def guard(ui, repo, *args, **opts): + '''set or print guards for a patch + + Guards control whether a patch can be pushed. A patch with no + guards is always pushed. A patch with a positive guard ("+foo") is + pushed only if the qselect command has activated it. A patch with + a negative guard ("-foo") is never pushed if the qselect command + has activated it. + + With no arguments, print the currently active guards. + With arguments, set guards for the named patch. + NOTE: Specifying negative guards now requires '--'. + + To set guards on another patch: + hg qguard -- other.patch +2.6.17 -stable + ''' + def status(idx): + guards = q.series_guards[idx] or ['unguarded'] + ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards))) + q = repo.mq + patch = None + args = list(args) + if opts['list']: + if args or opts['none']: + raise util.Abort(_('cannot mix -l/--list with options or arguments')) + for i in xrange(len(q.series)): + status(i) + return + if not args or args[0][0:1] in '-+': + if not q.applied: + raise util.Abort(_('no patches applied')) + patch = q.applied[-1].name + if patch is None and args[0][0:1] not in '-+': + patch = args.pop(0) + if patch is None: + raise util.Abort(_('no patch to work with')) + if args or opts['none']: + idx = q.find_series(patch) + if idx is None: + raise util.Abort(_('no patch named %s') % patch) + q.set_guards(idx, args) + q.save_dirty() + else: + status(q.series.index(q.lookup(patch))) + +def header(ui, repo, patch=None): + """print the header of the topmost or specified patch""" + q = repo.mq + + if patch: + patch = q.lookup(patch) + else: + if not q.applied: + ui.write('no patches applied\n') + return 1 + patch = q.lookup('qtip') + ph = patchheader(repo.mq.join(patch)) + + ui.write('\n'.join(ph.message) + '\n') + +def lastsavename(path): + (directory, base) = os.path.split(path) + names = os.listdir(directory) + namere = re.compile("%s.([0-9]+)" % base) + maxindex = None + maxname = None + for f in names: + m = namere.match(f) + if m: + index = int(m.group(1)) + if maxindex is None or index > maxindex: + maxindex = index + maxname = f + if maxname: + return (os.path.join(directory, maxname), maxindex) + return (None, None) + +def savename(path): + (last, index) = lastsavename(path) + if last is None: + index = 0 + newpath = path + ".%d" % (index + 1) + return newpath + +def push(ui, repo, patch=None, **opts): + """push the next patch onto the stack + + When -f/--force is applied, all local changes in patched files + will be lost. + """ + q = repo.mq + mergeq = None + + if opts['merge']: + if opts['name']: + newpath = repo.join(opts['name']) + else: + newpath, i = lastsavename(q.path) + if not newpath: + ui.warn(_("no saved queues found, please use -n\n")) + return 1 + mergeq = queue(ui, repo.join(""), newpath) + ui.warn(_("merging with queue at: %s\n") % mergeq.path) + ret = q.push(repo, patch, force=opts['force'], list=opts['list'], + mergeq=mergeq, all=opts.get('all')) + return ret + +def pop(ui, repo, patch=None, **opts): + """pop the current patch off the stack + + By default, pops off the top of the patch stack. If given a patch + name, keeps popping off patches until the named patch is at the + top of the stack. + """ + localupdate = True + if opts['name']: + q = queue(ui, repo.join(""), repo.join(opts['name'])) + ui.warn(_('using patch queue: %s\n') % q.path) + localupdate = False + else: + q = repo.mq + ret = q.pop(repo, patch, force=opts['force'], update=localupdate, + all=opts['all']) + q.save_dirty() + return ret + +def rename(ui, repo, patch, name=None, **opts): + """rename a patch + + With one argument, renames the current patch to PATCH1. + With two arguments, renames PATCH1 to PATCH2.""" + + q = repo.mq + + if not name: + name = patch + patch = None + + if patch: + patch = q.lookup(patch) + else: + if not q.applied: + ui.write(_('no patches applied\n')) + return + patch = q.lookup('qtip') + absdest = q.join(name) + if os.path.isdir(absdest): + name = normname(os.path.join(name, os.path.basename(patch))) + absdest = q.join(name) + if os.path.exists(absdest): + raise util.Abort(_('%s already exists') % absdest) + + if name in q.series: + raise util.Abort(_('A patch named %s already exists in the series file') % name) + + if ui.verbose: + ui.write('renaming %s to %s\n' % (patch, name)) + i = q.find_series(patch) + guards = q.guard_re.findall(q.full_series[i]) + q.full_series[i] = name + ''.join([' #' + g for g in guards]) + q.parse_series() + q.series_dirty = 1 + + info = q.isapplied(patch) + if info: + q.applied[info[0]] = statusentry(info[1], name) + q.applied_dirty = 1 + + util.rename(q.join(patch), absdest) + r = q.qrepo() + if r: + wlock = r.wlock() + try: + if r.dirstate[patch] == 'a': + r.dirstate.forget(patch) + r.dirstate.add(name) + else: + if r.dirstate[name] == 'r': + r.undelete([name]) + r.copy(patch, name) + r.remove([patch], False) + finally: + wlock.release() + + q.save_dirty() + +def restore(ui, repo, rev, **opts): + """restore the queue state saved by a revision""" + rev = repo.lookup(rev) + q = repo.mq + q.restore(repo, rev, delete=opts['delete'], + qupdate=opts['update']) + q.save_dirty() + return 0 + +def save(ui, repo, **opts): + """save current queue state""" + q = repo.mq + message = cmdutil.logmessage(opts) + ret = q.save(repo, msg=message) + if ret: + return ret + q.save_dirty() + if opts['copy']: + path = q.path + if opts['name']: + newpath = os.path.join(q.basepath, opts['name']) + if os.path.exists(newpath): + if not os.path.isdir(newpath): + raise util.Abort(_('destination %s exists and is not ' + 'a directory') % newpath) + if not opts['force']: + raise util.Abort(_('destination %s exists, ' + 'use -f to force') % newpath) + else: + newpath = savename(path) + ui.warn(_("copy %s to %s\n") % (path, newpath)) + util.copyfiles(path, newpath) + if opts['empty']: + try: + os.unlink(q.join(q.status_path)) + except: + pass + return 0 + +def strip(ui, repo, rev, **opts): + """strip a revision and all its descendants from the repository + + If one of the working directory's parent revisions is stripped, the + working directory will be updated to the parent of the stripped + revision. + """ + backup = 'all' + if opts['backup']: + backup = 'strip' + elif opts['nobackup']: + backup = 'none' + + rev = repo.lookup(rev) + p = repo.dirstate.parents() + cl = repo.changelog + update = True + if p[0] == nullid: + update = False + elif p[1] == nullid and rev != cl.ancestor(p[0], rev): + update = False + elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)): + update = False + + repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force']) + return 0 + +def select(ui, repo, *args, **opts): + '''set or print guarded patches to push + + Use the qguard command to set or print guards on patch, then use + qselect to tell mq which guards to use. A patch will be pushed if + it has no guards or any positive guards match the currently + selected guard, but will not be pushed if any negative guards + match the current guard. For example: + + qguard foo.patch -stable (negative guard) + qguard bar.patch +stable (positive guard) + qselect stable + + This activates the "stable" guard. mq will skip foo.patch (because + it has a negative match) but push bar.patch (because it has a + positive match). + + With no arguments, prints the currently active guards. + With one argument, sets the active guard. + + Use -n/--none to deactivate guards (no other arguments needed). + When no guards are active, patches with positive guards are + skipped and patches with negative guards are pushed. + + qselect can change the guards on applied patches. It does not pop + guarded patches by default. Use --pop to pop back to the last + applied patch that is not guarded. Use --reapply (which implies + --pop) to push back to the current patch afterwards, but skip + guarded patches. + + Use -s/--series to print a list of all guards in the series file + (no other arguments needed). Use -v for more information.''' + + q = repo.mq + guards = q.active() + if args or opts['none']: + old_unapplied = q.unapplied(repo) + old_guarded = [i for i in xrange(len(q.applied)) if + not q.pushable(i)[0]] + q.set_active(args) + q.save_dirty() + if not args: + ui.status(_('guards deactivated\n')) + if not opts['pop'] and not opts['reapply']: + unapplied = q.unapplied(repo) + guarded = [i for i in xrange(len(q.applied)) + if not q.pushable(i)[0]] + if len(unapplied) != len(old_unapplied): + ui.status(_('number of unguarded, unapplied patches has ' + 'changed from %d to %d\n') % + (len(old_unapplied), len(unapplied))) + if len(guarded) != len(old_guarded): + ui.status(_('number of guarded, applied patches has changed ' + 'from %d to %d\n') % + (len(old_guarded), len(guarded))) + elif opts['series']: + guards = {} + noguards = 0 + for gs in q.series_guards: + if not gs: + noguards += 1 + for g in gs: + guards.setdefault(g, 0) + guards[g] += 1 + if ui.verbose: + guards['NONE'] = noguards + guards = guards.items() + guards.sort(key=lambda x: x[0][1:]) + if guards: + ui.note(_('guards in series file:\n')) + for guard, count in guards: + ui.note('%2d ' % count) + ui.write(guard, '\n') + else: + ui.note(_('no guards in series file\n')) + else: + if guards: + ui.note(_('active guards:\n')) + for g in guards: + ui.write(g, '\n') + else: + ui.write(_('no active guards\n')) + reapply = opts['reapply'] and q.applied and q.appliedname(-1) + popped = False + if opts['pop'] or opts['reapply']: + for i in xrange(len(q.applied)): + pushable, reason = q.pushable(i) + if not pushable: + ui.status(_('popping guarded patches\n')) + popped = True + if i == 0: + q.pop(repo, all=True) + else: + q.pop(repo, i-1) + break + if popped: + try: + if reapply: + ui.status(_('reapplying unguarded patches\n')) + q.push(repo, reapply) + finally: + q.save_dirty() + +def finish(ui, repo, *revrange, **opts): + """move applied patches into repository history + + Finishes the specified revisions (corresponding to applied + patches) by moving them out of mq control into regular repository + history. + + Accepts a revision range or the -a/--applied option. If --applied + is specified, all applied mq revisions are removed from mq + control. Otherwise, the given revisions must be at the base of the + stack of applied patches. + + This can be especially useful if your changes have been applied to + an upstream repository, or if you are about to push your changes + to upstream. + """ + if not opts['applied'] and not revrange: + raise util.Abort(_('no revisions specified')) + elif opts['applied']: + revrange = ('qbase:qtip',) + revrange + + q = repo.mq + if not q.applied: + ui.status(_('no patches applied\n')) + return 0 + + revs = cmdutil.revrange(repo, revrange) + q.finish(repo, revs) + q.save_dirty() + return 0 + +def reposetup(ui, repo): + class mqrepo(repo.__class__): + @util.propertycache + def mq(self): + return queue(self.ui, self.join("")) + + def abort_if_wdir_patched(self, errmsg, force=False): + if self.mq.applied and not force: + parent = hex(self.dirstate.parents()[0]) + if parent in [s.rev for s in self.mq.applied]: + raise util.Abort(errmsg) + + def commit(self, text="", user=None, date=None, match=None, + force=False, editor=False, extra={}): + self.abort_if_wdir_patched( + _('cannot commit over an applied mq patch'), + force) + + return super(mqrepo, self).commit(text, user, date, match, force, + editor, extra) + + def push(self, remote, force=False, revs=None): + if self.mq.applied and not force and not revs: + raise util.Abort(_('source has mq patches applied')) + return super(mqrepo, self).push(remote, force, revs) + + def _findtags(self): + '''augment tags from base class with patch tags''' + result = super(mqrepo, self)._findtags() + + q = self.mq + if not q.applied: + return result + + mqtags = [(bin(patch.rev), patch.name) for patch in q.applied] + + if mqtags[-1][0] not in self.changelog.nodemap: + self.ui.warn(_('mq status file refers to unknown node %s\n') + % short(mqtags[-1][0])) + return result + + mqtags.append((mqtags[-1][0], 'qtip')) + mqtags.append((mqtags[0][0], 'qbase')) + mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent')) + tags = result[0] + for patch in mqtags: + if patch[1] in tags: + self.ui.warn(_('Tag %s overrides mq patch of the same name\n') + % patch[1]) + else: + tags[patch[1]] = patch[0] + + return result + + def _branchtags(self, partial, lrev): + q = self.mq + if not q.applied: + return super(mqrepo, self)._branchtags(partial, lrev) + + cl = self.changelog + qbasenode = bin(q.applied[0].rev) + if qbasenode not in cl.nodemap: + self.ui.warn(_('mq status file refers to unknown node %s\n') + % short(qbasenode)) + return super(mqrepo, self)._branchtags(partial, lrev) + + qbase = cl.rev(qbasenode) + start = lrev + 1 + if start < qbase: + # update the cache (excluding the patches) and save it + self._updatebranchcache(partial, lrev+1, qbase) + self._writebranchcache(partial, cl.node(qbase-1), qbase-1) + start = qbase + # if start = qbase, the cache is as updated as it should be. + # if start > qbase, the cache includes (part of) the patches. + # we might as well use it, but we won't save it. + + # update the cache up to the tip + self._updatebranchcache(partial, start, len(cl)) + + return partial + + if repo.local(): + repo.__class__ = mqrepo + +def mqimport(orig, ui, repo, *args, **kwargs): + if hasattr(repo, 'abort_if_wdir_patched'): + repo.abort_if_wdir_patched(_('cannot import over an applied patch'), + kwargs.get('force')) + return orig(ui, repo, *args, **kwargs) + +def uisetup(ui): + extensions.wrapcommand(commands.table, 'import', mqimport) + +seriesopts = [('s', 'summary', None, _('print first line of patch header'))] + +cmdtable = { + "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')), + "qclone": + (clone, + [('', 'pull', None, _('use pull protocol to copy metadata')), + ('U', 'noupdate', None, _('do not update the new working directories')), + ('', 'uncompressed', None, + _('use uncompressed transfer (fast over LAN)')), + ('p', 'patches', '', _('location of source patch repository')), + ] + commands.remoteopts, + _('hg qclone [OPTION]... SOURCE [DEST]')), + "qcommit|qci": + (commit, + commands.table["^commit|ci"][1], + _('hg qcommit [OPTION]... [FILE]...')), + "^qdiff": + (diff, + commands.diffopts + commands.diffopts2 + commands.walkopts, + _('hg qdiff [OPTION]... [FILE]...')), + "qdelete|qremove|qrm": + (delete, + [('k', 'keep', None, _('keep patch file')), + ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))], + _('hg qdelete [-k] [-r REV]... [PATCH]...')), + 'qfold': + (fold, + [('e', 'edit', None, _('edit patch header')), + ('k', 'keep', None, _('keep folded patch files')), + ] + commands.commitopts, + _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')), + 'qgoto': + (goto, + [('f', 'force', None, _('overwrite any local changes'))], + _('hg qgoto [OPTION]... PATCH')), + 'qguard': + (guard, + [('l', 'list', None, _('list all patches and guards')), + ('n', 'none', None, _('drop all guards'))], + _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')), + 'qheader': (header, [], _('hg qheader [PATCH]')), + "^qimport": + (qimport, + [('e', 'existing', None, _('import file in patch directory')), + ('n', 'name', '', _('name of patch file')), + ('f', 'force', None, _('overwrite existing files')), + ('r', 'rev', [], _('place existing revisions under mq control')), + ('g', 'git', None, _('use git extended diff format')), + ('P', 'push', None, _('qpush after importing'))], + _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')), + "^qinit": + (init, + [('c', 'create-repo', None, _('create queue repository'))], + _('hg qinit [-c]')), + "qnew": + (new, + [('e', 'edit', None, _('edit commit message')), + ('f', 'force', None, _('import uncommitted changes into patch')), + ('g', 'git', None, _('use git extended diff format')), + ('U', 'currentuser', None, _('add "From: <current user>" to patch')), + ('u', 'user', '', _('add "From: <given user>" to patch')), + ('D', 'currentdate', None, _('add "Date: <current date>" to patch')), + ('d', 'date', '', _('add "Date: <given date>" to patch')) + ] + commands.walkopts + commands.commitopts, + _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')), + "qnext": (next, [] + seriesopts, _('hg qnext [-s]')), + "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')), + "^qpop": + (pop, + [('a', 'all', None, _('pop all patches')), + ('n', 'name', '', _('queue name to pop')), + ('f', 'force', None, _('forget any local changes'))], + _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')), + "^qpush": + (push, + [('f', 'force', None, _('apply if the patch has rejects')), + ('l', 'list', None, _('list patch name in commit text')), + ('a', 'all', None, _('apply all patches')), + ('m', 'merge', None, _('merge from another queue')), + ('n', 'name', '', _('merge queue name'))], + _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')), + "^qrefresh": + (refresh, + [('e', 'edit', None, _('edit commit message')), + ('g', 'git', None, _('use git extended diff format')), + ('s', 'short', None, _('refresh only files already in the patch and specified files')), + ('U', 'currentuser', None, _('add/update author field in patch with current user')), + ('u', 'user', '', _('add/update author field in patch with given user')), + ('D', 'currentdate', None, _('add/update date field in patch with current date')), + ('d', 'date', '', _('add/update date field in patch with given date')) + ] + commands.walkopts + commands.commitopts, + _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')), + 'qrename|qmv': + (rename, [], _('hg qrename PATCH1 [PATCH2]')), + "qrestore": + (restore, + [('d', 'delete', None, _('delete save entry')), + ('u', 'update', None, _('update queue working directory'))], + _('hg qrestore [-d] [-u] REV')), + "qsave": + (save, + [('c', 'copy', None, _('copy patch directory')), + ('n', 'name', '', _('copy directory name')), + ('e', 'empty', None, _('clear queue status file')), + ('f', 'force', None, _('force copy'))] + commands.commitopts, + _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')), + "qselect": + (select, + [('n', 'none', None, _('disable all guards')), + ('s', 'series', None, _('list all guards in series file')), + ('', 'pop', None, _('pop to before first guarded applied patch')), + ('', 'reapply', None, _('pop, then reapply patches'))], + _('hg qselect [OPTION]... [GUARD]...')), + "qseries": + (series, + [('m', 'missing', None, _('print patches not in series')), + ] + seriesopts, + _('hg qseries [-ms]')), + "^strip": + (strip, + [('f', 'force', None, _('force removal with local changes')), + ('b', 'backup', None, _('bundle unrelated changesets')), + ('n', 'nobackup', None, _('no backups'))], + _('hg strip [-f] [-b] [-n] REV')), + "qtop": (top, [] + seriesopts, _('hg qtop [-s]')), + "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')), + "qfinish": + (finish, + [('a', 'applied', None, _('finish all applied changesets'))], + _('hg qfinish [-a] [REV]...')), +} |