Skip to content

Latest commit

History

History
271 lines (206 loc) 路 15.1 KB

12-CreatingACleanHistory.adoc

File metadata and controls

271 lines (206 loc) 路 15.1 KB

Creating a clean history

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

Writing a good commit message

We saw already in 01-LocalGit.adoc why small commits are better and how commit messages should be formatted. Let鈥檚 go into more detail on what is a good format to use when writing your commit messages and why.

Here鈥檚 an example of a good commit message format. It鈥檚 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.

Good commit message format
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.

Building a commit from parts of files: git add --patch

We鈥檝e 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:

Patch add output
# 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)
...
  1. Hunk line

  2. Stage question

  3. 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鈥檚 use s to split this hunk into two hunks. After entering this you鈥檒l see something resembling the following:

Patch split output
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.
  1. 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:

Status output after patch add
# 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)
  1. Staged file

  2. 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鈥檚 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.":

Patch commit output
# 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(+)
  1. 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鈥檛 stage any of the others with q. The output is appended with the "new commit (1)" information that we鈥檇 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.

Graphically building a commit from parts of files

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鈥檚 a task I鈥檝e found is far easier using a graphical application. In this section I鈥檒l show you how to do this with GitX or git-gui (which provides this functionality in a separate application).

Graphically building a commit in GitX

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:

12 GitXBeforeStage
Figure 1. GitX stage mode

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鈥檝e clicked the Stage button at the top-right of the hunk. If you had wanted to stage all changes in a file, you could鈥檝e right-clicked on the file name in the Unstaged Changes list and selected Stage Changes from the right-click menu.

12 GitXAfterStaged
Figure 2. GitX staged hunk

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鈥檇 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.

12 GitXStageCommit
Figure 3. GitX stage mode commit

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鈥檝e now been removed from this list.

Graphically building a commit in git gui

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:

12 GitGUIBeforeStage
Figure 4. Git GUI on Windows 8.1

Git GUI on Windows 8.1 shows the Git GUI user interface. It鈥檚 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鈥檝e selected Stage Hunk For Commit from the right-click menu. If you had wanted to stage all changes in a file, you could鈥檝e selected the file name in the Unstaged Changes list, clicked the Commit menu, and clicked Stage To Commit.

12 GitGUIBeforeCommit
Figure 5. Git GUI staged

Git GUI staged shows that a line has been staged in 00-Preface.asciidoc as it鈥檚 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.

Avoiding whitespace issues : git diff --check

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鈥檚 whitespace warnings. As a result it鈥檚 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鈥檛 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:

diff whitespace check output
# 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?
  1. Trailing whitespace

  2. 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鈥檚 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鈥檚 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.

Summary

In this chapter you learned:

  • How to use an email format and Markdown to write good commit messages

  • How to use git add --patch or git 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鈥檛 added any bad whitespace changes