<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Thu, Nov 16, 2017 at 8:16 AM, Boris Feld <span dir="ltr"><<a href="mailto:boris.feld@octobus.net" target="_blank">boris.feld@octobus.net</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"># HG changeset patch<br>
# User Boris Feld <<a href="mailto:boris.feld@octobus.net">boris.feld@octobus.net</a>><br>
# Date 1510800762 -3600<br>
#      Thu Nov 16 03:52:42 2017 +0100<br>
# Node ID 921ee05859ea9fc17f5a123baa16c2<wbr>bbe2c49001<br>
# Parent  c46c0f855a64868b90260da603769c<wbr>8bc1ebdcf7<br>
# EXP-Topic single-heads<br>
# Available At <a href="https://bitbucket.org/octobus/mercurial-devel/" rel="noreferrer" target="_blank">https://bitbucket.org/octobus/<wbr>mercurial-devel/</a><br>
#              hg pull <a href="https://bitbucket.org/octobus/mercurial-devel/" rel="noreferrer" target="_blank">https://bitbucket.org/octobus/<wbr>mercurial-devel/</a> -r 921ee05859ea<br>
server: introduce a 'server.single-head' option<br></blockquote><div><br></div><div>I like this feature and feel it should be supported by Mercurial out of the box. (Mozilla has a hook checking for single heads and we would love to delete custom code and use a core feature.)</div><div><br></div><div>I don't want to scope bloat you and say no to a good patch. However, this feature is the historical territory of hooks. I value having this feature in the core distribution. But I feel that a "built-in hooks" mechanism is the better vehicle for this feature. Only a few weeks ago Augie and I were discussing the idea of shipping some common, easy-to-enable hooks with Mercurial. There seems to be some level of agreement that the feature should exist. What we don't have is a mechanism to easily define them.</div><div><br></div><div>I would feel better if the config option naming for this were related to hooks somehow. If we do that, we can refactor the code to leverage hooks (or some hooks-like mechanism) later while preserving the user-facing API. Does that sound reasonable?<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
When the option is set, the repository will reject any transaction adding<br>
multiple heads to the same named branch.<br>
<br>
For now we reject all scenario with multiple heads. One could imagine handling<br>
closed branches differently. We prefer to keep things simple for now. The<br>
feature might get extended later. Branch closing is not the best experience<br>
Mercurial has to offer anyway.<br>
<br>
diff --git a/mercurial/configitems.py b/mercurial/configitems.py<br>
--- a/mercurial/configitems.py<br>
+++ b/mercurial/configitems.py<br>
@@ -787,6 +787,9 @@ coreconfigitem('server', 'validate',<br>
 coreconfigitem('server', 'zliblevel',<br>
     default=-1,<br>
 )<br>
+coreconfigitem('server', 'single-head',<br>
+    default=False,<br>
+)<br>
 coreconfigitem('share', 'pool',<br>
     default=None,<br>
 )<br>
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt<br>
--- a/mercurial/help/config.txt<br>
+++ b/mercurial/help/config.txt<br>
@@ -1723,6 +1723,10 @@ Alias definitions for revsets. See :hg:`<br>
<br>
 Controls generic server settings.<br>
<br>
+``single-head``<br>
+    Force the repository to always contains a single head for each named<br>
+    branches. Any push/pull commit adding a new heads will be rejected.<br>
+<br>
 ``compressionengines``<br>
     List of compression engines and their relative priority to advertise<br>
     to clients.<br>
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py<br>
--- a/mercurial/localrepo.py<br>
+++ b/mercurial/localrepo.py<br>
@@ -1244,6 +1244,8 @@ class localrepository(object):<br>
             # gating.<br>
             tracktags(tr2)<br>
             repo = reporef()<br>
+            if repo.ui.configbool('server', 'single-head'):<br>
+                scmutil.enforcesinglehead(<wbr>repo, tr2)<br></blockquote><div><br></div><div>Using "server" in the context of localrepo doesn't feel correct. This code is running as part of the generic transaction closing code, which means it applies to *all* repo contexts. I think the config option section should reflect that.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
             if hook.hashook(repo.ui, 'pretxnclose-bookmark'):<br>
                 for name, (old, new) in sorted(tr.changes['bookmarks']<wbr>.items()):<br>
                     args = tr.hookargs.copy()<br>
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py<br>
--- a/mercurial/scmutil.py<br>
+++ b/mercurial/scmutil.py<br>
@@ -1277,3 +1277,15 @@ def nodesummaries(repo, nodes, maxnumnod<br>
         return ' '.join(short(h) for h in nodes)<br>
     first = ' '.join(short(h) for h in nodes[:maxnumnodes])<br>
     return _("%s and %s others") % (first, len(nodes) - maxnumnodes)<br>
+<br>
+def enforcesinglehead(repo, tr):<br>
+    """check that no named branch has multiple heads"""<br>
+    visible = repo.filtered('visible')<br>
+    # possible improvement: we could restrict the check to affected branch<br>
+    for name, heads in visible.branchmap().iteritems(<wbr>):<br>
+        if len(heads) > 1:<br>
+            msg = _('rejecting multiple heads on branch "%s"')<br>
+            msg %= name<br>
+            hint = _('%d heads: %s')<br>
+            hint %= (len(heads), nodesummaries(repo, heads))<br>
+            raise error.Abort(msg, hint=hint)<br></blockquote><div><br></div><div>What happens when you `hg strip` a repo that already has multiple heads on a named branch? I /think/ that when the stripped changesets are re-applied to the repo, this code prevents them from being applied and the resulting repo is truncated and somewhere there is a backup or temp bundle containing the changesets that would have been re-applied. That's obviously bad. For this reason, many of Mozilla's "prevent changes" hooks no-op if the transaction "source" is "strip."<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
diff --git a/tests/test-single-head.t b/tests/test-single-head.t<br>
new file mode 100644<br>
--- /dev/null<br>
+++ b/tests/test-single-head.t<br>
@@ -0,0 +1,115 @@<br>
+=====================<br>
+Test workflow options<br>
+=====================<br>
+<br>
+  $ . "$TESTDIR/testlib/obsmarker-<wbr>common.sh"<br>
+<br>
+Test single head enforcing - Setup<br>
+=============================<wbr>================<br>
+<br>
+  $ cat << EOF >> $HGRCPATH<br>
+  > [experimental]<br>
+  > evolution = all<br>
+  > EOF<br>
+  $ hg init single-head-server<br>
+  $ cd single-head-server<br>
+  $ cat <<EOF >> .hg/hgrc<br>
+  > [phases]<br>
+  > publish = no<br>
+  > [server]<br>
+  > single-head = yes<br>
+  > EOF<br>
+  $ mkcommit ROOT<br>
+  $ mkcommit c_dA0<br>
+  $ cd ..<br>
+<br>
+  $ hg clone single-head-server client<br>
+  updating to branch default<br>
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved<br>
+<br>
+Test single head enforcing - with branch only<br>
+-----------------------------<wbr>----------------<br>
+<br>
+  $ cd client<br>
+<br>
+continuing the current defaultbranch<br>
+<br>
+  $ mkcommit c_dB0<br>
+  $ hg push<br>
+  pushing to $TESTTMP/single-head-server (glob)<br>
+  searching for changes<br>
+  adding changesets<br>
+  adding manifests<br>
+  adding file changes<br>
+  added 1 changesets with 1 changes to 1 files<br>
+<br>
+creating a new branch<br>
+<br>
+  $ hg up 'desc("ROOT")'<br>
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved<br>
+  $ hg branch branch_A<br>
+  marked working directory as branch branch_A<br>
+  (branches are permanent and global, did you want a bookmark?)<br>
+  $ mkcommit c_aC0<br>
+  $ hg push --new-branch<br>
+  pushing to $TESTTMP/single-head-server (glob)<br>
+  searching for changes<br>
+  adding changesets<br>
+  adding manifests<br>
+  adding file changes<br>
+  added 1 changesets with 1 changes to 1 files (+1 heads)<br>
+<br>
+Create a new head on the default branch<br>
+<br>
+  $ hg up 'desc("c_dA0")'<br>
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved<br>
+  $ mkcommit c_dD0<br>
+  created new head<br>
+  $ hg push -f<br>
+  pushing to $TESTTMP/single-head-server (glob)<br>
+  searching for changes<br>
+  adding changesets<br>
+  adding manifests<br>
+  adding file changes<br>
+  added 1 changesets with 1 changes to 1 files (+1 heads)<br>
+  transaction abort!<br>
+  rollback completed<br>
+  abort: rejecting multiple heads on branch "default"<br>
+  (2 heads: 286d02a6e2a2 9bf953aa81f6)<br>
+  [255]<br>
+<br>
+remerge them<br>
+<br>
+  $ hg merge<br>
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved<br>
+  (branch merge, don't forget to commit)<br>
+  $ mkcommit c_dE0<br>
+  $ hg push<br>
+  pushing to $TESTTMP/single-head-server (glob)<br>
+  searching for changes<br>
+  adding changesets<br>
+  adding manifests<br>
+  adding file changes<br>
+  added 2 changesets with 2 changes to 2 files<br>
+<br>
+Test single head enforcing - after rewrite<br>
+-----------------------------<wbr>-------------<br>
+<br>
+  $ mkcommit c_dF0<br>
+  $ hg push<br>
+  pushing to $TESTTMP/single-head-server (glob)<br>
+  searching for changes<br>
+  adding changesets<br>
+  adding manifests<br>
+  adding file changes<br>
+  added 1 changesets with 1 changes to 1 files<br>
+  $ hg commit --amend -m c_dF1<br>
+  $ hg push<br>
+  pushing to $TESTTMP/single-head-server (glob)<br>
+  searching for changes<br>
+  adding changesets<br>
+  adding manifests<br>
+  adding file changes<br>
+  added 1 changesets with 0 changes to 1 files (+1 heads)<br>
+  1 new obsolescence markers<br>
+  obsoleted 1 changesets<br>
______________________________<wbr>_________________<br>
Mercurial-devel mailing list<br>
<a href="mailto:Mercurial-devel@mercurial-scm.org">Mercurial-devel@mercurial-scm.<wbr>org</a><br>
<a href="https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel" rel="noreferrer" target="_blank">https://www.mercurial-scm.org/<wbr>mailman/listinfo/mercurial-<wbr>devel</a><br>
</blockquote></div><br></div></div>