Mercurial > evolve
comparison hgext/evolve.py @ 1021:200f2d9b9f39
evolve: saner locking an transaction in `hg evolve`
Each trouble solved used to handle locking and transaction on its own. We now
have a top level locking and transaction. This will helps use making sure phase
are moved within a transaction.
author | Pierre-Yves David <pierre-yves.david@fb.com> |
---|---|
date | Wed, 06 Aug 2014 18:06:17 -0700 |
parents | 155949287628 |
children | 6f4fd3e49d1c |
comparison
equal
deleted
inserted
replaced
1020:155949287628 | 1021:200f2d9b9f39 |
---|---|
834 class MergeFailure(util.Abort): | 834 class MergeFailure(util.Abort): |
835 pass | 835 pass |
836 | 836 |
837 def relocate(repo, orig, dest): | 837 def relocate(repo, orig, dest): |
838 """rewrite <rev> on dest""" | 838 """rewrite <rev> on dest""" |
839 if orig.rev() == dest.rev(): | |
840 raise util.Abort(_('tried to relocate a node on top of itself'), | |
841 hint=_("This shouldn't happen. If you still " | |
842 "need to move changesets, please do so " | |
843 "manually with nothing to rebase - working " | |
844 "directory parent is also destination")) | |
845 | |
846 rebase = extensions.find('rebase') | |
847 # dummy state to trick rebase node | |
848 if not orig.p2().rev() == node.nullrev: | |
849 raise util.Abort( | |
850 'no support for evolving merge changesets yet', | |
851 hint="Redo the merge a use `hg prune` to obsolete the old one") | |
852 destbookmarks = repo.nodebookmarks(dest.node()) | |
853 nodesrc = orig.node() | |
854 destphase = repo[nodesrc].phase() | |
839 try: | 855 try: |
840 if orig.rev() == dest.rev(): | 856 r = rebase.rebasenode(repo, orig.node(), dest.node(), |
841 raise util.Abort(_('tried to relocate a node on top of itself'), | 857 {node.nullrev: node.nullrev}, False) |
842 hint=_("This shouldn't happen. If you still " | 858 if r[-1]: #some conflict |
843 "need to move changesets, please do so " | |
844 "manually with nothing to rebase - working " | |
845 "directory parent is also destination")) | |
846 | |
847 rebase = extensions.find('rebase') | |
848 # dummy state to trick rebase node | |
849 if not orig.p2().rev() == node.nullrev: | |
850 raise util.Abort( | 859 raise util.Abort( |
851 'no support for evolving merge changesets yet', | 860 'unresolved merge conflicts (see hg help resolve)') |
852 hint="Redo the merge a use `hg prune` to obsolete the old one") | 861 cmdutil.duplicatecopies(repo, orig.node(), dest.node()) |
853 destbookmarks = repo.nodebookmarks(dest.node()) | 862 nodenew = rebase.concludenode(repo, orig.node(), dest.node(), |
854 nodesrc = orig.node() | 863 node.nullid) |
855 destphase = repo[nodesrc].phase() | 864 except util.Abort, exc: |
856 wlock = lock = None | 865 class LocalMergeFailure(MergeFailure, exc.__class__): |
857 try: | 866 pass |
858 wlock = repo.wlock() | 867 exc.__class__ = LocalMergeFailure |
859 lock = repo.lock() | 868 raise |
860 r = rebase.rebasenode(repo, orig.node(), dest.node(), | 869 oldbookmarks = repo.nodebookmarks(nodesrc) |
861 {node.nullrev: node.nullrev}, False) | 870 if nodenew is not None: |
862 if r[-1]: #some conflict | 871 retractboundary(repo, destphase, [nodenew]) |
863 raise util.Abort( | 872 createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))]) |
864 'unresolved merge conflicts (see hg help resolve)') | 873 for book in oldbookmarks: |
865 cmdutil.duplicatecopies(repo, orig.node(), dest.node()) | 874 repo._bookmarks[book] = nodenew |
866 nodenew = rebase.concludenode(repo, orig.node(), dest.node(), | 875 else: |
867 node.nullid) | 876 createmarkers(repo, [(repo[nodesrc], ())]) |
868 except util.Abort, exc: | 877 # Behave like rebase, move bookmarks to dest |
869 class LocalMergeFailure(MergeFailure, exc.__class__): | 878 for book in oldbookmarks: |
870 pass | |
871 exc.__class__ = LocalMergeFailure | |
872 raise | |
873 finally: | |
874 lockmod.release(lock, wlock) | |
875 oldbookmarks = repo.nodebookmarks(nodesrc) | |
876 if nodenew is not None: | |
877 retractboundary(repo, destphase, [nodenew]) | |
878 createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))]) | |
879 for book in oldbookmarks: | |
880 repo._bookmarks[book] = nodenew | |
881 else: | |
882 createmarkers(repo, [(repo[nodesrc], ())]) | |
883 # Behave like rebase, move bookmarks to dest | |
884 for book in oldbookmarks: | |
885 repo._bookmarks[book] = dest.node() | |
886 for book in destbookmarks: # restore bookmark that rebase move | |
887 repo._bookmarks[book] = dest.node() | 879 repo._bookmarks[book] = dest.node() |
888 if oldbookmarks or destbookmarks: | 880 for book in destbookmarks: # restore bookmark that rebase move |
889 repo._bookmarks.write() | 881 repo._bookmarks[book] = dest.node() |
890 return nodenew | 882 if oldbookmarks or destbookmarks: |
891 except util.Abort: | 883 repo._bookmarks.write() |
892 # Invalidate the previous setparents | 884 return nodenew |
893 repo.dirstate.invalidate() | |
894 raise | |
895 | 885 |
896 def _bookmarksupdater(repo, oldid): | 886 def _bookmarksupdater(repo, oldid): |
897 """Return a callable update(newid) updating the current bookmark | 887 """Return a callable update(newid) updating the current bookmark |
898 and bookmarks bound to oldid to newid. | 888 and bookmarks bound to oldid to newid. |
899 """ | 889 """ |
1248 seen = 1 | 1238 seen = 1 |
1249 count = allopt and _counttroubled(ui, repo) or 1 | 1239 count = allopt and _counttroubled(ui, repo) or 1 |
1250 | 1240 |
1251 while tro is not None: | 1241 while tro is not None: |
1252 progresscb() | 1242 progresscb() |
1253 result = _evolveany(ui, repo, tro, dryrunopt, progresscb=progresscb) | 1243 wlock = lock = tr = None |
1244 try: | |
1245 wlock = repo.wlock() | |
1246 lock = repo.lock() | |
1247 tr = repo.transaction("evolve") | |
1248 result = _evolveany(ui, repo, tro, dryrunopt, | |
1249 progresscb=progresscb) | |
1250 tr.close() | |
1251 finally: | |
1252 lockmod.release(tr, lock, wlock) | |
1254 progresscb() | 1253 progresscb() |
1255 seen += 1 | 1254 seen += 1 |
1256 if not allopt: | 1255 if not allopt: |
1257 return result | 1256 return result |
1258 progresscb() | 1257 progresscb() |
1362 if dryrun: | 1361 if dryrun: |
1363 repo.ui.write(todo) | 1362 repo.ui.write(todo) |
1364 else: | 1363 else: |
1365 repo.ui.note(todo) | 1364 repo.ui.note(todo) |
1366 if progresscb: progresscb() | 1365 if progresscb: progresscb() |
1367 lock = repo.lock() | |
1368 try: | 1366 try: |
1369 relocate(repo, orig, target) | 1367 relocate(repo, orig, target) |
1370 except MergeFailure: | 1368 except MergeFailure: |
1371 repo.opener.write('graftstate', orig.hex() + '\n') | 1369 repo.opener.write('graftstate', orig.hex() + '\n') |
1372 repo.ui.write_err(_('evolve failed!\n')) | 1370 repo.ui.write_err(_('evolve failed!\n')) |
1373 repo.ui.write_err( | 1371 repo.ui.write_err( |
1374 _('fix conflict and run "hg evolve --continue"\n')) | 1372 _('fix conflict and run "hg evolve --continue"\n')) |
1375 raise | 1373 raise |
1376 finally: | |
1377 lock.release() | |
1378 | 1374 |
1379 def _solvebumped(ui, repo, bumped, dryrun=False, progresscb=None): | 1375 def _solvebumped(ui, repo, bumped, dryrun=False, progresscb=None): |
1380 """Stabilize a bumped changeset""" | 1376 """Stabilize a bumped changeset""" |
1381 # For now we deny bumped merge | 1377 # For now we deny bumped merge |
1382 if len(bumped.parents()) > 1: | 1378 if len(bumped.parents()) > 1: |
1401 repo.ui.write('hg update %s;\n' % prec) | 1397 repo.ui.write('hg update %s;\n' % prec) |
1402 repo.ui.write('hg revert --all --rev %s;\n' % bumped) | 1398 repo.ui.write('hg revert --all --rev %s;\n' % bumped) |
1403 repo.ui.write('hg commit --msg "bumped update to %s"') | 1399 repo.ui.write('hg commit --msg "bumped update to %s"') |
1404 return 0 | 1400 return 0 |
1405 if progresscb: progresscb() | 1401 if progresscb: progresscb() |
1406 wlock = repo.wlock() | 1402 newid = tmpctx = None |
1403 tmpctx = bumped | |
1404 bmupdate = _bookmarksupdater(repo, bumped.node()) | |
1405 # Basic check for common parent. Far too complicated and fragile | |
1406 tr = repo.transaction('bumped-stabilize') | |
1407 try: | 1407 try: |
1408 newid = tmpctx = None | 1408 if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)): |
1409 tmpctx = bumped | 1409 # Need to rebase the changeset at the right place |
1410 lock = repo.lock() | 1410 repo.ui.status( |
1411 try: | 1411 _('rebasing to destination parent: %s\n') % prec.p1()) |
1412 bmupdate = _bookmarksupdater(repo, bumped.node()) | |
1413 # Basic check for common parent. Far too complicated and fragile | |
1414 tr = repo.transaction('bumped-stabilize') | |
1415 try: | 1412 try: |
1416 if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)): | 1413 tmpid = relocate(repo, bumped, prec.p1()) |
1417 # Need to rebase the changeset at the right place | 1414 if tmpid is not None: |
1418 repo.ui.status( | 1415 tmpctx = repo[tmpid] |
1419 _('rebasing to destination parent: %s\n') % prec.p1()) | 1416 createmarkers(repo, [(bumped, (tmpctx,))]) |
1420 try: | 1417 except MergeFailure: |
1421 tmpid = relocate(repo, bumped, prec.p1()) | 1418 repo.opener.write('graftstate', bumped.hex() + '\n') |
1422 if tmpid is not None: | 1419 repo.ui.write_err(_('evolution failed!\n')) |
1423 tmpctx = repo[tmpid] | 1420 repo.ui.write_err( |
1424 createmarkers(repo, [(bumped, (tmpctx,))]) | 1421 _('fix conflict and run "hg evolve --continue"\n')) |
1425 except MergeFailure: | 1422 raise |
1426 repo.opener.write('graftstate', bumped.hex() + '\n') | 1423 # Create the new commit context |
1427 repo.ui.write_err(_('evolution failed!\n')) | 1424 repo.ui.status(_('computing new diff\n')) |
1428 repo.ui.write_err( | 1425 files = set() |
1429 _('fix conflict and run "hg evolve --continue"\n')) | 1426 copied = copies.pathcopies(prec, bumped) |
1430 raise | 1427 precmanifest = prec.manifest() |
1431 # Create the new commit context | 1428 for key, val in bumped.manifest().iteritems(): |
1432 repo.ui.status(_('computing new diff\n')) | 1429 if precmanifest.pop(key, None) != val: |
1433 files = set() | 1430 files.add(key) |
1434 copied = copies.pathcopies(prec, bumped) | 1431 files.update(precmanifest) # add missing files |
1435 precmanifest = prec.manifest() | 1432 # commit it |
1436 for key, val in bumped.manifest().iteritems(): | 1433 if files: # something to commit! |
1437 if precmanifest.pop(key, None) != val: | 1434 def filectxfn(repo, ctx, path): |
1438 files.add(key) | 1435 if path in bumped: |
1439 files.update(precmanifest) # add missing files | 1436 fctx = bumped[path] |
1440 # commit it | 1437 flags = fctx.flags() |
1441 if files: # something to commit! | 1438 mctx = memfilectx(repo, fctx.path(), fctx.data(), |
1442 def filectxfn(repo, ctx, path): | 1439 islink='l' in flags, |
1443 if path in bumped: | 1440 isexec='x' in flags, |
1444 fctx = bumped[path] | 1441 copied=copied.get(path)) |
1445 flags = fctx.flags() | 1442 return mctx |
1446 mctx = memfilectx(repo, fctx.path(), fctx.data(), | 1443 raise IOError() |
1447 islink='l' in flags, | 1444 text = 'bumped update to %s:\n\n' % prec |
1448 isexec='x' in flags, | 1445 text += bumped.description() |
1449 copied=copied.get(path)) | 1446 |
1450 return mctx | 1447 new = context.memctx(repo, |
1451 raise IOError() | 1448 parents=[prec.node(), node.nullid], |
1452 text = 'bumped update to %s:\n\n' % prec | 1449 text=text, |
1453 text += bumped.description() | 1450 files=files, |
1454 | 1451 filectxfn=filectxfn, |
1455 new = context.memctx(repo, | 1452 user=bumped.user(), |
1456 parents=[prec.node(), node.nullid], | 1453 date=bumped.date(), |
1457 text=text, | 1454 extra=bumped.extra()) |
1458 files=files, | 1455 |
1459 filectxfn=filectxfn, | 1456 newid = repo.commitctx(new) |
1460 user=bumped.user(), | 1457 if newid is None: |
1461 date=bumped.date(), | 1458 createmarkers(repo, [(tmpctx, ())]) |
1462 extra=bumped.extra()) | 1459 newid = prec.node() |
1463 | 1460 else: |
1464 newid = repo.commitctx(new) | 1461 retractboundary(repo, bumped.phase(), [newid]) |
1465 if newid is None: | 1462 createmarkers(repo, [(tmpctx, (repo[newid],))], |
1466 createmarkers(repo, [(tmpctx, ())]) | 1463 flag=obsolete.bumpedfix) |
1467 newid = prec.node() | 1464 bmupdate(newid) |
1468 else: | 1465 tr.close() |
1469 retractboundary(repo, bumped.phase(), [newid]) | 1466 repo.ui.status(_('committed as %s\n') % node.short(newid)) |
1470 createmarkers(repo, [(tmpctx, (repo[newid],))], | |
1471 flag=obsolete.bumpedfix) | |
1472 bmupdate(newid) | |
1473 tr.close() | |
1474 repo.ui.status(_('committed as %s\n') % node.short(newid)) | |
1475 finally: | |
1476 tr.release() | |
1477 finally: | |
1478 lock.release() | |
1479 # reroute the working copy parent to the new changeset | |
1480 repo.dirstate.setparents(newid, node.nullid) | |
1481 finally: | 1467 finally: |
1482 wlock.release() | 1468 tr.release() |
1469 # reroute the working copy parent to the new changeset | |
1470 repo.dirstate.setparents(newid, node.nullid) | |
1483 | 1471 |
1484 def _solvedivergent(ui, repo, divergent, dryrun=False, progresscb=None): | 1472 def _solvedivergent(ui, repo, divergent, dryrun=False, progresscb=None): |
1485 base, others = divergentdata(divergent) | 1473 base, others = divergentdata(divergent) |
1486 if len(others) > 1: | 1474 if len(others) > 1: |
1487 othersstr = "[%s]" % (','.join([str(i) for i in others])) | 1475 othersstr = "[%s]" % (','.join([str(i) for i in others])) |
1535 ui.write('hg up -C %s &&\n' % base) | 1523 ui.write('hg up -C %s &&\n' % base) |
1536 ui.write('hg revert --all --rev tip &&\n') | 1524 ui.write('hg revert --all --rev tip &&\n') |
1537 ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' | 1525 ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' |
1538 % divergent) | 1526 % divergent) |
1539 return | 1527 return |
1540 wlock = lock = None | 1528 if divergent not in repo[None].parents(): |
1541 try: | 1529 repo.ui.status(_('updating to "local" conflict\n')) |
1542 wlock = repo.wlock() | 1530 hg.update(repo, divergent.rev()) |
1543 lock = repo.lock() | 1531 repo.ui.note(_('merging divergent changeset\n')) |
1544 if divergent not in repo[None].parents(): | 1532 if progresscb: progresscb() |
1545 repo.ui.status(_('updating to "local" conflict\n')) | 1533 stats = merge.update(repo, |
1546 hg.update(repo, divergent.rev()) | 1534 other.node(), |
1547 repo.ui.note(_('merging divergent changeset\n')) | 1535 branchmerge=True, |
1548 if progresscb: progresscb() | 1536 force=False, |
1549 stats = merge.update(repo, | 1537 partial=None, |
1550 other.node(), | 1538 ancestor=base.node(), |
1551 branchmerge=True, | 1539 mergeancestor=True) |
1552 force=False, | 1540 hg._showstats(repo, stats) |
1553 partial=None, | 1541 if stats[3]: |
1554 ancestor=base.node(), | 1542 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges " |
1555 mergeancestor=True) | 1543 "or 'hg update -C .' to abandon\n")) |
1556 hg._showstats(repo, stats) | 1544 if stats[3] > 0: |
1557 if stats[3]: | 1545 raise util.Abort('merge conflict between several amendments ' |
1558 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges " | 1546 '(this is not automated yet)', |
1559 "or 'hg update -C .' to abandon\n")) | 1547 hint="""/!\ You can try: |
1560 if stats[3] > 0: | |
1561 raise util.Abort('merge conflict between several amendments ' | |
1562 '(this is not automated yet)', | |
1563 hint="""/!\ You can try: | |
1564 /!\ * manual merge + resolve => new cset X | 1548 /!\ * manual merge + resolve => new cset X |
1565 /!\ * hg up to the parent of the amended changeset (which are named W and Z) | 1549 /!\ * hg up to the parent of the amended changeset (which are named W and Z) |
1566 /!\ * hg revert --all -r X | 1550 /!\ * hg revert --all -r X |
1567 /!\ * hg ci -m "same message as the amended changeset" => new cset Y | 1551 /!\ * hg ci -m "same message as the amended changeset" => new cset Y |
1568 /!\ * hg kill -n Y W Z | 1552 /!\ * hg kill -n Y W Z |
1569 """) | 1553 """) |
1570 if progresscb: progresscb() | 1554 if progresscb: progresscb() |
1571 tr = repo.transaction('stabilize-divergent') | 1555 tr = repo.transaction('stabilize-divergent') |
1572 try: | 1556 try: |
1573 repo.dirstate.setparents(divergent.node(), node.nullid) | 1557 repo.dirstate.setparents(divergent.node(), node.nullid) |
1574 oldlen = len(repo) | 1558 oldlen = len(repo) |
1575 amend(ui, repo, message='', logfile='') | 1559 amend(ui, repo, message='', logfile='') |
1576 if oldlen == len(repo): | 1560 if oldlen == len(repo): |
1577 new = divergent | 1561 new = divergent |
1578 # no changes | 1562 # no changes |
1579 else: | 1563 else: |
1580 new = repo['.'] | 1564 new = repo['.'] |
1581 createmarkers(repo, [(other, (new,))]) | 1565 createmarkers(repo, [(other, (new,))]) |
1582 retractboundary(repo, other.phase(), [new.node()]) | 1566 retractboundary(repo, other.phase(), [new.node()]) |
1583 tr.close() | 1567 tr.close() |
1584 finally: | |
1585 tr.release() | |
1586 finally: | 1568 finally: |
1587 lockmod.release(lock, wlock) | 1569 tr.release() |
1588 | |
1589 | 1570 |
1590 def divergentdata(ctx): | 1571 def divergentdata(ctx): |
1591 """return base, other part of a conflict | 1572 """return base, other part of a conflict |
1592 | 1573 |
1593 This only return the first one. | 1574 This only return the first one. |