diff --git a/README.md b/README.md index 38a6d7a05..2a2b8df25 100644 --- a/README.md +++ b/README.md @@ -569,6 +569,22 @@ Rugged.raw_to_hex("\277\336Y\315\320\337\254\035\211(\024\366j\225d\032\275\212\ --- +### Alternative backends + +You can store bare repositories in alternative backends instead of storing on disk. (see +`redbadger/rugged-redis` for an example of how a rugged backend works). + +```ruby +a_backend = Rugged::InMemory::Backend.new(opt1: 'setting', opt2: 'setting') + +repo = Rugged::Repository.init_at('repo_name', :bare, backend: a_backend) + +# or + +repo = Rugged::Repository.bare('repo_name', backend: a_backend) +``` +--- + ## Contributing Fork libgit2/rugged on GitHub, make it awesomer (preferably in a branch named diff --git a/ext/rugged/rugged.c b/ext/rugged/rugged.c index 5eafac235..80b1ae09c 100644 --- a/ext/rugged/rugged.c +++ b/ext/rugged/rugged.c @@ -455,6 +455,7 @@ void Init_rugged(void) Init_rugged_diff_line(); Init_rugged_blame(); Init_rugged_cred(); + Init_rugged_backend(); /* * Sort the repository contents in no particular ordering; diff --git a/ext/rugged/rugged.h b/ext/rugged/rugged.h index 81c375eaf..b4e070c78 100644 --- a/ext/rugged/rugged.h +++ b/ext/rugged/rugged.h @@ -74,6 +74,7 @@ void Init_rugged_diff_hunk(void); void Init_rugged_diff_line(void); void Init_rugged_blame(void); void Init_rugged_cred(void); +void Init_rugged_backend(void); VALUE rb_git_object_init(git_otype type, int argc, VALUE *argv, VALUE self); @@ -181,4 +182,10 @@ static inline VALUE rugged_create_oid(const git_oid *oid) return rb_str_new(out, 40); } + +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; + #endif diff --git a/ext/rugged/rugged_backend.c b/ext/rugged/rugged_backend.c new file mode 100644 index 000000000..b4bb391cd --- /dev/null +++ b/ext/rugged/rugged_backend.c @@ -0,0 +1,34 @@ +/* + * The MIT License + * + * Copyright (c) 2014 GitHub, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "rugged.h" + +extern VALUE rb_mRugged; + +VALUE rb_cRuggedBackend; + +void Init_rugged_backend(void) +{ + rb_cRuggedBackend = rb_define_class_under(rb_mRugged, "Backend", rb_cObject); +} diff --git a/ext/rugged/rugged_repo.c b/ext/rugged/rugged_repo.c index af7fa3e29..21d29b3df 100644 --- a/ext/rugged/rugged_repo.c +++ b/ext/rugged/rugged_repo.c @@ -24,6 +24,9 @@ #include "rugged.h" #include +#include +#include +#include extern VALUE rb_mRugged; extern VALUE rb_eRuggedError; @@ -35,6 +38,7 @@ extern VALUE rb_cRuggedCommit; extern VALUE rb_cRuggedTag; extern VALUE rb_cRuggedTree; extern VALUE rb_cRuggedReference; +extern VALUE rb_cRuggedBackend; extern VALUE rb_cRuggedCredPlaintext; extern VALUE rb_cRuggedCredSshKey; @@ -182,9 +186,77 @@ static void load_alternates(git_repository *repo, VALUE rb_alternates) rugged_exception_check(error); } +static void rugged_repo_new_with_backend(git_repository **repo, VALUE rb_path, VALUE rb_backend) +{ + Check_Type(rb_path, T_STRING); + char *path = StringValuePtr(rb_path); + + if(rb_obj_is_kind_of(rb_backend, rb_cRuggedBackend) == Qfalse) { + rb_raise(rb_eRuggedError, "Backend must be an instance of Rugged::Backend"); + } + + rugged_backend *backend; + Data_Get_Struct(rb_backend, rugged_backend, backend); + + git_odb *odb = NULL; + git_odb_backend *odb_backend = NULL; + git_refdb *refdb = NULL; + git_refdb_backend *refdb_backend = NULL; + git_reference *head = NULL; + + int error = 0; + + error = git_odb_new(&odb); + if (error) goto cleanup; + + error = backend->odb_backend(&odb_backend, backend, path); + if (error) goto cleanup; + + error = git_odb_add_backend(odb, odb_backend, 1); + if (error) goto cleanup; + + error = git_repository_wrap_odb(repo, odb); + if (error) goto cleanup; + + error = git_refdb_new(&refdb, *repo); + if (error) goto cleanup; + + error = backend->refdb_backend(&refdb_backend, backend, path); + if (error) goto cleanup; + + error = git_refdb_set_backend(refdb, refdb_backend); + if (error) goto cleanup; + + git_repository_set_refdb(*repo, refdb); + + error = git_reference_lookup(&head, *repo, "HEAD"); + + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = git_reference_symbolic_create(&head, *repo, "HEAD", "refs/heads/master", 0, NULL, NULL); + } + + if (!error) { + git_reference_free(head); + } else goto cleanup; + + return; + +cleanup: + git_repository_free(*repo); + git_odb_free(odb); + git_refdb_free(refdb); + + if (odb_backend != NULL) odb_backend->free(odb_backend); + if (refdb_backend != NULL) refdb_backend->free(refdb_backend); + + rugged_exception_check(error); +} + /* * call-seq: - * Repository.bare(path[, alternates]) -> repository + * Repository.bare(path[, alternates]) -> repository OR + * Repository.bare(path[, options]) -> repository * * Open a bare Git repository at +path+ and return a +Repository+ * object representing it. @@ -193,23 +265,52 @@ static void load_alternates(git_repository *repo, VALUE rb_alternates) * any +.git+ directory discovery, won't try to load the config options to * determine whether the repository is bare and won't try to load the workdir. * - * Optionally, you can pass a list of alternate object folders. + * Optionally, you can pass a list of alternate object folders or an options Hash. * * Rugged::Repository.bare(path, ['./other/repo/.git/objects']) + * Rugged::Repository.bare(path, opts) + * + * The following options can be passed in the +options+ Hash: + * + * :backend :: + * A Rugged::Backend instance + * :alternates :: + * A list of alternate object folders. + * Rugged::Repository.bare(path, :alternates => ['./other/repo/.git/objects']) */ static VALUE rb_git_repo_open_bare(int argc, VALUE *argv, VALUE klass) { - git_repository *repo; + git_repository *repo = NULL; int error = 0; - VALUE rb_path, rb_alternates; + VALUE rb_path, rb_options, rb_alternates = 0; - rb_scan_args(argc, argv, "11", &rb_path, &rb_alternates); - Check_Type(rb_path, T_STRING); + rb_scan_args(argc, argv, "11", &rb_path, &rb_options); - error = git_repository_open_bare(&repo, StringValueCStr(rb_path)); - rugged_exception_check(error); + if (!NIL_P(rb_options) && TYPE(rb_options) == T_ARRAY) + rb_alternates = rb_options; - load_alternates(repo, rb_alternates); + if (!NIL_P(rb_options) && TYPE(rb_options) == T_HASH) { + /* Check for `:backend` */ + VALUE rb_backend = rb_hash_aref(rb_options, CSTR2SYM("backend")); + + if (!NIL_P(rb_backend)) { + rugged_repo_new_with_backend(&repo, rb_path, rb_backend); + } + + /* Check for `:alternates` */ + rb_alternates = rb_hash_aref(rb_options, CSTR2SYM("alternates")); + } + + if (!repo) { + Check_Type(rb_path, T_STRING); + + error = git_repository_open_bare(&repo, StringValueCStr(rb_path)); + rugged_exception_check(error); + } + + if (rb_alternates) { + load_alternates(repo, rb_alternates); + } return rugged_repo_new(klass, repo); } @@ -260,7 +361,7 @@ static VALUE rb_git_repo_new(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Repository.init_at(path, is_bare = false) -> repository + * Repository.init_at(path, is_bare = false, opts = {}) -> repository * * Initialize a Git repository in +path+. This implies creating all the * necessary files on the FS, or re-initializing an already existing @@ -272,19 +373,36 @@ static VALUE rb_git_repo_new(int argc, VALUE *argv, VALUE klass) * of +path+. Non-bare repositories are created in a +.git+ folder and * use +path+ as working directory. * + * The following options can be passed in the +options+ Hash: + * + * :backend :: + * A Rugged::Backend instance + * + * * Rugged::Repository.init_at('~/repository', :bare) #=> # */ static VALUE rb_git_repo_init_at(int argc, VALUE *argv, VALUE klass) { - git_repository *repo; - VALUE rb_path, rb_is_bare; + git_repository *repo = NULL; + VALUE rb_path, rb_is_bare, rb_options; + int error; - rb_scan_args(argc, argv, "11", &rb_path, &rb_is_bare); + rb_scan_args(argc, argv, "11:", &rb_path, &rb_is_bare, &rb_options); Check_Type(rb_path, T_STRING); - rugged_exception_check( - git_repository_init(&repo, StringValueCStr(rb_path), RTEST(rb_is_bare)) - ); + if (!NIL_P(rb_options)) { + /* Check for `:backend` */ + VALUE rb_backend = rb_hash_aref(rb_options, CSTR2SYM("backend")); + + if (rb_backend && !NIL_P(rb_backend)) { + rugged_repo_new_with_backend(&repo, rb_path, rb_backend); + } + } + + if(!repo) { + error = git_repository_init(&repo, StringValueCStr(rb_path), RTEST(rb_is_bare)); + rugged_exception_check(error); + } return rugged_repo_new(klass, repo); } @@ -585,7 +703,7 @@ static VALUE rb_git_repo_merge_bases(VALUE self, VALUE rb_args) * :fastforward :: * The given commit is a fast-forward from HEAD and no merge needs to be * performed. HEAD can simply be set to the given commit. - * + * * :unborn :: * The HEAD of the current repository is "unborn" and does not point to * a valid commit. No merge can be performed, but the caller may wish @@ -1116,8 +1234,12 @@ static VALUE rb_git_repo_get_head(VALUE self) static VALUE rb_git_repo_path(VALUE self) { git_repository *repo; + const char *path; + Data_Get_Struct(self, git_repository, repo); - return rb_str_new_utf8(git_repository_path(repo)); + path = git_repository_path(repo); + + return path ? rb_str_new_utf8(path) : Qnil; } /*