Git: How to re-stage the staged files in a pre-commit hook Git: How to re-stage the staged files in a pre-commit hook git git

Git: How to re-stage the staged files in a pre-commit hook


Without the pre-commit hook context, you can get a list of the staged files with the following command:

git diff --name-only --cached

So if you want to re-index the staged files, you can use:

git diff --name-only --cached | xargs -l git add

In the pre-commit hook context, you should follow the advices of David Winterbottom and stash unstaged changes before anything else.

This technique allows you not to be worry about indexing, or alterate, a change that was not staged.So you don't have to stage all the staged files, but all the updated files:

# Stash unstaged changesgit stash -q --keep-index# Edit your project files here...# Stage updated filesgit add -u# Re-apply original unstaged changesgit stash pop -q


I liked @tzi's answer; however, in David Winterbottom's quoted article there is a edge case concern raised in the comments in which you will lose some commit history. Though, it's not as doom and gloom as the commenter makes it sound, and again is an edge case for people with problematic practices. It happens when

  1. You stage a file (version A)
  2. Edit the same file before committing (version B)
  3. Wished to commit the originally staged file (version A) and not the modified one (version B)

If your commit fails, or succeeds and pops the stash before a committing, you lose your originally staged file (v. A), as it was never commit and is overwritten (with v. B). Obviously not catastrophic, and you still have the latest edit (v. B), but it might hamper some people's workflows and (suboptimal) committing practices. To avoid this you just check the exit of your script and work some stashing tricks to revert to the original state (index has v. A and WD has v. B).

pre-commit

#!/bin/sh... # other pre-commit tasks## Stash unstaged changes, but keep the current index### Modified files in WD should be those of INDEX (v. A), everything else HEAD### Stashed was the WD of the original state (v. B)git stash save -q --keep-index "current wd"## script for editing project files### This is editing your original staged files version (v. A), since this is your WD ### (call changed files v. A')./your_script.sh## Check for exit errors of your_script.sh; on errors revert to original state ## (index has v. A and WD has v. B)RESULT=$?if [ $RESULT -ne 0 ]; thengit stash save -q "original index"git stash apply -q --index stash@{1}git stash drop -q; git stash drop -qfi[ $RESULT -ne 0 ] && exit 1## Stage your_script.sh modified files (v. A')git add -u

You should also move the git stash pop to the post-commit hook, as this is what overwrite the staged file (v. A) with the modified file (v. B) prior to committing. In practice mostly likely your script doesn't fail, but even so your git stash pop in the pre-commit hook creates a merge conflict with your script modified files (v . A') and your unstaged modifications (v. B). This then prevents the file from being committed at all, but you do have your script modified originally staged file (v. A') and your unstaged post-staging modified file(v. B) (arguably not losing any significant history assuming your_script.sh only does stuff such as indenting so v. A and v. A' are pretty much the same).

Summary: If you use best practices and commit staged files before modifying them again, the original answer is easiest and great. If you have, in my opinion, bad habits of not doing so and wanting both versions (staged and modified) in your history, you need to be careful (an argument for why this is a bad practice)! In any case, this could be a possible safety net.


Sadly, I don't think @NearHuscarl's answer above quite cuts it. It is the closest I've seen, but when you pop the stash in your post-commit hook you will still introduce a merge conflict. That's because what is stashed (even with the --keep-index flag) are both the unstaged changes (which we want) and the staged changes before you run an auto-formatter on them (which we don't want). That will create a merge conflict between the committed auto-formatted changes and the original not-yet-formatted stashed changes. As I understand it there's no easy way to tell git to ONLY stash unstaged changes. The --keep-index flag stashes unstaged changes and staged changes, while leaving the staged changes in place. That differs from the default behavior in that the staged changes will typically also be stashed away alongside the unstaged changes. But it does not stash the unstaged changes exclusively, which is really what we'd need.

I'd love to be wrong about this but I'm pretty sure there is no quick to implement solution in bash. Like any problem, it is of course solvable but it takes quite a bit of legwork. lint-staged handles this quite gracefully actually but not without putting in the work. Here's the PR where they introduced this feature and here's the corresponding discussion on the issue. Even with all that work there remain edge cases where they explicitly fail the hook and reset the WD to its original state. They simply can't always guarantee that they aren't introducing conflicts.

My take away is: if you're dealing with a javascript project, use lint-staged. If you're like me and you really want to stick to a simple bash script it might be worth just checking whether there are any partially staged files before doing anything else and aborting with a message telling the user to fix their partially staged files. Until something like lefthook introduces this feature (issue here), your other options are all pretty heinous.

But I'd love for someone to prove me wrong.