Thursday, May 29, 2008

Remote Git Repos on Ubuntu: The Right Way

Overview


Over the past week or so I've been setting up remote git repositories on my slice. I figured it would be helpful to put together a post regarding what I've found to be the right way to set up remote git repos. The info in this post is basically collected from several blogs and articles, but I've yet to see all of it in a single post.

Before we get started, I'm going to make a couple of assumptions:

  1. You're running ubuntu

  2. You have ssh access to your box

  3. You have access to a user on the sudoer list

  4. You have git installed on your server and your client machine

  5. You've generated a public/private key and it's in the standard location


Ok, enough with the chitchat, let's just get started.

Adding the git user


1. Create a git user on your server

On your server:

sudo adduser git

2. Create your .ssh folder and authorized_keys file for the git user

On your server:

su git # switch to the git user
cd ~ # change to git's home dir
mkdir .ssh # make the .ssh dir
touch .ssh/authorized_keys # create an empty authorized_keys file

3. Next, we need to copy the public key from our client system and add it to the authorized_keys for the git user on the server.

On your client:

ssh copy-id git@yourserver.com

4. Because we're going to be giving anyone access to the git user who needs access to the git repos, we want to restrict the actions the git user can take. To do so, we'll set the git users shell to git-shell. First, log back into your server on a user with sudo access (not the git user) and do the following:

On your server:

which git-shell # remember this location for the next step. mine is /usr/bin/git-shell
sudo vim /etc/passwd # find the line related to the git user, change the section that says
# /bin/sh to /usr/bin/git-shell (or whatever you got from the first step)


Creating a new git repo


So we've created our git user and now we'd like to create a new repository that we can use from the clients. It's actually really simple. We basically create a folder on the server in our git user's home directory named whatever we'd like the repo to be named and them we initialize it as a bare git repo. One thing to keep in is that all these commands will be performed from a user that has sudo access and is NOT the git user. Because we've set the git user's shell to git-shell, we can no longer switch to that user or ssh in as them.

On your server:

sudo mkdir /home/git/foo # create foo directory
cd /home/git/foo # change to that directory
sudo git --bare init # initialize a bare git repo
sudo chown -R git:git /home/git/foo # make the git user the owner of the repo


First Commit to the Repo


After initializing the repo, we're going to do a first commit from our client machine. The first commit is slightly complicated, but after you'll be all set to go.

On our client:

mkdir foo # make a new folder
cd foo

git init # initialize the git repo and add a file
touch README
git add .
git commit -m "added README"

git remote add origin git@yourserver.com:foo # add the remote git repo

git push origin master # push your commit to it


Cloning your Repo


Our repo is now all set to clone! On our client, we could futz around with the .git/config to make our master branch track the origin, but if we clone the repo it does it for us! So, we'll just delete the directory we've just created and reclone the repo. That way, we don't have to remember how to set up the .git/config file (and I'm lazy).

On our client:

rm -r foo/
git clone git@yourserver.com:foo

And that's really all there is to it. If you'd like to give someone else access to your git repo, simply add their public key to authorized_keys. Keep in mind that with this setup, all users will have access to all git repos you create in the git user's home directory. I'll talk more about giving different access in a later post. Hope this helps you all getting git remote repos up for all your projects.

Update: Now uses ssh copy-id to copy public key to the remote server.

Thursday, May 22, 2008

Don't Cry Over Spilled Context

Let's talk a bit about a DSL pattern called spill-over context. In keeping with the whole "picture worth a thousand words" thing, let's take a look at a snippet of code from a rakefile to get an idea of what spill-over context actually is:

desc "I describe the task below me!"
task :stuff
# do stuff here
end


What's cool about the code above is that the desc method seems to magically know how to attach itself to the next task defined. When I run "rake -T", I will see the description associated with my :stuff task. Very slick.

Like most things involved in DSLs, this seems magical at first. How the heck would the description know to attach itself to the next task? It's cool and very easy to see the intent of the code, but how the heck does it work? In truth, implementing the spill-over context pattern is pretty darn simple. Let's look at an example DSL I created:

make_a_sandwich = Task.new 'make a sandwich' do
description "open up the fridge"
step :open_fridge do
# do fridge opening stuff
end

description "get some bread"
step :get_bread do
# bread stuff here
end

description "get peanut butter and jelly"
step :ingredients do
# get the good stuff
end
end

make_a_sandwich.show_task


This DSL (which looks strikingly similiar to rake) lets us create tasks out of steps. We can describe each of the steps in plain english and then use the show_task method to print out a nice explanation of our task.

The code to implement this DSL is pretty easy to understand. At a high level, here's what happens. I've defined two instance methods on Task called describe and step. The describe method stores whatever sting is passed to it in an instance variable called @last_description. The step method does two things. First, it stores the block passed to it in a hash along with the step name as a key. Second, and most importantly, it checks to see if there is a description sitting in the instance variable @last_description. If so, it associates that description with the step it is creating, and then clears out the instance variable. That's all there is to spill-over context! The "magic" of the description attaching to a step is accomplished by storing the description in an instance variable and using it later. Very simple, but also a very powerful concept for creating interesting and usable DSLs.

Finally, the show_task method just iterates over the tasks and descriptions and prints them out nicely. So, without further ado, here's the implementation code:

class Task
def initialize(name,&block)
@name = name
@steps = {}
@descriptions = []
instance_eval(&block) if block_given?
end

def show_task
puts "Task: #{@name}"
puts "Steps:"
@descriptions.each_with_index do |(step,desc),i|
puts " #{i+1}: #{step} - #{desc}"
end
end

def step(name,&block)
@steps[name] = block
@descriptions << [name,@last_description]
@last_description = nil
end

def description(text)
@last_description = text
end
end

Wednesday, May 14, 2008

Conditional includes

This little problem came up at work a few days ago: what if you want to include a module but, based on the including class, get some different behavior? Enter conditional includes.

module Foo
module Bar
def hello
puts "hi from bar"
end
end

module Baz
def hello
puts "hi from baz"
end
end

def self.included(klass)
if klass.to_s == "Blah"
klass.send(:include,Bar)
else
klass.send(:include,Baz)
end
end
end


Of course, we can change the conditional logic to fit our needs. Now we'll include this module into two classes:

class Blah
include Foo
end

class Barf
include Foo
end

Blah.new.hello # => 'hi from bar'
Barf.new.hello # => 'hi from baz'


Yahoo! We get different behavior from each class without exposing the complexity to the end user.

Tuesday, May 13, 2008

Micro DSLs

Three things you need to know about me: I spend a lot of time on planes, I like DSLs and I'm lazy. With these powers combined, I've created my new hobby: creating micro DSLs. What is a micro DSL? Basically, during my (seemingly) never ending flights from San Fran to Chicago I try to recreate DSLs I like in as few lines as possible. Oh, and one more thing: I give the resulting DSLs self-degrading names to show the respect to the people who first created them.

A couple more rules: I decide how much of the original DSL I want to recreate and I can cut any corners that I'd like. In some cases, I try to improve the original DSL or tweak the syntax a bit.

The first DSL I've micro-ized is Jay Fields expectations. I find its syntax very nice and it had some unique challenges. The general approach I took to keep the code short was to use test/unit behind the scenes. This stopped me from having to recreate all the goodness that test/unit gives me for free: a test suite runner, error/failure tracking and assertions.

Anyway, the code is below. It's about 48 lines long and implements most of the features from the original DSL.
require 'test/unit'
require 'rubygems'
require 'mocha'

class Mocha::Expectation
def and
@mock
end

alias_method :go, :and
end

class LoweredExpectations
include Mocha::Standalone

COMPARATORS = {
Class => :kind_of?,
Range => :include?,
Regexp => :=~
}

def initialize
@test_case = Class.new(Test::Unit::TestCase)
@test_num = 0
end

def expect(value=true,&block)
test_name = "test_#{@test_num += 1}"

if value.kind_of? Mocha::Mock
@test_case.send(:define_method,test_name) do
block.call(value)
value.verify
end
else
comparator = COMPARATORS[value.class] || :==

@test_case.send(:define_method,test_name) do
assert(block.call.send(comparator,value))
end
end
end
end

def LoweredExpectations(&block)
LoweredExpectations.new.instance_eval(&block)
end

Below is an expectations file using the above code. Note there's a small different in the way mocks are used. The method and links expectations and go is used to send the mock to the block.

LoweredExpectations do
expect do
1 == 1
end

expect 2 do
1 + 1
end

expect "foo" do
"fo" + "o"
end

expect /bar\d/ do
"bar2"
end

expect Fixnum do
"4".to_i
end

expect mock.expects(:bar).and.expects(:baz).go do |foo|
foo.bar
foo.baz
end
end

Of course, the error reporting is much worse in my version than Jay's, but I'd say not too bad for ~50 lines of code. Let me know what you think.

Update:

Source is available on github. You can see my other projects on the right side of this blog.