git add -p
does not read a file-of-patches. Instead, it takes a file that is already under Git's source code control, and compares the index version of that file to the work-tree version of that file. Whatever is different, that's what git add -p
lets you interactively patch into the index.
Hence, instead of what you were doing, you would have had to save every modified file somewhere, switch commits, restore all the modified files into your work-tree, and then run git add -p
(with no arguments at all). But this is generally quite the wrong way to go—it loses everyone else's changes.
Instead of this sequence, here is what I would recommend, and why:
git pull
(before [starting work])
Split this into git fetch; git merge
, just so you know precisely what git pull
is doing to you. (One might say for you, but in the case of git pull
, it tends to do it to you instead of for you. My experience with the bad old days of Git 1.5 may be showing here. :-) git pull
used to break repositories badly in some corner cases.)
I actually use git merge --ff-only
here just to be doubly sure that nothing odd has occurred. (I have an alias, git mff
, that runs git merge --ff-only
, for short. Sometimes I use a few other tricks, but that's plenty for now.)
- changed some code
Keep this part. :-)
git diff > ~/my_diff
Don't bother with this. Instead, run git add
and git commit
. If you made a lot of changes in step 2, you can make a new branch name first:
git checkout -b mostly-ready # or some more suitable name
git add -u # or `.` or `-a` if you prefer
git status # check for unexpected new files, or untracked files
# make any adjustments you like here
git commit
git checkout - # go back to previous branch
git stash
This is no longer required. All git stash
does is make commits that are on no branch; and by doing the above, or a simpler git commit
, you have made a commit that is on some more suitable branch.
git pull
Split this into git fetch; git merge
or, if you made the commit on the branch itself rather than on a side branch, git fetch; git rebase
. (You can run git pull --rebase
to do the fetch-and-rebase sequence, but again, I think it's better to keep the two steps separate.)
git stash pop
Since you don't have a stash, there will be nothing to pop here. If you made the commit in place and used git rebase
, rebase will have the merge conflicts, if there were any, from transporting your commit forward. Otherwise your commit is on your side branch mostly-ready
, or whatever you called it, and now you can run:
git cherry-pick -n mostly-ready
The -n
tells Git not to commit the result. This gives you a chance to look it over and use git add -p
if you like. If there are merge conflicts, the -n
is redundant as Git won't be able to commit the result anyway. Note that there's an important caveat: some of your changes may already be staged.
What staged means, precisely
It's important to remember, here, that in Git, there are three copies of every file. Let's take a file named README.txt
for instance. The three copies are:
HEAD:README.txt
(try git show HEAD:README.txt
). This is a committed version of the file, specifically the one in the current commit (aka HEAD
aka @
).
This copy is frozen into a commit. Being in a commit, it is permanent (well, as long as the commit exists) and 100% read-only. It's stored internally in a Git-only, compressed format—sometimes very compressed. Few if any other programs can deal with it; it's basically a Git-only format, so you have to use git show
or git checkout
to get at it.
README.txt
. This is the work-tree version.
This is just an ordinary file. You can do anything you like with it. Git does not really care that much about this copy; Git just put it into your work-tree so that you can access it and work on / with it.
:0:README.txt
. This is the index version.
The index, which is also called the staging area or sometimes the cache, holds a version that Git does care about, but that is in the special, compressed, Git-only format. This file is ready to go into the next commit you will make—but unlike the frozen, read-only, committed version, you can overwrite the index copy.
That's what git add
does: git add README.txt
overwrites the index version, :0:README.txt
, with the work-tree version, README.txt
. This is when Git tells you that there are changes staged for commit. It's not that README.txt
has suddenly appeared in the staging area: it was there all along. It's just that now that you copied a modified README.txt
over top of the old :0:README.txt
, now the version in the staging area is different from the version frozen into the HEAD
commit.
So, the thing to remember here is that you have to work on your work-tree files: you can edit these and so whatever you like. But once you have done that, you then have to copy the work-tree version over top of the index version. Some Git commands, like git checkout
, start out by making the frozen committed version and index version the same. Some, like git cherry-pick
, can change the index version (usually while also changing the work-tree version). Using git add
overwrites the index version completely. Using git reset
, you can overwrite the staged version with the frozen HEAD
version:
git reset README.txt
means copy HEAD:README.txt
over top of :0:README.txt
, without touching the work-tree README.txt
at all.
When you do eventually run git commit
, what this does is really pretty simple: it freezes all the index versions into a new commit. They're already in the right form, sitting there in the index / staging area. The files in the work-tree are not important; only the files in the index matter!
Two very useful git diff
forms
Running:
git diff --cached
compares, for every file, the HEAD
(frozen) version to the index version, and shows you the difference. That's what's different in the commit you're about to make.
Running:
git diff
compares the index (ready-to-commit) version of every file to the work-tree version, and shows you the difference. That's what's different in the commit you could make, vs what you could git add
.
Running:
git status
runs both of these diffs for you, and then shows you the file names (only) that are different. The files that are different between HEAD
and index are staged for commit, and the files that are different between index and work-tree are not staged for commit.