Patchwork [1,of,2,V2] update: warn about other topological heads on bare update

login
register
mail settings
Submitter Pierre-Yves David
Date Feb. 7, 2016, 9:25 p.m.
Message ID <6721afd1a28833cf46bf.1454880309@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/13033/
State Accepted
Headers show

Comments

Pierre-Yves David - Feb. 7, 2016, 9:25 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1454424542 0
#      Tue Feb 02 14:49:02 2016 +0000
# Node ID 6721afd1a28833cf46bf8498b4222aa51355c751
# Parent  01a5143cd25f285f8c745a92986cd7186bb32c90
# EXP-Topic destination
# Available At http://hg.netv6.net/marmoute-wip/mercurial/
#              hg pull http://hg.netv6.net/marmoute-wip/mercurial/ -r 6721afd1a288
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:

  1 other divergent bookmarks for "foobar"

There is significant room to improve the message itself, and we should augment
it with hint about how to see theses other heads or handle the situation (see
in-line comment). But having "a" message is already a significant improvement
compared to the existing situation. Once we have it we can iterate on a better
version of it. As having such message is an important step toward changing the
default destination for update and other nicety, I would like to move forward
quickly on getting such message.

This was discussed during London - October 2015 Sprint.

Patch

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -6941,10 +6941,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:
@@ -6962,10 +6964,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)
@@ -6988,11 +6991,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 and parents()', curhead):
+        # we are on the active bookmark
+        bmheads = [b for b in bmheads if curhead != b]
+        if 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
@@ -571,10 +571,11 @@  update to active bookmark if it's not th
 pull --update works the same as pull && update
 
   $ hg bookmark -r3 Y
   moving bookmark 'Y' forward from db815d6d32e6
   $ cp -r  ../cloned-bookmarks-update ../cloned-bookmarks-manual-update
+  $ cp -r  ../cloned-bookmarks-update ../cloned-bookmarks-manual-update-with-divergence
 
 (manual version)
 
   $ hg -R ../cloned-bookmarks-manual-update update Y
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -615,10 +616,38 @@  pull --update works the same as pull && 
   updating bookmark Y
   updating bookmark Z
   updating to active bookmark Y
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
+We warn about divergent during bare update to the active bookmark
+
+  $ hg -R ../cloned-bookmarks-manual-update-with-divergence update Y
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark Y)
+  $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks -r X2 Y@1
+  $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks
+     X2                        1:925d80f479bb
+   * Y                         2:db815d6d32e6
+     Y@1                       1:925d80f479bb
+     Z                         2:db815d6d32e6
+     x  y                      2:db815d6d32e6
+  $ hg -R ../cloned-bookmarks-manual-update-with-divergence pull
+  pulling from $TESTTMP
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  updating bookmark Y
+  updating bookmark Z
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
+  updating to active bookmark Y
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark Y)
+  1 other divergent bookmarks for "Y"
+
 test wrongly formated bookmark
 
   $ echo '' >> .hg/bookmarks
   $ hg bookmarks
      X2                        1:925d80f479bb
@@ -713,10 +742,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