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()