mercurial/destutil.py
branchstable
changeset 26813 b66e3ca0b90c
parent 26728 e8f1b7285917
child 27262 3d0feb2f978b
equal deleted inserted replaced
26535:d3712209921d 26813:b66e3ca0b90c
       
     1 # destutil.py - Mercurial utility function for command destination
       
     2 #
       
     3 #  Copyright Matt Mackall <mpm@selenic.com> and other
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 from .i18n import _
       
     9 from . import (
       
    10     bookmarks,
       
    11     error,
       
    12     obsolete,
       
    13 )
       
    14 
       
    15 def _destupdatevalidate(repo, rev, clean, check):
       
    16     """validate that the destination comply to various rules
       
    17 
       
    18     This exists as its own function to help wrapping from extensions."""
       
    19     wc = repo[None]
       
    20     p1 = wc.p1()
       
    21     if not clean:
       
    22         # Check that the update is linear.
       
    23         #
       
    24         # Mercurial do not allow update-merge for non linear pattern
       
    25         # (that would be technically possible but was considered too confusing
       
    26         # for user a long time ago)
       
    27         #
       
    28         # See mercurial.merge.update for details
       
    29         if p1.rev() not in repo.changelog.ancestors([rev], inclusive=True):
       
    30             dirty = wc.dirty(missing=True)
       
    31             foreground = obsolete.foreground(repo, [p1.node()])
       
    32             if not repo[rev].node() in foreground:
       
    33                 if dirty:
       
    34                     msg = _("uncommitted changes")
       
    35                     hint = _("commit and merge, or update --clean to"
       
    36                              " discard changes")
       
    37                     raise error.UpdateAbort(msg, hint=hint)
       
    38                 elif not check:  # destination is not a descendant.
       
    39                     msg = _("not a linear update")
       
    40                     hint = _("merge or update --check to force update")
       
    41                     raise error.UpdateAbort(msg, hint=hint)
       
    42 
       
    43 def _destupdateobs(repo, clean, check):
       
    44     """decide of an update destination from obsolescence markers"""
       
    45     node = None
       
    46     wc = repo[None]
       
    47     p1 = wc.p1()
       
    48     movemark = None
       
    49 
       
    50     if p1.obsolete() and not p1.children():
       
    51         # allow updating to successors
       
    52         successors = obsolete.successorssets(repo, p1.node())
       
    53 
       
    54         # behavior of certain cases is as follows,
       
    55         #
       
    56         # divergent changesets: update to highest rev, similar to what
       
    57         #     is currently done when there are more than one head
       
    58         #     (i.e. 'tip')
       
    59         #
       
    60         # replaced changesets: same as divergent except we know there
       
    61         # is no conflict
       
    62         #
       
    63         # pruned changeset: no update is done; though, we could
       
    64         #     consider updating to the first non-obsolete parent,
       
    65         #     similar to what is current done for 'hg prune'
       
    66 
       
    67         if successors:
       
    68             # flatten the list here handles both divergent (len > 1)
       
    69             # and the usual case (len = 1)
       
    70             successors = [n for sub in successors for n in sub]
       
    71 
       
    72             # get the max revision for the given successors set,
       
    73             # i.e. the 'tip' of a set
       
    74             node = repo.revs('max(%ln)', successors).first()
       
    75             if bookmarks.isactivewdirparent(repo):
       
    76                 movemark = repo['.'].node()
       
    77     return node, movemark, None
       
    78 
       
    79 def _destupdatebook(repo, clean, check):
       
    80     """decide on an update destination from active bookmark"""
       
    81     # we also move the active bookmark, if any
       
    82     activemark = None
       
    83     node, movemark = bookmarks.calculateupdate(repo.ui, repo, None)
       
    84     if node is not None:
       
    85         activemark = node
       
    86     return node, movemark, activemark
       
    87 
       
    88 def _destupdatebranch(repo, clean, check):
       
    89     """decide on an update destination from current branch"""
       
    90     wc = repo[None]
       
    91     movemark = node = None
       
    92     try:
       
    93         node = repo.branchtip(wc.branch())
       
    94         if bookmarks.isactivewdirparent(repo):
       
    95             movemark = repo['.'].node()
       
    96     except error.RepoLookupError:
       
    97         if wc.branch() == 'default': # no default branch!
       
    98             node = repo.lookup('tip') # update to tip
       
    99         else:
       
   100             raise error.Abort(_("branch %s not found") % wc.branch())
       
   101     return node, movemark, None
       
   102 
       
   103 # order in which each step should be evalutated
       
   104 # steps are run until one finds a destination
       
   105 destupdatesteps = ['evolution', 'bookmark', 'branch']
       
   106 # mapping to ease extension overriding steps.
       
   107 destupdatestepmap = {'evolution': _destupdateobs,
       
   108                      'bookmark': _destupdatebook,
       
   109                      'branch': _destupdatebranch,
       
   110                      }
       
   111 
       
   112 def destupdate(repo, clean=False, check=False):
       
   113     """destination for bare update operation
       
   114 
       
   115     return (rev, movemark, activemark)
       
   116 
       
   117     - rev: the revision to update to,
       
   118     - movemark: node to move the active bookmark from
       
   119                 (cf bookmark.calculate update),
       
   120     - activemark: a bookmark to activate at the end of the update.
       
   121     """
       
   122     node = movemark = activemark = None
       
   123 
       
   124     for step in destupdatesteps:
       
   125         node, movemark, activemark = destupdatestepmap[step](repo, clean, check)
       
   126         if node is not None:
       
   127             break
       
   128     rev = repo[node].rev()
       
   129 
       
   130     _destupdatevalidate(repo, rev, clean, check)
       
   131 
       
   132     return rev, movemark, activemark
       
   133 
       
   134 def _destmergebook(repo):
       
   135     """find merge destination in the active bookmark case"""
       
   136     node = None
       
   137     bmheads = repo.bookmarkheads(repo._activebookmark)
       
   138     curhead = repo[repo._activebookmark].node()
       
   139     if len(bmheads) == 2:
       
   140         if curhead == bmheads[0]:
       
   141             node = bmheads[1]
       
   142         else:
       
   143             node = bmheads[0]
       
   144     elif len(bmheads) > 2:
       
   145         raise error.Abort(_("multiple matching bookmarks to merge - "
       
   146             "please merge with an explicit rev or bookmark"),
       
   147             hint=_("run 'hg heads' to see all heads"))
       
   148     elif len(bmheads) <= 1:
       
   149         raise error.Abort(_("no matching bookmark to merge - "
       
   150             "please merge with an explicit rev or bookmark"),
       
   151             hint=_("run 'hg heads' to see all heads"))
       
   152     assert node is not None
       
   153     return node
       
   154 
       
   155 def _destmergebranch(repo):
       
   156     """find merge destination based on branch heads"""
       
   157     node = None
       
   158     branch = repo[None].branch()
       
   159     bheads = repo.branchheads(branch)
       
   160     nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
       
   161 
       
   162     if len(nbhs) > 2:
       
   163         raise error.Abort(_("branch '%s' has %d heads - "
       
   164                            "please merge with an explicit rev")
       
   165                          % (branch, len(bheads)),
       
   166                          hint=_("run 'hg heads .' to see heads"))
       
   167 
       
   168     parent = repo.dirstate.p1()
       
   169     if len(nbhs) <= 1:
       
   170         if len(bheads) > 1:
       
   171             raise error.Abort(_("heads are bookmarked - "
       
   172                                "please merge with an explicit rev"),
       
   173                              hint=_("run 'hg heads' to see all heads"))
       
   174         if len(repo.heads()) > 1:
       
   175             raise error.Abort(_("branch '%s' has one head - "
       
   176                                "please merge with an explicit rev")
       
   177                              % branch,
       
   178                              hint=_("run 'hg heads' to see all heads"))
       
   179         msg, hint = _('nothing to merge'), None
       
   180         if parent != repo.lookup(branch):
       
   181             hint = _("use 'hg update' instead")
       
   182         raise error.Abort(msg, hint=hint)
       
   183 
       
   184     if parent not in bheads:
       
   185         raise error.Abort(_('working directory not at a head revision'),
       
   186                          hint=_("use 'hg update' or merge with an "
       
   187                                 "explicit revision"))
       
   188     if parent == nbhs[0]:
       
   189         node = nbhs[-1]
       
   190     else:
       
   191         node = nbhs[0]
       
   192     assert node is not None
       
   193     return node
       
   194 
       
   195 def destmerge(repo):
       
   196     if repo._activebookmark:
       
   197         node = _destmergebook(repo)
       
   198     else:
       
   199         node = _destmergebranch(repo)
       
   200     return repo[node].rev()