I've recently discovered git's patch
option to the add
command, and I must say it really is a fantastic feature.
I also discovered that a large hunk could be split into smaller hunks by hitting the s key, which adds to the precision of the commit.
But what if I want even more precision, if the split hunk is not small enough?
For example, consider this already split hunk:
@@ -34,12 +34,7 @@
width: 440px;
}
-/*#field_teacher_id {
- display: block;
-} */
-
-form.table-form #field_teacher + label,
-form.table-form #field_producer_distributor + label {
+#user-register form.table-form .field-type-checkbox label {
width: 300px;
}
How can I add the CSS comment removal only to the next commit ? The s
option is not available anymore!
If you're using
git add -p
and even after splitting with s, you don't have a small enough change, you can use e to edit the patch directly.This can be a little confusing, but if you carefully follow the instructions in the editor window that will be opened up after pressing e then you'll be fine. In the case you've quoted, you would want to replace the
-
with a space at the beginning of these lines:... and delete the the following line, i.e. the one that begins with
+
. If you then save and exit your editor, just the removal of the CSS comment will be staged.Let's say your
example.css
looks like this:Now let's change the style selectors in the middle block, and while we're at it, delete some old commented-out style we don't need anymore.
That was easy, now let's commit. But wait, I want to maintain logical separation of changes in version control for simple step-wise code review, and so that my team and I can easily search commit history for specifics.
Deleting old code is logically separate from the other style selector change. We're going to need two distinct commits, so let's add hunks for a patch.
Whoops, looks like the changes are too close, so git has hunked them together.
Even trying to split it by pressing s has the same result because the split isn't granular enough for our precision changes. Unchanged lines are required between changed lines for git to be able to automatically split the patch.
So, let's manually edit it by pressing e
git will open the patch in our editor of choice.
Let's review the goal:
We want to split this into two commits:
The first commit involves deleting some lines (comment removal).
To remove the commented lines, just leave them alone, they are already marked to track the deletions in version control just like we want.
-/*#field_teacher_id {
- display: block;
-} */
The second commit is a change, which is tracked by recording both deletions and additions:
Deletions (old selector lines removed)
To keep the old selector lines (do not delete them during this commit), we want...
...which literally means replacing the minus
-
signs with a spacecharacter.
So these three lines...
-
-form.table-form #field_teacher + label,
-form.table-form #field_producer_distributor + label {
...will become (notice the single space at the first of all 3 lines):
form.table-form #field_teacher + label,
form.table-form #field_producer_distributor + label {
Additions (new selector line added)
To not pay attention to the new selector line added during this commit, we want...
...which literally means to delete the whole line:
+#user-register form.table-form .field-type-checkbox label {
(Bonus: If you happen to be using vim as your editor, press dd to delete a line. Nano users press Ctrl+K)
Your editor should look like this when you save:
Now let's commit.
And just to make sure, let's see the changes from the last commit.
Perfect - you can see that only the deletions were included in that atomic commit. Now let's finish the job and commit the rest.
Finally you can see the last commit only includes the selector changes.
If you can use git gui, it allows you to stage changes line by line. Unfortunately, I don't know how to do it from the command line - or even if it is possible.
One other option I've used in the past is rolling back part of the change (keep the editor open), commit the bits I want, undo and re-save from the editor. Not very elegant, but gets the job done. :)
EDIT (git-gui usage):
I am not sure if the git-gui is the same in msysgit and linux versions, I've only used the msysgit one. But assuming it is the same, when you run it, there are four panes: top-left pane is your working directory changes, bottom-left is your stages changes, top-right is the diff for the selected file (be it working dir or staged), and bottom right is for description of the commit (I suspect you won't need it). When you click a file in the top-right one, you will see the diff. If you right-click on a diff line, you'll see a context menu. The two options to note are "stage hunk for commit" and "stage line for commit". You keep selecting "stage line for commit" on the lines you want to commit, and you are done. You can even select several lines and stage them if you want. You can always click the file in the staging box to see what you are bout to commit.
As for committing, you can use either the gui tool or the command line.
One way to do it is to skip the chunk,
git add
whatever else you need, and then rungit add
again. If this is the only chunk, you'll be able to split it.If you're worried about the order of commits, just use
git rebase -i
.