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.