Mercurial > hg-stable
changeset 35196:66ecde8a704d
server: introduce a 'experimental.single-head-per-branch' option
When the option is set, the repository will reject any transaction adding
multiple heads to the same named branch.
For now we reject all scenario with multiple heads. One could imagine handling
closed branches differently. We prefer to keep things simple for now. The
feature might get extended later. Branch closing is not the best experience
Mercurial has to offer anyway.
author | Boris Feld <boris.feld@octobus.net> |
---|---|
date | Thu, 16 Nov 2017 03:52:42 +0100 |
parents | bc775b8cc020 |
children | aef2b98d9352 |
files | mercurial/configitems.py mercurial/localrepo.py mercurial/scmutil.py tests/test-single-head.t |
diffstat | 4 files changed, 223 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/configitems.py Thu Nov 16 03:52:38 2017 +0100 +++ b/mercurial/configitems.py Thu Nov 16 03:52:42 2017 +0100 @@ -535,6 +535,9 @@ coreconfigitem('experimental', 'revlogv2', default=None, ) +coreconfigitem('experimental', 'single-head-per-branch', + default=False, +) coreconfigitem('experimental', 'spacemovesdown', default=False, )
--- a/mercurial/localrepo.py Thu Nov 16 03:52:38 2017 +0100 +++ b/mercurial/localrepo.py Thu Nov 16 03:52:42 2017 +0100 @@ -1247,6 +1247,8 @@ # gating. tracktags(tr2) repo = reporef() + if repo.ui.configbool('experimental', 'single-head-per-branch'): + scmutil.enforcesinglehead(repo, tr2, desc) if hook.hashook(repo.ui, 'pretxnclose-bookmark'): for name, (old, new) in sorted(tr.changes['bookmarks'].items()): args = tr.hookargs.copy()
--- a/mercurial/scmutil.py Thu Nov 16 03:52:38 2017 +0100 +++ b/mercurial/scmutil.py Thu Nov 16 03:52:42 2017 +0100 @@ -1286,6 +1286,21 @@ first = ' '.join(short(h) for h in nodes[:maxnumnodes]) return _("%s and %s others") % (first, len(nodes) - maxnumnodes) +def enforcesinglehead(repo, tr, desc): + """check that no named branch has multiple heads""" + if desc in ('strip', 'repair'): + # skip the logic during strip + return + visible = repo.filtered('visible') + # possible improvement: we could restrict the check to affected branch + for name, heads in visible.branchmap().iteritems(): + if len(heads) > 1: + msg = _('rejecting multiple heads on branch "%s"') + msg %= name + hint = _('%d heads: %s') + hint %= (len(heads), nodesummaries(repo, heads)) + raise error.Abort(msg, hint=hint) + def wrapconvertsink(sink): """Allow extensions to wrap the sink returned by convcmd.convertsink() before it is used, whether or not the convert extension was formally loaded.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-single-head.t Thu Nov 16 03:52:42 2017 +0100 @@ -0,0 +1,203 @@ +===================== +Test workflow options +===================== + + $ . "$TESTDIR/testlib/obsmarker-common.sh" + +Test single head enforcing - Setup +============================================= + + $ cat << EOF >> $HGRCPATH + > [experimental] + > evolution = all + > EOF + $ hg init single-head-server + $ cd single-head-server + $ cat <<EOF >> .hg/hgrc + > [phases] + > publish = no + > [experimental] + > single-head-per-branch = yes + > 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 (glob) + 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 (glob) + 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 (glob) + 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: rejecting multiple heads on branch "default" + (2 heads: 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 (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files + +Test single head enforcing - after rewrite +------------------------------------------ + + $ mkcommit c_dF0 + $ hg push + pushing to $TESTTMP/single-head-server (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + $ hg commit --amend -m c_dF1 + $ hg push + pushing to $TESTTMP/single-head-server (glob) + 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 + +Check it does to interfer with strip +------------------------------------ + +setup + + $ hg branch branch_A --force + marked working directory as branch branch_A + $ mkcommit c_aG0 + created new head + $ hg update 'desc("c_dF1")' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkcommit c_dH0 + $ hg update 'desc("c_aG0")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ mkcommit c_aI0 + $ hg log -G + @ changeset: 10:49003e504178 + |\ branch: branch_A + | | tag: tip + | | parent: 8:a33fb808fb4b + | | parent: 3:840af1c6bc88 + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: c_aI0 + | | + | | o changeset: 9:fe47ea669cea + | | | parent: 7:99a2dc242c5d + | | | user: test + | | | date: Thu Jan 01 00:00:00 1970 +0000 + | | | summary: c_dH0 + | | | + | o | changeset: 8:a33fb808fb4b + | |/ branch: branch_A + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: c_aG0 + | | + | o changeset: 7:99a2dc242c5d + | | parent: 5:6ed1df20edb1 + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: c_dF1 + | | + | o changeset: 5:6ed1df20edb1 + | |\ parent: 4:9bf953aa81f6 + | | | parent: 2:286d02a6e2a2 + | | | user: test + | | | date: Thu Jan 01 00:00:00 1970 +0000 + | | | summary: c_dE0 + | | | + | | o changeset: 4:9bf953aa81f6 + | | | parent: 1:134bc3852ad2 + | | | user: test + | | | date: Thu Jan 01 00:00:00 1970 +0000 + | | | summary: c_dD0 + | | | + o | | changeset: 3:840af1c6bc88 + | | | branch: branch_A + | | | parent: 0:ea207398892e + | | | user: test + | | | date: Thu Jan 01 00:00:00 1970 +0000 + | | | summary: c_aC0 + | | | + | o | changeset: 2:286d02a6e2a2 + | |/ user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: c_dB0 + | | + | o changeset: 1:134bc3852ad2 + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: c_dA0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + +actual stripping + + $ hg strip --config extensions.strip= --rev 'desc("c_dH0")' + saved backup bundle to $TESTTMP/client/.hg/strip-backup/fe47ea669cea-a41bf5a9-backup.hg (glob) +