Mercurial > public > mercurial-scm > hg
comparison mercurial/patch.py @ 14388:37c997d21752
patch: stop handling hunkless git blocks out of stream
Patch changes are emitted by iterhunks() in two separate events: 'file' when
hunks have to be applied and 'git' to describe other modifications like copies
or mode changes. Note that a file which mode is changed and which content is
modified by the same patch will be emitted in both events. It is more
convenient to handle all file modifications in a single event. This patch
"zips" git actions with regular changes so both kinds can be emitted at the
same place.
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Thu, 19 May 2011 22:44:01 +0200 |
parents | e1b4a7a7263a |
children | 909ac6b9636b |
comparison
equal
deleted
inserted
replaced
14387:e1b4a7a7263a | 14388:37c997d21752 |
---|---|
1111 - ("hunk", hunk): a new hunk is ready to be applied, follows a | 1111 - ("hunk", hunk): a new hunk is ready to be applied, follows a |
1112 "file" event. | 1112 "file" event. |
1113 - ("git", gitchanges): current diff is in git format, gitchanges | 1113 - ("git", gitchanges): current diff is in git format, gitchanges |
1114 maps filenames to gitpatch records. Unique event. | 1114 maps filenames to gitpatch records. Unique event. |
1115 """ | 1115 """ |
1116 changed = {} | |
1117 afile = "" | 1116 afile = "" |
1118 bfile = "" | 1117 bfile = "" |
1119 state = None | 1118 state = None |
1120 hunknum = 0 | 1119 hunknum = 0 |
1121 emitfile = newfile = False | 1120 emitfile = newfile = False |
1122 git = False | 1121 gitpatches = None |
1123 | 1122 |
1124 # our states | 1123 # our states |
1125 BFILE = 1 | 1124 BFILE = 1 |
1126 context = None | 1125 context = None |
1127 lr = linereader(fp) | 1126 lr = linereader(fp) |
1132 break | 1131 break |
1133 if state == BFILE and ( | 1132 if state == BFILE and ( |
1134 (not context and x[0] == '@') | 1133 (not context and x[0] == '@') |
1135 or (context is not False and x.startswith('***************')) | 1134 or (context is not False and x.startswith('***************')) |
1136 or x.startswith('GIT binary patch')): | 1135 or x.startswith('GIT binary patch')): |
1137 gp = changed.get(bfile) | 1136 gp = None |
1137 if gitpatches and gitpatches[-1][0] == bfile: | |
1138 gp = gitpatches.pop()[1] | |
1138 if x.startswith('GIT binary patch'): | 1139 if x.startswith('GIT binary patch'): |
1139 h = binhunk(gp, lr) | 1140 h = binhunk(gp, lr) |
1140 else: | 1141 else: |
1141 if context is None and x.startswith('***************'): | 1142 if context is None and x.startswith('***************'): |
1142 context = True | 1143 context = True |
1144 remove = bfile == '/dev/null' or gp and gp.op == 'DELETE' | 1145 remove = bfile == '/dev/null' or gp and gp.op == 'DELETE' |
1145 h = hunk(x, hunknum + 1, lr, context, create, remove) | 1146 h = hunk(x, hunknum + 1, lr, context, create, remove) |
1146 hunknum += 1 | 1147 hunknum += 1 |
1147 if emitfile: | 1148 if emitfile: |
1148 emitfile = False | 1149 emitfile = False |
1149 yield 'file', (afile, bfile, h, gp and gp.mode or None) | 1150 yield 'file', (afile, bfile, h, gp) |
1150 yield 'hunk', h | 1151 yield 'hunk', h |
1151 elif x.startswith('diff --git'): | 1152 elif x.startswith('diff --git'): |
1152 m = gitre.match(x) | 1153 m = gitre.match(x) |
1153 if not m: | 1154 if not m: |
1154 continue | 1155 continue |
1155 if not git: | 1156 if gitpatches is None: |
1156 # scan whole input for git metadata | 1157 # scan whole input for git metadata |
1157 git = True | 1158 gitpatches = [('b/' + gp.path, gp) for gp |
1158 gitpatches = scangitpatch(lr, x) | 1159 in scangitpatch(lr, x)] |
1159 for gp in gitpatches: | 1160 yield 'git', [g[1] for g in gitpatches] |
1160 changed['b/' + gp.path] = gp | 1161 gitpatches.reverse() |
1161 yield 'git', gitpatches | |
1162 afile = 'a/' + m.group(1) | 1162 afile = 'a/' + m.group(1) |
1163 bfile = 'b/' + m.group(2) | 1163 bfile = 'b/' + m.group(2) |
1164 gp = changed[bfile] | 1164 while bfile != gitpatches[-1][0]: |
1165 gp = gitpatches.pop()[1] | |
1166 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp) | |
1167 gp = gitpatches[-1][1] | |
1165 # copy/rename + modify should modify target, not source | 1168 # copy/rename + modify should modify target, not source |
1166 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: | 1169 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: |
1167 afile = bfile | 1170 afile = bfile |
1168 newfile = True | 1171 newfile = True |
1169 elif x.startswith('---'): | 1172 elif x.startswith('---'): |
1196 newfile = False | 1199 newfile = False |
1197 emitfile = True | 1200 emitfile = True |
1198 state = BFILE | 1201 state = BFILE |
1199 hunknum = 0 | 1202 hunknum = 0 |
1200 | 1203 |
1204 while gitpatches: | |
1205 gp = gitpatches.pop()[1] | |
1206 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp) | |
1207 | |
1201 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'): | 1208 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'): |
1202 """Reads a patch from fp and tries to apply it. | 1209 """Reads a patch from fp and tries to apply it. |
1203 | 1210 |
1204 The dict 'changed' is filled in with all of the filenames changed | 1211 The dict 'changed' is filled in with all of the filenames changed |
1205 by the patch. Returns 0 for a clean patch, -1 if any rejects were | 1212 by the patch. Returns 0 for a clean patch, -1 if any rejects were |
1227 if ret > 0: | 1234 if ret > 0: |
1228 err = 1 | 1235 err = 1 |
1229 elif state == 'file': | 1236 elif state == 'file': |
1230 if current_file: | 1237 if current_file: |
1231 rejects += current_file.close() | 1238 rejects += current_file.close() |
1232 afile, bfile, first_hunk, mode = values | 1239 current_file = None |
1240 afile, bfile, first_hunk, gp = values | |
1241 if gp: | |
1242 changed[gp.path] = gp | |
1243 if gp.op == 'DELETE': | |
1244 backend.unlink(gp.path) | |
1245 continue | |
1246 if gp.op == 'RENAME': | |
1247 backend.unlink(gp.oldpath) | |
1248 if gp.mode and not first_hunk: | |
1249 if gp.op == 'ADD': | |
1250 # Added files without content have no hunk and must be created | |
1251 backend.writelines(gp.path, [], gp.mode) | |
1252 else: | |
1253 backend.setmode(gp.path, gp.mode[0], gp.mode[1]) | |
1254 if not first_hunk: | |
1255 continue | |
1233 try: | 1256 try: |
1257 mode = gp and gp.mode or None | |
1234 current_file, missing = selectfile(backend, afile, bfile, | 1258 current_file, missing = selectfile(backend, afile, bfile, |
1235 first_hunk, strip) | 1259 first_hunk, strip) |
1236 current_file = patcher(ui, current_file, backend, mode, | 1260 current_file = patcher(ui, current_file, backend, mode, |
1237 missing=missing, eolmode=eolmode) | 1261 missing=missing, eolmode=eolmode) |
1238 except PatchError, inst: | 1262 except PatchError, inst: |
1245 gp.path = pathstrip(gp.path, strip - 1)[1] | 1269 gp.path = pathstrip(gp.path, strip - 1)[1] |
1246 if gp.oldpath: | 1270 if gp.oldpath: |
1247 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] | 1271 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] |
1248 if gp.op in ('COPY', 'RENAME'): | 1272 if gp.op in ('COPY', 'RENAME'): |
1249 backend.copy(gp.oldpath, gp.path) | 1273 backend.copy(gp.oldpath, gp.path) |
1250 changed[gp.path] = gp | |
1251 else: | 1274 else: |
1252 raise util.Abort(_('unsupported parser state: %s') % state) | 1275 raise util.Abort(_('unsupported parser state: %s') % state) |
1253 | 1276 |
1254 if current_file: | 1277 if current_file: |
1255 rejects += current_file.close() | 1278 rejects += current_file.close() |
1256 | |
1257 # Handle mode changes without hunk | |
1258 removed = set() | |
1259 for gp in changed.itervalues(): | |
1260 if not gp: | |
1261 continue | |
1262 if gp.op == 'DELETE': | |
1263 removed.add(gp.path) | |
1264 continue | |
1265 if gp.op == 'RENAME': | |
1266 removed.add(gp.oldpath) | |
1267 if gp.mode: | |
1268 if gp.op == 'ADD' and not backend.exists(gp.path): | |
1269 # Added files without content have no hunk and must be created | |
1270 backend.writelines(gp.path, [], gp.mode) | |
1271 else: | |
1272 backend.setmode(gp.path, gp.mode[0], gp.mode[1]) | |
1273 for path in sorted(removed): | |
1274 backend.unlink(path) | |
1275 | 1279 |
1276 if rejects: | 1280 if rejects: |
1277 return -1 | 1281 return -1 |
1278 return err | 1282 return err |
1279 | 1283 |
1384 changed = set() | 1388 changed = set() |
1385 for state, values in iterhunks(fp): | 1389 for state, values in iterhunks(fp): |
1386 if state == 'hunk': | 1390 if state == 'hunk': |
1387 continue | 1391 continue |
1388 elif state == 'file': | 1392 elif state == 'file': |
1389 afile, bfile, first_hunk, mode = values | 1393 afile, bfile, first_hunk, gp = values |
1394 if gp: | |
1395 changed.add(gp.path) | |
1396 if gp.op == 'RENAME': | |
1397 changed.add(gp.oldpath) | |
1398 if not first_hunk: | |
1399 continue | |
1390 current_file, missing = selectfile(backend, afile, bfile, | 1400 current_file, missing = selectfile(backend, afile, bfile, |
1391 first_hunk, strip) | 1401 first_hunk, strip) |
1392 changed.add(current_file) | 1402 changed.add(current_file) |
1393 elif state == 'git': | 1403 elif state == 'git': |
1394 for gp in values: | 1404 for gp in values: |
1395 gp.path = pathstrip(gp.path, strip - 1)[1] | 1405 gp.path = pathstrip(gp.path, strip - 1)[1] |
1396 changed.add(gp.path) | |
1397 if gp.oldpath: | 1406 if gp.oldpath: |
1398 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] | 1407 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] |
1399 if gp.op == 'RENAME': | |
1400 changed.add(gp.oldpath) | |
1401 else: | 1408 else: |
1402 raise util.Abort(_('unsupported parser state: %s') % state) | 1409 raise util.Abort(_('unsupported parser state: %s') % state) |
1403 return changed | 1410 return changed |
1404 finally: | 1411 finally: |
1405 fp.close() | 1412 fp.close() |