The purpose of the staging area is to have a flexible space for your commit. I think this will become clearer if you contrast git to version control systems that are centralized, such as subversion.
Subversion
In subversion you can select to commit certain files of your working copy. But only the complete files. Now: What if you want to stage file A
, not file B
, and the parts of file C
that relate to file A
yet not the parts that depend on changes in file B
(since then you would have a problem with commit consistency).
Git
Git solves this by providing the staging are as second working copy. Within the staging area you are putting together a snapshot that you will commit (roughly speaking).
Therefore within the staging area you can create a snapshot that includes changes to A
and a version of file C
which only reflects the changes in A
.
For the specific questions
You can stage at any point you want. i personally prefer to stage right before I launch the commit.
When having changes in a file staged and then changing that file in the working copy, you have not altered the staged file of course. You can either decide whether to stage those as well or whether not to stage thos changes. I.e. if you run git gui citool
you will see diffs of staged and unstaged versions (nice and simple tools for line-wise staging and committing).
Git is cautious here, which is probably a good thing.
General commit strategy: Granular commits
I think when talking about the question "When should I stage", one should also talk about commit habits.
Centralized VCS
In centralized version control systems where you commit to a central server, it was important for you co-workers that your commits are complete and well-tested. So people would try to commit not-so-often and then commit the state of complete files to minimize the possibility of an error. Thus, a commits tend to be quite large chunks which include lots of changes (if they are not simple fixes). The changes in a commit can be totally unrelated.
Git
In Git a commit is performed locally, only pushing them out to a server makes them public. Therefore a commit is cheap in a sense. A commit in the subversion sense is rather comparable to several git commit
followed by git push
. This difference matters.
Git allows you to commit single lines of code, even if you have changed other lines in the same file as well. This gives you a lot of benefits since you can for example commit a security bugfix in line 100 while having changed lines 300-350 introducing a new feature.
- You can separate different changes in different commits. This separates them nicely in your version history and even allows you to revert one but not the other.
- Your commit does not necessarily have to reflect a "compiling" state of your working copy (although I try to keep it that way).
So where is the "quality control" and build-guarantee in a commit that a subversion user would expect? It is shifted to other actions in git. You still want to push out a functioning state of the program in a public repository. Therefore you make sure that tests are successful and the program works before pushing out your changes.
Also, try to utilize branches to the max. When commiting lots of small changes you will end up with a pretty big version history. If you work in branches, you can categorize those granular commits by the branch name and then merge them back (option --no-ff
will also preserve that these features lived in a unique branch).
I.e. you can keep the habit of merging to the master
branch only, if the branch is in a good state. You can also use tags to track milestones and releases.
Now to come back to staging: Once you commit a few lines per commit, you will stage directly before commiting. (At least that is how I do it).
git diff
andgit diff --cached
are good, but sometimes I want more).