diff mercurial/destutil.py @ 28385:3f9e25a42e69

destutil: choose non-closed branch head at first (BC) Before this patch, destupdate() returns the tipmost (descendant) branch head regardless of closed or not. But updating to closed branch head isn't reasonable for ordinary workflow, because: - "hg heads" doesn't show closed heads (= updated parent itself) by default - subsequent committing on it re-opens closed branch even if inactivation of closed head is needed, update destination isn't it, because it should be merged into to another branch in such case. This patch chooses non-closed descendant branch head as default update destination at first. If all descendant branch heads are closed, destupdate() returns the tipmost closed branch head. For simplicity, this patch chooses adding _destupdatebranchfallback() instead largely changing _destupdatebranch(). This patch changes not only normal lookup code path, but also the "no default branch" code path, for consistency.
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Mon, 07 Mar 2016 03:14:19 +0900
parents de8b09482fb7
children d0210a35c81a
line wrap: on
line diff
--- a/mercurial/destutil.py	Tue Mar 08 04:08:33 2016 -0800
+++ b/mercurial/destutil.py	Mon Mar 07 03:14:19 2016 +0900
@@ -88,30 +88,55 @@
     return node, movemark, activemark
 
 def _destupdatebranch(repo, clean, check):
-    """decide on an update destination from current branch"""
+    """decide on an update destination from current branch
+
+    This ignores closed branch heads.
+    """
     wc = repo[None]
     movemark = node = None
     currentbranch = wc.branch()
     if currentbranch in repo.branchmap():
-        heads = repo.branchheads(currentbranch, closed=True)
+        heads = repo.branchheads(currentbranch)
         if heads:
             node = repo.revs('max(.::(%ln))', heads).first()
         if bookmarks.isactivewdirparent(repo):
             movemark = repo['.'].node()
     else:
         if currentbranch == 'default': # no default branch!
-            node = repo.lookup('tip') # update to tip
+            # update to the tipmost non-closed branch head
+            node = repo.revs('max(head() and not closed())').first()
         else:
             raise error.Abort(_("branch %s not found") % currentbranch)
     return node, movemark, None
 
+def _destupdatebranchfallback(repo, clean, check):
+    """decide on an update destination from closed heads in current branch"""
+    wc = repo[None]
+    currentbranch = wc.branch()
+    movemark = None
+    if currentbranch in repo.branchmap():
+        # here, all descendant branch heads are closed
+        heads = repo.branchheads(currentbranch, closed=True)
+        assert heads, "any branch has at least one head"
+        node = repo.revs('max(.::(%ln))', heads).first()
+        assert node is not None, ("any revision has at least "
+                                  "one descendant branch head")
+        if bookmarks.isactivewdirparent(repo):
+            movemark = repo['.'].node()
+    else:
+        # here, no "default" branch, and all branches are closed
+        node = repo.lookup('tip')
+        assert node is not None, "'tip' exists even in empty repository"
+    return node, movemark, None
+
 # order in which each step should be evalutated
 # steps are run until one finds a destination
-destupdatesteps = ['evolution', 'bookmark', 'branch']
+destupdatesteps = ['evolution', 'bookmark', 'branch', 'branchfallback']
 # mapping to ease extension overriding steps.
 destupdatestepmap = {'evolution': _destupdateobs,
                      'bookmark': _destupdatebook,
                      'branch': _destupdatebranch,
+                     'branchfallback': _destupdatebranchfallback,
                      }
 
 def destupdate(repo, clean=False, check=False):
@@ -362,8 +387,27 @@
     heads = repo.branchheads(currentbranch)
     if repo.revs('%ln and parents()', allheads):
         # we are on a head, even though it might be closed
+        #
+        #  on closed otherheads
+        #  ========= ==========
+        #      o        0       all heads for current branch are closed
+        #               N       only descendant branch heads are closed
+        #      x        0       there is only one non-closed branch head
+        #               N       there are some non-closed branch heads
+        #  ========= ==========
         otherheads = repo.revs('%ln - parents()', heads)
-        if otherheads:
+        if repo['.'].closesbranch():
+            ui.status(_('updated to a closed branch head, '
+                        'because all descendant heads are closed.\n'
+                        'beware of re-opening closed head '
+                        'by subsequent commit here.\n'))
+            if otherheads:
+                ui.status(_('%i other heads for branch "%s"\n') %
+                          (len(otherheads), currentbranch))
+            else:
+                ui.status(_('all heads for branch "%s" are closed.\n') %
+                          currentbranch)
+        elif otherheads:
             ui.status(_('%i other heads for branch "%s"\n') %
                       (len(otherheads), currentbranch))