Tuesday, August 26, 2008

Howto Setup a Subversion (svn) Repository for a Rails Project + Bonus

Setting up a Subversion (svn) repository is something development teams have to do fairly regularly, not that I want to use Subversion. :) If you listen to me, go use Git. Subversion is undoubtedly very good. But after using Git for about a year, you can't simply get me to switch back. Git is that good. :) I've written about Git before.

In cases where you can't use Git (or you feel too castrated by TortoiseSVN, pardon me for the pun) you can use Subversion. In this post I'll go through the steps you have to follow to get a basic Subversion setup up and running on a CentOS 5 Linux host. However I think you should be able to use this on other Linux distros too.

There's more than one way to host a Subversion repo. I'm going to stick with one way involving WebDAV. Don't mind the buzz word. It's the most common usage for this purpose. If you want a repository where you want to checkout and commit remotely, this is an easy way of getting it done. In addition to that I'll have some information targeting Ruby on Rails projects. However, I think it's not Rails specific, but also useful with other things too.

In addition I'll mention about a problem I came across where act_as_ferret caused an error and what was the reason.

1. Selection of Tools

We are going to use the following tools:
  • Apache HTTPD (Web Server) 2.2
  • mod_dav_svn for Apache
  • Subversion
If these are not installed already, go ahead and install them. You can use any method you like. This is how you do it using YUM tool

# yum install mod_dav_svn subversion httpd

You can verify whether you have them installed via the RPM system by using

# rpm -qa subversion httpd mod_dav_svn

If installed it'll show something similar to
Note: If you didn't use yum/rpm to install, most probably you won't be able to use the above rpm command.

Now that you are set to it let's move forward.

2. Create the Subversion Configuration File for Apache

For this create a file under /etc/httpd/conf.d/ which has a name ending with .conf and enter the following lines
(Eg: /etc/httpd/conf.d/subversion.conf)

LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so

<Location /repos>
DAV svn
SVNPath /var/www/svn/repos
AuthzSVNAccessFile /etc/svn-acl-conf
AuthType Basic
AuthName "UberCool Subversion Repository"
AuthUserFile /etc/svn-auth-conf
Require valid-user

Replace the values for the following according to your requirement

Location - The location you want in the URL (In this case: http://example.com/repos)
- The path to the directory you wish to store your repositories
AuthzSVNAccessFile - The file where you would hold the access control details for users
AuthName - A description you like the users to see
AuthUserFile - The file where you would hold the username/passwords for your users

Note: In CentOS 5 files with a *.conf name under the /etc/httpd/conf.d/ will get automatically get loaded. If you want to change the behavior or add another location for configuration files, the magic line is in the /etc/httpd/conf/httpd.conf file
Include conf.d/*.conf

3. Create Subversion Users and Set Access Control

Here I'll tell how to manage Subversion users and their access levels (basic).

3.1 Manage Subversion Users

Let's now add users to our little still non-existing repo. This step is not mandatory to precede the repo creation.

What we are going to use is basic HTTP authentication. If you want to use different authentication systems like LDAP you'll have to look elsewhere for that.

Use htpasswd command to create a password file and then to add/modify/remove users

Create a Password file:
# htpasswd -cm /etc/svn-auth-conf yourfirstuser

Add/Modify Users:
# htpasswd -m /etc/svn-auth-conf yourseconduser
# htpasswd -m /etc/svn-auth-conf yourotheruser

Remove User:
# htpasswd -D /etc/svn-auth-conf yourotheruser

3.2 Set Access Control

What happens if you want certain users to have full access to the repository, but only read-only access for a few users. You can achieve this level of access control easily. This is the purpose we mentioned a file called /etc/svn-acl-conf in the configuration file.

Edit the file with your favorite editor ans enter the details. I'll put some data I have, replace these with your actual users. Please note that "r" stands for Read and "RW" for Read-Write.

yourfirstuser = rw
yourseconduser = rw
yourthirduser = r

Save the file and restart the Web Server (Eg: # service httpd restart)

4. Create Repository Storage Area

It's not much, nothing too fancy. :) Create the directory you specified in the Apache configuration file, create the repo and change permission so that Apache could read it. Here we go,

# mkdir -p /var/www/svn
# cd /var/www/svn

# svnadmin create repos
# chown -R apache.apache repos

If things went fine this far now you'll be able to see an empty repo. Just start/restart the web server (# service httpd restart) and browse to the relevant URL (http://example.com/repos). If all is good you'll see a page saying Revision 0.

5. Set to Import Your Rails Project to Subversion Repository

**Note: In this post I'll go with creating proper Subversion repo with branch/tag/trunk layout. If you want to host more than one project in a repository, here is a good place to start with. (Which by the way is the source where much of the information in this post came from. I'm just compiling them into my experience) The above link will point you how to do a simple import of an existing project. However, I'm traveling the longer path because it gives us more flexibility in setting repository parameter which you will see in step 6.

I'll assume you already have a Rails project. Since Rails projects will have certain peculiar requirements, it'll be a great way to experiment Subversion. Let's assume your project is in your /bak directory and is called UberCool (directory name is ubercool).

First lets create a proper Subversion top level layout with
# svn mkdir --message="Setting project layout" file:///var/www/svn/repos/trunk file:///var/www/svn/repos/tags file:///var/www/svn/repos/branches

Then, checkout our repository to the project directory by
# svn checkout file:///var/www/svn/repos/trunk /bak/ubercool

Note: We are set to import here. We didn't add or commit any files yet. So if you are following this post (*not* the above mentioned guide from CentOS wiki), please proceed to step 6. We are not done yet.

6. Tweak Your Subversion Repository

Although we are set to import the project in the step 5, we didn't actually import the project yet. What we are trying to do is set the special tweaks we need in the repository before we import the project. So here we go.

Change into the project directory. Then, add the files to the Subversion repo. This is not the same as committing. We are going to mark the files to be imported from the project directory (or technically commit in) to the repo.
# cd /bak/ubercool
# svn add . --force

The --force option is required because Subversion does not know yet that these file are part of the project. So we have to instruct it sternly to add the files. :)

6.1 Stop unnecessary files from being Version Controlled

As you might know there are files that we do not wish to be version controlled. In a Rails project most definitely you don't want to keep track of the history of log files. In most cases you don't want to version track images (and/or binary files such as JPEG, MPEG, FLV, etc. files). You can remove these files from version control. For example, here's a few things I'll do in a typical Rails project:

# svn revert log/*
# svn revert config/database.yml
# svn propset svn:ignore "*.log" log
# svn propset svn:ignore "database.yml" config
# svn propset svn:ignore "database.yml" config
# svn revert public/index.html
# rm public/index.html

The above will remove log files and database.yml from version controlling and will ignore future versions of those file. It will remove the public/index.html file from version controlling too.

Rails projects also usually require a tmp directory which is not available here. So let's create it and but keep it away from version controlling. The tmp directory is required usually. If you've used Mongrel, you know what I mean. :) Whatever else directories or files you need to be available, yet not be version controlled,...... create now and remove from version controlling. (Eg: database migration files)

# svn mkdir tmp db/migrate
# svn propset svn:ignore "*" tmp

6.2 Mark Executable Files

There are files that you'd want to keep as executable files. If you are not sure let's consider a situation where I've been. Skip the background story if you don't want to hear it.

[Start of Side Story]
At my last work place we had a project which refused to obey when we call $ rake db:fixtures:load We knew this error was introduced only after act_as_ferret was introduced to the application. We were able to confirm it when we noticed fixtures without and ferret index could be loaded successfully.

After some work we trace the cause to ferret_server. In fact the ferret_server script failed to run. I know some of you might not have know about such a server was associated with act_as_ferret. But it is. The script should run if act_as_ferret were to function.

So what was the issue? It was the common Unix execution issue. Simply the script/ferret_server did not have execution permission on the deployment. The initiation point was, the ferret plugin being committed from a Windows box where you don't have a notion about execution permission. But even if we committed from a Linux box it would not make any difference for us. Our Subversion repository did not have the execution notion either.

Ultimately I had to solve the issue by modifying our deployment (Capistrano) scripts. Ideally I should have made the Subversion repo execution permission aware, but I didn't have that luxury due to the fact that our particular Subversion hosting was provided by a third party. However you can have that convenience and mark necessary files as executable in your repo. Read ahead.
[End of Side Story]

So let's make sure that contents in the scripts directory (and other required) of our Rails project are executable files. Please note that this works only on files. You cannot set execution property for directories in Subversion.

# svn propset svn:executable script/performance/* script/process/* script/* public/dispatch.*

Or, a better Unix-y way:

# svn propset svn:executable `find ./script -type f | grep -v '.svn'` public/dispatch.*

This was all I was felt was necessary, but browsing the web I found one more important tweak. Since all deployment hosts doesn't handle line ending in the same way (Eg: Unix & Windows) it's best to allow the OS to choose the line ending of the dispatchers.

# svn propset svn:eol-style native public/dispatch.*

According to the above, we just set Unix users will have LF line ending while Windows users will get a CRLF ending. Don't worry about it much if you don't know what I say about line endings. :)

7. Commit Your Hard Work and Sigh

Now you have completed all the hard labor of creating and tweaking a Subversion setup. Let's commit the work we've been doing now.

# svn commit --message="Initial commit, worked hard for this,.. really"

There you go. I know that's some work, but it's not too hard. After all we admins are paid to do these work. :)

Now you can let out a sigh of relief and enjoy your shiny new Subversion repository and work on your Uber Cool project. You can checkout your new repo using any Subversion (SVN) client with the URL of http://example.com/repos/ubercool/trunk

Eg: $ svn co http://example.com/repos/ubercool/trunk ubercool

That's all for now. This post (rather long) was written in haste, so I beg your pardon for any mistake or poor quality you encounter. If you need more information about Subversion administration, don't forget to check the Subverion Book. I've referred to it all the time whenever I'm not allowed to use Git. :P

PS: If you do not believe that Git is so good,............. go fork yourself!