Skip to content

Check out the default branch when the clone has no origin/HEAD - #158

Merged
thalida merged 4 commits into
mainfrom
fix/clone-empty-city-no-origin-head
Aug 10, 2026
Merged

Check out the default branch when the clone has no origin/HEAD#158
thalida merged 4 commits into
mainfrom
fix/clone-empty-city-no-origin-head

Conversation

@thalida

@thalida thalida commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Loading https://github.com/PostHog/posthog rendered a completely empty city, and stayed that way on every reload.

What was wrong

The clone on disk had everything except a checkout:

HEAD                        → ref: refs/heads/.invalid   (dangling)
refs/remotes/origin/HEAD    → missing
refs/remotes/origin/master  → 7da1c469…   ✅
remote-tracking branches    → 13,114
working tree                → just .git/

refs/remotes/origin/HEAD is written by git clone and never by git fetch, so a clone that never got one has no local record of which branch is default. _resolve_default_branch read that missing ref as "the remote has no commits" and skipped the checkout — an inference that only holds for a genuinely empty remote. A repo with 13,114 branches took the same path as a brand-new empty one.

Skipping the reset isn't an error either, so the self-heal (update failed → discard clone → re-clone) never fired. Every reload re-fetched, re-decided "no commits", re-scanned 0 files.

Fixing that exposed a second bug. reset --hard moves whatever HEAD already points at; it does not repoint a broken HEAD. Files landed on disk while HEAD stayed on the dangling refs/heads/.invalid:

git rev-parse HEAD → fatal: unknown revision
git log            → fatal: your current branch appears to be broken

_run_git returns "" on failure, so the scan silently read zero commits — no dates, no ages, no trees, no timeline, while the city otherwise looked fine.

The fix

  • Missing ≠ empty. When origin/HEAD is absent, re-derive the default: ask the remote what HEAD points at, else a conventional name among the branches already fetched, else the lone branch. Return None only when nothing was fetched at all.
  • Record it back to refs/remotes/origin/HEAD so the repair is permanent.
  • _reset_to() puts HEAD on a real branch before resetting when it isn't on a usable one.
  • Guard the fresh-clone pathgit clone exits 0 with nothing checked out, so the first load of such a repo would otherwise still be empty.

Verification

The regression test reproduces the exact on-disk shape (populated clone, origin/HEAD deleted as a symref, HEAD parked on refs/heads/.invalid, tree emptied). I disabled each half of the fix in turn to confirm both assertions bite — the checkout assertion and the git log assertion each go red on their own.

Against the real repo after the fix: branch: master, root created_min: 2020-01-23 (was the checkout timestamp), 42,969 files with 0 missing creation dates, only 38 falling back to mtime, spanning 2020–2026. The manifest grew 21.9 MB → 34.7 MB, all recovered history.

403 passed, coverage 90%. ruff clean. Frontend 2,992 passed.

Also here

One unrelated commit: the building lightness ramp's top end drops 50 → 40, so recently-modified files stop washing out toward white. Happy to split it out if you'd rather.

🤖 Generated with Claude Code

thalida and others added 2 commits August 9, 2026 22:06
A repo could load as a completely empty city and stay that way. PostHog
hit it: 13,114 branches and all of master's history on disk, working tree
holding nothing but .git/, scan walking 0 files, every reload identical.

refs/remotes/origin/HEAD is written by git clone and never by git fetch,
so a clone that never got one has no local record of the default branch.
_resolve_default_branch read that missing ref as "the remote has no
commits" and skipped the checkout entirely, which is only true for a
genuinely empty remote. Skipping isn't an error either, so the self-heal
re-clone never fired.

Re-derive the default instead: ask the remote what HEAD points at, else
pick a conventional name from the branches already fetched, else the lone
branch. Only conclude the remote is empty when nothing was fetched at
all. Record the answer back to origin/HEAD so the repair sticks.

git clone also exits 0 when it can't resolve the remote's HEAD, checking
nothing out, so guard the fresh-clone path too.

Restoring the files alone left a second failure: reset --hard moves
whatever HEAD already points at rather than repointing it, so HEAD stayed
on the dangling refs/heads/.invalid. Files landed on disk while every
history command failed, and _run_git returns "" on failure, so the scan
silently read zero commits — a city with no ages, no trees and no
timeline. Put HEAD on a real branch before resetting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Recently-modified files were washing out toward white at 50. 40 keeps
them bright without losing the color.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread api/services/clone.py
thalida and others added 2 commits August 9, 2026 22:19
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thalida
thalida merged commit da8c59d into main Aug 10, 2026
1 of 2 checks passed
@thalida
thalida deleted the fix/clone-empty-city-no-origin-head branch August 10, 2026 02:38
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.

1 participant