Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/patch.py @ 10189:e451e599fbcf
patch: support diff data loss detection and upgrade
In worst case, generating diff in upgrade mode can be two times more expensive
than generating it in git mode directly: we may have to regenerate the whole
diff again whenever a git feature is detected. Also, the first diff attempt is
completely buffered instead of being streamed. That said, even without having
profiled it yet, I am convinced we can fast-path the upgrade mode if necessary
were it to be used in regular diff commands, and not only in mq where avoiding
data loss is worth the price.
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Fri, 01 Jan 2010 20:54:05 +0100 |
parents | c7355a0e1f39 |
children | 3ca8f2ae5fee |
comparison
equal
deleted
inserted
replaced
10188:fd6e9c7cd98c | 10189:e451e599fbcf |
---|---|
1244 for l in chunk(zlib.compress(tn)): | 1244 for l in chunk(zlib.compress(tn)): |
1245 ret.append(fmtline(l)) | 1245 ret.append(fmtline(l)) |
1246 ret.append('\n') | 1246 ret.append('\n') |
1247 return ''.join(ret) | 1247 return ''.join(ret) |
1248 | 1248 |
1249 def _addmodehdr(header, omode, nmode): | 1249 class GitDiffRequired(Exception): |
1250 if omode != nmode: | 1250 pass |
1251 header.append('old mode %s\n' % omode) | 1251 |
1252 header.append('new mode %s\n' % nmode) | 1252 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None, |
1253 | 1253 losedatafn=None): |
1254 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None): | |
1255 '''yields diff of changes to files between two nodes, or node and | 1254 '''yields diff of changes to files between two nodes, or node and |
1256 working directory. | 1255 working directory. |
1257 | 1256 |
1258 if node1 is None, use first dirstate parent instead. | 1257 if node1 is None, use first dirstate parent instead. |
1259 if node2 is None, compare node1 with working directory.''' | 1258 if node2 is None, compare node1 with working directory. |
1259 | |
1260 losedatafn(**kwarg) is a callable run when opts.upgrade=True and | |
1261 every time some change cannot be represented with the current | |
1262 patch format. Return False to upgrade to git patch format, True to | |
1263 accept the loss or raise an exception to abort the diff. It is | |
1264 called with the name of current file being diffed as 'fn'. If set | |
1265 to None, patches will always be upgraded to git format when | |
1266 necessary. | |
1267 ''' | |
1260 | 1268 |
1261 if opts is None: | 1269 if opts is None: |
1262 opts = mdiff.defaultopts | 1270 opts = mdiff.defaultopts |
1263 | 1271 |
1264 if not node1 and not node2: | 1272 if not node1 and not node2: |
1286 if not changes: | 1294 if not changes: |
1287 changes = repo.status(ctx1, ctx2, match=match) | 1295 changes = repo.status(ctx1, ctx2, match=match) |
1288 modified, added, removed = changes[:3] | 1296 modified, added, removed = changes[:3] |
1289 | 1297 |
1290 if not modified and not added and not removed: | 1298 if not modified and not added and not removed: |
1291 return | 1299 return [] |
1292 | |
1293 date1 = util.datestr(ctx1.date()) | |
1294 man1 = ctx1.manifest() | |
1295 | 1300 |
1296 revs = None | 1301 revs = None |
1297 if not repo.ui.quiet and not opts.git: | 1302 if not repo.ui.quiet: |
1298 hexfunc = repo.ui.debugflag and hex or short | 1303 hexfunc = repo.ui.debugflag and hex or short |
1299 revs = [hexfunc(node) for node in [node1, node2] if node] | 1304 revs = [hexfunc(node) for node in [node1, node2] if node] |
1300 | 1305 |
1301 if opts.git: | 1306 copy = {} |
1302 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid]) | 1307 if opts.git or opts.upgrade: |
1308 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0] | |
1303 copy = copy.copy() | 1309 copy = copy.copy() |
1304 for k, v in copy.items(): | 1310 for k, v in copy.items(): |
1305 copy[v] = k | 1311 copy[v] = k |
1306 | 1312 |
1313 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2, | |
1314 modified, added, removed, copy, getfilectx, opts, losedata) | |
1315 if opts.upgrade and not opts.git: | |
1316 try: | |
1317 def losedata(fn): | |
1318 if not losedatafn or not losedatafn(fn=fn): | |
1319 raise GitDiffRequired() | |
1320 # Buffer the whole output until we are sure it can be generated | |
1321 return list(difffn(opts.copy(git=False), losedata)) | |
1322 except GitDiffRequired: | |
1323 return difffn(opts.copy(git=True), None) | |
1324 else: | |
1325 return difffn(opts, None) | |
1326 | |
1327 def _addmodehdr(header, omode, nmode): | |
1328 if omode != nmode: | |
1329 header.append('old mode %s\n' % omode) | |
1330 header.append('new mode %s\n' % nmode) | |
1331 | |
1332 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed, | |
1333 copy, getfilectx, opts, losedatafn): | |
1334 | |
1335 date1 = util.datestr(ctx1.date()) | |
1336 man1 = ctx1.manifest() | |
1337 | |
1307 gone = set() | 1338 gone = set() |
1308 gitmode = {'l': '120000', 'x': '100755', '': '100644'} | 1339 gitmode = {'l': '120000', 'x': '100755', '': '100644'} |
1340 | |
1341 if opts.git: | |
1342 revs = None | |
1309 | 1343 |
1310 for f in sorted(modified + added + removed): | 1344 for f in sorted(modified + added + removed): |
1311 to = None | 1345 to = None |
1312 tn = None | 1346 tn = None |
1313 dodiff = True | 1347 dodiff = True |
1315 if f in man1: | 1349 if f in man1: |
1316 to = getfilectx(f, ctx1).data() | 1350 to = getfilectx(f, ctx1).data() |
1317 if f not in removed: | 1351 if f not in removed: |
1318 tn = getfilectx(f, ctx2).data() | 1352 tn = getfilectx(f, ctx2).data() |
1319 a, b = f, f | 1353 a, b = f, f |
1320 if opts.git: | 1354 if opts.git or losedatafn: |
1321 if f in added: | 1355 if f in added: |
1322 mode = gitmode[ctx2.flags(f)] | 1356 mode = gitmode[ctx2.flags(f)] |
1323 if f in copy: | 1357 if f in copy: |
1324 a = copy[f] | 1358 if opts.git: |
1325 omode = gitmode[man1.flags(a)] | 1359 a = copy[f] |
1326 _addmodehdr(header, omode, mode) | 1360 omode = gitmode[man1.flags(a)] |
1327 if a in removed and a not in gone: | 1361 _addmodehdr(header, omode, mode) |
1328 op = 'rename' | 1362 if a in removed and a not in gone: |
1329 gone.add(a) | 1363 op = 'rename' |
1364 gone.add(a) | |
1365 else: | |
1366 op = 'copy' | |
1367 header.append('%s from %s\n' % (op, a)) | |
1368 header.append('%s to %s\n' % (op, f)) | |
1369 to = getfilectx(a, ctx1).data() | |
1330 else: | 1370 else: |
1331 op = 'copy' | 1371 losedatafn(f) |
1332 header.append('%s from %s\n' % (op, a)) | |
1333 header.append('%s to %s\n' % (op, f)) | |
1334 to = getfilectx(a, ctx1).data() | |
1335 else: | 1372 else: |
1336 header.append('new file mode %s\n' % mode) | 1373 if opts.git: |
1374 header.append('new file mode %s\n' % mode) | |
1375 elif ctx2.flags(f): | |
1376 losedatafn(f) | |
1337 if util.binary(tn): | 1377 if util.binary(tn): |
1338 dodiff = 'binary' | 1378 if opts.git: |
1379 dodiff = 'binary' | |
1380 else: | |
1381 losedatafn(f) | |
1382 if not opts.git and not tn: | |
1383 # regular diffs cannot represent new empty file | |
1384 losedatafn(f) | |
1339 elif f in removed: | 1385 elif f in removed: |
1340 # have we already reported a copy above? | 1386 if opts.git: |
1341 if f in copy and copy[f] in added and copy[copy[f]] == f: | 1387 # have we already reported a copy above? |
1342 dodiff = False | 1388 if f in copy and copy[f] in added and copy[copy[f]] == f: |
1343 else: | 1389 dodiff = False |
1344 header.append('deleted file mode %s\n' % | 1390 else: |
1345 gitmode[man1.flags(f)]) | 1391 header.append('deleted file mode %s\n' % |
1392 gitmode[man1.flags(f)]) | |
1393 elif not to: | |
1394 # regular diffs cannot represent empty file deletion | |
1395 losedatafn(f) | |
1346 else: | 1396 else: |
1347 omode = gitmode[man1.flags(f)] | 1397 oflag = man1.flags(f) |
1348 nmode = gitmode[ctx2.flags(f)] | 1398 nflag = ctx2.flags(f) |
1349 _addmodehdr(header, omode, nmode) | 1399 binary = util.binary(to) or util.binary(tn) |
1350 if util.binary(to) or util.binary(tn): | 1400 if opts.git: |
1351 dodiff = 'binary' | 1401 _addmodehdr(header, gitmode[oflag], gitmode[nflag]) |
1352 header.insert(0, mdiff.diffline(revs, a, b, opts)) | 1402 if binary: |
1403 dodiff = 'binary' | |
1404 elif binary or nflag != oflag: | |
1405 losedatafn(f) | |
1406 if opts.git: | |
1407 header.insert(0, mdiff.diffline(revs, a, b, opts)) | |
1408 | |
1353 if dodiff: | 1409 if dodiff: |
1354 if dodiff == 'binary': | 1410 if dodiff == 'binary': |
1355 text = b85diff(to, tn) | 1411 text = b85diff(to, tn) |
1356 else: | 1412 else: |
1357 text = mdiff.unidiff(to, date1, | 1413 text = mdiff.unidiff(to, date1, |