git_remote_branch 0.3 – Awesomeness for the masses
Awesomeness for the masses
git_remote_branch 0.3 has been released!
Previous releases were pretty much only usable by rubyists on OS X.
No more. This release is mostly focused on making sure git_remote_branch works on a broader range of platforms. A few actual features squeaked in, but nothing big like introducing new commands.
If you don’t care about the details just type the following at your command-line.
sudo gem install git_remote_branch
And check the help
grb --help
If you encounter installation problems, refer to the readme.
Platforms
git_remote_branch 0.3 has been tested with the following configurations:
- OS X Leopard / Ruby 1.8.6 / Git 1.5.4.3 and 1.6.0.2
- OS X Leopard / Ruby 1.9.1 / Git 1.5.4.3 and 1.6.0.2
- Ubuntu Intrepid Ibex / Ruby 1.8.7 / Git 1.5.6.3
- Windows XP / Ruby 1.8.6 / Git 1.6.0.2 (the msys version)
Features
Better track
Track now works even if you already have a local branch of the same name. It uses git config instead of branch —track in that case. The subtlety can be observed by running (from a git repository):
grb explain track master
grb explain track non_existent_branch
Force the use of a specific git executable
Set the environment variable GRB_GIT to point to it and grb will use this one for all its operations.
Documentation
I’ve also worked quite a bit on the actual documentation. I used to be ashamed at the quality and availability of the documentation of git_remote_branch. At last I’ll be able to sleep at night :-)
git_remote_branch in a nutshell
I’ve rewritten the intro of the readme to be (hopefully) a bit clearer.
git_remote_branch is a simple command-line tool that makes it very easy to manipulate branches published in shared repositories.
It achieves this goal by sticking to a few principles:
- keep grb’s commands extremely regular (they all look alike)
- support aliases for commands
- print all commands it runs on your behalf in red, so you eventually learn them
Another nice thing about git_remote_branch is that it can simply explain a command (print out all the corresponding git commands) instead of running them on your behalf.
Note: git_remote_branch assumes that the local and
remote branches have the same name. Multiple remote
repositories (or origins) are supported.
Documentation availability
The main readme is now available on the main grb page on rubyforge.
Documentation quality
I’ve added clearer information on getting grb to run in different kinds of situation, due to helpful feedback from Axelson and Glenn Rempe.
I’ve also added some information for playing with the code for git_remote_branch (test dependencies and so on). See the end of the readme.
Finally, I’ve updated the links section quite a bit:
| Documentation | http://grb.rubyforge.org |
|---|---|
| News | http://programblings.com/category/git/git_remote_branch/ |
| Bug tracker | Lighthouse |
| Code | http://github.com/webmat/git_remote_branch |
| Mailing list | http://groups.google.com/group/git_remote_branch |
Dare
I dare you to find a platform on which git_remote_branch doesn’t work :-)
If you do, please send me feedback through GitHub or via email. I’m using gmail and, as usual, I go by the handle of webmat.
Last note: the git_remote_branch gem on GitHub
Excerpt from the readme:
Note that the only stable version of the gem you should trust is the one from Rubyforge. The GitHub gem is a development gem. The GitHub gem WILL be rebuilt with the same version number, and other horrible things like that. If you use the GitHub version of git_remote_branch, children will die!
You’ve been warned.
To install any ruby gem which has a command-line interface on Ubuntu 8.10, you have to add a path to your PATH environment variable. In your .bashrc file, add the following line:
export PATH=$PATH:/var/lib/gems/1.8/bin
Also worth noting is the fact that the default ruby interpreter on 8.10 is back to the 1.8 branch: it’s 1.8.7 (1.9 was the default on 8.04 iirc). 1.9 also be installed right besides 1.8.
$ ruby --version ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux] $ ruby1.9 --version ruby 1.9.0 (2008-06-20 revision 17482) [i486-linux]
Neither comes installed by default, however. You must install them explicitly.
sudo aptitude install ruby irb rubygems
While I’m at it, why not mention that rubygems 1.2.0 is installed by default. It doesn’t want to update to 1.3.0 with the usual “gem update –system” command. Since it’s not my main machine I didn’t investigate further, but the suggestion is to use apt-get or aptitude. The repos don’t seem to be up to date with 1.3.0, but rather with a version named something like 1.3.0really1.2.0.
In praise of Shoulda macros
Shoulda contexts let you to share setup code between different tests. This is for me one of Shoulda’s most attractive features.
When you combine this with the technique of defining your own macros to encapsulate assertions or setups that come up often, you end up with seriously DRY and readable tests.
I see a few different kinds of Shoulda macros:
Assertion macros
Assertions macros often begin with should_. They encapsulate one or a few assertions.
For ActiveRecord models:
should_require_attributes :name, :phone_number
They may even accept a block and do assertions on its execution, like should_raise:
should_raise(LoadError, :message => /vespene/) do
require "more vespene gas"
end
To learn more about assertion macros, you can take a look at Shoulda macros allows you to embrace your inner slacker by Josh Nichols. Inner slacker? I’m right there!
Setup macros
This kind of macro encapsulates a setup that comes up often in your test suite. One inspired by Restful Authentication’s login_as helper method could be used like this:
logged_in_as :mat do
# Shoulda tests
end
These kinds of macros accept a block that defines more Shoulda tests, rather than a block of code testing your app per se.
Turnkey macros
Turnkey macros are beefed up assertion macros. The main difference is their extent. They contain many contexts and a lot of should blocks. They usually accept substantial options hashes or are configured with a setup block. Like should_be_restful in the following example, inspired by the Shoulda documentation:
logged_in_as :stranger do
should_be_restful do |resource|
resource.create.params = { :subject => "test", :body => "message" }
resource.denied.actions = [:edit, :update, :destroy]
resource.denied.redirect = "login_url"
resource.denied.flash = /only the owner can/i
end
end
Two Shoulda best practices around setup macros
This article is specifically about setup macros.
Here’s the implementation of a pretty generic Shoulda macro I could define in my test_helper*. This is an implementation of the macro I mentioned at the beginning:
# Sets the current person in the session from the person fixtures.
def self.logged_in_as(person, &block)
context "logged in as #{person}" do
setup do
@request.session[:person] = people(person).id
end
yield
end
end
Which can then be used like this in any controller test:
logged_in_as :mat do
# tests for users
end
logged_in_as :admin do
# tests for admin
end
Setup macros have a very subtle catch, however. Here’s a modified version of the first example above:
logged_in_as :mat do
setup do
@request.session[:last_login] = Time.now
end
# Some tests
end
The setup block you see here is never going to be executed. Why?
If we were to replace the logged_in_as macro by the actual code it contains, here’s what it would look like:
context "logged in as #{person}" do
setup do
@request.session[:person] = people(person).id
end
setup do
@request.session[:last_login] = Time.now
end
# Some tests
end
Does that make sense? Not so sure.
Shoulda doesn’t like to have multiple setup blocks for a given context. That part does make sense.
Best practice #1: Always describe the situation with a context.
You should always describe the situation in which your test takes place (what your setup is doing) with a context.
logged_in_as :mat do
context "with last login set to now" do
setup do
@request.session[:last_login] = Time.now
end
# Some tests
end
end
Fair enough. We blame it on the user of the macro :-)
Since we’re using Ruby, most of us are probably in agreement with Matz’ “Make the programmer happy” motto.
So can we also solve the problem from the other end? Create a setup macro that supports a direct inner setup block? Of course we can, this is Ruby, not VB.
Best practice #2: Create setup macros that support a second setup block
A setup is grafted to a context that describes it. As the creator of the macro, I don’t know what crazy setup blocks programmers will put inside their macro. So I simply create a mute context:
# Sets the current person in the session from the person fixtures.
def self.logged_in_as(person, &block)
context "logged in as #{person}" do
setup do
@request.session[:person] = people(person).id
end
context '' do
yield
end
end
end
Now my macro supports the following test without a hitch:
logged_in_as :mat do
setup do
@request.session[:last_login] = Time.now
end
# Some tests
end
And of course, programmers who stick to best practice #1 can still write a cleaner test without a problem. The awesomeness of contexts lies in the fact that they can be nested:
logged_in_as :mat do
context "with last login set to now" do
setup do
@request.session[:last_login] = Time.now
end
# Some tests
end
end
Conclusion
Best practice #1 is simple. A setup block should be described by its encompassing context. It’s a question of readability. Nesting a setup block immediately inside a Shoulda macro is a dubious practice.
Best practice #2 is a more pragmatic solution to the problem. Ok, nesting a block right inside a macro isn’t always the best idea.
But when you don’t have the macro right under your nose, it may take you a while before you think about looking at said macro. I don’t know about you, but I have a tendency to have a great deal of confidence in macros that work well across my test suite.
So after you’ve spent an hour questioning Shoulda (or your sanity, or whether you should have become a gardener instead of a software developer) because your setup block isn’t executing, best practice #2 starts to make sense.
It may or may not be necessary in all your setup macros. I find it’s especially useful for macros that are generic enough to be used across your test suite. Or most of all, in setup macros you will share with the world.
Best practice #2 makes setup macros bulletproof to the problem of multiple setups.
Now go refactor your setup macros!
To learn more about Shoulda, check out Thoughtbot’s comprehensive documentation.
* A note on where to define Shoulda macros. Shoulda 2 can now auto load macros that are in the right location. This will help you keep your test_helper cleaner. Read more about it succinctly in Shoulda can automatically load custom macros by Josh Nichols or in the Shoulda 2.0 release post.
If you use Shoulda and, like me, you hate Test::Unit’s assert_raise(), I may have something of interest for you.
Why the hate?
Well, assert_raise accepts an *args list of exception types.
If you don’t pass any, you get some nonsense because an empty array doesn’t jive with the exception raised by your block. Useful. So if you don’t care what exception is raised, assert_raise isn’t gonna help you.
Also, assert_raise doesn’t let you specify what kind of exception message you’re expecting. I actually don’t mind that a given assertion should verify exactly one thing. On the other hand I have to jump through hoops to capture the exception if I want to assert on the error message.
A shoulda macro to the rescue
As usual, Shoulda is there to help us keep our test code DRY and intuitive. I’ve concocted a useful macro called should_raise. Here’s the gist:
It must be called with the block you expect to raise an exception, of course. You can also specify two optional arguments, the exception type and the message.
:kind_of or :instance_of
If not specified, an assertion is made that an exception was raised, but with no restriction on the type of the exception.
If you specify :kind_of, the assertion will be that much more precise. It will check that the exception raised was of the type specified, or a descendant.
If you specify :instance_of, the assertion is now that the exception raised was exactly of the type specified.
A shorthand is also available, where the type of the exception is supplied directly, like should_raise(LoadError), in which case the assertion is the same as with :instance_of.
In all of these cases, exactly one assertion is generated, whether or not :type is specified.
:message
If :message is specified, a second assertion will be added in order to make sure the error message matches the parameter. This can either be a string or a regex. The assertion is simply an assert_match.
If not specified, no assertion is generated for the message.
Examples
should_raise do
require "more vespene gas"
end
# 1 assertion
should_raise(LoadError) do
require "more vespene gas"
end
# 1 more restrictive assertion
should_raise(:instance_of => LoadError) do
require "more vespene gas"
end
# 1 assertion, the same as should_raise(LoadError)
should_raise(:kind_of => ScriptError) do
require "more vespene gas"
end
# 1 assertion, slightly less strict than with :instance_of (note: LoadError < ScriptError)
should_raise(:message => "no such file to load") do
require "more vespene gas"
end
# 2 assertions
should_raise(:message => /vespene/) do
require "more vespene gas"
end
# 2 assertions
should_raise(LoadError, :message => "such file to load") do
require "more vespene gas"
end
# 2 assertions
should_raise(:kind_of => LoadError, :message => "file to load") do
require "more vespene gas"
end
# 2 assertions
should_raise(:instance_of => LoadError, :message => "to load") do
require "more vespene gas"
end
# 2 assertions
Conclusion
As you can tell, I’m eagerly awaiting Starcraft II.
No, I meant: check out the code on gist 20019. I’ve included a reasonable suite of unit tests in a comment at the end.
Feel free to use it in any way you like. Just make sure you don’t sue me.
I don’t know about you, but for me, using git is so low-friction that I use it basically for everything where I may need a powerful undo button. In other words, I don’t use it only for team software development projects.
For example, I’ve frequently used it in the past to keep track of the modifications I make to an article I work on for few days. God knows I write a lot of these. There’s a reason I called this blog Programblings ;-)
To be honest, I’m using git as I write even this short article.
I also use it for trivial one evening coding projects. As soon as I spend more than an hour on code, whatever it is, I’ll usually create a local git repo for it.
One of the annoying things I realized when creating repositories more and more often, is that I always ended up ignoring the same files. Over and over again. Boring.
Fortunately for me, git can be configured to take into consideration a global ignore file. Heck, I can even create a system-wide ignore file if I want (check out git config’s doc for more info).
Configure your personal ignore file
I like to stick to conventions so I call my file .gitignore, and I put it in my home directory. But that’s up to you, really.
git config --global core.excludesfile ~/.gitignore
Note that there’s one little gotcha to be aware of. If you prefer to edit the .gitconfig file directly (or if you use a weird shell), git expects an absolute path. In the example above, bash converted the ~ shorthand to my home directory.
Now I just add ignore globs to it like any other project level (directory level, really) git ignore file.
echo .DS_Store >> ~/.gitignore
Once I’ve ignored all my favorite useless files, I can get cracking and never worry about them again.
I still have to ignore files
When I create a new repository on which people may actually contribute, I’ll still create a proper ignore file, however. Otherwise I’d convey the message that I consider contributors as slaves who only deserve the boring work of creating ignore files. Since I’m pure of heart, that’s not how I roll.
