[PATCH 1 of 2] update: warn about other topological heads on bare update

Pierre-Yves David pierre-yves.david at ens-lyon.org
Wed Feb 3 12:43:59 EST 2016


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at fb.com>
# Date 1454424542 0
#      Tue Feb 02 14:49:02 2016 +0000
# Node ID d61062a8e69c5586171a9207994c13dfc69ea975
# Parent  8e79ad2da8a69735488402fd018dd82bc1eb9309
# EXP-Topic destination
# Available At http://hg.netv6.net/marmoute-wip/mercurial/
#              hg pull http://hg.netv6.net/marmoute-wip/mercurial/ -r d61062a8e69c
update: warn about other topological heads on bare update

A concern around the user experience of Mercurial is user getting stuck on there
own topological branch forever. For example, someone pulling another topological
branch, missing that message in pull asking them to merge and getting stuck on
there own local branch.

The current way to "address" this concern was for bare 'hg update' to target the
tipmost (also latest pulled) changesets and complain when the update was not
linear. That way, failure to merge newly pulled changesets would result in some
kind of failure.

Yet the failure was quite obscure, not working in all cases (eg: commit right
after pull) and the behavior was very impractical in the common case
(eg: issue4673).

To be able to change that behavior, we need to provide other ways to alert a
user stucks on one of many topological head. We do so with an extra message after
bare update:

  1 other heads for branch "default"

Bookmark get its own special version:

  %i other divergent bookmarks for "%s"\n

This was discussed during London - October 2015 Sprint.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -6944,10 +6944,12 @@ def update(ui, repo, node=None, rev=None
         raise error.Abort(_("please specify just one revision"))
 
     if rev is None or rev == '':
         rev = node
 
+    warndest = False
+
     with repo.wlock():
         cmdutil.clearunfinished(repo)
 
         if date:
             if rev is not None:
@@ -6965,10 +6967,11 @@ def update(ui, repo, node=None, rev=None
         if check:
             cmdutil.bailifchanged(repo, merge=False)
         if rev is None:
             updata = destutil.destupdate(repo, clean=clean, check=check)
             rev, movemarkfrom, brev = updata
+            warndest = True
 
         repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
 
         if clean:
             ret = hg.clean(repo, rev)
@@ -6991,11 +6994,12 @@ def update(ui, repo, node=None, rev=None
         elif brev:
             if repo._activebookmark:
                 ui.status(_("(leaving bookmark %s)\n") %
                           repo._activebookmark)
             bookmarks.deactivate(repo)
-
+        if warndest:
+            destutil.statusotherdests(ui, repo)
     return ret
 
 @command('verify', [])
 def verify(ui, repo):
     """verify the integrity of the repository
diff --git a/mercurial/destutil.py b/mercurial/destutil.py
--- a/mercurial/destutil.py
+++ b/mercurial/destutil.py
@@ -216,5 +216,36 @@ def desthistedit(ui, repo):
             # take the first revision. So do this manually.
             revs.sort()
             return revs.first()
 
     return None
+
+def _statusotherbook(ui, repo):
+    bmheads = repo.bookmarkheads(repo._activebookmark)
+    curhead = repo[repo._activebookmark].node()
+    if repo.revs('%n - parents()', curhead):
+        # we are on the active bookmark
+        bmheads = [b for b in bmheads if curhead == bmheads[0]]
+        if 1 < len(bmheads):
+            msg = _('%i other divergent bookmarks for "%s"\n')
+            ui.status(msg % (len(bmheads), repo._activebookmark))
+
+def _statusotherbranchheads(ui, repo):
+    currentbranch = repo.dirstate.branch()
+    heads = repo.branchheads(currentbranch)
+    l = len(heads)
+    if repo.revs('%ln and parents()', heads):
+        # we are on a head
+        heads = repo.revs('%ln - parents()', heads)
+        if heads and l != len(heads):
+            ui.status(_('%i other heads for branch "%s"\n') %
+                      (len(heads), currentbranch))
+
+def statusotherdests(ui, repo):
+    """Print message about other head"""
+    # XXX we should probably include a hint:
+    # - about what to do
+    # - how to see such heads
+    if repo._activebookmark:
+        _statusotherbook(ui, repo)
+    else:
+        _statusotherbranchheads(ui, repo)
diff --git a/tests/test-bisect2.t b/tests/test-bisect2.t
--- a/tests/test-bisect2.t
+++ b/tests/test-bisect2.t
@@ -242,10 +242,11 @@ log
 
 hg up -C
 
   $ hg up -C
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  3 other heads for branch "default"
 
 complex bisect test 1  # first bad rev is 9
 
   $ hg bisect -r
   $ hg bisect -g 0
diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -119,10 +119,11 @@ extension and python hooks - use the eol
   $ echo '[hooks]' >> .hg/hgrc
   $ echo 'update = echo hooked' >> .hg/hgrc
   $ hg update
   hooked
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ hg blackbox -l 5
   1970/01/01 00:00:00 bob (*)> update (glob)
   1970/01/01 00:00:00 bob (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
   1970/01/01 00:00:00 bob (*)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
   1970/01/01 00:00:00 bob (*)> exthook-update: echo hooked finished in * seconds (glob)
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -713,10 +713,11 @@ test non-linear update not clearing acti
   (leaving bookmark four)
   $ hg book drop
   $ hg up -C
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (leaving bookmark drop)
+  1 other heads for branch "default"
   $ hg sum
   parent: 2:db815d6d32e6 
    2
   branch: default
   bookmarks: should-end-on-two
diff --git a/tests/test-conflict.t b/tests/test-conflict.t
--- a/tests/test-conflict.t
+++ b/tests/test-conflict.t
@@ -230,10 +230,11 @@ internal:merge3
 Add some unconflicting changes on each head, to make sure we really
 are merging, unlike :local and :other
 
   $ hg up -C
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ printf "\n\nEnd of file\n" >> a
   $ hg ci -m "Add some stuff at the end"
   $ hg up -r 1
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ printf "Start of file\n\n\n" > tmp
@@ -267,10 +268,11 @@ Now test :merge-other and :merge-local
   
   End of file
 
   $ hg up -C
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ hg merge --tool :merge-local
   merging a
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ cat a
diff --git a/tests/test-largefiles-cache.t b/tests/test-largefiles-cache.t
--- a/tests/test-largefiles-cache.t
+++ b/tests/test-largefiles-cache.t
@@ -200,10 +200,11 @@ Inject corruption into the largefiles st
   $ hg up -C
   getting changed largefiles
   large: data corruption in $TESTTMP/src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020 with hash 6a7bb2556144babe3899b25e5428123735bb1e27 (glob)
   0 largefiles updated, 0 removed
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 other heads for branch "default"
   $ hg st
   ! large
   ? z
   $ rm .hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
 
diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t
--- a/tests/test-largefiles-update.t
+++ b/tests/test-largefiles-update.t
@@ -66,10 +66,11 @@ update the corresponding standins.
 Verify that it actually marks the clean files as clean in lfdirstate so
 we don't have to hash them again next time we update.
 
   $ hg up
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ hg debugdirstate --large --nodate
   n 644          7 set                 large1
   n 644         13 set                 large2
 
 Test that lfdirstate keeps track of last modification of largefiles and
@@ -80,10 +81,11 @@ prevents unnecessary hashing of content 
   $ hg debugdirstate --large --nodate
   n 644          7 set                 large1
   n 644         13 set                 large2
   $ hg up
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ hg debugdirstate --large --nodate
   n 644          7 set                 large1
   n 644         13 set                 large2
 
 Test that "hg merge" updates largefiles from "other" correctly
diff --git a/tests/test-merge-changedelete.t b/tests/test-merge-changedelete.t
--- a/tests/test-merge-changedelete.t
+++ b/tests/test-merge-changedelete.t
@@ -106,10 +106,11 @@ Non-interactive merge:
 
 Interactive merge:
 
   $ hg co -C
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --config ui.interactive=true <<EOF
   > c
   > d
   > EOF
@@ -163,10 +164,11 @@ Interactive merge:
 
 Interactive merge with bad input:
 
   $ hg co -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --config ui.interactive=true <<EOF
   > foo
   > bar
   > d
@@ -232,10 +234,11 @@ Interactive merge with bad input:
 
 Interactive merge with not enough input:
 
   $ hg co -C
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --config ui.interactive=true <<EOF
   > d
   > EOF
   local changed file1 which remote deleted
@@ -287,10 +290,11 @@ Interactive merge with not enough input:
 
 Choose local versions of files
 
   $ hg co -C
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --tool :local
   0 files updated, 3 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ status 2>&1 | tee $TESTTMP/local.status
@@ -328,10 +332,11 @@ Choose local versions of files
 
 Choose other versions of files
 
   $ hg co -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --tool :other
   0 files updated, 2 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ status 2>&1 | tee $TESTTMP/other.status
@@ -369,10 +374,11 @@ Choose other versions of files
 
 Fail
 
   $ hg co -C
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --tool :fail
   0 files updated, 0 files merged, 0 files removed, 3 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
   [1]
@@ -413,10 +419,11 @@ Fail
 
 Force prompts with no input (should be similar to :fail)
 
   $ hg co -C
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --config ui.interactive=True --tool :prompt
   local changed file1 which remote deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
   remote changed file2 which local deleted
@@ -465,10 +472,11 @@ Force prompts with no input (should be s
 
 Force prompts
 
   $ hg co -C
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --tool :prompt
   local changed file1 which remote deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
   remote changed file2 which local deleted
@@ -515,10 +523,11 @@ Force prompts
 
 Choose to merge all files
 
   $ hg co -C
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
 
   $ hg merge --tool :merge3
   local changed file1 which remote deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
   remote changed file2 which local deleted
diff --git a/tests/test-merge-default.t b/tests/test-merge-default.t
--- a/tests/test-merge-default.t
+++ b/tests/test-merge-default.t
@@ -31,10 +31,11 @@ Should fail because not at a head:
   (run 'hg heads .' to see heads)
   [255]
 
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 other heads for branch "default"
 
 Should fail because > 2 heads:
 
   $ HGMERGE=internal:other; export HGMERGE
   $ hg merge
diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t
--- a/tests/test-merge-types.t
+++ b/tests/test-merge-types.t
@@ -153,10 +153,11 @@ Update to link without local change shou
 
   $ hg up -C 0
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ hg st
   ? a.orig
 
 Update to link with local change should cause a merge prompt (issue3200):
 
@@ -173,10 +174,11 @@ Update to link with local change should 
   picked tool ':prompt' for a (binary False symlink True changedelete False)
   no tool found to merge a
   keep (l)ocal, take (o)ther, or leave (u)nresolved? u
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
+  1 other heads for branch "default"
   [1]
   $ hg diff --git
   diff --git a/a b/a
   old mode 120000
   new mode 100644
diff --git a/tests/test-merge5.t b/tests/test-merge5.t
--- a/tests/test-merge5.t
+++ b/tests/test-merge5.t
@@ -22,10 +22,11 @@
   abort: uncommitted changes
   [255]
   $ hg revert b
   $ hg update -c
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ mv a c
 
 Should abort:
 
   $ hg update 1
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -285,10 +285,11 @@ after strip of merge parent
   
   $ restore
 
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ hg log -G
   @  changeset:   4:264128213d29
   |  tag:         tip
   |  parent:      1:ef3a871183d7
   |  user:        test
diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
--- a/tests/test-subrepo.t
+++ b/tests/test-subrepo.t
@@ -662,10 +662,11 @@ a subrepo store may be clean versus one 
 update
 
   $ cd ../t
   $ hg up -C # discard our earlier merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 other heads for branch "default"
   $ echo blah > t/t
   $ hg ci -m13
   committing subrepository t
 
 backout calls revert internally with minimal opts, which should not raise
@@ -675,10 +676,11 @@ KeyError
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   changeset c373c8102e68 backed out, don't forget to commit.
 
   $ hg up -C # discard changes
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 other heads for branch "default"
 
 pull
 
   $ cd ../tc
   $ hg pull
@@ -716,10 +718,11 @@ should pull t
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 other heads for branch "default"
   $ cat t/t
   blah
 
 bogus subrepo path aborts
 
@@ -1183,10 +1186,11 @@ Check hg update --clean
   M s/a
   A s/b
   ? s/c
   $ hg update -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 other heads for branch "default"
   $ hg status -S
   ? s/b
   ? s/c
 
 Sticky subrepositories, no changes
diff --git a/tests/test-transplant.t b/tests/test-transplant.t
--- a/tests/test-transplant.t
+++ b/tests/test-transplant.t
@@ -407,10 +407,11 @@ transplant --continue
 
 transplant -c shouldn't use an old changeset
 
   $ hg up -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   $ rm added
   $ hg transplant --continue
   abort: no transplant to continue
   [255]
   $ hg transplant 1
diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t
--- a/tests/test-update-branches.t
+++ b/tests/test-update-branches.t
@@ -165,10 +165,11 @@ Cases are run as shown in that table, ro
   parent=1
   M sub/suba
 
   $ norevtest '-c clean same'   clean 2 -c
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 other heads for branch "default"
   parent=3
 
   $ revtest '-cC dirty linear'  dirty 1 2 -cC
   abort: cannot specify both -c/--check and -C/--clean
   parent=1


More information about the Mercurial-devel mailing list