changeset 3262:f286eefbd20d

topic: add an option to enforce a single head per name in a repository
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Mon, 30 Oct 2017 19:24:14 +0100
parents 31493a1b0e39
children 678a9802c56b
files CHANGELOG hgext3rd/topic/__init__.py hgext3rd/topic/flow.py tests/test-topic-flow-single-head.t
diffstat 4 files changed, 232 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,11 @@
   * evolve: rename '--contentdivergent' flag to '--content-divergent'
   * evolve: rename '--phasedivergent' flag to '--phase-divergent'
 
+topic
+
+  * add an experimental flag to enforce one head per name policy,
+    (off by default, see 'hg help -e topic' for details)
+
 6.8.0 -- 2017-10-23
 -------------------
 
--- a/hgext3rd/topic/__init__.py
+++ b/hgext3rd/topic/__init__.py
@@ -63,6 +63,15 @@
     topic-mode = enforce-all # abort the commit (even for merge)
     topic-mode = random # use a randomized generated topic (except for merge)
     topic-mode = random-all # use a randomized generated topic (even for merge)
+
+Single head enforcing
+=====================
+
+The extensions come with an option to enforce that there is only one heads for
+each name in the repository at any time.
+
+    [experimental]
+    enforce-single-head = yes
 """
 
 from __future__ import absolute_import
@@ -99,6 +108,7 @@
 from . import (
     compat,
     constants,
+    flow,
     revset as topicrevset,
     destination,
     stack,
@@ -152,6 +162,9 @@
     configitem('experimental', 'enforce-topic',
                default=False,
     )
+    configitem('experimental', 'enforce-single-head',
+               default=False,
+    )
     configitem('experimental', 'topic-mode',
                default=None,
     )
@@ -368,6 +381,16 @@
             if desc in ('strip', 'repair') or ctr is not None:
                 return tr
 
+            if repo.ui.configbool('experimental', 'enforce-single-head'):
+                reporef = weakref.ref(self)
+                origvalidator = tr.validator
+
+                def validator(tr2):
+                    repo = reporef()
+                    flow.enforcesinglehead(repo, tr2)
+                    origvalidator(tr2)
+                tr.validator = validator
+
             # real transaction start
             ct = self.currenttopic
             if not ct:
new file mode 100644
--- /dev/null
+++ b/hgext3rd/topic/flow.py
@@ -0,0 +1,15 @@
+from __future__ import absolute_import
+
+from mercurial import (
+    error,
+    node,
+)
+
+from mercurial.i18n import _
+
+def enforcesinglehead(repo, tr):
+    for name, heads in repo.filtered('visible').branchmap().iteritems():
+        if len(heads) > 1:
+            hexs = [node.short(n) for n in heads]
+            raise error.Abort(_('%d heads on "%s"') % (len(heads), name),
+                              hint=(', '.join(hexs)))
new file mode 100644
--- /dev/null
+++ b/tests/test-topic-flow-single-head.t
@@ -0,0 +1,189 @@
+=====================
+Test workflow options
+=====================
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+  $ . "$TESTDIR/testlib/common.sh"
+
+Test single head enforcing - Setup
+=============================================
+
+  $ hg init single-head-server
+  $ cd single-head-server
+  $ cat <<EOF >> .hg/hgrc
+  > [phases]
+  > publish = no
+  > [experimental]
+  > enforce-single-head = yes
+  > evolution = all
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_dA0
+  $ cd ..
+
+  $ hg clone single-head-server client
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Test single head enforcing - with branch only
+---------------------------------------------
+
+  $ cd client
+
+continuing the current defaultbranch
+
+  $ mkcommit c_dB0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+creating a new branch
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg branch branch_A
+  marked working directory as branch branch_A
+  (branches are permanent and global, did you want a bookmark?)
+  $ mkcommit c_aC0
+  $ hg push --new-branch
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+
+Create a new head on the default branch
+
+  $ hg up 'desc("c_dA0")'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dD0
+  created new head
+  $ hg push -f
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: 2 heads on "default"
+  (286d02a6e2a2, 9bf953aa81f6)
+  [255]
+
+remerge them
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ mkcommit c_dE0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+
+Test single head enforcing - with topic
+---------------------------------------
+
+pushing a new topic
+
+  $ hg topic foo
+  marked working directory as topic: foo
+  $ mkcommit c_dF0
+  active topic 'foo' grew its first changeset
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+pushing a new topo branch (with a topic)
+
+  $ hg up 'desc("c_dD0")'
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg topic bar
+  marked working directory as topic: bar
+  $ mkcommit c_dG0
+  active topic 'bar' grew its first changeset
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+
+detect multiple heads on the topic
+
+  $ mkcommit c_dH0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ hg up 'desc("c_dG0")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dI0
+  $ hg push  -f
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: 2 heads on "default:bar"
+  (5194f5dcd542, 48a01453c1c5)
+  [255]
+
+merge works fine
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ mkcommit c_dJ0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+
+Test single head enforcing - by phase move
+------------------------------------------
+
+  $ hg -R ../single-head-server phase --public 'desc("c_dJ0")'
+  abort: 2 heads on "default"
+  (6ed1df20edb1, 678bca4de98c)
+  [255]
+
+Test single head enforcing - after rewrite
+------------------------------------------
+
+  $ hg up foo
+  switching to topic foo
+  3 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ hg commit --amend -m c_dF1
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets