crates/gunnar-cli/tests/head_advertisement.rs
#![cfg(feature = "server")]
//! **A repository whose default branch was never created is still clonable,
//! and moving the default branch moves what is advertised.**
//!
//! # The defect, as it was found
//!
//! The laptop's roundtrip harness pushed `foot` — default branch `master` — into a
//! freshly created gunnar repository on 2026-08-06. The push succeeded:
//! `unpack ok`, `ok refs/heads/master`, and `ls-remote` listed the branch. The
//! clone back failed, and `ls-remote` had returned exactly one line and **no
//! `HEAD` line at all**.
//!
//! ```text
//! repo create ──▶ catalog.default_branch = refs/heads/main
//! HEAD on disk = refs/heads/main (init_bare)
//! push master ──▶ refs/heads/master exists, refs/heads/main never will
//! advertise ──▶ head_ref() resolves HEAD ──▶ refs/heads/main ──▶ no object
//! ──▶ the entry is DROPPED, and the clone has no branch
//! set-default-branch master
//! ──▶ catalog.default_branch = refs/heads/master ← moved
//! HEAD on disk = refs/heads/main ← did NOT
//! ```
//!
//! Two places held the default branch and only one of them ever moved. The
//! catalog is the authority; the repository's `HEAD` is a projection of it, and
//! there was no projection at all — nothing wrote `HEAD` after `init_bare`.
//! `gunnar_core::point_head_at` is that projection and
//! `ServerStores::point_head` is its only caller.
//!
//! # Why these are integration tests and not unit tests
//!
//! The bug was invisible to every unit test in the tree, and it had to be:
//! `Repos.SetDefaultBranch` answered correctly, `Repos.Get` read the value back
//! correctly, and `catalog.rs`'s own suite asserted exactly that round-trip.
//! Nothing asked the **wire** what it advertised. So the client here is stock
//! `git`, which nobody in this repository wrote.
//!
//! # And why the assertions are on files and not on exit codes
//!
//! `git clone` of a repository with no usable `HEAD` prints *"remote HEAD
//! refers to nonexistent ref, unable to checkout"*, writes an **empty**
//! worktree, and exits **0**. MEASURED 2026-08-06. A test that checked the exit
//! code of the clone would have passed against the bug.
mod common;
use common::{tools_present, Harness};
use gunnar_core::HashKind;
/// The two branch names this file pushes, spelled once each.
const MASTER: &str = "refs/heads/master";
const MAIN: &str = "refs/heads/main";
/// A local repository on `branch`, with one commit whose file names the branch.
fn local_repo_on(harness: &Harness, work: &std::path::Path, branch: &str) {
std::fs::create_dir_all(work).expect("create the working tree");
harness
.git(
work,
&[
"init",
"--quiet",
"--object-format=sha1",
&format!("--initial-branch={branch}"),
".",
],
)
.expect_ok("git init");
std::fs::write(work.join("donor.txt"), format!("pushed from {branch}\n"))
.expect("write the file");
harness
.git(work, &["add", "donor.txt"])
.expect_ok("git add");
harness
.git(
work,
&[
"-c",
"user.email=head@nordisk.invalid",
"-c",
"user.name=head",
"commit",
"--quiet",
"-m",
"the only commit",
],
)
.expect_ok("git commit");
}
/// Push `refs`, verbatim, with stock git.
fn push(harness: &Harness, work: &std::path::Path, refs: &[&str]) {
let url = harness.url("team", "project");
let mut args = vec!["push", &url];
args.extend(refs.iter().copied());
harness
.git(work, &args)
.expect_ok(&format!("push {refs:?}"));
}
/// `git ls-remote --symref` against the harness, as stock git sees it.
fn ls_remote(harness: &Harness, work: &std::path::Path) -> String {
let url = harness.url("team", "project");
harness
.git(work, &["ls-remote", "--symref", &url])
.expect_ok("ls-remote")
.stdout
.clone()
}
/// The default branch the catalog holds, straight out of `Repos.Get` over the
/// real gRPC control plane rather than out of the catalog in-process: what an
/// operator reads has to be what is asserted.
fn repo_get_default_branch(harness: &Harness) -> String {
let mut repos = gunnar_proto::repos_client::ReposClient::new(connect(harness));
harness
.runtime
.block_on(repos.get(harness.authed(
"Repos.Get",
gunnar_proto::GetRepoRequest {
path: Some(repo_path()),
},
)))
.expect("Repos.Get")
.into_inner()
.repo
.expect("a repo in the response")
.default_branch
}
/// Move the catalog's default branch the way an operator does: over the real
/// gRPC control plane, against the running server.
fn set_default_branch(harness: &Harness, branch: &str) -> String {
let mut repos = gunnar_proto::repos_client::ReposClient::new(connect(harness));
harness
.runtime
.block_on(repos.set_default_branch(harness.authed(
"Repos.SetDefaultBranch",
gunnar_proto::SetDefaultBranchRequest {
path: Some(repo_path()),
default_branch: branch.to_owned(),
},
)))
.expect("SetDefaultBranch")
.into_inner()
.repo
.expect("a repo in the response")
.default_branch
}
fn repo_path() -> gunnar_proto::RepoPath {
gunnar_proto::RepoPath {
namespace: "team".into(),
repo: "project".into(),
}
}
fn connect(harness: &Harness) -> tonic::transport::Channel {
harness
.runtime
.block_on(
tonic::transport::Endpoint::from_shared(harness.grpc_url())
.expect("a gRPC endpoint")
.connect(),
)
.expect("connect to the control plane")
}
/// A harness whose principal may also run the admin verbs.
///
/// The default grant is `Write`, which is what a push needs;
/// `Repos.SetDefaultBranch` is an admin verb. Widened here rather than in the
/// harness so every other test keeps the narrower grant it was written against.
fn harness_with_admin() -> Harness {
let harness = Harness::start_with_grpc("team", "project", HashKind::Sha1);
harness
.stores
.auth()
.grant(gunnar_core::Grant::repo(
"tester",
gunnar_core::RepoRef::new("team", "project").expect("a legal path"),
gunnar_core::Level::Admin,
))
.expect("grant admin on the repository");
harness
}
/// Clone with stock git and return the checked-out branch, asserting that the
/// worktree is not empty.
fn clone_and_check_out(harness: &Harness, dest: &std::path::Path) -> String {
let url = harness.url("team", "project");
harness
.git(
harness.dir.path(),
&["clone", "--quiet", &url, &dest.to_string_lossy()],
)
.expect_ok("clone");
// The file, not the exit code. See this module's opening note.
assert!(
dest.join("donor.txt").is_file(),
"the clone produced no worktree; a HEAD-less clone is empty and still exits 0"
);
harness
.git(dest, &["symbolic-ref", "--short", "HEAD"])
.expect_ok("the clone's HEAD")
.stdout
.trim()
.to_owned()
}
/// **Moving the default branch moves what the wire advertises.**
///
/// Both branches exist here, so `HEAD` resolves throughout and nothing repairs
/// it on the way past: the only thing that can move the advertisement is
/// `SetDefaultBranch` itself. That is the arm that used to answer correctly and
/// change nothing.
#[test]
fn setting_the_default_branch_moves_the_advertised_head() {
if !tools_present() {
eprintln!("git or ssh is missing; this test asserts on stock git and cannot be faked");
return;
}
let harness = harness_with_admin();
let work = harness.dir.path().join("donor");
local_repo_on(&harness, &work, "main");
harness
.git(&work, &["branch", "master"])
.expect_ok("a second branch");
push(
&harness,
&work,
&[
"refs/heads/main:refs/heads/main",
"refs/heads/master:refs/heads/master",
],
);
let before = ls_remote(&harness, &work);
assert!(
before.contains(&format!("ref: {MAIN}\tHEAD")),
"HEAD does not name the configured default branch before anything moved it, so this \
test is measuring the wrong starting state:\n{before}"
);
assert_eq!(
set_default_branch(&harness, MASTER),
MASTER,
"SetDefaultBranch answered with a branch that is not the one it was given"
);
assert_eq!(
repo_get_default_branch(&harness),
MASTER,
"Repos.Get did not read back what SetDefaultBranch wrote"
);
let after = ls_remote(&harness, &work);
assert!(
after.contains(&format!("ref: {MASTER}\tHEAD")),
"the advertisement still names the old default branch, so the catalog moved and the \
repository's own HEAD did not — which is the whole defect:\n{after}"
);
assert_eq!(
clone_and_check_out(&harness, &harness.dir.path().join("cloned-back")),
"master",
"the clone checked out something other than the remote's new default branch"
);
}
/// **A `master` donor pushed into a `main` repository is clonable with no
/// second command at all.**
///
/// `foot`, `ripgrep` and `mdBook` are this shape. Nothing between the
/// successful push and the empty checkout used to say a word about it.
#[test]
fn a_push_into_a_repository_whose_default_branch_does_not_exist_is_still_clonable() {
if !tools_present() {
eprintln!("git or ssh is missing; this test asserts on stock git and cannot be faked");
return;
}
let harness = harness_with_admin();
let work = harness.dir.path().join("donor");
local_repo_on(&harness, &work, "master");
// `repo create` gave this repository `refs/heads/main`, and nothing here
// ever creates that branch or calls `SetDefaultBranch`.
assert_eq!(repo_get_default_branch(&harness), MAIN);
push(&harness, &work, &["refs/heads/master:refs/heads/master"]);
let after = ls_remote(&harness, &work);
assert!(
after.contains(&format!("ref: {MASTER}\tHEAD")),
"the pushed branch was not adopted, so HEAD still names a branch that does not exist \
and nothing can clone this repository:\n{after}"
);
assert_eq!(
repo_get_default_branch(&harness),
MASTER,
"the advertisement was repaired and the catalog was not; those two must never be \
allowed to disagree again, which is the whole point of the fix"
);
assert_eq!(
clone_and_check_out(&harness, &harness.dir.path().join("cloned-back")),
"master"
);
}
/// Adoption is narrow: it must never touch a repository that already works.
///
/// The scary reading of the test above is "a push can rename my default
/// branch". It cannot. The condition is that `HEAD` resolves to nothing after
/// the push, which is only true of a repository nobody can clone.
#[test]
fn a_push_never_changes_a_default_branch_that_exists() {
if !tools_present() {
eprintln!("git or ssh is missing; this test asserts on stock git and cannot be faked");
return;
}
let harness = harness_with_admin();
let work = harness.dir.path().join("donor");
local_repo_on(&harness, &work, "main");
push(&harness, &work, &["refs/heads/main:refs/heads/main"]);
assert_eq!(repo_get_default_branch(&harness), MAIN);
// Every later push, of a branch and of a tag, on a repository whose default
// branch exists.
harness
.git(&work, &["branch", "feature"])
.expect_ok("a feature branch");
harness.git(&work, &["tag", "v1"]).expect_ok("a tag");
push(&harness, &work, &["refs/heads/feature:refs/heads/feature"]);
push(&harness, &work, &["refs/tags/v1:refs/tags/v1"]);
assert_eq!(
repo_get_default_branch(&harness),
MAIN,
"a push moved the default branch of a repository whose default branch already existed"
);
let after = ls_remote(&harness, &work);
assert!(
after.contains(&format!("ref: {MAIN}\tHEAD")),
"the advertised HEAD moved off the configured default branch:\n{after}"
);
}
/// The repository's own `HEAD` is repaired the next time the server opens it,
/// with no migration step and nobody asking.
///
/// This is the arm that matters for the repositories that were pushed **before**
/// the fix: `set-default-branch` had already been run on them, so the catalog is
/// right and only the repository's own `HEAD` is wrong, and nothing would ever
/// call `SetDefaultBranch` on them a second time.
///
/// # 🔴 What this test used to break, and why that stopped being `HEAD`
///
/// It wrote `<git_dir>/HEAD` with `std::fs::write` and then asserted that file
/// came back saying `master`. That is packfile-engine reasoning and it has been
/// wrong since `e31ab8f4` put the server back on znippy-git storage: on the
/// engine that ships, `HEAD` is a **row in the ref log** — read by
/// `GitServe::head` off the same fold `GitOps::refs` reads, written by
/// `set_head` / `set_head_projected` — and no code path anywhere consults a
/// file of that name. So the old fixture broke an inert file: the store still
/// said `master`, `point_head_at` saw nothing to repair and correctly did
/// nothing, the advertisement was right for the whole test, and the only thing
/// that failed was the assertion about the file. **The premise was wrong, not
/// the server.**
///
/// The same property, through what the engine really has: break `HEAD` in the
/// store, close the handle, and re-open it. The order below is deliberate — the
/// store's own `head()` is asserted **before** any wire traffic, so a repair
/// that only happened on the advertisement path cannot satisfy it, and the
/// advertisement is asserted after, so a repair that never reached the wire
/// cannot either. That pair is what "somewhere other than the one writer" was
/// reaching for.
#[test]
fn a_repository_whose_head_was_left_wrong_on_disk_is_repaired_when_it_is_opened() {
use gunnar_store::git_storage_trait::GitServe;
if !tools_present() {
eprintln!("git or ssh is missing; this test asserts on stock git and cannot be faked");
return;
}
let harness = harness_with_admin();
let work = harness.dir.path().join("donor");
local_repo_on(&harness, &work, "master");
push(&harness, &work, &["refs/heads/master:refs/heads/master"]);
assert_eq!(
repo_get_default_branch(&harness),
MASTER,
"the setup for this test never got a catalog that says `master`, so breaking HEAD by \
hand below would not produce the disagreement it is written to detect"
);
// Put the repository back into the broken state by hand: the catalog says
// `master`, the repository's `HEAD` says `main`. That is exactly what every
// repository created by a pre-fix gunnar looks like, and writing it is the
// only way to get one, since this build cannot produce it any more.
let path = gunnar_core::RepoRef::new("team", "project").expect("a legal path");
let repo = harness
.stores
.catalog()
.get(&path)
.expect("read the catalogue")
.expect("the repository is catalogued");
{
// Opening REPAIRS, so the break has to happen after the handle exists
// and the handle has to be dropped before the reopen below.
let store = harness
.stores
.repo_store(&repo)
.expect("open the repository");
GitServe::set_head(store.serve(), MAIN).expect("point HEAD at the wrong branch");
// …and the break TOOK. Without this, a `set_head` that silently did
// nothing would leave HEAD already correct and every assertion below
// would pass with nothing having been repaired.
assert_eq!(
GitServe::head(store.serve())
.expect("read HEAD back")
.and_then(|row| row.symref_target)
.as_deref(),
Some(MAIN),
"the fixture could not break HEAD, so this test measures nothing"
);
}
harness.stores.close_repo(&path);
// ── the one writer, on open, and BEFORE anything asks over the wire ────
let repaired = {
let store = harness
.stores
.repo_store(&repo)
.expect("re-open the repository");
GitServe::head(store.serve())
.expect("read HEAD")
.and_then(|row| row.symref_target)
};
assert_eq!(
repaired.as_deref(),
Some(MASTER),
"re-opening the repository did not repair a HEAD that disagreed with the catalog, so a \
repository broken by an older gunnar stays broken"
);
// ── and it reaches the wire, which is where the 2026-08-06 defect showed ─
let after = ls_remote(&harness, &work);
assert!(
after.contains(&format!("ref: {MASTER}\tHEAD")),
"the store's HEAD was repaired but the advertisement did not follow, which means the \
wire reads the default branch from somewhere other than the one writer:\n{after}"
);
}