Imagine this scenario: you're working on a feature that requires touching lots of files, and you've got a lot of things staged, and a lot of things not staged (like debugging code, temporary comments for yourself to remember to do/undo certain things and not forget to add bits that you didn't yet have time to add), and then you see a simple one-line change that you must make, but which belongs in its own commit.
Is there any way to simply commit that without pulling everything out of the staging area that you've meticulously added, without stashing (and risking losing your careful selections of what to stage and what not to stage) and just commit that one line?
I realize that fiddling with multiple staging areas would probably make this possible, but I'm hoping there is a simpler solution than that. Some switch that lets me skip the staging area would be more convenient than mucking about with GIT_INDEX_FILE
to have 2 of them.
My ideal solution would be something sort of like this:
git commit --skip-stage --patch ./app/models/whatever.rb
If this is impossible, then I will simply stash and use --index
when I pop it back, and hope I didn't accidentally do something between stashing and popping that breaks the ability to cleanly restore the index.
Because I know someone will wonder "if you know about --index
with git stash pop
, why are you asking this question? Because it is as much about pushing the envelope of what I can do with Git as it is about solving a practical problem. Just because one solution to a problem exists doesn't mean it is the best solution or that one should stop looking for alternatives. That applies to all of life, not just Git.
There is the -a
flag, git commit -a
, and the --only
and --include
flags (which can be shortened to -o
and -i
) that allow you to do:
git commit --only file1 file2
or:
git commit --include file3
but these work by creating a new temporary index, as I outlined in my answer to your linked question.
What these do is a little bit magic, and may or may not be what you want. In particular, the temporary index file(s) that these create include .git/index.lock
, which is Git's internal temporary new index, which—if the commit succeeds—will become the (regular) index afterward. This has a some very powerful (and somewhat peculiar) consequences. Let's look at each mode of operation. The --only
variant comes close to what you want, and might sometimes be what you want, but might sometimes destroy something valuable.
git commit --only file1 file2
This starts by making a new temporary index that is copied from HEAD
. Into this new temporary index, Git copies file1
and file2
from the work-tree, as if by git add file1 file2
. So now this temporary index matches HEAD
except for the two named files.
Git also creates .git/index.lock
by copying the current index contents, i.e., the staged files. Into this temporary index, Git copies file1
and file2
as before. So this temporary index—which is different from the first one—has all your staged files except that file1
and file2
are overwritten from the work-tree.
Now Git makes a new commit, using the first temporary index—the one that mostly matches HEAD
, except for the two files. If this commit succeeds, Git updates the current branch as usual, then removes this first temporary index, and unlocks and updates the real / regular index by renaming the second temporary index, .git/index.lock
, to .git/index
. So now the normal index is the way it was before except that file1
and file2
have been replaced in it, as if by git add file1 file2
.
If you had carefully staged a different version of file1
and/or file2
, which are (of course) not in your work-tree right now—because the work-tree versions are the ones you're committing with --only
—that special version is gone, wiped out by the git add
step. If not, this is probably what you wanted!
git commit --include file3
Here, let's assume that you've git add
ed file1
earlier—perhaps a slightly different version of file1
than is now in your work-tree, so that file1
in the real / regular index differs from file1
in HEAD
.
Git starts by making a new temporary index named .git/index.lock
, copied from the real / regular index. Then, Git copies file3
into this temporary index, as if by git add file3
.
Now Git makes a new commit, using the temporary .git/index.lock
index. If this commit succeeds, Git updates the current branch as usual, then renames the temporary index from .git/index.lock
to .git/index
. So now the real / regular index still has the file1
you added earlier (which perhaps doesn't match the file1
in the work-tree but is different from the old commit's file1
), plus the file3
you added via git commit --include
.
(This mode never destroys any carefully-staged files, but also doesn't do what you describe in your question.)