# HG changeset patch # User Patrick Mezard # Date 2011-06-11 12:17:25 # Node ID d0c2cc11e611c28a62792acd6418dc1a06277241 # Parent 3cacc232f27ff39ee1bf08d8342c400188612f61 patch: generalize the use of patchmeta in applydiff() - Add patchmeta.copy() and emit copies from iterhunks. Modifying patchmeta instances in applydiff() makes things simpler. - Rename selectfile() into makepatchmeta(). It is responsible for creating patchmeta for regular patches. - Pass patchmeta objects to patchfile() directly patchmeta instances were associated with git patches, for regular patches we had to pass additional variables to tell the patch intent to patchfile(). Instead, we generate patchmeta for regular patches and pass them. This will also help with patch filtering by matcher objects. diff --git a/hgext/keyword.py b/hgext/keyword.py --- a/hgext/keyword.py +++ b/hgext/keyword.py @@ -595,12 +595,10 @@ def reposetup(ui, repo): wlock.release() # monkeypatches - def kwpatchfile_init(orig, self, ui, fname, backend, store, mode, create, - remove, eolmode=None, copysource=None): + def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None): '''Monkeypatch/wrap patch.patchfile.__init__ to avoid rejects or conflicts due to expanded keywords in working dir.''' - orig(self, ui, fname, backend, store, mode, create, remove, - eolmode, copysource) + orig(self, ui, gp, backend, store, eolmode) # shrink keywords read from working dir self.lines = kwt.shrinklines(self.fname, self.lines) diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -281,6 +281,14 @@ class patchmeta(object): isexec = mode & 0100 self.mode = (islink, isexec) + def copy(self): + other = patchmeta(self.path) + other.oldpath = self.oldpath + other.mode = self.mode + other.op = self.op + other.binary = self.binary + return other + def __repr__(self): return "" % (self.op, self.path) @@ -509,9 +517,8 @@ contextdesc = re.compile('(---|\*\*\*) ( eolmodes = ['strict', 'crlf', 'lf', 'auto'] class patchfile(object): - def __init__(self, ui, fname, backend, store, mode, create, remove, - eolmode='strict', copysource=None): - self.fname = fname + def __init__(self, ui, gp, backend, store, eolmode='strict'): + self.fname = gp.path self.eolmode = eolmode self.eol = None self.backend = backend @@ -519,17 +526,17 @@ class patchfile(object): self.lines = [] self.exists = False self.missing = True - self.mode = mode - self.copysource = copysource - self.create = create - self.remove = remove + self.mode = gp.mode + self.copysource = gp.oldpath + self.create = gp.op in ('ADD', 'COPY', 'RENAME') + self.remove = gp.op == 'DELETE' try: - if copysource is None: - data, mode = backend.getfile(fname) + if self.copysource is None: + data, mode = backend.getfile(self.fname) self.exists = True else: - data, mode = store.getfile(copysource) - self.exists = backend.exists(fname) + data, mode = store.getfile(self.copysource) + self.exists = backend.exists(self.fname) self.missing = False if data: self.lines = data.splitlines(True) @@ -549,7 +556,7 @@ class patchfile(object): nlines.append(l) self.lines = nlines except IOError: - if create: + if self.create: self.missing = False if self.mode is None: self.mode = (False, False) @@ -1016,14 +1023,7 @@ def pathstrip(path, strip): count -= 1 return path[:i].lstrip(), path[i:].rstrip() -def selectfile(backend, afile_orig, bfile_orig, hunk, strip, gp): - if gp: - # Git patches do not play games. Excluding copies from the - # following heuristic avoids a lot of confusion - fname = pathstrip(gp.path, strip - 1)[1] - create = gp.op in ('ADD', 'COPY', 'RENAME') - remove = gp.op == 'DELETE' - return fname, create, remove +def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip): nulla = afile_orig == "/dev/null" nullb = bfile_orig == "/dev/null" create = nulla and hunk.starta == 0 and hunk.lena == 0 @@ -1065,7 +1065,12 @@ def selectfile(backend, afile_orig, bfil else: raise PatchError(_("undefined source and destination files")) - return fname, create, remove + gp = patchmeta(fname) + if create: + gp.op = 'ADD' + elif remove: + gp.op = 'DELETE' + return gp def scangitpatch(lr, firstline): """ @@ -1134,7 +1139,7 @@ def iterhunks(fp): hunknum += 1 if emitfile: emitfile = False - yield 'file', (afile, bfile, h, gp) + yield 'file', (afile, bfile, h, gp and gp.copy() or None) yield 'hunk', h elif x.startswith('diff --git'): m = gitre.match(x) @@ -1144,14 +1149,14 @@ def iterhunks(fp): # scan whole input for git metadata gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp in scangitpatch(lr, x)] - yield 'git', [g[2] for g in gitpatches + yield 'git', [g[2].copy() for g in gitpatches if g[2].op in ('COPY', 'RENAME')] gitpatches.reverse() afile = 'a/' + m.group(1) bfile = 'b/' + m.group(2) while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]: gp = gitpatches.pop()[2] - yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp) + yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) gp = gitpatches[-1][2] # copy/rename + modify should modify target, not source if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: @@ -1191,7 +1196,7 @@ def iterhunks(fp): while gitpatches: gp = gitpatches.pop()[2] - yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp) + yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): """Reads a patch from fp and tries to apply it. @@ -1228,41 +1233,38 @@ def _applydiff(ui, fp, patcher, backend, rejects += current_file.close() current_file = None afile, bfile, first_hunk, gp = values - copysource = None if gp: path = pstrip(gp.path) + gp.path = pstrip(gp.path) if gp.oldpath: - copysource = pstrip(gp.oldpath) - if gp.op == 'RENAME': - backend.unlink(copysource) - if not first_hunk: - if gp.op == 'DELETE': - backend.unlink(path) - continue - data, mode = None, None - if gp.op in ('RENAME', 'COPY'): - data, mode = store.getfile(copysource) - if gp.mode: - mode = gp.mode - if gp.op == 'ADD': - # Added files without content have no hunk and - # must be created - data = '' - if data or mode: - if (gp.op in ('ADD', 'RENAME', 'COPY') - and backend.exists(path)): - raise PatchError(_("cannot create %s: destination " - "already exists") % path) - backend.setfile(path, data, mode, copysource) + gp.oldpath = pstrip(gp.oldpath) + else: + gp = makepatchmeta(backend, afile, bfile, first_hunk, strip) + if gp.op == 'RENAME': + backend.unlink(gp.oldpath) if not first_hunk: + if gp.op == 'DELETE': + backend.unlink(gp.path) + continue + data, mode = None, None + if gp.op in ('RENAME', 'COPY'): + data, mode = store.getfile(gp.oldpath) + if gp.mode: + mode = gp.mode + if gp.op == 'ADD': + # Added files without content have no hunk and + # must be created + data = '' + if data or mode: + if (gp.op in ('ADD', 'RENAME', 'COPY') + and backend.exists(gp.path)): + raise PatchError(_("cannot create %s: destination " + "already exists") % gp.path) + backend.setfile(gp.path, data, mode, gp.oldpath) continue try: - mode = gp and gp.mode or None - current_file, create, remove = selectfile( - backend, afile, bfile, first_hunk, strip, gp) - current_file = patcher(ui, current_file, backend, store, mode, - create, remove, eolmode=eolmode, - copysource=copysource) + current_file = patcher(ui, gp, backend, store, + eolmode=eolmode) except PatchError, inst: ui.warn(str(inst) + '\n') current_file = None @@ -1395,14 +1397,14 @@ def changedfiles(ui, repo, patchpath, st if state == 'file': afile, bfile, first_hunk, gp = values if gp: - changed.add(pathstrip(gp.path, strip - 1)[1]) - if gp.op == 'RENAME': - changed.add(pathstrip(gp.oldpath, strip - 1)[1]) - if not first_hunk: - continue - current_file, create, remove = selectfile( - backend, afile, bfile, first_hunk, strip, gp) - changed.add(current_file) + gp.path = pathstrip(gp.path, strip - 1)[1] + if gp.oldpath: + gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] + else: + gp = makepatchmeta(backend, afile, bfile, first_hunk, strip) + changed.add(gp.path) + if gp.op == 'RENAME': + changed.add(gp.oldpath) elif state not in ('hunk', 'git'): raise util.Abort(_('unsupported parser state: %s') % state) return changed