19 Apr 2008
I want to take a moment to elaborate on what makes a well formed commit message. I think the best practices for commit message formatting is one of the little details that makes Git great. Understandably, some of the first commits to rails.git have messages of the really-long-line variety, and I want to expand on why this is a poor practice.
Here’s a model Git 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, followed by a
single space, with blank lines in between, but conventions vary here
- Use a hanging indent
Let’s start with a few of the reasons why wrapping your commit messages to 72 columns is a good thing.
git log
doesn’t do any special special wrapping of the commit messages. With the default pager of less -S
, this means your paragraphs flow far off the edge of the screen, making them difficult to read. On an 80 column terminal, if we subtract 4 columns for the indent on the left and 4 more for symmetry on the right, we’re left with 72 columns.
git format-patch --stdout
converts a series of commits to a series of emails, using the messages for the message body. Good email netiquette dictates we wrap our plain text emails such that there’s room for a few levels of nested reply indicators without overflow in an 80 column terminal. (The current rails.git workflow doesn’t include email, but who knows what the future will bring.)
Vim users can meet this requirement by installing my vim-git runtime files, or by simply setting the following option in your git commit message file:
:set textwidth=72
For Textmate, you can adjust the “Wrap Column” option under the view menu, then use ^Q
to rewrap paragraphs (be sure there’s a blank line afterwards to avoid mixing in the comments). Here’s a shell command to add 72 to the menu so you don’t have to drag to select each time:
$ defaults write com.macromates.textmate OakWrapColumns '( 40, 72, 78 )'
More important than the mechanics of formatting the body is the practice of having a subject line. As the example indicates, you should shoot for about 50 characters (though this isn’t a hard maximum) and always, always follow it with a blank line. This first line should be a concise summary of the changes introduced by the commit; if there are any technical details that cannot be expressed in these strict size constraints, put them in the body instead. The subject line is used all over Git, oftentimes in truncated form if too long of a message was used. The following are just a handful of examples of where it ends up:
git log --pretty=oneline
shows a terse history mapping containing the commit id and the summary
git rebase --interactive
provides the summary for each commit in the editor it invokes
- if the config option
merge.summary
is set, the summaries from all merged commits will make their way into the merge commit message
git shortlog
uses summary lines in the changelog-like output it produces
git format-patch
, git send-email
, and related tools use it as the subject for emails
- reflogs, a local history accessible with
git reflog
intended to help you recover from stupid mistakes, get a copy of the summary
gitk
has a column for the summary
- GitHub uses the summary in various places in their user interface
The subject/body distinction may seem unimportant but it’s one of many subtle factors that makes Git history so much more pleasant to work with than Subversion.
18 Apr 2008
In our previous post in the series, we took a look at how a Rails core committer could best pull in a branch submitted by a contributer. Today, we’re going to be looking at things from the opposite side: How a contributer can best maintain a branch intended for pulling.
Note that contrary to the initial assumptions of many, git pull
is not necessarily the be-all end-all solution for getting your work included upstream. For simple bug fixes and features, it’s generally easier for all involved if you submit git format-patch --stdout
output. The real gains of a merge based workflow come when you start to collaborate with others on a topic branch. Having said that, let’s get on with the example.
Let’s start with the basics. We’ll be using GitHub for the upstream repository in this example. If you haven’t already done, so grab the clone the latest upstream over at GitHub.
$ git clone git://github.com/rails/rails.git
$ cd rails
Next, set up a public repository. With GitHub, this is straightforward: sign in and click the fork button on the rails/rails.git repository page. Once that’s done, take note of your push URL and add it to your repository. Here, we use the name mine
, to distinguish it from the origin
remote which is the upstream we cloned from.
$ git remote add mine git@github.com:yourname/rails.git
With that set up, we can get to work. Start by checking out a branch for your feature. Below, we use git fetch
to download the latest updates, then create a new branch my_wonderful_feature
based off the master
branch of the remote origin
, which is our upstream.
$ git fetch
$ git checkout -b my_wonderful_feature origin/master
Obviously, unless it’s a bug fix or feature targeted for the stable line, we should start by building off the tip of the upstream master
branch, right? Well, not necessarily; that’s really a holdover thought process from Subversion. There are several advantages to picking a recent tag like v2.0.2
and building off of that instead:
- No churn of constantly updating to the latest.
- Not having to worry about breakages in the latest edge. If a test breaks, you know (in theory) that you’re the one that broke it.
- People can easily test your changes in their application without testing everything else introduced by the particular revision of edge that you forked.
Of course, this is a trade off, as there are advantages to working on edge as well:
- Get to leverage the very latest infrastructure changes in Rails.
- Lesser chance of something that works in your version breaking once it’s merged into a later revision.
The right choice depends both on your feature and the current state of the upstream. Given that the last release of Rails happened back in Subversion and the next is just around the corner, it generally makes sense right now to base things off of a recent commit. However, once 2.1 hits, I’d seriously advise people to consider branching off of v2.1.0
instead. Even if you do choose the edge route, don’t be afraid to back up a few revisions if the current one has broken tests or otherwise doesn’t suit your needs.
Okay, so you’ve thought it through and decided on origin/master
for the my_wonderful_feature
branch you’re implementing. You’ve created your branch, done a few commits, and you’re ready to publicize it. Using the mine
remote we created earlier, this is a simple process:
$ git push mine my_wonderful_feature
If you want the public branch name to be named differently, that’s a snap too:
$ git push mine my_wonderful_feature:my_humble_request
With that done, your branch is ready to be pulled. Here’s the command a core committer would use to merge your work, which can be mentioned in the Lighthouse ticket, or on #rails-contrib, or however you want to communicate it to the powers that be:
$ git pull git://github.com/yourname/rails.git my_wonderful_feature
Great. So a few days later, you get some feedback, and decide to do some further work on your branch. If you’re a Subversion veteran, your first reaction is probably to update to the latest edge before continuing:
$ git pull
$ git merge origin/master
But wait! Is there a reason you need to update? Frequently updating complicates the history graph and adds noise to the logs:
Merge branch 'master' of git://github.com/rails/rails
There are certainly times when it’s appropriate to merge the latest upstream, like if there are conflicts that need to be resolved before it can be added upstream. When these times come, go ahead and merge, but give a descriptive commit message:
$ git merge v2.0.3 -m 'Synchronize with 2.0.3 release'
$ git merge origin/master -m 'Leverage new Rails.public_path method'
$ git merge origin/master -m 'Resolve conflicts with upstream'
If you’d like to try merging (for example, to test that tests still pass) but discard it when you’re done (for example, you’re planning on doing more work on the branch before publishing), use the following pattern. HEAD@{1}
means “the previous commit referenced by HEAD
”, that is, the commit before the merge.
$ git merge origin/master
$ rake test
$ git reset --hard HEAD@{1}
All right, you made the needed changes and your contribution was merged upstream. Here’s the syntax to delete a branch from your remote repository so it doesn’t clutter things up:
$ git push mine :my_wonderful_feature
17 Apr 2008
Welcome to my series of articles providing example workflows for working with the Rails core Git repository. My first post is designed for Rails core committers and explains one way to deal with contributions that come in the form of a Git URL and a branch name.
Based on my interactions with the core, I’d deduced the following are common requirements when merging a contributor’s work, and modeled my example around them:
- Preserve history: don’t squash to a single commit
- Provide a paper trail of which core committer merged the contribution
- Add a
CHANGELOG
entry on top
First, a recommended configuration option. This will add a short summary of the changes a merge introduces to the commit message.
$ git config --global merge.summary true
On to the merge. The first thing we want to do is bring our master
branch up to date. The assumption here is that we don’t have any local work done in master
: it’s a clean branch tracking the upstream, so that git pull
will simply fast forward the latest changes.
$ git checkout master
$ git pull
Next, let’s pull in our contributor’s branch.
$ git pull --no-ff --no-commit git://github.com/someone/rails.git branch
We use --no-ff
to disallow fast forwarding, even if the remote branch is up to date. This ensures there will always be a merge commit, providing for the paper trail requirement mentioned earlier. The --no-commit
option gives us a chance to alter the work tree as part of the merge commit. Putting hefty changes here would be confusing, but it is the perfect chance to make CHANGELOG
edits.
$ edit activesupport/CHANGELOG
$ git add activesupport/CHANGELOG
Once the CHANGELOG
is to your liking, we are ready to finalize it. The commit message is already in place but feel free to replace it if it doesn’t give a clear picture of what changed.
$ git commit
$ git push
13 Apr 2008
The Ruby on Rails core is now hosted on Git This is great news for Git fans like myself. For those of Rails core contributors who are coming late to the party, here’s a quick list of tips I’ve put together especially for you. This no substitute for a proper tutorial but rather a Rails biased supplement to one.
The first thing you do should be configure a real name and email. By default, Git chooses a default name based on the GECOS data (which is probably right) and a default email based on your login and hostname (which is almost certainly wrong). Best practices dictate you use your real name and email here, not your login, IRC handle, or any other aliases you may have. These fields will be immortalized in the repository history so make sure you get them right.
$ git config --global user.name "Tim Pope"
$ git config --global user.email "foo@gmail.com"
While you’re configuring, you may want to enable coloring for some commands:
$ git config --global color.diff auto
$ git config --global color.status auto
$ git config --global color.branch auto
$ git config --global color.interactive auto
While Git will accept just about any commit message you feed to it, sticking to best practices makes the log a lot easier to work with. A model commit message is shown below.
Short (50 chars or less) summary of changes
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 present tense: "Fix bug" and not "Fixed
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
As far as submitting your work to the Rails core, the workflow here is still being fleshed out. For now, either give a public URL and branch where your contribution can be found, or use the following series of commands to get a file that can be easily applied by anyone with the git am
command to reconstruct your history locally.
$ git checkout my_funky_branch
$ git rebase origin/master
$ git format-patch --stdout origin/master.. > my_funky_patches
Here’s a tip for keeping up to date: In lieu of using git pull
to download the latest changes, use git pull --rebase
. Instead of cluttering the history with a merge commit, it reapplies your changes to the latest upstream. The only caveat is that you shouldn’t use this method if you’ve already published the changes to another repository. Doing so would cause problems for anyone who has already downloaded the original commits.
03 May 2007
Today I devised a little script to generate Ruby examples showing code and output.
% rubydemo '[1,2,3].inject {|m,o|m+o}'
[1,2,3].inject {|m,o|m+o} #=> 6
% rubydemo 1/0
1/0 #=> #<ZeroDivisionError: divided by 0>
Here’s the script I used for generation. (It could have been one line if I didn’t care so much about exception formatting.)
#!/usr/bin/env ruby
print ARGV.join(" ") + " #=> "
begin
p(eval(ARGV.join(" "),binding,"(demo)"))
rescue Exception => e
puts "#<#{e.class}: #{e.message[/.*/]}>"
end
The real killer app, though, is using it in conjunction with IRC. Here’s the alias I used in Irssi.
/alias rd exec -nosh - -out rubydemo $*
Now I need merely do /rd 2+2
to get a beautifully formatted 2+2 #=>
4
in any conversation.