Skip to content

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

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.

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