# HG changeset patch # User Anton Shestakov # Date 1641328719 -10800 # Node ID 053a5bf508dae16cedffe9ba8aa6882c403132be # Parent f8f2ecdde4b525ff8c10cfa3113da0d8fc69e1ac discovery: port _postprocessobsolete() changes from evolve, add tests Let's make this function obsolescence-aware as well. Now we check that revisions are not obsolete first, and only then check that they are going to be common after exchange. The tests make sure that rewriting changesets doesn't confuse head computation. They rely on experimental.single-head-per-branch feature to show that remote correctly sees the expected result (i.e. either aborts the push or allows it to succeed). They are ported from evolve as well. Differential Revision: https://phab.mercurial-scm.org/D12098 diff -r f8f2ecdde4b5 -r 053a5bf508da mercurial/discovery.py --- a/mercurial/discovery.py Fri Jan 07 11:53:23 2022 +0300 +++ b/mercurial/discovery.py Tue Jan 04 23:38:39 2022 +0300 @@ -19,6 +19,7 @@ bookmarks, branchmap, error, + obsolete, phases, pycompat, scmutil, @@ -545,12 +546,16 @@ if len(localcandidate) == 1: return unknownheads | set(candidate_newhs), set() + obsrevs = obsolete.getrevs(unfi, b'obsolete') + futurenonobsolete = frozenset(futurecommon) - obsrevs + # actually process branch replacement while localcandidate: nh = localcandidate.pop() + r = torev(nh) current_branch = unfi[nh].branch() # run this check early to skip the evaluation of the whole branch - if torev(nh) in futurecommon or ispublic(torev(nh)): + if ispublic(r) or r not in obsrevs: newhs.add(nh) continue @@ -572,7 +577,7 @@ # * if we have no markers to push to obsolete it. if ( any(ispublic(r) for r in branchrevs) - or any(torev(n) in futurecommon for n in branchnodes) + or any(torev(n) in futurenonobsolete for n in branchnodes) or any(not hasoutmarker(n) for n in branchnodes) ): newhs.add(nh) diff -r f8f2ecdde4b5 -r 053a5bf508da tests/test-single-head-obsolescence-named-branch-A1.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-single-head-obsolescence-named-branch-A1.t Tue Jan 04 23:38:39 2022 +0300 @@ -0,0 +1,114 @@ +========================================= +Testing single head enforcement: Case A-1 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 1: A fully obsolete branch kept visible by another one + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z on top of them +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 2 changesets on branch default superseding the other ones +.. +.. expected-result: +.. +.. * only one head detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ● (branch Z) +.. | +.. B ø⇠◔ B' +.. | | +.. A ø⇠◔ A' +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A1 + $ cd A1 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ mkcommit B0 + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit C0 + $ mkcommit D0 + $ hg push --new-branch + pushing to $TESTTMP/A1/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 0 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mkcommit A1 + created new head + $ mkcommit B1 + $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 3 new orphan changesets + $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"` + 1 new obsolescence markers + obsoleted 1 changesets + $ hg heads + 262c8c798096 [default] (draft): B1 + cdf1dbb37a67 [Z] (draft): D0 + $ hg log -G --hidden + @ 262c8c798096 [default] (draft): B1 + | + o f6082bc4ffef [default] (draft): A1 + | + | * cdf1dbb37a67 [Z] (draft): D0 + | | + | * 3213e3e16c67 [Z] (draft): C0 + | | + | x d73caddc5533 [default] (draft): B0 + | | + | x 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + + $ hg push -r 'desc("B1")' + pushing to $TESTTMP/A1/server + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + 2 new obsolescence markers + obsoleted 2 changesets + 2 new orphan changesets diff -r f8f2ecdde4b5 -r 053a5bf508da tests/test-single-head-obsolescence-named-branch-A2.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-single-head-obsolescence-named-branch-A2.t Tue Jan 04 23:38:39 2022 +0300 @@ -0,0 +1,113 @@ +========================================= +Testing single head enforcement: Case A-2 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 2: A branch is split in two, effectively creating two heads + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z on top of them +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 1 changeset on branch default unchanged +.. * 1 changeset on branch default superseding the other ones +.. +.. expected-result: +.. +.. * two heads detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ● (branch Z) +.. | +.. B ø⇠◔ B' +.. | | +.. A ● | +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A2 + $ cd A2 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ mkcommit B0 + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit C0 + $ mkcommit D0 + $ hg push --new-branch + pushing to $TESTTMP/A2/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 0 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mkcommit B1 + created new head + $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 2 new orphan changesets + $ hg heads + 25c56d33e4c4 [default] (draft): B1 + cdf1dbb37a67 [Z] (draft): D0 + 8aaa48160adc [default] (draft): A0 + $ hg log -G --hidden + @ 25c56d33e4c4 [default] (draft): B1 + | + | * cdf1dbb37a67 [Z] (draft): D0 + | | + | * 3213e3e16c67 [Z] (draft): C0 + | | + | x d73caddc5533 [default] (draft): B0 + | | + | o 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + +(force push to make sure we get the changeset on the remote) + + $ hg push -r 'desc("B1")' --force + pushing to $TESTTMP/A2/server + searching for changes + adding changesets + adding manifests + adding file changes + transaction abort! + rollback completed + abort: rejecting multiple heads on branch "default" + (2 heads: 8aaa48160adc 25c56d33e4c4) + [255] diff -r f8f2ecdde4b5 -r 053a5bf508da tests/test-single-head-obsolescence-named-branch-A3.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-single-head-obsolescence-named-branch-A3.t Tue Jan 04 23:38:39 2022 +0300 @@ -0,0 +1,120 @@ +========================================= +Testing single head enforcement: Case A-3 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 3: Full superseding of a branch interleaved with another + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z interleaved with the other +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 2 changesets on branch default superseding the other ones +.. +.. expected-result: +.. +.. * only one head detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ø⇠◔ C' +.. | | +.. B ● | (branch Z) +.. | | +.. A ø⇠◔ A' +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A3 + $ cd A3 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit B0 + $ hg branch default --force + marked working directory as branch default + $ mkcommit C0 + created new head + $ hg branch Z --force + marked working directory as branch Z + $ mkcommit D0 + created new head + $ hg push --new-branch + pushing to $TESTTMP/A3/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 0 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mkcommit A1 + created new head + $ mkcommit C1 + $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 3 new orphan changesets + $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"` + 1 new obsolescence markers + obsoleted 1 changesets + $ hg heads + 0c76bc104656 [default] (draft): C1 + 78578c4306ce [Z] (draft): D0 + $ hg log -G --hidden + @ 0c76bc104656 [default] (draft): C1 + | + o f6082bc4ffef [default] (draft): A1 + | + | * 78578c4306ce [Z] (draft): D0 + | | + | x afc55ba2ce61 [default] (draft): C0 + | | + | * 93e5c1321ece [Z] (draft): B0 + | | + | x 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + + $ hg push -r 'desc("C1")' + pushing to $TESTTMP/A3/server + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + 2 new obsolescence markers + obsoleted 2 changesets + 2 new orphan changesets diff -r f8f2ecdde4b5 -r 053a5bf508da tests/test-single-head-obsolescence-named-branch-A4.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-single-head-obsolescence-named-branch-A4.t Tue Jan 04 23:38:39 2022 +0300 @@ -0,0 +1,117 @@ +========================================= +Testing single head enforcement: Case A-4 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 4: Partial rewrite of a branch to deinterleave it + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z interleaved with the other one +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 1 changeset on default untouched (the lower one) +.. * 1 changeset on default moved on the other one +.. +.. expected-result: +.. +.. * only one head detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ø⇠◔ C' +.. | | +.. B ● | (branch Z) +.. |/ +.. A ● +.. | +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A4 + $ cd A4 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit B0 + $ hg branch default --force + marked working directory as branch default + $ mkcommit C0 + created new head + $ hg branch Z --force + marked working directory as branch Z + $ mkcommit D0 + created new head + $ hg push --new-branch + pushing to $TESTTMP/A4/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 'desc("A0")' + 0 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ mkcommit C1 + created new head + $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + $ hg heads + cfe9ed94fa4a [default] (draft): C1 + 78578c4306ce [Z] (draft): D0 + $ hg log -G --hidden + @ cfe9ed94fa4a [default] (draft): C1 + | + | * 78578c4306ce [Z] (draft): D0 + | | + | x afc55ba2ce61 [default] (draft): C0 + | | + | o 93e5c1321ece [Z] (draft): B0 + |/ + o 8aaa48160adc [default] (draft): A0 + | + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + +(force push to make sure we get the changeset on the remote) + + $ hg push -r 'desc("C1")' --force + pushing to $TESTTMP/A4/server + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets diff -r f8f2ecdde4b5 -r 053a5bf508da tests/test-single-head-obsolescence-named-branch-A5.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-single-head-obsolescence-named-branch-A5.t Tue Jan 04 23:38:39 2022 +0300 @@ -0,0 +1,108 @@ +========================================= +Testing single head enforcement: Case A-5 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 5: Obsoleting a merge reveals two heads + +.. old-state: +.. +.. * 3 changesets on branch default (2 on their own branch + 1 merge) +.. * 1 changeset on branch Z (children of the merge) +.. +.. new-state: +.. +.. * 2 changesets on branch default (merge is obsolete) each a head +.. * 1 changeset on branch Z keeping the merge visible +.. +.. expected-result: +.. +.. * 2 heads detected (because we skip the merge) +.. +.. graph-summary: +.. +.. C ● (branch Z) +.. | +.. M ⊗ +.. |\ +.. A ● ● B +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A5 + $ cd A5 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ hg up 0 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkcommit B0 + created new head + $ hg merge + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'M0' + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit C0 + $ hg push --new-branch + pushing to $TESTTMP/A5/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 2 files + $ hg debugobsolete `getid "desc(M0)"` --record-parents + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + $ hg heads + 61c95483cc12 [Z] (draft): C0 + 74ff5441d343 [default] (draft): B0 + 8aaa48160adc [default] (draft): A0 + $ hg log -G --hidden + @ 61c95483cc12 [Z] (draft): C0 + | + x 14d3d4d41d1a [default] (draft): M0 + |\ + | o 74ff5441d343 [default] (draft): B0 + | | + o | 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + +(force push to make sure we get the changeset on the remote) + + $ hg push -r 'desc("C0")' --force + pushing to $TESTTMP/A5/server + searching for changes + no changes found + transaction abort! + rollback completed + abort: rejecting multiple heads on branch "default" + (2 heads: 8aaa48160adc 74ff5441d343) + [255] diff -r f8f2ecdde4b5 -r 053a5bf508da tests/testlib/push-checkheads-util.sh --- a/tests/testlib/push-checkheads-util.sh Fri Jan 07 11:53:23 2022 +0300 +++ b/tests/testlib/push-checkheads-util.sh Tue Jan 04 23:38:39 2022 +0300 @@ -41,4 +41,10 @@ mkcommit A0 cd .. hg clone server client + + if [ "$1" = "single-head" ]; then + echo >> "server/.hg/hgrc" "[experimental]" + echo >> "server/.hg/hgrc" "# enforce a single name per branch" + echo >> "server/.hg/hgrc" "single-head-per-branch = yes" + fi }