TL;DR: You do not need to re-add files that are in "changes to be committed".
Git's "index"
Git has a thing called, variously, the index or the staging area, or sometimes even the cache (as in git diff --cached
). It's not very well explained, in part because it's complicated. Fortunately, the use of the index is pretty simple, except (alas) during merges. :-)
What's in the index is best described as the next commit you will make. It also helps to remember that Git always has a current commit, named HEAD
, and (except in --bare
repositories), a work-tree or working tree, which is where you have your files ready for viewing, compiling, editing, and whatever else it is you do with your files.
Initially, your work-tree, index, and HEAD
commit all match (when you first clone a repository). You can't touch files directly within the index, so you make any changes—or even create new files—in the work-tree, where you do all your work. Then, to put a modified file back into the index, you run git add
on the path, so that Git copies the modified file into the index. To put a new file into the index, you also run git add
on the path.
If you were, for some reason, to get all excited and run git commit
right now, Git would read your current index and package it up as a new commit. The new commit would have, as its work-tree snapshot, every file that is in the index now, in the state that it is now, in the index.
If that state matches the HEAD
commit, git commit
just says: "Huhwha? Why you try to make new commit now?" So presumably you've changed something in the index by this point. git status
will show you what's in the index that's different from the HEAD
commit: these are "changes to be committed".
Merging
Running git merge
sometimes reveals a secret about the index. Each entry in the index actually has four slots, which are numbered. Normally, you only ever see slot zero, which is reserved for "normal" files, ready to be committed.
Merging is intended to combine two (or sometimes more, but let's stick with two) sets of changes. For instance, perhaps you and Bob started with the same files. Then you made some changes to some files, and git add
-ed and committed new snapshots of the source. Meanwhile Bob made some changes to some files—maybe the same files, even—and also git add
-ed and committed, and now it's time to combine your changes and Bob's changes.
To perform a merge—to merge-as-a-verb some file(s)—Git first finds the merge base, i.e., the point where you and Bob were in sync. Then Git runs two git diff
s: one from that merge base to your latest commit, and one from that same merge base to Bob's latest commit. This shows "what you changed" and "what Bob changed".
Git then does its best to combine those changes (including, as in this case, figuring out and following file renames, and figuring out what to do if only one of you renamed some file—but most merge conflicts don't include file renames, unless you write a lot of Java code...). Sometimes, though, Git can't do this on its own. In that particular case, Git puts the merge work back to you, and it's at this point that you see all the slots in the index.
Index slot 1 is where Git stores the base version of the file. That's the common version, from which you both started.
Index slot 2 is the HEAD
or --ours
version of the file. (It is also sometimes called the "local" version.)
Index slot 3 is the other or --theirs
(or sometimes "remote") version of the file.
That's almost all there is to it: the three slots hold the three versions, and you end up with "unmerged paths". The file in your work-tree holds Git's initial effort to merge, along with conflict markers and some mash-up of the --ours
and --theirs
version. If you set merge.conflictstyle
to diff3
, Git will also include the parts from the base version of the file, stored in index slot 1. It's now up to you to resolve the merge.
Once you've figured out the correct resolution, and updated the work-tree version of the file, you must git add
the path. This stores the work-tree version into index slot zero, wiping out the three entries in slots 1-3. Since slot zero holds the normal, to-be-committed version, the file is now ready to commit.
For files where there were no changes at all, or just one of you changed something, or where Git thinks it correctly combined your changes and Bob's changes, those files are already updated in both work-tree and (slot zero of) index. If the result differs from the current HEAD
commit, git status
shows the file as "changes to be committed".
The last bits
I said above that this is almost all there is to it. The last few things to remember about the index slots are:
Some slots might be empty. Suppose both you and Bob added a file. In this case, you'll get an "add/add conflict". What version of the file is the common base version? There is none, of course: you both added a new file. So in this case, the base slot—slot 1—is empty.
The same happens if you delete a file and Bob changes it. In this case, the --ours
slot, slot 2, is empty, while slots 1 and 3 hold the base version and Bob's version. It's up to you to decide whether to keep Bob's version, keep the file removed, or use yet a third variant, but once again only two of the three slots are filled.
If one or both of you renames a file, the file versions in the three slots may come from files that had different names in earlier commits. For instance, your output shows:
renamed: app/OldName.php -> app/NewName.php
There was no conflict here, but if there had been, at least one of the slots for app/NewName.php
would be filled from some other commit's version of app/OldName.php
.
This mainly matters when you go to look at the old version. If, in a separate clone or work-tree, you check out one of the commits where the file is not yet renamed—or if you use git show <commit>:app/OldName.php
to view the file without checking it out—you must use the old name wherever it has the old name. But if you use git checkout --ours
or git checkout --theirs
to extract just one of these two versions into the work-tree, you must use the new name, because the index now has the file stored under the new name.
If you do use git checkout --ours
or git checkout --theirs
to get your or, respectively, Bob's version of a file, this does not resolve the file, but does clobber Git's attempt to merge them in the work-tree. If you want to restore Git's attempt to merge them, use git checkout -m
. Note that all of these overwrite the work-tree version of the file, while leaving the three unresolved index slots alone.
Weirdly, though, if you git checkout <commit-id> -- <path>
to get an old version of a file out of some specific commit, that overwrites the index: it copies the commit's version of the file to index slot zero, clobbering the slot 1-3 entries. The file now appears resolved! Again, you can restore the unresolved state with git checkout -m
. (Of course that overwrites the work-tree version.)
Similarly, if you mistakenly resolve the file with git add
when it's not quite done yet, you can git checkout -m
to unresolve it—but of course, that overwrites the work-tree version. If you're mostly done resolving, you might as well finish resolving and re-git add
the result. This replaces the slot-zero index entry with a version freshly copied from the work-tree: the file stays resolved, but the version ready for the next commit changes to the latest git add
-ed version.