fix(pull): unborn branch misread as dirty, diff-index vs unresolvable HEAD refuses the first sync #373
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sixfold-space/madtea#373
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What happens
madtea pullrefuses to pull into a freshly-cloned repo whose local branch is still unborn (zero commits) while it already tracks a populatedorigin/main:The tree is definitionally clean: the branch has no commits, nothing is staged or modified, no untracked files. The refusal names zero files, and that emptiness is the tell.
Root cause
Pullgates on the dirty-tree precheck atinternal/service/git/pull.go:133:HasUncommittedChangesDir(internal/git/state.go:573):This relies on
git diff-index --quiet HEADexiting 1 to signal "dirty". But on an unborn branchHEADdoes not resolve, so the command exits 128 (fatal: ambiguous argument 'HEAD': unknown revision), a genuine error rather than the exit-1 diff signal.err != nilcollapses both into "dirty", so the guard reports a repo with no commits as having uncommitted changes.UncommittedFilesDir(internal/git/state.go:586) hits the samediff-index HEADerror and returns nil, which is why the refusal cannot name any files.Reproduce
Workaround:
git merge --ff-only origin/main(the ref is already fetched) brings the tree in; the nextmadtea pullworks normally once HEAD exists. This is exactly the "first checkout into sync" path, so the refusal blocks a legitimate, common state.Fix direction
Distinguish an unborn HEAD (clean: no tracked change is possible against a nonexistent HEAD) from a real diff before the blanket
err != nil, e.g. short-circuit when HEAD does not verify:Caveat:
HasUncommittedChangesDiris shared by the branch-create and worktree "stranger's uncommitted work" guards (internal/mcp/tools_localgit.go:184,internal/service/git/worktree.go:34), and the comment already attools_localgit.go:178notes this unborn-HEAD error case. An unborn branch with a staged index is a real (if rare) dirty state those guards may still want to catch, so treating "unborn implies clean" is too blunt if it drops that. A safer shape: when HEAD is unborn, fall back togit diff --cached --quiet(which diffs the index against the empty tree and correctly reports staged-but-uncommitted files) instead of treating thediff-index HEADerror as dirty. Whatever the approach, the pull precheck must allow the first fast-forward into an unborn checkout.