Mercurial > hg
changeset 14451:c78d41db6f88
patch: refactor file creation/removal detection
The patcher has to know if a file is being created or removed to check if the
target already exists, or to actually unlink the file when a hunk emptying it
is applied. This was done by embedding the creation/removal information in the
first (and only) hunk attached to the file.
There are two problems with this approach:
- creation/removal is really a property of the file being patched and not its
hunk.
- for regular patches, file creation cannot be deduced at parsing time: there
are case where the *stripped* file paths must be compared. Modifying hunks
after their creation is clumsy and prevent further refactorings related to
copies handling.
Instead, we delegate this job to selectfile() which has all the relevant
information, and remove the hunk createfile() and rmfile() methods.
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Fri, 27 May 2011 21:50:09 +0200 |
parents | d1a1578c5f78 |
children | ee574cfd0c32 |
files | hgext/keyword.py mercurial/patch.py |
diffstat | 2 files changed, 31 insertions(+), 47 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/keyword.py Fri May 27 15:59:52 2011 +0200 +++ b/hgext/keyword.py Fri May 27 21:50:09 2011 +0200 @@ -595,11 +595,11 @@ wlock.release() # monkeypatches - def kwpatchfile_init(orig, self, ui, fname, backend, mode, + def kwpatchfile_init(orig, self, ui, fname, backend, mode, create, remove, missing=False, eolmode=None): '''Monkeypatch/wrap patch.patchfile.__init__ to avoid rejects or conflicts due to expanded keywords in working dir.''' - orig(self, ui, fname, backend, mode, missing, eolmode) + orig(self, ui, fname, backend, mode, create, remove, missing, eolmode) # shrink keywords read from working dir self.lines = kwt.shrinklines(self.fname, self.lines)
--- a/mercurial/patch.py Fri May 27 15:59:52 2011 +0200 +++ b/mercurial/patch.py Fri May 27 21:50:09 2011 +0200 @@ -504,7 +504,7 @@ eolmodes = ['strict', 'crlf', 'lf', 'auto'] class patchfile(object): - def __init__(self, ui, fname, backend, mode, missing=False, + def __init__(self, ui, fname, backend, mode, create, remove, missing=False, eolmode='strict'): self.fname = fname self.eolmode = eolmode @@ -515,6 +515,8 @@ self.exists = False self.missing = missing self.mode = mode + self.create = create + self.remove = remove if not missing: try: data, mode = self.backend.getfile(fname) @@ -620,13 +622,13 @@ self.rej.append(h) return -1 - if self.exists and h.createfile(): + if self.exists and self.create: self.ui.warn(_("file %s already exists\n") % self.fname) self.rej.append(h) return -1 if isinstance(h, binhunk): - if h.rmfile(): + if self.remove: self.backend.unlink(self.fname) else: self.lines[:] = h.new() @@ -654,7 +656,7 @@ # when the hunk cleanly applies at start + skew, so skip the # fast case code if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0: - if h.rmfile(): + if self.remove: self.backend.unlink(self.fname) else: self.lines[start : start + h.lena] = h.new() @@ -710,7 +712,7 @@ return len(self.rej) class hunk(object): - def __init__(self, desc, num, lr, context, create=False, remove=False): + def __init__(self, desc, num, lr, context): self.number = num self.desc = desc self.hunk = [desc] @@ -723,8 +725,6 @@ self.read_context_hunk(lr) else: self.read_unified_hunk(lr) - self.create = create - self.remove = remove and not create def getnormalized(self): """Return a copy with line endings normalized to LF.""" @@ -738,7 +738,7 @@ return nlines # Dummy object, it is rebuilt manually - nh = hunk(self.desc, self.number, None, None, False, False) + nh = hunk(self.desc, self.number, None, None) nh.number = self.number nh.desc = self.desc nh.hunk = self.hunk @@ -748,8 +748,6 @@ nh.startb = self.startb nh.lena = self.lena nh.lenb = self.lenb - nh.create = self.create - nh.remove = self.remove return nh def read_unified_hunk(self, lr): @@ -891,12 +889,6 @@ def complete(self): return len(self.a) == self.lena and len(self.b) == self.lenb - def createfile(self): - return self.starta == 0 and self.lena == 0 and self.create - - def rmfile(self): - return self.startb == 0 and self.lenb == 0 and self.remove - def fuzzit(self, l, fuzz, toponly): # this removes context lines from the top and bottom of list 'l'. It # checks the hunk to make sure only context lines are removed, and then @@ -942,18 +934,11 @@ class binhunk: 'A binary patch file. Only understands literals so far.' - def __init__(self, gitpatch, lr): - self.gitpatch = gitpatch + def __init__(self, lr): self.text = None self.hunk = ['GIT binary patch\n'] self._read(lr) - def createfile(self): - return self.gitpatch.op == 'ADD' - - def rmfile(self): - return self.gitpatch.op == 'DELETE' - def complete(self): return self.text is not None @@ -1020,10 +1005,14 @@ # Git patches do not play games. Excluding copies from the # following heuristic avoids a lot of confusion fname = pathstrip(gp.path, strip - 1)[1] - missing = not hunk.createfile() and not backend.exists(fname) - return fname, missing + create = gp.op == 'ADD' + remove = gp.op == 'DELETE' + missing = not create and not backend.exists(fname) + return fname, missing, create, remove nulla = afile_orig == "/dev/null" nullb = bfile_orig == "/dev/null" + create = nulla and hunk.starta == 0 and hunk.lena == 0 + remove = nullb and hunk.startb == 0 and hunk.lenb == 0 abase, afile = pathstrip(afile_orig, strip) gooda = not nulla and backend.exists(afile) bbase, bfile = pathstrip(bfile_orig, strip) @@ -1031,20 +1020,16 @@ goodb = gooda else: goodb = not nullb and backend.exists(bfile) - createfunc = hunk.createfile - missing = not goodb and not gooda and not createfunc() + missing = not goodb and not gooda and not create # some diff programs apparently produce patches where the afile is # not /dev/null, but afile starts with bfile abasedir = afile[:afile.rfind('/') + 1] bbasedir = bfile[:bfile.rfind('/') + 1] - if missing and abasedir == bbasedir and afile.startswith(bfile): - # this isn't very pretty - hunk.create = True - if createfunc(): - missing = False - else: - hunk.create = False + if (missing and abasedir == bbasedir and afile.startswith(bfile) + and hunk.starta == 0 and hunk.lena == 0): + create = True + missing = False # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the # diff is between a file and its backup. In this case, the original @@ -1065,7 +1050,7 @@ else: raise PatchError(_("undefined source and destination files")) - return fname, missing + return fname, missing, create, remove def scangitpatch(lr, firstline): """ @@ -1125,13 +1110,11 @@ if gitpatches and gitpatches[-1][0] == bfile: gp = gitpatches.pop()[1] if x.startswith('GIT binary patch'): - h = binhunk(gp, lr) + h = binhunk(lr) else: if context is None and x.startswith('***************'): context = True - create = afile == '/dev/null' or gp and gp.op == 'ADD' - remove = bfile == '/dev/null' or gp and gp.op == 'DELETE' - h = hunk(x, hunknum + 1, lr, context, create, remove) + h = hunk(x, hunknum + 1, lr, context) hunknum += 1 if emitfile: emitfile = False @@ -1250,10 +1233,11 @@ continue try: mode = gp and gp.mode or None - current_file, missing = selectfile(backend, afile, bfile, - first_hunk, strip, gp) + current_file, missing, create, remove = selectfile( + backend, afile, bfile, first_hunk, strip, gp) current_file = patcher(ui, current_file, backend, mode, - missing=missing, eolmode=eolmode) + create, remove, missing=missing, + eolmode=eolmode) except PatchError, inst: ui.warn(str(inst) + '\n') current_file = None @@ -1386,8 +1370,8 @@ changed.add(pathstrip(gp.oldpath, strip - 1)[1]) if not first_hunk: continue - current_file, missing = selectfile(backend, afile, bfile, - first_hunk, strip, gp) + current_file, missing, create, remove = selectfile( + backend, afile, bfile, first_hunk, strip, gp) changed.add(current_file) elif state not in ('hunk', 'git'): raise util.Abort(_('unsupported parser state: %s') % state)