Skip to content

Unstaged changes in unrelated files are discarded by git checkout . when restoring hidden changes fails #1480

Closed

Description

@PocoPota

Description

With a job that rewrites files — a formatter or linter run with --write / --fix — the hidden unstaged changes of a partially staged file can no longer be re-applied afterwards. When that happens, lefthook reverts the whole worktree with git checkout . — RevertAllUnstagedChanges() in guard.go.

Only partially staged files are covered by the patch that gets re-applied afterwards, so unstaged changes in every other file are silently discarded. The lefthook auto backup stash is dropped in the same run, so nothing is left to recover from.

Introduced in #1417. The abort-and-restore behaviour itself is a clear improvement — only the revert is wider than the backup. 2.1.6 and earlier don't touch the worktree here, but have #1369 instead.

lefthook.yml

pre-commit:
  jobs:
    - name: formatter
      glob: "**/*.txt"
      run: perl -pi -e 's/foo/formatted/' {staged_files}
      stage_fixed: true

Commands to reproduce

Stage a hunk in a.txt containing foo, then edit that same line again without staging it. Leave an unstaged edit in an unrelated b.txt — already tracked, not a new file, since git checkout . leaves untracked files alone. Commit: the job rewrites foo, re-applying the saved patch fails, and the hook aborts as designed — a.txt is restored correctly, but b.txt is reverted too.

Lefthook version

2.1.10 (also reproduces on 2.1.7)

Possible solution

I assume the wide revert is deliberate: jobs can rewrite files outside the partially staged set, and those changes have to be undone too, so narrowing it to the hidden files alone would leave them behind. That's the tension — the revert has to cover everything the jobs touched, while the patch replayed right after only restores the partially staged ones.

Two things in scope at that point look relevant: withHiddenUnstagedChanges still holds the partiallyStagedFiles list it passed to RevertUnstagedChanges when hiding, and the lefthook auto backup stash created a few lines earlier is a full pre-hook snapshot that is currently only ever dropped, never read back. Happy to test a fix against the case above.

Activity

  1. kucharzyk-sebastian commented on Aug 14, 2026

    @kucharzyk-sebastian

    We've been hit by this issue quite severely. I'd love to see the fix being merged.

  2. rujorgensen commented on Sep 8, 2026

    @rujorgensen

    Got bitten by this today on 2.1.12, so here's a data point from a real repo.

    I was splitting a repo-wide formatting pass into several commits: 128 files partially staged, ~2000 more with unstaged-only changes, and a pre-commit job running our formatter on {staged_files}. Ran it again afterwards with LEFTHOOK_VERBOSE=1 in a throwaway clone of the same state, and it's exactly what the issue describes:

    • stash create/store, patch the unstaged half of the 128 files, checkout --force them
    • formatter rewrites those 128 files
    • git apply --check on the patch fails (fair enough)
    • git checkout . -> all ~2000 unrelated files reverted
    • patch re-applies fine, so the 128 partially staged files look untouched, which is what makes it so confusing
    • git stash drop of the backup, so nothing left in stash list

    We only got the work back because the backup stash was still a dangling commit (git fsck --unreachable). Would be nice if the backup survived on that failure path and its hash was printed, even once the revert is scoped to the hidden files.

    #1483 looks like it covers it. Happy to run it against the same state if that helps.

  3. rujorgensen commented on Oct 1, 2026

    @rujorgensen

    Bump

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions