Repository navigation
Make HEAD a symbolic ref that tracks the current branch - #69
Merged
Merged
Conversation
Previously HEAD stored a bare commit hash, with no notion of a "current
branch". commit_index advanced HEAD and then every branch that happened to
point at the old commit, so committing on one branch silently moved its
siblings; checkout, reset and merge had the same ambiguity.
Model the ref graph the way Git does. A ref is now either direct
(commit_hash set) or symbolic (symbolic_target set, naming another ref).
HEAD is normally symbolic and points at the current branch (e.g. 'master');
a detached HEAD is direct. A CHECK enforces exactly one of the two columns.
New helpers in the schema fragment:
- resolve_ref(repo, name) - follow symbolic refs to a commit hash
- current_branch(repo) - the branch HEAD tracks, or NULL if detached
- set_head_symbolic / set_head_detached
- advance_head(repo, commit) - move the current branch (or detached HEAD)
Rewrites so only the current branch moves:
- init_repository: HEAD is symbolic -> master
- commit_index / cherry_pick / revert: advance_head after committing
- reset_soft / reset_mixed: advance_head
- merge_branches: advance_head for 'HEAD', else move the named branch
- checkout_branch: attach HEAD symbolically (or create then attach)
- create_branch: create at resolved HEAD without moving HEAD
- list_branches: is_current compares against current_branch()
All other HEAD readers (log, status, tag, blame, grep, whatchanged,
archive, stash, push) now resolve through resolve_ref, and gc ignores the
symbolic HEAD's NULL commit_hash when collecting roots.
Tests: add symbolic_head_test.sql (commit advances only the current branch,
siblings and master stay put, detached HEAD advances itself). Existing
assertions that read HEAD's commit_hash column now use resolve_ref('HEAD').
seanwevans
force-pushed
the
claude/symbolic-head
branch
from
July 5, 2026 16:22
7f7a4a0 to
4148448
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the data-model flaw where committing on one branch silently moved others. Previously
HEADstored a bare commit hash with no notion of a current branch, socommit_indexadvancedHEADand every branch that happened to point at the old commit.checkout,reset, andmergeshared the same ambiguity.This models the ref graph the way Git does.
Model
A ref is now either direct (
commit_hashset) or symbolic (symbolic_targetset, naming another ref).HEADis normally symbolic and points at the current branch (e.g.master); a detached HEAD is direct. ACHECKenforces exactly one of the two columns.New helpers (in the schema fragment):
resolve_ref(repo, name)— follow symbolic refs to a commit hashcurrent_branch(repo)— the branch HEAD tracks, orNULLif detachedset_head_symbolic/set_head_detachedadvance_head(repo, commit)— move the current branch (or the detached HEAD)Behaviour changes (only the current branch moves)
init_repository: HEAD is symbolic →mastercommit_index/cherry_pick/revert:advance_headafter committingreset_soft/reset_mixed:advance_headmerge_branches:advance_headfor the defaultHEADtarget, else move the named branchcheckout_branch: attach HEAD symbolically (creating the branch first with-c)create_branch: create at resolved HEAD without moving HEADlist_branches:is_currentcompares againstcurrent_branch(), so branches sharing a commit are no longer all "current"All other HEAD readers (
get_log,get_status,create_tag,blame,grep,whatchanged,create_archive,stash_save,push) resolve throughresolve_ref, andgcignores the symbolic HEAD'sNULLcommit_hashwhen collecting reachability roots.Tests
test/sql/symbolic_head_test.sql(12 assertions): committing advances only the current branch while a sibling branch andmasterstay put;checkoutswitches the current branch; a detached HEAD advances itself and leaves every branch untouched.commit_hashcolumn were updated to useresolve_ref('HEAD')(that column isNULLfor a symbolic HEAD).make test-coreandRUN_INTEGRATION=1 RUN_PERF=1 make test-all— PASS.Notes for reviewers
refs(nullablecommit_hash, newsymbolic_target, new CHECK). FreshCREATE EXTENSIONreflects the new model; there is noALTER EXTENSION … UPDATEmigration script since 0.4.0 is regenerated from fragments and has no released consumers. Happy to add a versioned upgrade path if you'd prefer.mainand lightly overlaps the still-open fix PRs Fix "column reference is ambiguous" errors in whatchanged, merge_trees and GPG verification #65 (whatchanged), Implement revert's missing apply_inverse_diff helper #66 (revert), and Fix create_archive: operator precedence, malformed zip bytea literals, repo scoping #68 (create_archive) in the specific HEAD-resolution blocks. If those merge first I'll rebase and regenerate; the underlying changes don't conflict semantically.🤖 Generated with Claude Code
Generated by Claude Code