--- a/hgext/mq.py Thu Jul 26 07:56:27 2007 -0400
+++ b/hgext/mq.py Wed Feb 06 19:57:52 2008 -0800
@@ -34,7 +34,7 @@
from mercurial import repair
import os, sys, re, errno
-commands.norepo += " qclone qversion"
+commands.norepo += " qclone"
# Patch names looks like unix-file names.
# They must be joinable with queue directory and result in the patch path.
@@ -224,7 +224,7 @@
def write_list(items, path):
fp = self.opener(path, 'w')
for i in items:
- print >> fp, i
+ 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)
@@ -455,7 +455,8 @@
repo.dirstate.invalidate()
raise
finally:
- del lock, wlock, tr
+ del tr, lock, wlock
+ self.removeundo(repo)
def _apply(self, repo, series, list=False, update_status=True,
strict=False, patchdir=None, merge=None, all_files={}):
@@ -527,7 +528,6 @@
self.ui.warn("fuzz found when applying patch, stopping\n")
err = 1
break
- self.removeundo(repo)
return (err, n)
def delete(self, repo, patches, opts):
@@ -587,7 +587,7 @@
top = revlog.bin(self.applied[-1].rev)
pp = repo.dirstate.parents()
if top not in pp:
- raise util.Abort(_("queue top not at same revision as working directory"))
+ raise util.Abort(_("working directory revision is not qtip"))
return top
return None
def check_localchanges(self, repo, force=False, refresh=True):
@@ -600,9 +600,19 @@
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, patch, *pats, **opts):
msg = opts.get('msg')
force = opts.get('force')
+ user = opts.get('user')
+ date = opts.get('date')
+ self.check_reserved_name(patch)
if os.path.exists(self.join(patch)):
raise util.Abort(_('patch "%s" already exists') % patch)
if opts.get('include') or opts.get('exclude') or pats:
@@ -610,15 +620,14 @@
m, a, r, d = repo.status(files=fns, match=match)[:4]
else:
m, a, r, d = self.check_localchanges(repo, force)
+ fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
commitfiles = m + a + r
self.check_toppatch(repo)
wlock = repo.wlock()
try:
insert = self.full_series_end()
- if msg:
- n = repo.commit(commitfiles, msg, force=True)
- else:
- n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True)
+ commitmsg = msg and msg or ("[mq]: %s" % patch)
+ n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
if n == None:
raise util.Abort(_("repo commit failed"))
self.full_series[insert:insert] = [patch]
@@ -627,6 +636,15 @@
self.series_dirty = 1
self.applied_dirty = 1
p = self.opener(patch, "w")
+ if date:
+ p.write("# HG changeset patch\n")
+ if user:
+ p.write("# User " + user + "\n")
+ p.write("# Date " + date + "\n")
+ p.write("\n")
+ elif user:
+ p.write("From: " + user + "\n")
+ p.write("\n")
if msg:
msg = msg + "\n"
p.write(msg)
@@ -635,7 +653,7 @@
r = self.qrepo()
if r: r.add([patch])
if commitfiles:
- self.refresh(repo, short=True)
+ self.refresh(repo, short=True, git=opts.get('git'))
self.removeundo(repo)
finally:
del wlock
@@ -654,6 +672,9 @@
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:
del lock, wlock
@@ -810,9 +831,9 @@
del wlock
def pop(self, repo, patch=None, force=False, update=True, all=False):
- def getfile(f, rev):
+ def getfile(f, rev, flags):
t = repo.file(f).read(rev)
- repo.wfile(f, "w").write(t)
+ repo.wwrite(f, t, flags)
wlock = repo.wlock()
try:
@@ -859,10 +880,16 @@
start = info[0]
rev = revlog.bin(info[1])
+ if update:
+ top = self.check_toppatch(repo)
+
+ if repo.changelog.heads(rev) != [revlog.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:
- top = self.check_toppatch(repo)
qp = self.qparents(repo, rev)
changes = repo.changelog.read(qp)
mmap = repo.manifest.read(changes[0])
@@ -870,10 +897,9 @@
if d:
raise util.Abort("deletions found between repo revs")
for f in m:
- getfile(f, mmap[f])
+ getfile(f, mmap[f], mmap.flags(f))
for f in r:
- getfile(f, mmap[f])
- util.set_exec(repo.wjoin(f), mmap.execf(f))
+ getfile(f, mmap[f], mmap.flags(f))
for f in m + r:
repo.dirstate.normal(f)
for f in a:
@@ -886,8 +912,8 @@
except: pass
repo.dirstate.forget(f)
repo.dirstate.setparents(qp, revlog.nullid)
+ del self.applied[start:end]
self.strip(repo, rev, update=False, backup='strip')
- del self.applied[start:end]
if len(self.applied):
self.ui.write("Now at: %s\n" % self.applied[-1].name)
else:
@@ -914,6 +940,8 @@
self.check_toppatch(repo)
(top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
top = revlog.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)
message, comments, user, date, patchfound = self.readheaders(patchfn)
@@ -925,22 +953,59 @@
if line.startswith('diff --git'):
self.diffopts().git = True
break
+
+ msg = opts.get('msg', '').rstrip()
+ if msg and comments:
+ # Remove existing message, keeping the rest of the comments
+ # fields.
+ # If comments contains 'subject: ', message will prepend
+ # the field and a blank line.
+ if message:
+ subj = 'subject: ' + message[0].lower()
+ for i in xrange(len(comments)):
+ if subj == comments[i].lower():
+ del comments[i]
+ message = message[2:]
+ break
+ ci = 0
+ for mi in xrange(len(message)):
+ while message[mi] != comments[ci]:
+ ci += 1
+ del comments[ci]
+
+ def setheaderfield(comments, prefixes, new):
+ # Update all references to a field in the patch header.
+ # If none found, add it email style.
+ res = False
+ for prefix in prefixes:
+ for i in xrange(len(comments)):
+ if comments[i].startswith(prefix):
+ comments[i] = prefix + new
+ res = True
+ break
+ return res
+
+ newuser = opts.get('user')
+ if newuser:
+ if not setheaderfield(comments, ['From: ', '# User '], newuser):
+ try:
+ patchheaderat = comments.index('# HG changeset patch')
+ comments.insert(patchheaderat + 1,'# User ' + newuser)
+ except ValueError:
+ comments = ['From: ' + newuser, ''] + comments
+ user = newuser
+
+ newdate = opts.get('date')
+ if newdate:
+ if setheaderfield(comments, ['# Date '], newdate):
+ date = newdate
+
+ if msg:
+ comments.append(msg)
+
patchf.seek(0)
patchf.truncate()
- msg = opts.get('msg', '').rstrip()
- if msg:
- if comments:
- # Remove existing message.
- ci = 0
- subj = None
- for mi in xrange(len(message)):
- if comments[ci].lower().startswith('subject: '):
- subj = comments[ci][9:]
- while message[mi] != comments[ci] and message[mi] != subj:
- ci += 1
- del comments[ci]
- comments.append(msg)
if comments:
comments = "\n".join(comments) + '\n\n'
patchf.write(comments)
@@ -1017,9 +1082,8 @@
copies = {}
for dst in a:
src = repo.dirstate.copied(dst)
- if src is None:
- continue
- copies.setdefault(src, []).append(dst)
+ if src is not None:
+ copies.setdefault(src, []).append(dst)
repo.dirstate.add(dst)
# remember the copies between patchparent and tip
# this may be slow, so don't do it if we're not tracking copies
@@ -1049,7 +1113,7 @@
for f in m:
repo.dirstate.normal(f)
for f in mm:
- repo.dirstate.normaldirty(f)
+ repo.dirstate.normallookup(f)
for f in forget:
repo.dirstate.forget(f)
@@ -1061,12 +1125,16 @@
else:
message = msg
+ if not user:
+ user = changes[1]
+
+ self.applied.pop()
+ self.applied_dirty = 1
self.strip(repo, top, update=False,
backup='strip')
- n = repo.commit(filelist, message, changes[1], match=matchfn,
+ n = repo.commit(filelist, message, user, date, match=matchfn,
force=1)
- self.applied[-1] = statusentry(revlog.hex(n), patchfn)
- self.applied_dirty = 1
+ self.applied.append(statusentry(revlog.hex(n), patchfn))
self.removeundo(repo)
else:
self.printdiff(repo, patchparent, fp=patchf)
@@ -1216,7 +1284,7 @@
self.ui.warn("saved queue repository parents: %s %s\n" %
(hg.short(qpp[0]), hg.short(qpp[1])))
if qupdate:
- print "queue directory updating"
+ self.ui.status(_("queue directory updating\n"))
r = self.qrepo()
if not r:
self.ui.warn("Unable to load queue repository\n")
@@ -1355,6 +1423,7 @@
if not patchname:
patchname = normname('%d.diff' % r)
+ self.check_reserved_name(patchname)
checkseries(patchname)
checkfile(patchname)
self.full_series.insert(0, patchname)
@@ -1377,6 +1446,7 @@
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:
@@ -1391,6 +1461,7 @@
raise util.Abort(_("unable to read %s") % patchname)
if not patchname:
patchname = normname(os.path.basename(filename))
+ self.check_reserved_name(patchname)
checkfile(patchname)
patchf = self.opener(patchname, "w")
patchf.write(text)
@@ -1508,13 +1579,18 @@
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'
cmdutil.setremoteconfig(ui, opts)
if dest is None:
dest = hg.defaultdest(source)
sr = hg.repository(ui, ui.expandpath(source))
- patchdir = opts['patches'] or (sr.url() + '/.hg/patches')
+ patchespath = opts['patches'] or patchdir(sr)
try:
- pr = hg.repository(ui, patchdir)
+ pr = hg.repository(ui, patchespath)
except hg.RepoError:
raise util.Abort(_('versioned patch repository not found'
' (see qinit -c)'))
@@ -1535,10 +1611,8 @@
update=False,
stream=opts['uncompressed'])
ui.note(_('cloning patch repo\n'))
- spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
- dr.url() + '/.hg/patches',
- pull=opts['pull'],
- update=not opts['noupdate'],
+ spr, dpr = 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:
@@ -1593,6 +1667,13 @@
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
@@ -1611,6 +1692,7 @@
if opts['edit']:
message = ui.edit(message, ui.username())
opts['msg'] = message
+ setupheaderopts(ui, opts)
q.new(repo, patch, *args, **opts)
q.save_dirty()
return 0
@@ -1628,11 +1710,15 @@
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
(message, comment, user, date, hasdiff) = q.readheaders(patch)
message = ui.edit('\n'.join(message), user or ui.username())
+ setupheaderopts(ui, opts)
ret = q.refresh(repo, pats, msg=message, **opts)
q.save_dirty()
return ret
@@ -2081,6 +2167,12 @@
return tagscache
mqtags = [(revlog.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'
+ % revlog.short(mqtags[-1][0]))
+ return tagscache
+
mqtags.append((mqtags[-1][0], 'qtip'))
mqtags.append((mqtags[0][0], 'qbase'))
mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
@@ -2097,11 +2189,17 @@
if not q.applied:
return super(mqrepo, self)._branchtags()
+ cl = self.changelog
+ qbasenode = revlog.bin(q.applied[0].rev)
+ if qbasenode not in cl.nodemap:
+ self.ui.warn('mq status file refers to unknown node %s\n'
+ % revlog.short(qbasenode))
+ return super(mqrepo, self)._branchtags()
+
self.branchcache = {} # avoid recursion in changectx
- cl = self.changelog
partial, last, lrev = self._readbranchcache()
- qbase = cl.rev(revlog.bin(q.applied[0].rev))
+ qbase = cl.rev(qbasenode)
start = lrev + 1
if start < qbase:
# update the cache (excluding the patches) and save it
@@ -2123,6 +2221,12 @@
seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
+headeropts = [
+ ('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'))]
+
cmdtable = {
"qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
"qclone":
@@ -2131,10 +2235,8 @@
('U', 'noupdate', None, _('do not update the new working directories')),
('', 'uncompressed', None,
_('use uncompressed transfer (fast over LAN)')),
- ('e', 'ssh', '', _('specify ssh command to use')),
('p', 'patches', '', _('location of source patch repo')),
- ('', 'remotecmd', '',
- _('specify hg command to run on the remote side'))],
+ ] + commands.remoteopts,
_('hg qclone [OPTION]... SOURCE [DEST]')),
"qcommit|qci":
(commit,
@@ -2143,9 +2245,8 @@
"^qdiff":
(diff,
[('g', 'git', None, _('use git extended diff format')),
- ('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ('U', 'unified', 3, _('number of lines of context to show'))],
+ ('U', 'unified', 3, _('number of lines of context to show')),
+ ] + commands.walkopts,
_('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
"qdelete|qremove|qrm":
(delete,
@@ -2184,9 +2285,8 @@
(new,
[('e', 'edit', None, _('edit commit message')),
('f', 'force', None, _('import uncommitted changes into patch')),
- ('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ] + commands.commitopts,
+ ('g', 'git', None, _('use git extended diff format')),
+ ] + commands.walkopts + commands.commitopts + headeropts,
_('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
"qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
"qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
@@ -2209,9 +2309,7 @@
[('e', 'edit', None, _('edit commit message')),
('g', 'git', None, _('use git extended diff format')),
('s', 'short', None, _('refresh only files already in the patch')),
- ('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ] + commands.commitopts,
+ ] + commands.walkopts + commands.commitopts + headeropts,
_('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
'qrename|qmv':
(rename, [], _('hg qrename PATCH1 [PATCH2]')),