We saw in 01-LocalGit.adoc that a clean history can make it easier to find useful information. For a history to be clean, it should use good, well-formatted commit messages for commits which are as small as possible.
In this chapter you will learn how to create a clean history by learning the following topics:
-
How to write a good commit message
-
How to build a commit using only certain changes in a file
-
How to build a commit using a graphical application
-
How to avoid committing bad whitespace
We saw already in 01-LocalGit.adoc why small commits are better and how commit messages should be formatted. Let’s go into more detail on what is a good format to use when writing your commit messages and why.
Here’s an example of a good commit message format. It’s strongly influenced by a guide written by Tim Pope, which is now at http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html.
Commits: first line is a summary (<51 characters).
Commit messages should be structured like emails. The first line (the
commit subject) should be treated like the subject of an email. It
should make a brief summary that is elaborated on in the rest of the
commit message. It should have 50 characters or fewer and always be
separated by a new line from the rest of the commit message body.
Without this new line, various output formats that try to display only
the first line of the commit may get confused.
The commit's message body can be split into multiple paragraphs which
should be wrapped at 72 characters or fewer. The wrapping is done to
ensure the output of tools like `git log` remains readable even when it
adds indentation for diffs. Otherwise the commit message has no other
limits on length; it should be as long as it needs to be to fully
explain the commit. While the subject might describe what the commit
does, the body should expand on why the change was made. It should also
use the present tense to match the tense used by commit messages
generated by commands such as git merge.
If you're using GitHub (and some other tools) then the contents of
commit messages can contain Markdown. You may use Markdown to add some
formatting that looks good in ASCII or rendered such as using **bold**,
_italics_, ~~strikethrough~~, `monospace` or lists bulleted with a `*`
or numbered with, for example `1.`. You shouldn't go overboard with
this but it can add some useful, basic formatting.
We’ve seen previously in the book how to create commits from all the changes in an individual file. As commits should be as small as possible (recall 01-LocalGit.adoc), sometimes there may be multiple changes to a file that you want to split into multiple commits. Of course you could manually undo and redo these changes to the files, but that would be tedious. Thankfully Git provides you with various tools to add only certain changes in certain files to the index staging area or directly commit them.
git add
has a --patch
(or -p
) flag that provides an interactive menu in which you can select what parts of files you want to add. Make some changes to 00-Preface.asciidoc
, 01-IntroducingGitInPractice.asciidoc
, and 02-AdvancedGitInPractice.asciidoc
in the GitInPracticeRedux
repository and run git add --patch
. This will prompt for an action for the first change in 00-Preface.asciidoc
and should resemble the following:
# git add --patch
diff --git a/00-Preface.asciidoc b/00-Preface.asciidoc
index d7aa4f8..4b43488 100644
--- a/00-Preface.asciidoc
+++ b/00-Preface.asciidoc
@@ -1,2 +1,5 @@
= Git In Practice
+// Git Through Praxis?
The hotly anticipated sequel to Git In Paris.
+ (1)
+Copyright Mike McQuaid
Stage this hunk [y,n,q,a,d,/,e,?] ? (2)
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk nor any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk nor any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help (3)
...
-
Hunk line
-
Stage question
-
Help lines
From the patch add output:
-
"Hunk line (1)" shows the first line of the two-line hunk. A hunk is one or more nearby lines that have all been changed and Git groups them together.
-
"Stage question (2)" shows the available options for this particular hunk. I selected
?
, which printed the shown help output. -
"Help lines (3)" shows the help output with all the different available options.
You can see from the help lines, many different options are available at each stage. These options should be fairly self-explanatory.
Let’s use s
to split this hunk into two hunks. After entering this you’ll see something resembling the following:
Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 2 hunks.
@@ -1,2 +1,3 @@
= Git In Practice
+// Git Through Praxis? (1)
The hotly anticipated sequel to Git In Paris.
-
Split hunk
The patch split output shows that the previous hunk, which contained two changes (// Git Through Praxis?
and Copyright Mike McQuaid
), has been split into a single hunk (shown by "split hunk (1)"). This allows the breaking up of hunks into smaller sections to allow the addition (and eventual committing) of individual lines.
After viewing the help, I answered y
to stage the hunk shown in the listing and q
to not stage any of the remaining hunks. After doing this, the git status
output should resemble the following:
# git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: 00-Preface.asciidoc (1)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working
directory)
modified: 00-Preface.asciidoc (2)
modified: 01-IntroducingGitInPractice.asciidoc (2)
modified: 02-AdvancedGitInPractice.asciidoc (2)
-
Staged file
-
Unstaged file
From the status output after patch add:
-
"Staged file (1)" shows the file that had a hunk staged.
-
"Unstaged file (2)" shows the two files that have changes but none of them were staged and the first file that had a single hunk staged and some hunks unstaged.
Let’s undo this add to the staging area now by running git reset master
.
git commit
also has a --patch
(or -i
but, confusingly, not -p
) flag. It also provides the same interactive menu. Now run git commit --patch --message "Preface: add potential new title."
:
# git commit --patch --message "Preface: add potential new title."
diff --git a/00-Preface.asciidoc b/00-Preface.asciidoc
index d7aa4f8..4b43488 100644
--- a/00-Preface.asciidoc
+++ b/00-Preface.asciidoc
@@ -1,2 +1,5 @@
= Git In Practice
+// Git Through Praxis?
The hotly anticipated sequel to Git In Paris.
+
+Copyright Mike McQuaid
Stage this hunk [y,n,q,a,d,/,e,?]? s
Split into 2 hunks.
@@ -1,2 +1,3 @@
= Git In Practice
+// Git Through Praxis?
The hotly anticipated sequel to Git In Paris.
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
@@ -2 +3,3 @@
The hotly anticipated sequel to Git In Paris.
+
+Copyright Mike McQuaid
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? q
[master eec78b2] Preface: add potential new title. (1)
1 file changed, 1 insertion(+)
-
New commit
git commit --patch
is equivalent to git add --patch && git commit
. I performed the same actions with git commit --patch
as with git add --patch
; I split the first hunk with s
, staged the first hunk split with y
, and then didn’t stage any of the others with q
. The output is appended with the "new commit (1)" information that we’d expect from git commit
but otherwise identical to that from git add --patch
. `
Now run git reset HEAD^
to undo the current commit so we can try and stage hunks graphically.
As you may have noticed, throughout this book I mostly prefer to use (and therefore teach you to use) the Git command-line application rather than GUIs. There are a few exceptions: I use GitX (or gitk
) (first seen in 01-LocalGit.adoc) to easily visualize the history of a repository.
We saw in Building a commit from parts of files: git add --patch how to build commits from parts of files from the Git command-line application, but it’s a task I’ve found is far easier using a graphical application. In this section I’ll show you how to do this with GitX or git-gui
(which provides this functionality in a separate application).
GitX provides a staging mode that also allows adding entire files or individual hunks to staging provides support for graphically staging hunks. If you click on the Stage hunk button in the top-right of GitX it should resemble the following:
You can see from GitX stage mode that the staging mode shows a selection of files to stage, the changes to the selected file, and allows staging of hunks or selected lines through their respective buttons. Stage the // Git Through Praxis?
line by clicking on it and then clicking the Stage line button that appears on the right of that line. If you had wanted to stage all the lines in a hunk you could’ve clicked the Stage button at the top-right of the hunk. If you had wanted to stage all changes in a file, you could’ve right-clicked on the file name in the Unstaged Changes list and selected Stage Changes from the right-click menu.
GitX staged hunk shows after the changes to the file were staged and a commit message has been entered. The file now shows in both the Unstaged Changes and Staged Changes file lists. If the file had all its hunks staged, and it would no longer be present in the Unstaged Changes list.
The staging area used by GitX is the same staging area used by the rest of Git. If you quit GitX now and ran git status
, you’d see the same result as before: some changes in 00-Preface.asciidoc
had been staged.
Now that there are some staged changes, the Commit button has become enabled. After the commit message has been entered, you can click it.
Now that the changes have been committed GitX stage mode commit shows a large message with the new SHA-1. The Unstaged Changes remain the same but the Staged Changes were used to create the new commit, so they’ve now been removed from this list.
Although GitX combines staging and viewing history into one application, by default Git provides two GUI applications for this: gitk
(first seen in 01-LocalGit.adoc) and git gui
.
Run git reset HEAD^
to undo the current commit so we can try and stage hunks using Git GUI. Now run git gui
:
Git GUI on Windows 8.1 shows the Git GUI user interface. It’s similar to GitX 's stage mode but the two Unstaged Changes and Staged Changes (Will Commit) file lists are shown on the left side rather than left and right of the commit message.
You select the file whose changes you want to view by clicking on it in the Unstaged Changes list. Stage the // Git Through Praxis?
line by right-clicking on it and selecting Stage Line For Commit from the right-click menu. If you had wanted to stage all the lines in a hunk, you could’ve selected Stage Hunk For Commit from the right-click menu. If you had wanted to stage all changes in a file, you could’ve selected the file name in the Unstaged Changes list, clicked the Commit menu, and clicked Stage To Commit.
Git GUI staged shows that a line has been staged in 00-Preface.asciidoc
as it’s now displayed in the Staged Changes (Will Commit) list. You can now enter a commit message and press Commit.
After pressing this there is no sign of the commit other than the 00-Preface.asciidoc
being removed from the Staged Changes (Will Commit) list. Like GitX though, it has successfully committed a file.
Git expects certain whitespace usage in files. As a result of this, many Git users (and almost all Git-based open-source projects) want to try to avoid Git’s whitespace warnings. As a result it’s generally always a good idea to try to ensure your whitespace follows good Git practice. To do this ensure that:
-
No lines in files end with whitespace (trailing tab or space characters)
-
No lines in files start the line with one or more space characters and follow it immediately with one or more tab characters
-
All files end with one or more new line character(s)--a line-feed character on Unix or a carriage-return and a line-feed character on Windows
You can check that you haven’t violated any of these rules by running git diff --check
. For example, if we added some whitespace errors to 00-Preface.asciidoc
, the output might resemble the following:
# git diff --check
00-Preface.asciidoc:1: trailing whitespace. (1)
+= Git In Practice
00-Preface.asciidoc:2: space before tab in indent. (2)
+ // Git Through Praxis?
-
Trailing whitespace
-
Space before tab
From the diff whitespace check output:
-
"Trailing whitespace (1)" shows that on line 1 of
00-Preface.asciidoc
there was whitespace at the end of the line. -
"Space before tab (2)" shows the on line 2 of
00-Preface.asciidoc
there was a space character before a tab character at the beginning of the line.
Regular git diff
(but, bizarrely, not git diff --check
) will show \ No newline at end of file
if the file’s trailing newline is missing. If you have Git 2.0 (which was released May 28, 2014) or newer, or if you enabled colored output in 07-PersonalizingGit.adoc, git diff
will display whitespace errors with a red background.
It’s also worth checking whether you can configure your text editor of choice to fix any of these errors for you when you save files. This is a fairly commonly available feature.
In this chapter you learned:
-
How to use an email format and Markdown to write good commit messages
-
How to use
git add --patch
orgit commit --patch
to stage only chosen hunks for a new commit -
How to use GitX or Git GUI to stage only selected lines or hunks for a new commit
-
How to use
git diff --check
to make sure you haven’t added any bad whitespace changes