--- a/hgext/mq.py Fri Aug 18 10:24:04 2006 -0700
+++ b/hgext/mq.py Fri Aug 18 13:01:40 2006 -0700
@@ -252,6 +252,9 @@
for line in file(pf):
line = line.rstrip()
+ if line.startswith('diff --git'):
+ diffstart = 2
+ break
if diffstart:
if line.startswith('+++ '):
diffstart = 2
@@ -298,8 +301,10 @@
return (message, comments, user, date, diffstart > 1)
def printdiff(self, repo, node1, node2=None, files=None,
- fp=None, changes=None, opts=None):
- patch.diff(repo, node1, node2, files,
+ fp=None, changes=None, opts={}):
+ fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
+
+ patch.diff(repo, node1, node2, fns, match=matchfn,
fp=fp, changes=changes, opts=self.diffopts())
def mergeone(self, repo, mergeq, head, patch, rev, wlock):
@@ -408,7 +413,7 @@
self.ui.warn("patch failed, unable to continue (try -v)\n")
return (False, [], False)
- return (True, files.keys(), fuzz)
+ return (True, files, fuzz)
def apply(self, repo, series, list=False, update_status=True,
strict=False, patchdir=None, merge=None, wlock=None):
@@ -421,42 +426,37 @@
lock = repo.lock()
tr = repo.transaction()
n = None
- for patch in series:
- pushable, reason = self.pushable(patch)
+ for patchname in series:
+ pushable, reason = self.pushable(patchname)
if not pushable:
- self.explain_pushable(patch, all_patches=True)
+ self.explain_pushable(patchname, all_patches=True)
continue
- self.ui.warn("applying %s\n" % patch)
- pf = os.path.join(patchdir, patch)
+ self.ui.warn("applying %s\n" % patchname)
+ pf = os.path.join(patchdir, patchname)
try:
- message, comments, user, date, patchfound = self.readheaders(patch)
+ message, comments, user, date, patchfound = self.readheaders(patchname)
except:
- self.ui.warn("Unable to read %s\n" % pf)
+ self.ui.warn("Unable to read %s\n" % patchname)
err = 1
break
if not message:
- message = "imported patch %s\n" % patch
+ message = "imported patch %s\n" % patchname
else:
if list:
- message.append("\nimported patch %s" % patch)
+ message.append("\nimported patch %s" % patchname)
message = '\n'.join(message)
(patcherr, files, fuzz) = self.patch(repo, pf)
patcherr = not patcherr
- if merge and len(files) > 0:
+ if merge and files:
# Mark as merged and update dirstate parent info
- repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
+ repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
p1, p2 = repo.dirstate.parents()
repo.dirstate.setparents(p1, merge)
- if len(files) > 0:
- cwd = repo.getcwd()
- cfiles = files
- if cwd:
- cfiles = [util.pathto(cwd, f) for f in files]
- cmdutil.addremove(repo, cfiles, wlock=wlock)
+ files = patch.updatedir(self.ui, repo, files, wlock=wlock)
n = repo.commit(files, message, user, date, force=1, lock=lock,
wlock=wlock)
@@ -464,11 +464,11 @@
raise util.Abort(_("repo commit failed"))
if update_status:
- self.applied.append(statusentry(revlog.hex(n), patch))
+ self.applied.append(statusentry(revlog.hex(n), patchname))
if patcherr:
if not patchfound:
- self.ui.warn("patch %s is empty\n" % patch)
+ self.ui.warn("patch %s is empty\n" % patchname)
err = 0
else:
self.ui.warn("patch failed, rejects left in working dir\n")
@@ -904,15 +904,15 @@
else:
self.ui.write("Patch queue now empty\n")
- def diff(self, repo, files):
+ 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.printdiff(repo, qp, files=files)
+ self.printdiff(repo, qp, files=pats, opts=opts)
- def refresh(self, repo, msg='', short=False):
+ def refresh(self, repo, pats=None, **opts):
if len(self.applied) == 0:
self.ui.write("No patches applied\n")
return
@@ -925,7 +925,7 @@
message, comments, user, date, patchfound = self.readheaders(patch)
patchf = self.opener(patch, "w")
- msg = msg.rstrip()
+ msg = opts.get('msg', '').rstrip()
if msg:
if comments:
# Remove existing message.
@@ -939,6 +939,7 @@
comments = "\n".join(comments) + '\n\n'
patchf.write(comments)
+ fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
tip = repo.changelog.tip()
if top == tip:
# if the top of our patch queue is also the tip, there is an
@@ -956,7 +957,7 @@
# caching against the next repo.status call
#
mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
- if short:
+ if opts.get('short'):
filelist = mm + aa + dd
else:
filelist = None
@@ -992,16 +993,27 @@
m = list(util.unique(mm))
r = list(util.unique(dd))
a = list(util.unique(aa))
- filelist = list(util.unique(m + r + a))
+ filelist = filter(matchfn, util.unique(m + r + a))
self.printdiff(repo, patchparent, files=filelist,
changes=(m, a, r, [], u), fp=patchf)
patchf.close()
changes = repo.changelog.read(tip)
repo.dirstate.setparents(*cparents)
+ copies = [(f, repo.dirstate.copied(f)) for f in a]
repo.dirstate.update(a, 'a')
+ for dst, src in copies:
+ repo.dirstate.copy(src, dst)
repo.dirstate.update(r, 'r')
+ # if the patch excludes a modified file, mark that file with mtime=0
+ # so status can see it.
+ mm = []
+ for i in range(len(m)-1, -1, -1):
+ if not matchfn(m[i]):
+ mm.append(m[i])
+ del m[i]
repo.dirstate.update(m, 'n')
+ repo.dirstate.update(mm, 'n', st_mtime=0)
repo.dirstate.forget(forget)
if not msg:
@@ -1216,7 +1228,7 @@
if not self.ui.verbose:
p = pname
else:
- p = str(self.series.index(pname)) + " " + p
+ p = str(self.series.index(pname)) + " " + pname
return p
def top(self, repo):
@@ -1411,17 +1423,24 @@
changes unless -f is specified, in which case the patch will
be initialised with them.
- -m or -l set the patch header as well as the commit message.
- If neither is specified, the patch header is empty and the
+ -e, -m or -l set the patch header as well as the commit message.
+ If none is specified, the patch header is empty and the
commit message is 'New patch: PATCH'"""
q = repo.mq
message = commands.logmessage(opts)
+ if opts['edit']:
+ message = ui.edit(message, ui.username())
q.new(repo, patch, msg=message, force=opts['force'])
q.save_dirty()
return 0
-def refresh(ui, repo, **opts):
- """update the current patch"""
+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.
+ """
q = repo.mq
message = commands.logmessage(opts)
if opts['edit']:
@@ -1430,14 +1449,13 @@
patch = q.applied[-1].name
(message, comment, user, date, hasdiff) = q.readheaders(patch)
message = ui.edit('\n'.join(message), user or ui.username())
- q.refresh(repo, msg=message, short=opts['short'])
+ q.refresh(repo, pats, msg=message, **opts)
q.save_dirty()
return 0
-def diff(ui, repo, *files, **opts):
+def diff(ui, repo, *pats, **opts):
"""diff of the current patch"""
- # deep in the dirstate code, the walkhelper method wants a list, not a tuple
- repo.mq.diff(repo, list(files))
+ repo.mq.diff(repo, pats, opts)
return 0
def fold(ui, repo, *files, **opts):
@@ -1469,20 +1487,21 @@
patches = []
messages = []
for f in files:
- patch = q.lookup(f)
- if patch in patches or patch == parent:
- ui.warn(_('Skipping already folded patch %s') % patch)
- if q.isapplied(patch):
- raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
- patches.append(patch)
+ 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 patch in patches:
+ for p in patches:
if not message:
- messages.append(q.readheaders(patch)[0])
- pf = q.join(patch)
+ messages.append(q.readheaders(p)[0])
+ pf = q.join(p)
(patchsuccess, files, fuzz) = q.patch(repo, pf)
if not patchsuccess:
- raise util.Abort(_('Error folding patch %s') % patch)
+ raise util.Abort(_('Error folding patch %s') % p)
+ patch.updatedir(ui, repo, files)
if not message:
message, comments, user = q.readheaders(parent)[0:3]
@@ -1495,29 +1514,26 @@
message = ui.edit(message, user or ui.username())
q.refresh(repo, msg=message)
-
- for patch in patches:
- q.delete(repo, patch, keep=opts['keep'])
-
+ q.delete(repo, patches, keep=opts['keep'])
q.save_dirty()
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 aways pushed. a patch with posative guard ("+foo") is
- pushed only if qselect command enables guard "foo". a patch with
- nagative guard ("-foo") is never pushed if qselect command enables
- guard "foo".
+ 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, default is to print current active guards.
- with arguments, set active guards for patch.
+ With no arguments, print the currently active guards.
+ With arguments, set guards for the named patch.
- to set nagative guard "-foo" on topmost patch ("--" is needed so
- hg will not interpret "-foo" as argument):
+ To set a negative guard "-foo" on topmost patch ("--" is needed so
+ hg will not interpret "-foo" as an option):
hg qguard -- -foo
- to set guards on other patch:
+ To set guards on another patch:
hg qguard other.patch +2.6.17 -stable
'''
def status(idx):
@@ -1723,32 +1739,34 @@
def select(ui, repo, *args, **opts):
'''set or print guarded patches to push
- use qguard command to set or print guards on patch. then use
- qselect to tell mq which guards to use. example:
+ 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 (nagative guard)
- qguard bar.patch +stable (posative guard)
+ qguard foo.patch -stable (negative guard)
+ qguard bar.patch +stable (positive guard)
qselect stable
- this sets "stable" guard. mq will skip foo.patch (because it has
- nagative match) but push bar.patch (because it has posative
- match). patch is pushed if any posative guards match and no
- nagative guards match.
+ 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, default is to print current active guards.
- with arguments, set active guards as given.
+ 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 active, patches with posative guards are skipped,
- patches with nagative guards are pushed.
+ 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 guards of applied patches. it does not pop
- guarded patches by default. use --pop to pop back to last applied
- patch that is not guarded. use --reapply (implies --pop) to push
- back to current patch afterwards, but skip guarded patches.
+ 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 list of all guards in series file (no
- other arguments needed). use -v for more information.'''
+ 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()
@@ -1885,7 +1903,10 @@
(commit,
commands.table["^commit|ci"][1],
'hg qcommit [OPTION]... [FILE]...'),
- "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
+ "^qdiff": (diff,
+ [('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+ 'hg qdiff [-I] [-X] [FILE]...'),
"qdelete|qremove|qrm":
(delete,
[('k', 'keep', None, _('keep patch file'))],
@@ -1914,10 +1935,11 @@
'hg qinit [-c]'),
"qnew":
(new,
- [('m', 'message', '', _('use <text> as commit message')),
+ [('e', 'edit', None, _('edit commit message')),
+ ('m', 'message', '', _('use <text> as commit message')),
('l', 'logfile', '', _('read the commit message from <file>')),
('f', 'force', None, _('import uncommitted changes into patch'))],
- 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
+ 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
"qnext": (next, [], 'hg qnext'),
"qprev": (prev, [], 'hg qprev'),
"^qpop":
@@ -1939,8 +1961,10 @@
[('e', 'edit', None, _('edit commit message')),
('m', 'message', '', _('change commit message with <text>')),
('l', 'logfile', '', _('change commit message with <file> content')),
- ('s', 'short', None, 'short refresh')],
- 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
+ ('s', 'short', None, 'short refresh'),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+ 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
'qrename|qmv':
(rename, [], 'hg qrename PATCH1 [PATCH2]'),
"qrestore":
--- a/mercurial/patch.py Fri Aug 18 10:24:04 2006 -0700
+++ b/mercurial/patch.py Fri Aug 18 13:01:40 2006 -0700
@@ -11,6 +11,28 @@
demandload(globals(), "cmdutil mdiff util")
demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
+# helper functions
+
+def copyfile(src, dst, basedir=None):
+ if not basedir:
+ basedir = os.getcwd()
+
+ abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
+ if os.path.exists(absdst):
+ raise util.Abort(_("cannot create %s: destination already exists") %
+ dst)
+
+ targetdir = os.path.dirname(absdst)
+ if not os.path.isdir(targetdir):
+ os.makedirs(targetdir)
+ try:
+ shutil.copyfile(abssrc, absdst)
+ shutil.copymode(abssrc, absdst)
+ except shutil.Error, inst:
+ raise util.Abort(str(inst))
+
+# public functions
+
def extract(ui, fileobj):
'''extract patch from data read from fileobj.
@@ -174,21 +196,7 @@
if not p.copymod:
continue
- if os.path.exists(p.path):
- raise util.Abort(_("cannot create %s: destination already exists") %
- p.path)
-
- (src, dst) = [os.path.join(os.getcwd(), n)
- for n in (p.oldpath, p.path)]
-
- targetdir = os.path.dirname(dst)
- if not os.path.isdir(targetdir):
- os.makedirs(targetdir)
- try:
- shutil.copyfile(src, dst)
- shutil.copymode(src, dst)
- except shutil.Error, inst:
- raise util.Abort(str(inst))
+ copyfile(p.oldpath, p.path)
# rewrite patch hunk
while pfline < p.lineno:
@@ -281,6 +289,45 @@
ignoreblanklines=(opts.get('ignore_blank_lines') or
ui.configbool('diff', 'ignoreblanklines', None)))
+def updatedir(ui, repo, patches, wlock=None):
+ '''Update dirstate after patch application according to metadata'''
+ if not patches:
+ return
+ copies = []
+ removes = []
+ cfiles = patches.keys()
+ copts = {'after': False, 'force': False}
+ cwd = repo.getcwd()
+ if cwd:
+ cfiles = [util.pathto(cwd, f) for f in patches.keys()]
+ for f in patches:
+ ctype, gp = patches[f]
+ if ctype == 'RENAME':
+ copies.append((gp.oldpath, gp.path, gp.copymod))
+ removes.append(gp.oldpath)
+ elif ctype == 'COPY':
+ copies.append((gp.oldpath, gp.path, gp.copymod))
+ elif ctype == 'DELETE':
+ removes.append(gp.path)
+ for src, dst, after in copies:
+ if not after:
+ copyfile(src, dst, repo.root)
+ repo.copy(src, dst, wlock=wlock)
+ if removes:
+ repo.remove(removes, True, wlock=wlock)
+ for f in patches:
+ ctype, gp = patches[f]
+ if gp and gp.mode:
+ x = gp.mode & 0100 != 0
+ dst = os.path.join(repo.root, gp.path)
+ util.set_exec(dst, x)
+ cmdutil.addremove(repo, cfiles, wlock=wlock)
+ files = patches.keys()
+ files.extend([r for r in removes if r not in files])
+ files.sort()
+
+ return files
+
def diff(repo, node1=None, node2=None, files=None, match=util.always,
fp=None, changes=None, opts=None):
'''print diff of changes to files between two nodes, or node and
@@ -296,10 +343,27 @@
if not node1:
node1 = repo.dirstate.parents()[0]
+
+ clcache = {}
+ def getchangelog(n):
+ if n not in clcache:
+ clcache[n] = repo.changelog.read(n)
+ return clcache[n]
+ mcache = {}
+ def getmanifest(n):
+ if n not in mcache:
+ mcache[n] = repo.manifest.read(n)
+ return mcache[n]
+ fcache = {}
+ def getfile(f):
+ if f not in fcache:
+ fcache[f] = repo.file(f)
+ return fcache[f]
+
# reading the data for node1 early allows it to play nicely
# with repo.status and the revlog cache.
- change = repo.changelog.read(node1)
- mmap = repo.manifest.read(change[0])
+ change = getchangelog(node1)
+ mmap = getmanifest(change[0])
date1 = util.datestr(change[2])
if not changes:
@@ -320,17 +384,32 @@
if not modified and not added and not removed:
return
+ def renamedbetween(f, n1, n2):
+ r1, r2 = map(repo.changelog.rev, (n1, n2))
+ src = None
+ while r2 > r1:
+ cl = getchangelog(n2)[0]
+ m = getmanifest(cl)
+ try:
+ src = getfile(f).renamed(m[f])
+ except KeyError:
+ return None
+ if src:
+ f = src[0]
+ n2 = repo.changelog.parents(n2)[0]
+ r2 = repo.changelog.rev(n2)
+ return src
+
if node2:
- change = repo.changelog.read(node2)
- mmap2 = repo.manifest.read(change[0])
+ change = getchangelog(node2)
+ mmap2 = getmanifest(change[0])
_date2 = util.datestr(change[2])
def date2(f):
return _date2
def read(f):
- return repo.file(f).read(mmap2[f])
+ return getfile(f).read(mmap2[f])
def renamed(f):
- src = repo.file(f).renamed(mmap2[f])
- return src and src[0] or None
+ return renamedbetween(f, node1, node2)
else:
tz = util.makedate()[1]
_date2 = util.datestr()
@@ -343,7 +422,18 @@
def read(f):
return repo.wread(f)
def renamed(f):
- return repo.dirstate.copies.get(f)
+ src = repo.dirstate.copies.get(f)
+ parent = repo.dirstate.parents()[0]
+ if src:
+ f = src[0]
+ of = renamedbetween(f, node1, parent)
+ if of:
+ return of
+ elif src:
+ cl = getchangelog(parent)[0]
+ return (src, getmanifest(cl)[src])
+ else:
+ return None
if repo.ui.quiet:
r = None
@@ -357,7 +447,7 @@
src = renamed(f)
if src:
copied[f] = src
- srcs = [x[1] for x in copied.items()]
+ srcs = [x[1][0] for x in copied.items()]
all = modified + added + removed
all.sort()
@@ -366,7 +456,7 @@
tn = None
dodiff = True
if f in mmap:
- to = repo.file(f).read(mmap[f])
+ to = getfile(f).read(mmap[f])
if f not in removed:
tn = read(f)
if opts.git:
@@ -385,13 +475,13 @@
else:
mode = gitmode(util.is_exec(repo.wjoin(f), None))
if f in copied:
- a = copied[f]
+ a, arev = copied[f]
omode = gitmode(mmap.execf(a))
addmodehdr(header, omode, mode)
op = a in removed and 'rename' or 'copy'
header.append('%s from %s\n' % (op, a))
header.append('%s to %s\n' % (op, f))
- to = repo.file(a).read(mmap[a])
+ to = getfile(a).read(arev)
else:
header.append('new file mode %s\n' % mode)
elif f in removed: