libgit2/rugged — pull request #410 by charypar (2014-09-08 14:57:33+00:00)
Title: Add alternative backends support (originally #365)

This is a second pull request with this (originally #365, which is now very out of date - still against `development` branch).
I addressed the comments in the original PR. Sorry for the inactivity, this time I'm fully determined to get this in :)

/cc @arthurschreiber

# Original description

Surfaces libgit2 backends support in rugged in a way that shouldn't
affect performance. The change enables using rugged in environments with ephemeral
file-systems (e.g. Heroku), or as an in-memory data versioning tool for something
like very complex undo/redo, for example. It's probably of no use for normal
code repos, but is very useful for using git repos as a storage format for other
things (something like gist, for example)

Sorry for the length of the description, it's not the simplest thing to explain. :)

## Interface

The interface between rugged and the backend has two parts: 1) `Rugged::Backend`
ruby class (empty) to inherit in the backend implementation acting as a wrapper for 2)
a `rugged_backend` C struct.

The backend implementation has to provide two constructor functions that take
the repository path and return the ODB and RefDB backend structs.

```c
typedef struct _rugged_backend {
int (* odb_backend)(git_odb_backend **backend_out, struct _rugged_backend *backend, const char* path);
int (* refdb_backend)(git_refdb_backend **backend_out, struct _rugged_backend *backend, const char* path);
} rugged_backend;
```

Creating some backends requires special configuration (redis connection
settings for example) and the backend is supposed to wrap the `rugged_backend`
struct with it's own struct containing the extra information needed. Example:

```c
typedef struct {
rugged_backend backend;

char *host;
int port;
char *password;
} rugged_redis_backend;
```

It also needs to provide the constructor functions which use that information to
call the actual backend constructors. Example:

```c
static int rugged_redis_odb_backend(git_odb_backend **backend_out, rugged_backend *backend, const char* path)
{
rugged_redis_backend *rugged_backend = (rugged_redis_backend *) backend;
return git_odb_backend_hiredis(backend_out, "rugged", path, rugged_backend->host, rugged_backend->port, rugged_backend->password);
}

static int rugged_redis_refdb_backend(git_refdb_backend **backend_out, rugged_backend *backend, const char* path)
{
rugged_redis_backend *rugged_backend = (rugged_redis_backend *) backend;
return git_refdb_backend_hiredis(backend_out, "rugged", path, rugged_backend->host, rugged_backend->port, rugged_backend->password);
}
```

The reason for this extra indirection is the `path` parameter, which is only known
at the moment of creating/opening a repo.

Returned backends are then passed to `git_odb_add_backend` and `git_refdb_set_backend`
and eventually to `git_repository_wrap_odb` to create a repo. From then on, everything
works the same way, except the storage itself.

The backend wrapper implementation is responsible for surfacing a ruby level API for
providing the extra configuration.

See [rugged-redis](https://github.com/redbadger/rugged-redis) for a complete example
of a backend.

## API Changes

This directly affects two rugged methods: `Rugged::Repository#bare` and
`Rugged::Repository#init_at`. For the former it's a breaking change replacing the optional
`alternates` parameter with an options hash and moving alternates into it, but it's hopefully a
minor change affecting a small percentage of clients.

I only added support for bare repositories, because there is no way to provide
a place for a working directory.

## Usage example

Constructing a bare repo using an alternative backend looks something like this

```ruby
require 'rugged-redis'

redis_backend = Rugged::Redis::Backend.new(
host: Rails.configuration.redis_host,
port: Rails.configuration.redis_port,
password: Rails.configuration.redis_password)

repo = Rugged::Repository.init_at('my_repo', backend: redis_backend)

# use 'repo'
```

## Problems

One issue is that the created repositories don't have path. I fixed a crash
in `rb_git_repo_path` where `git_repository_path` returns `NULL`, but it would be
nicer if it was possible to provide the path to the repo created by wrapping an ODB.

Second slight problem is that the backend implementation needs `rugged.h`, which
makes writing the `extconf.rb` tricky, but possible.