Friday, 20 August 2010

The power of git - splitting one file into multiple commits


Another really handy thing with Git, which I do use regularly, is it's ability to split lots of changes to the same file into separate logical commits.

As far as I know, any of the other VCSs I've used in the past haven't supported this and the only sane way I know of doing so would be to copy the file away, and use vimdiff to copy the logical commits one-by-one, committing each in turn.

Thankfully, git makes this really easy with the git add command.

Set up an example repository
# Create a new git repository for this example
cd /tmp/
mkdir git-add-pi
cd git-add-pi/
git init

# Make our first commit
echo "Example line" > example-file
git add example-file
git commit -m "First part of the example file"

Make some changes to our example file
Make a change to the beginning of the file:
(echo "Some big sweeping change to the file"; cat example-file) > tmp; mv tmp example-file
and make a change to the end of the file:
echo "Another big sweeping change to the same file" >> example-file

The magic
We actually wanted to split that file into two logical commits. With subversion, that would be a pain, but with git it's really easy with git add --pi:

523 git-add-pi:master> git add -pi example-file
diff --git a/example-file b/example-file
index d503a0c..82d4c17 100644
--- a/example-file
+++ b/example-file
@@ -1 +1,3 @@
+Some big sweeping change to the file
 Example line
+Another big sweeping change to the same file
Stage this hunk [y/n/a/d/s/?]?

Because the lines are so close to one another, git hasn't automatically split them up. Specify s to split them:
Stage this hunk [y/n/a/d/s/?]? s
Split into 2 hunks.
@@ -1 +1,2 @@
+Some big sweeping change to the file
 Example line
Stage this hunk [y/n/a/d/j/J/?]?
Well, we want to commit this hunk, so hit y
Stage this hunk [y/n/a/d/j/J/?]? y
@@ -1 +2,2 @@
 Example line
+Another big sweeping change to the same file
Stage this hunk [y/n/a/d/K/?]? n
We didn't want this hunk as part of this logical commit, so choose n

Checking what we've done
We can double check what we've added so far:
524 git-add-pi:master> git diff --cached
diff --git a/example-file b/example-file
index d503a0c..8bca897 100644
--- a/example-file
+++ b/example-file
@@ -1 +1,2 @@
+Some big sweeping change to the file
 Example line
And commit as normal:
525 git-add-pi:master> git commit -m "First change"
Created commit ad01f32: First change
 1 files changed, 1 insertions(+), 0 deletions(-)

What about the rest of the file?
You can repeat the add -pi as much as you like, or since we only have one more line to commit:
526 git-add-pi:master> git add .
git commi527 git-add-pi:master> git commit -m "final commit"
Created commit 16722ed: final commit
 1 files changed, 1 insertions(+), 0 deletions(-)