Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/patch.py @ 16506:fc4e0fecf403 stable
patch: fix patch hunk/metdata synchronization (issue3384)
Git patches are parsed in two phases: 1) extract metadata, 2) parse actual
deltas and merge them with the previous metadata. We do this to avoid
dependency issues like "modify a; copy a to b", where "b" must be copied from
the unmodified "a".
Issue3384 is caused by flaky code I wrote to synchronize the patch metadata
with the emitted hunk:
if (gitpatches and
(gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
gp = gitpatches.pop()[2]
With a patch like:
diff --git a/a b/c
copy from a
copy to c
--- a/a
+++ b/c
@@ -1,1 +1,2 @@
a
+a
@@ -2,1 +2,2 @@
a
+a
diff --git a/a b/a
--- a/a
+++ b/a
@@ -1,1 +1,2 @@
a
+b
the first hunk of the first block is matched with the metadata for the block
"diff --git a/a b/c", then the second hunk of the first block is matched with
the metadata of the second block "diff --git a/a b/a", because of the "or" in
the code paste above. Turning the "or" into an "and" is not enough as we have
to deal with /dev/null cases for each file.
We I remove this broken piece of code:
# copy/rename + modify should modify target, not source
if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
afile = bfile
because "afile = bfile" set "afile" to stuff like "b/file" instead of "a/file",
and because this only happens for git patches, which afile/bfile are ignored
anyway by applydiff().
v2:
- Avoid a traceback on git metadata desynchronization
author | Patrick Mezard <patrick@mezard.eu> |
---|---|
date | Sat, 21 Apr 2012 21:40:25 +0200 |
parents | 1f75c1decdeb |
children | a8065323c003 |
comparison
equal
deleted
inserted
replaced
16505:db85c24dcdea | 16506:fc4e0fecf403 |
---|---|
287 other.oldpath = self.oldpath | 287 other.oldpath = self.oldpath |
288 other.mode = self.mode | 288 other.mode = self.mode |
289 other.op = self.op | 289 other.op = self.op |
290 other.binary = self.binary | 290 other.binary = self.binary |
291 return other | 291 return other |
292 | |
293 def _ispatchinga(self, afile): | |
294 if afile == '/dev/null': | |
295 return self.op == 'ADD' | |
296 return afile == 'a/' + (self.oldpath or self.path) | |
297 | |
298 def _ispatchingb(self, bfile): | |
299 if bfile == '/dev/null': | |
300 return self.op == 'DELETE' | |
301 return bfile == 'b/' + self.path | |
302 | |
303 def ispatching(self, afile, bfile): | |
304 return self._ispatchinga(afile) and self._ispatchingb(bfile) | |
292 | 305 |
293 def __repr__(self): | 306 def __repr__(self): |
294 return "<patchmeta %s %r>" % (self.op, self.path) | 307 return "<patchmeta %s %r>" % (self.op, self.path) |
295 | 308 |
296 def readgitpatch(lr): | 309 def readgitpatch(lr): |
1178 (not context and x[0] == '@') | 1191 (not context and x[0] == '@') |
1179 or (context is not False and x.startswith('***************')) | 1192 or (context is not False and x.startswith('***************')) |
1180 or x.startswith('GIT binary patch')): | 1193 or x.startswith('GIT binary patch')): |
1181 gp = None | 1194 gp = None |
1182 if (gitpatches and | 1195 if (gitpatches and |
1183 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)): | 1196 gitpatches[-1].ispatching(afile, bfile)): |
1184 gp = gitpatches.pop()[2] | 1197 gp = gitpatches.pop() |
1185 if x.startswith('GIT binary patch'): | 1198 if x.startswith('GIT binary patch'): |
1186 h = binhunk(lr) | 1199 h = binhunk(lr) |
1187 else: | 1200 else: |
1188 if context is None and x.startswith('***************'): | 1201 if context is None and x.startswith('***************'): |
1189 context = True | 1202 context = True |
1195 yield 'hunk', h | 1208 yield 'hunk', h |
1196 elif x.startswith('diff --git'): | 1209 elif x.startswith('diff --git'): |
1197 m = gitre.match(x) | 1210 m = gitre.match(x) |
1198 if not m: | 1211 if not m: |
1199 continue | 1212 continue |
1200 if not gitpatches: | 1213 if gitpatches is None: |
1201 # scan whole input for git metadata | 1214 # scan whole input for git metadata |
1202 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp | 1215 gitpatches = scangitpatch(lr, x) |
1203 in scangitpatch(lr, x)] | 1216 yield 'git', [g.copy() for g in gitpatches |
1204 yield 'git', [g[2].copy() for g in gitpatches | 1217 if g.op in ('COPY', 'RENAME')] |
1205 if g[2].op in ('COPY', 'RENAME')] | |
1206 gitpatches.reverse() | 1218 gitpatches.reverse() |
1207 afile = 'a/' + m.group(1) | 1219 afile = 'a/' + m.group(1) |
1208 bfile = 'b/' + m.group(2) | 1220 bfile = 'b/' + m.group(2) |
1209 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]: | 1221 while gitpatches and not gitpatches[-1].ispatching(afile, bfile): |
1210 gp = gitpatches.pop()[2] | 1222 gp = gitpatches.pop() |
1211 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) | 1223 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) |
1212 gp = gitpatches[-1][2] | 1224 if not gitpatches: |
1213 # copy/rename + modify should modify target, not source | 1225 raise PatchError(_('failed to synchronize metadata for "%s"') |
1214 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: | 1226 % afile[2:]) |
1215 afile = bfile | 1227 gp = gitpatches[-1] |
1216 newfile = True | 1228 newfile = True |
1217 elif x.startswith('---'): | 1229 elif x.startswith('---'): |
1218 # check for a unified diff | 1230 # check for a unified diff |
1219 l2 = lr.readline() | 1231 l2 = lr.readline() |
1220 if not l2.startswith('+++'): | 1232 if not l2.startswith('+++'): |
1245 emitfile = True | 1257 emitfile = True |
1246 state = BFILE | 1258 state = BFILE |
1247 hunknum = 0 | 1259 hunknum = 0 |
1248 | 1260 |
1249 while gitpatches: | 1261 while gitpatches: |
1250 gp = gitpatches.pop()[2] | 1262 gp = gitpatches.pop() |
1251 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) | 1263 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) |
1252 | 1264 |
1253 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): | 1265 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): |
1254 """Reads a patch from fp and tries to apply it. | 1266 """Reads a patch from fp and tries to apply it. |
1255 | 1267 |