One of the advantages that Git has over Subversion and CVS is the use of its index as a staging area, which turns out to be a much more flexible model than Subversion. One of the things that always annoyed me about Subversion was that there seemed to be no elegant way to only commit only some of your changes to a particular tracked file. Subversion deals only in files in the working copy, and if you want to commit changes to a file, you have to commit all the changes in that file, even if they’re not related.

Where Subversion falls short

As an example, suppose you’re making changes to a working copy of a Subversion repository called myproject , and you’ve made a few changes to the main file, myproject.php ; on one line, you’ve fixed a bug caused by getting the parameters for htmlentities() in the wrong order. On another, near the head of the file, you’ve changed a php.ini setting to allow the script to run for a long time. Here’s what the output of svn status and svn diff might look like in this case:

$ svn status M myproject.php $ svn diff Index: myproject.php ================================================================ --- myproject.php (revision 2) +++ myproject.php (working copy) @@ -1,5 +1,7 @@ <?php +ini_set("max_execution_time", 300); + /** * Open main class. */ @@ -120,7 +122,7 @@ public function dumpvalue($value) { - print htmlentities($value, "UTF-8", ENT_COMPAT); + print htmlentities($value, ENT_COMPAT, "UTF-8"); }

Under Subversion, unless you move files around, you can’t commit only one of these changes; you need to commit both. This isn’t really the end of the world, since you could include a commit message describing both things you changed:

$ svn commit -m "Allowed longer runtime, fixed parameter order bug" Transmitting file data . Committed revision 3.

But if you’re finicky like me, and you’d prefer to think of commits as grouping semantically related changes as much as possible, it would be much better to be able to commit these two changes separately, and this is where Git’s use of an index shines.

Git’s method

Let’s work with the same project again, but this time as a Git repository. We’ll make the same changes again, and view the output of git status and git diff :

$ git status # On branch master # Changes not staged for commit: # # modified: myproject.php # no changes added to commit $ git diff diff --git a/myproject.php b/myproject.php index 7c20f21..c149190 100644 --- a/myproject.php +++ b/myproject.php @@ -1,5 +1,7 @@ <?php +ini_set("max_execution_time", 300); + /** * Open main class. */ @@ -120,7 +122,7 @@ class MyProject public function dumpvalue($value) { - print htmlentities($value, "UTF-8", ENT_COMPAT); + print htmlentities($value, ENT_COMPAT, "UTF-8"); }

So far, so good. Now when we run git add myproject.php to stage the changes in the index ready for commit, by default it does the same thing Subversion does, putting all of the changes in that file into the staging area. That’s probably fine in most cases, but today we want to commit one change, and then the other. The most basic way to do this is using Git’s --patch option.

The --patch option can be added to git add , and to some other Git commands concerned with manipulating the index as well, to explicitly prompt you about staging or not staging different sections of the file, that it terms hunks. In our case, the process of including only the first change would look something like this:

$ git add --patch myproject.php diff --git a/myproject.php b/myproject.php index 7c20f21..c149190 100644 --- a/myproject.php +++ b/myproject.php @@ -1,5 +1,7 @@ <?php +ini_set("max_execution_time", 300); + /** * Open main class. */ Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y @@ -120,7 +122,7 @@ class MyProject public function dumpvalue($value) { - print htmlentities($value, "UTF-8", ENT_COMPAT); + print htmlentities($value, ENT_COMPAT, "UTF-8"); } Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n

This done, if you compare the output of git diff --staged and git diff , you’ll notice that there are changes staged ready for commit in the file, and also changes that are not staged that we can commit separately later:

$ git diff --staged diff --git a/myproject.php b/myproject.php index 7c20f21..4bb2362 100644 --- a/myproject.php +++ b/myproject.php @@ -1,5 +1,7 @@ <?php +ini_set("max_execution_time", 300); + /** * Open main class. */ $ git diff diff --git a/myproject.php b/myproject.php index 4bb2362..c149190 100644 --- a/myproject.php +++ b/myproject.php @@ -122,7 +122,7 @@ class MyProject public function dumpvalue($value) { - print htmlentities($value, "UTF-8", ENT_COMPAT); + print htmlentities($value, ENT_COMPAT, "UTF-8"); }

So your staging area is all ready with just that one change in it, and all you need to do is type git commit with an appropriate message:

$ git commit -m "Allowed longer runtime" [master 19d9068] Allowed longer runtime 1 files changed, 2 insertions(+), 0 deletions(-)

And the other change you made is still there, waiting to be staged and committed whenever you see fit:

$ git diff diff --git a/myproject.php b/myproject.php index 4bb2362..c149190 100644 --- a/myproject.php +++ b/myproject.php @@ -122,7 +122,7 @@ class MyProject public function dumpvalue($value) { - print htmlentities($value, "UTF-8", ENT_COMPAT); + print htmlentities($value, ENT_COMPAT, "UTF-8"); }

Other methods