Skip to content

feat(wal): record HEAD at creation and heal a dangling HEAD on push - #59

Merged
yordis merged 4 commits into
mainfrom
yordis/feat-wal-record-head
Oct 31, 2026
Merged

yordis merged 4 commits into
mainfrom
yordis/feat-wal-record-head

Conversation

@yordis

@yordis yordis commented Oct 31, 2026

Copy link
Copy Markdown
Member
  • A repository whose first push does not create the host's default branch kept HEAD pointing at nothing, so clones checked out no branch and the web UI had no default view.
  • Teams whose default branch is not main had no way to say so at creation or to move HEAD afterwards without touching the bucket by hand.
  • Replays as of an older sequence must agree with live state on where HEAD points, or history views disagree with what clients see.
  • Brings in feat: record HEAD at creation, heal a dangling HEAD on push, admin route to move it tobi/walgit#99 by Jonas Grosch, cherry-picked with original authorship; its decision is recorded as D54 because D53 is taken here.

@cursor

cursor Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes core WAL publish and repository-create CAS paths where HEAD is recorded and healed; mistakes would affect default branch behavior across instances, though branch validation and extensive WAL/API tests constrain scope.

Overview
D54 makes HEAD a first-class WAL ref: its target lives in checkpoint RefSnapshot.head_target and symbolic HEAD log updates; repos with no record use the format constant IMPLICIT_HEAD (refs/heads/main), not host config.

Configuration and creation add git.default_branch (validated short branch name via branch_ref), wired into walgit repo create --default-branch, PUT /{o}/{r}?default_branch=, and the JS SDK's repo.create({ defaultBranch }). Choosing main stays a single manifest Create; any other default claims a log slot first, then lists it on the manifest Create (two store round trips, one commit).

Publish path appends an automatic HEAD retarget when a push creates refs/heads/* while HEAD still resolves to nothing—preferring the host default if that branch was created, otherwise the lexicographically first new branch—with no extra requests and recomputed on each CAS retry. RepoHandle::publish_head and admin PUT …/api/head move HEAD through the same publisher (branch must exist on every attempt; idempotent seq: 0). Symbolic HEAD updates no longer live in the ref map; replay and sim folding skip non-OID symbolic rows appropriately.

Policy/docs clarify that clients cannot push HEAD as a refname; HEAD moves are heal/admin/import only and do not emit ref events.

Reviewed by Cursor Bugbot for commit d826e4f. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 36 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 258c154f-b68c-4874-9ec9-b084adedf0b1
Commits

Reviewing files that changed from the base of the PR and between b7fa171 and d826e4f.

Files selected for processing: 25
  • AGENTS.md
  • crates/walgit-cli/src/lib.rs
  • crates/walgit-cli/src/repo.rs
  • crates/walgit-config/src/lib.rs
  • crates/walgit-config/src/refs.rs
  • crates/walgit-git/src/lib.rs
  • crates/walgit-server/src/admin.rs
  • crates/walgit-server/src/lib.rs
  • crates/walgit-server/src/policy.rs
  • crates/walgit-server/src/settings.rs
  • crates/walgit-server/src/web/v1.rs
  • crates/walgit-server/tests/api_v1.rs
  • crates/walgit-server/tests/sim.rs
  • crates/walgit-wal/src/handle.rs
  • crates/walgit-wal/src/log_reader.rs
  • crates/walgit-wal/src/publish.rs
  • crates/walgit-wal/src/registry.rs
  • crates/walgit-wal/tests/wal.rs
  • docs/CONTRACT.md
  • docs/POLICY.md
  • docs/ROUNDTRIPS.md
  • walgit.example.toml
  • web/API.md
  • web/sdk/README.md
  • web/sdk/repos.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@yordis yordis closed this Oct 31, 2026
@yordis yordis reopened this Oct 31, 2026
@yordis
yordis force-pushed the yordis/feat-wal-record-head branch 4 times, most recently from 530dad0 to 4d0935e Compare October 31, 2026 17:44
jonasgrosch-conxai and others added 4 commits October 31, 2026 14:07
…ute to move it

A repository created by a push that contains no `main` kept HEAD -> refs/heads/main
(nonexistent): the UI showed "This repository is empty" and clones warned "remote HEAD
refers to nonexistent ref" although the repository had branches. Receive-pack cannot
set HEAD, and only `walgit import` wrote symbolic HEAD updates.

HEAD stays where refs live (checkpoint RefSnapshot.head_target + symbolic HEAD updates in
log transactions); no record means walgit_git::IMPLICIT_HEAD (refs/heads/main), now a
named format constant. No proto change.

- `git.default_branch` (default "main", validated as a short Git branch name) and
  `PUT /{o}/{r}?default_branch=` name HEAD's branch for a new repository. The implicit
  target records nothing (one manifest Create, as before); any other target is the
  repository's first entry (HEAD-only REF_UPDATE): its log slot is claimed as a publish
  claims one and the manifest Create lists it, so the repository appears with its HEAD in
  one commit. Lost Create replies resolve through cas_landed. `walgit repo create
  --default-branch`, `Registry::create_with_default_branch`.
- Heal: a publish that creates refs/heads/* while HEAD resolves to nothing before and
  after it appends `HEAD -> <target>` to its own entry's transaction: default_branch if
  created, else the lexicographically first created branch. Computed on each attempt's
  CAS basis inside process_batch, so it commits with the refs that justify it. A txn that
  names HEAD keeps it; deleting HEAD's branch leaves it dangling until such a publish.
  Clients never see HEAD in report-status.
- `PUT /{o}/{r}/api[-browser]/head {"branch"}` (admin): `RepoHandle::publish_head` through
  the same publisher; the target must exist on the attempt's CAS basis (re-checked on
  every retry); idempotent (seq 0). 409 when the branch does not exist.
- Policy: the pushed-`HEAD <oid>` refusal (`ng funny refname` under every policy) comes
  from the base, conxai/receive-pack-refname (689d96e); this commit only rewords its
  comment to name the heal rule and the admin route as HEAD's symbolic writers.
- Fix: apply_txn_to_map stored a symbolic HEAD update in the ref map instead of
  head_target, so later requests of the same batch saw the old HEAD.
- Events: unchanged. Symbolic retargets already emit nothing.

Round trips (docs/ROUNDTRIPS.md): push/publish unchanged (heal is 0 extra); create
1 -> 1 for the implicit HEAD, 2 sequential for any other (once per repository); admin
HEAD route = refs sync -> log PUT -> CAS (3 rounds, like a settings publish). Budget test
healthy_request_round_trip_budgets unchanged.

Docs: AGENTS.md D54, walgit.example.toml, web/API.md, SDK (repo.create({defaultBranch}),
repo.setHead), docs/CONTRACT.md, docs/POLICY.md, docs/ROUNDTRIPS.md.

Rebased onto conxai/receive-pack-refname from 846d8ed, dropping its duplicate of the
funny-refname check and test.

Adapted: the decision is D54 here (D50-D53 are taken); create keeps this branch's reset marker and resolves a 412 or lost reply on its claimed log slot through slot_evidence, so a retried Create that already landed never loses its own segment.

(cherry picked from commit e3cab23)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
default_branch_is_chosen_at_creation_healed_by_push_and_moved_by_admin declared
two consts after statements; clippy -D warnings (items_after_statements, all
targets) failed on them. They are promoted 'static lets now, renamed so they do
not shadow the test's array-valued writer/admin.

(cherry picked from commit 9e347a4)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Every sim scenario pushes refs/heads/p<N> into a fresh repository, so since the HEAD
heal (D54) each first push's log entry also carries `HEAD -> refs/heads/p0` (a
symbolic update, no oid). check_truth folded every update's new_oid and then parsed
each as an object id: `A hash sized 0 hexadecimal characters is invalid`, 10 of 19
sim tests red under `just sim`. conxai/default-head did not touch sim.rs; the
failure is the topic's, surfaced by the full tree run. The fold now tracks object
moves only; HEAD's target is not an object to materialize.

(cherry picked from commit fe8c639)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
… one

refs_at_seq and refs_as_of started from an empty snapshot, so a repository with no
HEAD record reported no HEAD at a cut while every live copy and checkpoint says
refs/heads/main. The replay now starts from the implicit default, and the sim comment
names the renumbered decision (D54).

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis
yordis force-pushed the yordis/feat-wal-record-head branch from 4d0935e to d826e4f Compare October 31, 2026 18:07
@yordis
yordis merged commit da0335e into main Oct 31, 2026
7 checks passed
@yordis
yordis deleted the yordis/feat-wal-record-head branch October 31, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants