[PATCH 5 of 6] patch: add mempatching support
Augie Fackler
durin42 at gmail.com
Mon Apr 19 18:16:38 CDT 2010
# HG changeset patch
# User Augie Fackler <durin42 at gmail.com>
# Date 1271716503 18000
# Node ID 9b8a41043ab3634a237292cf6de53dd967f2ca2f
# Parent 03eea121b5531319bd3cb703371e390cd25490e3
patch: add mempatching support
Thanks to Patrick Mezard for his help figuring out all the fiddly bits
of what I was doing wrong.
* * *
fold missing errno import
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -6,12 +6,12 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import cStringIO, email.Parser, os, re
+import cStringIO, email.Parser, os, re, errno
import tempfile, zlib
from i18n import _
from node import hex, nullid, short
-import base85, cmdutil, mdiff, util, diffhelpers, copies
+import base85, cmdutil, mdiff, util, diffhelpers, copies, context
gitre = re.compile('diff --git a/(.*) b/(.*)')
@@ -630,6 +630,108 @@
self.rej.append(horig)
return -1
+
+def _renamed(fctx):
+ copy = fctx.renamed()
+ if copy:
+ return copy[0]
+
+class mempatchfile(patchfile):
+ def __init__(self, ui, fname, opener, missing=False, eolmode='strict',
+ parentctx=None, files=None):
+ """Create a mempatch object.
+
+ This patches files from a context in memory, and saves a dict
+ of the resulting files.
+ """
+ assert parentctx is not None
+ assert files is not None
+ self.files = files
+ self.parentctx = parentctx
+ patchfile.__init__(self, ui, fname, None, False, eolmode)
+
+ def readlines(self, fname):
+ if fname not in self.parentctx:
+ raise IOError(errno.ENOENT, 'Cannot find %r to patch' % fname)
+ # TODO(durin42): refactor so we can skip StringIO here
+ lr = linereader(cStringIO.StringIO(self.parentctx[fname].data()),
+ self.eolmode != 'strict')
+ self.eol = lr.eol
+ return list(lr)
+
+ def writelines(self, fname, lines):
+ copy = islink = isexec = False
+ pendingctx = self.files.get(fname, None)
+ if pendingctx is not None:
+ islink = 'l' in pendingctx.flags()
+ isexec = 'x' in pendingctx.flags()
+ copy = _renamed(pendingctx)
+ elif fname in self.parentctx:
+ srcfctx = self.parentctx.filectx(fname)
+ islink = 'l' in srcfctx.flags()
+ isexec = 'x' in srcfctx.flags()
+ nfctx = context.memfilectx(fname, ''.join(lines),
+ islink, isexec, copy)
+ self.files[fname] = nfctx
+
+ def write_rej(self):
+ if self.rej:
+ raise PatchError('in-memory patching failed!')
+
+ def unlink(self, fname):
+ self.files[fname] = None
+
+
+class mempatcher(object):
+ def __init__(self, ctx):
+ self.ctx = ctx
+ self.files = {}
+
+ def _filectx(self, f):
+ if self.files.get(f, None) is not None:
+ return self.files[f]
+ elif f in self.ctx:
+ return self.ctx.filectx(f)
+
+ def patchfile(self, *args, **kwargs):
+ return mempatchfile(*args, parentctx=self.ctx, files=self.files,
+ **kwargs)
+
+ def copyfile(self, src, dest, basedir):
+ if self.files.get(dest, None) is not None or dest in self.ctx:
+ raise PatchError('%s already exists' % dest)
+ srcfctx = self._filectx(src)
+ if srcfctx is None:
+ raise util.Abort('missing copy source %s for %s' %
+ (src, dest))
+ self.files[dest] = context.memfilectx(
+ dest, srcfctx.data(), 'l' in srcfctx.flags(),
+ 'x' in srcfctx.flags(), src)
+
+ def setmode(self, f, islink, isexec):
+ basectx = self._filectx(f)
+ if basectx is not None:
+ if (islink == ('l' in basectx.flags())
+ and isexec == ('x' in basectx.flags())):
+ return
+ data = basectx.data()
+ copied = _renamed(basectx)
+ else:
+ data = ''
+ copied = False
+ self.files[f] = context.memfilectx(f, data, islink, isexec, copied)
+
+ def filectxfn(self, repo, ctx, path):
+ """Function to pass to memctx as filectxfn to commit this patch.
+ """
+ if self.files[path] is None:
+ raise IOError(errno.ENOENT, '%s is deleted' % path)
+ return self.files[path]
+
+ def unlink(self, fn):
+ self.files[fn] = None
+
+
class hunk(object):
def __init__(self, desc, num, lr, context, create=False, remove=False):
self.number = num
@@ -1146,6 +1248,30 @@
changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
+def memapplydiff(ui, fp, ctx, strip=1, eolmode='strict'):
+ """Apply a diff in memory to ctx.
+
+ If the diff doesn't apply cleanly, this will raise a PatchError,
+ since reject files can't be sanely written.
+ """
+ patcher = mempatcher(ctx)
+ changed = {}
+ err = _applydiff(ui, fp, patcher.patchfile, patcher.copyfile,
+ changed, strip=strip, sourcefile=None, eolmode=eolmode)
+ if err != 0:
+ raise PatchError('in-memory diff application failed')
+ # perform updatedir-like updates of things patching doesn't catch
+ for f, gp in changed.iteritems():
+ if gp and gp.op in ('RENAME', 'DELETE'):
+ if gp.op == 'RENAME':
+ patcher.unlink(gp.oldpath)
+ else:
+ patcher.unlink(gp.path)
+ if gp and gp.mode:
+ islink, isexec = gp.mode
+ patcher.setmode(f, islink, isexec)
+ return patcher
+
def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
sourcefile=None, eolmode='strict'):
rejects = 0
More information about the Mercurial-devel
mailing list