Git provides a very valuable feature that many developers seem to either overlook or blatantly not care about; it enables you to add distinct sections to a commit message. Specifically, it allows you to add a short summary for the commit and a long and detailed body.
A great commit message
- Capitalized, short (50 chars or less) summary
- More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of an email and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); tools like rebase can get confused if you run the two together.
- Write your commit message in the imperative: “Fix bug” and not “Fixed bug” or “Fixes bug.” This convention matches up with commit messages generated by commands like git merge and git revert.
- Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here
- Use a hanging indent
Key points of a well-formed commit message
- Must have a summary line
- Summary line must be 50 characters or less
- Should have a well thought out and meaningful description
- No line in the description should be over 72 characters long
- First line is summary
- Second line is empty
- Third line starts the in-depth description
- Be sure to write your summary in the present tense imperative. For example, instead of “Adds new copy to homepage” use “Add new copy to homepage” or instead of “Fixes bug #234234” use “Fix bug #234234”. This follows the convention that git itself uses. Have you ever noticed when you merge a branch the commit message is “Merge [branch]” and not “Merges [branch]” or “Merged [branch]”?
A lot of this info comes from a great blog post by Tim Pope in 2008. I didn’t make the rules I just follow them.™ Various git commands pull the summary automatically and will just truncate your commit message if it is too long. This makes scanning the commit history very cumbersome and annoying. Commit summaries allow you to quickly convey the full intent of the commit in a single and succinct line. Think of it like the subject line of an email.
Yesterday I was needing a break from normal work and decided to work on some git hooks that would make me follow these rules.
Git provides several hooks into git events for you to do some pre-defined action prior to or immediately after a commit
[master][~/dev] ls .git/hooks applypatch-msg.sample post-update.sample pre-commit.sample prepare-commit-msg.sample commit-msg.sample pre-applypatch.sample pre-rebase.sample update.sample
I want to validate the commit message after the user creates the message but prior to it being committed to the repository. The specific hook I need for this is the `commit-msg` hook. If you remove the `.sample` from these files git will run them at their appropriate times. They are shell scripts and I could have written my message checker in a shell script like bash but I’m really not good at awk and sed and all that crazy shell scripting voodoo. So I just use the commit-msg shell script to call my own python script.
#!/bin/sh exec < /dev/tty .git/hooks/validate-commit.py $1
What I want this script to do
- Verify I have a summary line on my commit
- Verify the summary line is not over 50 characters
- Verify no line is over 72 characters
- If there are any errors, reject my commit and ask me to reformat
- If I choose to reformat my commit, bring me back into the commit editor and show me what exactly was wrong with my commit in comments on the commit message
After a few beers and some tinkering I ended up with this messy bit of python code:
#!/usr/bin/python import sys, os from subprocess import call if os.environ.get('EDITOR') != 'none': editor = os.environ['EDITOR'] else: editor = "vim" message_file = sys.argv def check_format_rules(lineno, line): real_lineno = lineno + 1 if lineno == 0: if len(line) > 50: return "Error %d: First line should be less than 50 characters " "in length." % (real_lineno) if lineno == 1: if line: return "Error %d: Second line should be empty." % (real_lineno) if not line.startswith('#'): if len(line) > 72: return "Error %d: No line should be over 72 characters long." % ( real_lineno) return False while True: commit_msg = list() errors = list() with open(message_file) as commit_fd: for lineno, line in enumerate(commit_fd): stripped_line = line.strip() commit_msg.append(line) e = check_format_rules(lineno, stripped_line) if e: errors.append(e) if errors: with open(message_file, 'w') as commit_fd: commit_fd.write('%sn' % '# GIT COMMIT MESSAGE FORMAT ERRORS:') for error in errors: commit_fd.write('# %sn' % (error,)) for line in commit_msg: commit_fd.write(line) re_edit = raw_input('Invalid git commit message format. Press y to edit and n to cancel the commit. [y/n]') if re_edit.lower() in ('n','no'): sys.exit(1) call('%s %s' % (editor, message_file), shell=True) continue break
Now if I try to commit a badly formed commit message like this:
Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit. Cras mattis consectetur purus sit amet fermentum. Maecenas faucibus mollis interdum. Etiam porta sem malesuada magna mollis euismod. Nulla vitae elit libero, a pharetra augue. Donec ullamcorper nulla non metus auctor fringilla. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # #> new file: 31234 # diff --git a/31234 b/31234 new file mode 100644 index 0000000..e69de29
It will kick me back to the shell and prompt me with this message:
"Invalid git commit message format. Press y to edit and n to cancel the commit. [y/n]"
Once you press “y” to edit your commit it will send you back to your commit editor and allow you to fix the bad formatting and even tell you which lines were incorrect and why!
Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit. Cras mattis consectetur purus sit amet fermentum. Maecenas faucibus mollis interdum. Etiam porta sem malesuada magna mollis euismod. Nulla vitae elit libero, a pharetra augue. Donec ullamcorper nulla non metus auctor fringilla. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. # GIT COMMIT MESSAGE FORMAT ERRORS: # Error 1: First line should be less than 50 characters in length. # Error 2: Second line should be empty. # Error 7: No line should be over 72 characters long. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # #> new file: 1234098 # diff --git a/1234098 b/1234098 new file mode 100644 index 0000000..e69de29
This tells us that we have errors on line 1, 2 and 7 of our commit. I think I’ve accomplished all my goals! Now I’ll be forced to follow good practices on my commit messages. Sometimes you need a little push to do what’s right!
I actually wrote this intially in Ruby, if you know me you know I am definitely not a python programmer, but had some problems I couldn’t figure out with forking subprocesses for the editor after a second commit rejection. I needed it to stay in the error loop and check the message again but if you rejected your commit and then tried to use the exact same message with the errors again, it would succeed and commit to the repository. Being a little side project I didn’t plan on spending more than a couple hours on I had to abandon it and move on to an alternate method. Someday soon I’ll dig into that more but for now this python script will certainly do!
Now go create some git-hooks to improve and automate your own workflow!
Do you follow any guidelines on commit message format?
Written by Addam Hardy