hgext3rd/evolve/evolvecmd.py
changeset 3463 f994c480cea9
parent 3462 e147c18ed064
child 3464 908d2b5dfa7e
equal deleted inserted replaced
3462:e147c18ed064 3463:f994c480cea9
     6 # This software may be used and distributed according to the terms of the
     6 # This software may be used and distributed according to the terms of the
     7 # GNU General Public License version 2 or any later version.
     7 # GNU General Public License version 2 or any later version.
     8 
     8 
     9 """logic related to hg evolve command"""
     9 """logic related to hg evolve command"""
    10 
    10 
       
    11 import re
       
    12 
    11 from mercurial import (
    13 from mercurial import (
       
    14     bookmarks as bookmarksmod,
    12     cmdutil,
    15     cmdutil,
    13     context,
    16     context,
    14     copies,
    17     copies,
    15     error,
    18     error,
    16     hg,
    19     hg,
    17     lock as lockmod,
    20     lock as lockmod,
    18     merge,
    21     merge,
    19     node,
    22     node,
    20     obsolete,
    23     obsolete,
    21     phases,
    24     phases,
       
    25     util,
    22 )
    26 )
    23 
    27 
    24 from mercurial.i18n import _
    28 from mercurial.i18n import _
    25 
    29 
    26 from . import (
    30 from . import (
    32 )
    36 )
    33 
    37 
    34 TROUBLES = compat.TROUBLES
    38 TROUBLES = compat.TROUBLES
    35 shorttemplate = utility.shorttemplate
    39 shorttemplate = utility.shorttemplate
    36 _bookmarksupdater = rewriteutil.bookmarksupdater
    40 _bookmarksupdater = rewriteutil.bookmarksupdater
    37 
    41 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
    38 from . import relocate, divergentdata, MergeFailure
    42 
       
    43 from . import divergentdata
    39 
    44 
    40 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
    45 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
    41     """Resolve the troubles affecting one revision
    46     """Resolve the troubles affecting one revision
    42 
    47 
    43     returns a tuple (bool, newnode) where,
    48     returns a tuple (bool, newnode) where,
   385         obsolete.createmarkers(repo, [(other, (new,))])
   390         obsolete.createmarkers(repo, [(other, (new,))])
   386         phases.retractboundary(repo, tr, other.phase(), [new.node()])
   391         phases.retractboundary(repo, tr, other.phase(), [new.node()])
   387         return (True, new.node())
   392         return (True, new.node())
   388     finally:
   393     finally:
   389         repo.ui.restoreconfig(emtpycommitallowed)
   394         repo.ui.restoreconfig(emtpycommitallowed)
       
   395 
       
   396 class MergeFailure(error.Abort):
       
   397     pass
       
   398 
       
   399 def relocate(repo, orig, dest, pctx=None, keepbranch=False):
       
   400     """rewrites the orig rev on dest rev
       
   401 
       
   402     returns the node of new commit which is formed
       
   403     """
       
   404     if orig.rev() == dest.rev():
       
   405         raise error.Abort(_('tried to relocate a node on top of itself'),
       
   406                           hint=_("This shouldn't happen. If you still "
       
   407                                  "need to move changesets, please do so "
       
   408                                  "manually with nothing to rebase - working "
       
   409                                  "directory parent is also destination"))
       
   410 
       
   411     if pctx is None:
       
   412         if len(orig.parents()) == 2:
       
   413             raise error.Abort(_("tried to relocate a merge commit without "
       
   414                                 "specifying which parent should be moved"),
       
   415                               hint=_("Specify the parent by passing in pctx"))
       
   416         pctx = orig.p1()
       
   417 
       
   418     commitmsg = orig.description()
       
   419 
       
   420     cache = {}
       
   421     sha1s = re.findall(sha1re, commitmsg)
       
   422     unfi = repo.unfiltered()
       
   423     for sha1 in sha1s:
       
   424         ctx = None
       
   425         try:
       
   426             ctx = unfi[sha1]
       
   427         except error.RepoLookupError:
       
   428             continue
       
   429 
       
   430         if not ctx.obsolete():
       
   431             continue
       
   432 
       
   433         successors = compat.successorssets(repo, ctx.node(), cache)
       
   434 
       
   435         # We can't make any assumptions about how to update the hash if the
       
   436         # cset in question was split or diverged.
       
   437         if len(successors) == 1 and len(successors[0]) == 1:
       
   438             newsha1 = node.hex(successors[0][0])
       
   439             commitmsg = commitmsg.replace(sha1, newsha1[:len(sha1)])
       
   440         else:
       
   441             repo.ui.note(_('The stale commit message reference to %s could '
       
   442                            'not be updated\n') % sha1)
       
   443 
       
   444     tr = repo.currenttransaction()
       
   445     assert tr is not None
       
   446     try:
       
   447         r = _evolvemerge(repo, orig, dest, pctx, keepbranch)
       
   448         if r[-1]: # some conflict
       
   449             raise error.Abort(_('unresolved merge conflicts '
       
   450                                 '(see hg help resolve)'))
       
   451         nodenew = _relocatecommit(repo, orig, commitmsg)
       
   452     except error.Abort as exc:
       
   453         with repo.dirstate.parentchange():
       
   454             repo.setparents(repo['.'].node(), node.nullid)
       
   455             repo.dirstate.write(tr)
       
   456             # fix up dirstate for copies and renames
       
   457             compat.duplicatecopies(repo, repo[None], dest.rev(), orig.p1().rev())
       
   458 
       
   459         class LocalMergeFailure(MergeFailure, exc.__class__):
       
   460             pass
       
   461         exc.__class__ = LocalMergeFailure
       
   462         tr.close() # to keep changes in this transaction (e.g. dirstate)
       
   463         raise
       
   464     _finalizerelocate(repo, orig, dest, nodenew, tr)
       
   465     return nodenew
       
   466 
       
   467 def _relocatecommit(repo, orig, commitmsg):
       
   468     if commitmsg is None:
       
   469         commitmsg = orig.description()
       
   470     extra = dict(orig.extra())
       
   471     if 'branch' in extra:
       
   472         del extra['branch']
       
   473     extra['rebase_source'] = orig.hex()
       
   474 
       
   475     backup = repo.ui.backupconfig('phases', 'new-commit')
       
   476     try:
       
   477         targetphase = max(orig.phase(), phases.draft)
       
   478         repo.ui.setconfig('phases', 'new-commit', targetphase, 'evolve')
       
   479         # Commit might fail if unresolved files exist
       
   480         nodenew = repo.commit(text=commitmsg, user=orig.user(),
       
   481                               date=orig.date(), extra=extra)
       
   482     finally:
       
   483         repo.ui.restoreconfig(backup)
       
   484     return nodenew
       
   485 
       
   486 def _finalizerelocate(repo, orig, dest, nodenew, tr):
       
   487     destbookmarks = repo.nodebookmarks(dest.node())
       
   488     nodesrc = orig.node()
       
   489     oldbookmarks = repo.nodebookmarks(nodesrc)
       
   490     bmchanges = []
       
   491 
       
   492     if nodenew is not None:
       
   493         obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
       
   494         for book in oldbookmarks:
       
   495             bmchanges.append((book, nodenew))
       
   496     else:
       
   497         obsolete.createmarkers(repo, [(repo[nodesrc], ())])
       
   498         # Behave like rebase, move bookmarks to dest
       
   499         for book in oldbookmarks:
       
   500             bmchanges.append((book, dest.node()))
       
   501     for book in destbookmarks: # restore bookmark that rebase move
       
   502         bmchanges.append((book, dest.node()))
       
   503     if bmchanges:
       
   504         compat.bookmarkapplychanges(repo, tr, bmchanges)
       
   505 
       
   506 def _evolvemerge(repo, orig, dest, pctx, keepbranch):
       
   507     """Used by the evolve function to merge dest on top of pctx.
       
   508     return the same tuple as merge.graft"""
       
   509     if repo['.'].rev() != dest.rev():
       
   510         merge.update(repo,
       
   511                      dest,
       
   512                      branchmerge=False,
       
   513                      force=True)
       
   514     if repo._activebookmark:
       
   515         repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
       
   516     bookmarksmod.deactivate(repo)
       
   517     if keepbranch:
       
   518         repo.dirstate.setbranch(orig.branch())
       
   519     if util.safehasattr(repo, 'currenttopic'):
       
   520         # uurrgs
       
   521         # there no other topic setter yet
       
   522         if not orig.topic() and repo.vfs.exists('topic'):
       
   523                 repo.vfs.unlink('topic')
       
   524         else:
       
   525             with repo.vfs.open('topic', 'w') as f:
       
   526                 f.write(orig.topic())
       
   527 
       
   528     return merge.graft(repo, orig, pctx, ['destination', 'evolving'], True)