hgext/evolve.py
changeset 1021 200f2d9b9f39
parent 1020 155949287628
child 1022 6f4fd3e49d1c
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.