[PATCH 11/15] remote: reject duplicate destinations on an empty remote
- From
Jon Simons <jon@jonsimons.org>
- Date
- Oct 9, 2026, 19:29 UTC
- Message-ID
- <20261009192953.81794-12-jon@jonsimons.org>
- In-Reply-To
- <20261009192953.81794-1-jon@jonsimons.org>
match_explicit_refs() passes the head of the remote ref list, 'dst', to match_explicit() for every refspec. match_explicit() searches that list via count_refspec_match() and, when nothing matches, appends a new destination through 'dst_tail'.
This works when the remote already has refs: 'dst' is non-NULL, so each loop iteration walks from the same head and finds destinations linked at the tail.
This fails when the remote is empty and 'dst' is NULL. match_push_refs() computes 'dst_tail' with tail_ref(), which for an empty list is the head slot itself: so the first link writes through '*dst'. But each loop iteration still provides the original NULL to match_explicit(), with the result that newly-linked destinations are not seen.
The behavior dates back to f88395ac23 (Renaming push., 2005-08-03), where match_explicit_refs() searches the destination list from the 'dst' it was given while appending new destinations through 'dst_tail'.
Pass the remote refs head by pointer in match_explicit_refs() so that each loop iteration re-reads '*dst' and detects a destination created by an earlier refspec.
With this, failing tests in t5408, t5516 now pass and are toggled to test_expect_success.
Signed-off-by: Jon Simons <jon@jonsimons.org> --- remote.c | 6 +++--- t/t5408-send-pack-stdin.sh | 6 +++--- t/t5516-fetch-push.sh | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/remote.c b/remote.c index 91d35b37fe..f4cf63b756 100644 --- a/remote.c +++ b/remote.c @@ -1416,12 +1416,12 @@ static int match_explicit(struct ref *src, struct ref *dst, return ret; } -static int match_explicit_refs(struct ref *src, struct ref *dst, +static int match_explicit_refs(struct ref *src, struct ref **dst, struct ref ***dst_tail, struct refspec *rs) { int i, errs; for (i = errs = 0; i < rs->nr; i++) - errs += match_explicit(src, dst, dst_tail, &rs->items[i]); + errs += match_explicit(src, *dst, dst_tail, &rs->items[i]); return errs; } @@ -1673,7 +1673,7 @@ int match_push_refs(struct ref *src, struct ref **dst, if (!rs->nr) refspec_append(rs, ":"); - errs = match_explicit_refs(src, *dst, &dst_tail, rs); + errs = match_explicit_refs(src, dst, &dst_tail, rs); /* pick the remainder */ for (ref = src; ref; ref = ref->next) { diff --git a/t/t5408-send-pack-stdin.sh b/t/t5408-send-pack-stdin.sh index 0321519f21..1e34880323 100755 --- a/t/t5408-send-pack-stdin.sh +++ b/t/t5408-send-pack-stdin.sh @@ -97,21 +97,21 @@ test_expect_success '--stdin refs are sent after cmdline refs' ' verify_push A bar ' -test_expect_failure 'two cmdline refs for the same destination are rejected' ' +test_expect_success 'two cmdline refs for the same destination are rejected' ' clear_remote && test_must_fail git send-pack remote.git A:foo B:foo 2>err && test_grep "dst ref refs/heads/foo receives from more than one src" err && test_must_fail git --git-dir=remote.git rev-parse foo ' -test_expect_failure 'three cmdline refs for the same destination are rejected' ' +test_expect_success 'three cmdline refs for the same destination are rejected' ' clear_remote && test_must_fail git send-pack remote.git A:foo B:foo C:foo 2>err && test_grep "dst ref refs/heads/foo receives from more than one src" err && test_must_fail git --git-dir=remote.git rev-parse foo ' -test_expect_failure 'cmdline and --stdin refs for the same destination are rejected' ' +test_expect_success 'cmdline and --stdin refs for the same destination are rejected' ' clear_remote && echo A:foo >input && test_must_fail git send-pack remote.git --stdin B:foo <input 2>err && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index e150246577..2f46ddc9ba 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -412,25 +412,25 @@ test_expect_success 'push two refspecs targeting the same ref fails' ' test_grep "dst ref refs/heads/frotz receives from more than one src" err ' -test_expect_failure 'push --dry-run two refspecs creating the same ref fails on empty repo' ' +test_expect_success 'push --dry-run two refspecs creating the same ref fails on empty repo' ' mk_empty testrepo && test_must_fail git push --dry-run testrepo main:frotz main:frotz 2>err && test_grep "dst ref refs/heads/frotz receives from more than one src" err ' -test_expect_failure 'push two refspecs creating the same ref fails on empty repo' ' +test_expect_success 'push two refspecs creating the same ref fails on empty repo' ' mk_empty testrepo && test_must_fail git push testrepo main:frotz main:frotz 2>err && test_grep "dst ref refs/heads/frotz receives from more than one src" err ' -test_expect_failure 'push --dry-run abbreviated then full refspec creating the same ref fails on empty repo' ' +test_expect_success 'push --dry-run abbreviated then full refspec creating the same ref fails on empty repo' ' mk_empty testrepo && test_must_fail git push --dry-run testrepo main:frotz main:refs/heads/frotz 2>err && test_grep "dst ref refs/heads/frotz receives from more than one src" err ' -test_expect_failure 'push abbreviated then full refspec creating the same ref fails on empty repo' ' +test_expect_success 'push abbreviated then full refspec creating the same ref fails on empty repo' ' mk_empty testrepo && test_must_fail git push testrepo main:frotz main:refs/heads/frotz 2>err && test_grep "dst ref refs/heads/frotz receives from more than one src" err
-- 2.55.0