Prerequisite
Install
gitsudo apt-get install git-core
Initializing gitosis
Grab
gitosiskamal@dev:~$ git clone git://eagain.net/gitosis.git Initialized empty Git repository in /home/kamal/gitosis/.git/ remote: Generating pack... remote: Done counting 549 objects. remote: Deltifying 549 objects... remote: 100% (549/549) done Indexing 549 objects... remote: Total 549 (delta 379), reused 136 (delta 95) 100% (549/549) done Resolving 379 deltas... 100% (379/379) doneInstall
gitosiskamal@dev:~/gitosis$ cd gitosis && sudo python setup.py install running install running bdist_egg running egg_info creating gitosis.egg-info ... Installing gitosis-init script to /usr/bin Installing gitosis-run-hook script to /usr/bin Installing gitosis-serve script to /usr/bin Installed /usr/lib/python2.4/site-packages/gitosis-0.2-py2.4.egg Processing dependencies for gitosis==0.2 Finished processing dependencies for gitosis==0.2Create the
gituser on the serverkamal@dev:~$ sudo adduser \ > --system \ > --shell /bin/sh \ > --gecos 'git version control' \ > --group \ > --disabled-password \ > --home /var/local/git \ > git Adding system user `git' (UID 106) ... Adding new group `git' (GID 109) ... Adding new user `git' (UID 106) with group `git' ... Creating home directory `/var/local/git' ...Back on your local machine,
scpyour SSH public key to the serverMacBook-Pro:~ kamal$ scp ~/.ssh/id_dsa.pub dev.ror.com.my:/tmp id_dsa.pub 100% 613 0.6KB/s 00:00Back on the server, initialize
gitosiskamal@dev:~/gitosis$ sudo -H -u git gitosis-init < /tmp/id_dsa.pub Initialized empty Git repository in ./ Initialized empty Git repository in ./Back on your local machine, clone the automatically created
gitosis-adminprojectMacBook-Pro:~ kamal$ git clone git@dev.ror.com.my:gitosis-admin.git Initialized empty Git repository in /Users/kamal/gitosis-admin/.git/ remote: Generating pack... remote: Done counting 5 objects. remote: Deltifying 5 objects... remote: 100% (5/5) done remote: Total 5 (delta 1), reused 5 (delta 1) Indexing 5 objects... 100% (5/5) done Resolving 1 deltas... 100% (1/1) doneCheck out the contents of
gitosis-adminMacBook-Pro:~ kamal$ cd gitosis-admin/ MacBook-Pro:gitosis-admin kamal$ ls gitosis.conf keydir MacBook-Pro:gitosis-admin kamal$ more gitosis.conf [gitosis] [group gitosis-admin] writable = gitosis-admin members = kamal@MacBook-Pro.local MacBook-Pro:gitosis-admin kamal$ ls keydir/ kamal@MacBook-Pro.local.pubNote: The main configuration file lives in
gitosis.confand the users are identified by their SSH public keys which are inkeydir
Adding Users
Adding Aizat as a gitosis-admin member is easy
MacBook-Pro:gitosis-admin kamal$ cp /tmp/aizat-id_dsa.pub keydir/aizat@Zatto.local.pubNote: The filename in the keydir has to match the last bit in the public key file plus
.pubEdit gitosis.conf
[gitosis] [group gitosis-admin] writable = gitosis-admin members = kamal@MacBook-Pro.local aizat@Zatto.localAdd the new file to git
MacBook-Pro:gitosis-admin kamal$ git add keydir/aizat\@Zatto.local.pubCommit the changes
MacBook-Pro:gitosis-admin kamal$ git-commit -a -m "Added Aizat as a gitosis admin" Created commit 37c0966: Added Aizat as a gitosis admin 2 files changed, 2 insertions(+), 1 deletions(-) create mode 100644 keydir/aizat@Zatto.local.pubAnd push to the server
MacBook-Pro:gitosis-admin kamal$ git push updating 'refs/heads/master' from 3b5462b027a02a5b29bf43f4ca878350ccc8b913 to 37c0966f285715cc38bd801a3955bb6f319ccdf4 Also local refs/remotes/origin/master Generating pack... Done counting 8 objects. Result has 5 objects. Deltifying 5 objects... 100% (5/5) done Writing 5 objects... 100% (5/5) done Total 5 (delta 1), reused 0 (delta 0) refs/heads/master: 3b5462b027a02a5b29bf43f4ca878350ccc8b913 -> 37c0966f285715cc38bd801a3955bb6f319ccdf4To add non-admin users, the process is the roughly the same. You only need to create a new group in
gitosis.conf.
Adding Projects
Add a new group for other users and set a new test project as the writable project
[gitosis] [group gitosis-admin] writable = gitosis-admin members = kamal@MacBook-Pro.local aizat@Zatto.local [group rsb] writable = test_project members = @gitosis-admin kegan@MacBook.local saimer@MacBook.local seanx2@Dell.localCommit and push
MacBook-Pro:gitosis-admin kamal$ git-commit -a -m "Added a new group for RSB and set write perms on a new test project" MacBook-Pro:gitosis-admin kamal$ git push updating 'refs/heads/master' from 37c0966f285715cc38bd801a3955bb6f319ccdf4 to 2fc5d9c394218fb757ce6fe468b9ea8096e99dcf Also local refs/remotes/origin/master Generating pack... Done counting 8 objects. Result has 6 objects. Deltifying 6 objects... 100% (6/6) done Writing 6 objects... 100% (6/6) done Total 6 (delta 1), reused 0 (delta 0) refs/heads/master: 37c0966f285715cc38bd801a3955bb6f319ccdf4 -> 2fc5d9c394218fb757ce6fe468b9ea8096e99dcfCreate the test project
MacBook-Pro:~ kamal$ mkdir test_project MacBook-Pro:~ kamal$ cd test_project/ MacBook-Pro:test_project kamal$ git init Initialized empty Git repository in .git/ MacBook-Pro:test_project kamal$ git remote add origin git@dev.ror.com.my:test_project.git MacBook-Pro:test_project kamal$ echo "This is an test project hosted in git" > README MacBook-Pro:test_project kamal$ git add README MacBook-Pro:test_project kamal$ git commit -a -m "Added a README file" Created initial commit f70c2f7: Added a README file 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 READMENote: It is important that you have files to add, otherwise the next command will fail. I think this has to do with
gitnot tracking empty directories?Push the test_project to the server
MacBook-Pro:test_project kamal$ git push origin master:refs/heads/master updating 'refs/heads/master' from 0000000000000000000000000000000000000000 to f70c2f74d71fbbb1edd0f5da2554fd208f75051b Also local refs/remotes/origin/master Generating pack... Done counting 3 objects. Deltifying 3 objects... 100% (3/3) done Writing 3 objects... 100% (3/3) done Total 3 (delta 0), reused 0 (delta 0) refs/heads/master: 0000000000000000000000000000000000000000 -> f70c2f74d71fbbb1edd0f5da2554fd208f75051bTest out by cloning the project
MacBook-Pro:~ kamal$ git clone git@dev.ror.com.my:test_project.git test_project2 Initialized empty Git repository in /Users/kamal/test_project2/.git/ remote: Generating pack... remote: Done counting 3 objects. remote: Deltifying 3 objects... remote: 100% (3/3) done remote: Total 3 (delta 0), reused 0 (delta 0) Indexing 3 objects... 100% (3/3) done
And you’re done!
In the next article, I will explore migrating existing Subversion repositories to git. Stay tuned!
… namely the UI Developer: me.
I’m not even going into the Rails file structure. However, just know that if you want to make your Rails programmer’s life much, much easier (and to make sure they won’t pee in your shampoo, ever) put your files in the public folder. Needless to say, images go in the public/images folder and stylesheets in public/stylesheets. Simple, yes?
Okay, fluff aside and since sharing is caring, today I’ll be sharing the first three sexy lines of Rails code I picked up!
THE BASIC
What’s a UI dev without his/her stylesheet? So first thing’s first: call that stylesheet!
<%= stylesheet_link_tag 'style' %>
Rails is intelligent enough to know that your stylesheets are indeed stylesheets and they’re housed in the public/stylehseets folder without you actually having to specify! How nifty is that? :)
THE SIMPLE BITS
How linking works in Rails:
<%= link_to('RSB, holla!', 'http://blog.ror.com.my') %>
which is actually your
<a href="http://blog.ror.com.my">RSB, holla!</a>
FUN STUFF
Making those images prettier with image_tag
Because Rails is emazing at compacting code, your generic <img src="images/spiffy.png" alt="spiffy!" class="wrapper" /> now becomes:
<%= image_tag 'spiffy.png', :class => 'wrapper', :alt => 'spiffy!' %>
Alrighties, so we’ve gotten the easy part out of the way :) Next week we’ll dive a little into how partials and conditionals work! :) And no, it’s not rocket science, it’s Ruby magic!
p/s: It is secretly fun harassing Kamal for code logic! :P
Rails Routing and Facebook’s POSTs
If you have done any amount of Facebook development with Rails, you will quickly encounter the number one thing that has pissed off every Rails developer - Everything Is A POST!
With Facebooker’s pseudo-resource route generator, things are not so bad. It allows us declarations such as
facebook_resources :entries
facebook_resources :photos
in config/routes.rb which is nice and concise. However, the paths generated are so old school!
GET /entries # index
GET /entries/new # new
POST /entries/create # create
GET /entries/1/show # show
GET /entries/1/edit # edit
POST /entries/1/update # update
POST /entries/1/destroy # destroy
Yuck!
Rails taught us to love HTTP verbs, did it not? The above could be simplified if we took into account the HTTP request method, like so,
GET /entries # index
GET /entries/new # new
POST /entries # create
GET /entries/1 # show
GET /entries/1/edit # edit
PUT /entries/1 # update
DELETE /entries/1 # destroy
Unfortunately, you are stuck with just POST, buddy. Don’t even dream about your PUT and DELETE. Is there anything we can do? Fortunately, yes!
We Can Have Our RESTful Routes Back!
So, how do you solve this with Facebook? Facebook very recently added an additional request parameter to disambiguate between GET and POST via fb_sig_request_method. The actual requests from Facebook are still POSTs but we can make use of fb_sig_request_method as an added condition in our routes.
This is how you do it. Stick the following piece of code in lib and require it in a config/initializers file, or if you are using Facebooker as a plugin, modify vendor/plugins/facebooker/lib/facebooker/rails/routing.rb
def facebook_resources(name_sym)
name = name_sym.to_s
with_options :controller => name, :conditions => { :method => :post } do |map|
map.with_options :conditions => { :fb_sig_request_method => 'GET' } do |get|
get.named_route("new_#{name.singularize}", "#{name}/new", :action => 'new')
get.named_route(name, name, :action => 'index')
get.named_route(name.singularize, "#{name}/:id", :action => 'show', :id => /\d+/)
get.named_route("edit_#{name.singularize}", "#{name}/:id/edit", :action => 'edit', :id => /\d+/)
end
map.with_options :conditions => { :fb_sig_request_method => 'POST' } do |post|
post.named_route("create_#{name.singularize}", name, :action => 'create')
post.named_route("update_#{name.singularize}", "#{name}/:id", :action => 'update', :id => /\d+/)
post.named_route("destroy_#{name.singularize}", "#{name}/:id/destroy", :action => 'destroy', :id => /\d+/)
end
end
end
Notice the new :condition => { :fb_sig_request_method => 'GET'} and :condition => { :fb_sig_request_method => 'POST'}? That is the key we need to be able to overload a path like /entries/1 to call #show or #update depending on the condition. As a result, our routes now generate much nicer paths. Check it:
GET /entries # index
GET /entries/new # new
POST /entries # create
GET /entries/1 # show
GET /entries/1/edit # edit
POST /entries/1 # update
POST /entries/1/destroy # destroy
A patch has been submitted to Facebooker. Hopefully the maintainers will see it fit to merge this in. As an added bonus, the patch also includes singleton resources via
facebook_resource :entry
to generate the following routes:
GET /entry # show
GET /entry/new # new
POST /entry # create
GET /entry/edit # edit
POST /entry/update # update
POST /entry/destroy # destroy
Note that this is still very much a workaround until Facebook moves all the fb_sig metadata into the request headers and use real GETs and POSTs. But until then, happy programming!
At RSB, we strongly believe in tests - copious amounts of tests. We have so many tests that we had to modify our Capistrano deployment recipes to NOT checkout our spec directory. Okay, I’m exaggerating. We do have decent amount of test, especially on the still-under-development Rails plugin to ease an aspect of Facebook development (we like to call it our secret sauce - look out for that soon).
So, we have autotest to keep us honest. The thing that bugs us is that autotest eats significant CPU resources as it constantly polls the filesystem to check file modification times. Aizat Faiz, one of our newest member to the RSB family, decided to hack autotest to utilize Leopard’s new File System Events with a bit of RubyCocoa glue. Now, autotest is informed of filesystem changes. Beautiful.
Go check out the blog entry, grab the code and paste it in your ~/.autotest file. Your machine will thank you.

