/*
* 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;
extern VALUE rb_cRuggedSubmodule;
VALUE rb_cRuggedSubmoduleCollection;
/*
* call-seq:
* SubmoduleCollection.new(repo) -> submodules
*
* Creates and returns a new collection of submodules for the given +repo+.
*/
static VALUE rb_git_submodule_collection_initialize(VALUE self, VALUE rb_repo)
{
rugged_check_repo(rb_repo);
rugged_set_owner(self, rb_repo);
return self;
}
static void rb_git_submodule__free(git_submodule *submodule)
{
git_submodule_free(submodule);
}
VALUE rugged_submodule_new(VALUE owner, git_submodule *submodule)
{
VALUE rb_submodule;
rb_submodule = Data_Wrap_Struct(
rb_cRuggedSubmodule,
NULL,
&rb_git_submodule__free,
submodule);
rugged_set_owner(rb_submodule, owner);
return rb_submodule;
}
/*
* call-seq:
* submodules[name] -> submodule or nil
*
* Lookup +submodule+ by +name+ or +path+ (they are usually the same) in
* +repository+.
*
* Returns +nil+ if submodule does not exist.
*
* Raises Rugged::SubmoduleError if submodule exists only in working
* directory (i.e. there is a subdirectory that is a valid self-contained git
* repository) and is not mentioned in the +HEAD+, the index and the config.
*/
static VALUE rb_git_submodule_collection_aref(VALUE self, VALUE rb_name)
{
git_repository *repo;
git_submodule *submodule;
int error;
VALUE rb_repo = rugged_owner(self);
Data_Get_Struct(rb_repo, git_repository, repo);
Check_Type(rb_name, T_STRING);
error = git_submodule_lookup(
&submodule,
repo,
StringValueCStr(rb_name)
);
if (error == GIT_ENOTFOUND)
return Qnil;
rugged_exception_check(error);
return rugged_submodule_new(rb_repo, submodule);
}
static int cb_submodule__each(git_submodule *submodule, const char *name, void *data)
{
struct rugged_cb_payload *payload = data;
git_repository *repo;
git_submodule *dummy_sm;
VALUE rb_repo;
rb_repo = payload->rb_data;
Data_Get_Struct(rb_repo, git_repository, repo);
/* The submodule passed here has it's refcount decreased just after
* the foreach finishes. The only way to increase that refcount is
* to lookup the submodule.
*
* This should not return an error as the submodule name here is valid
* and exists, as it was just passed to this callback.
*/
git_submodule_lookup(&dummy_sm, repo, git_submodule_name(submodule));
rb_protect(
rb_yield,
rugged_submodule_new(rb_repo, dummy_sm),
&payload->exception
);
return (payload->exception) ? GIT_ERROR : GIT_OK;
}
/*
* call-seq:
* submodules.each { |submodule| block }
* submodules.each -> enumerator
*
* Iterate through all the tracked submodules in the collection's +repository+.
*
* The given +block+ will be called once with each +submodule+
* as a Rugged::Submodule instance.
* If no block is given, an enumerator will be returned.
*/
static VALUE rb_git_submodule_collection_each(VALUE self)
{
git_repository *repo;
int error;
struct rugged_cb_payload payload;
VALUE rb_repo = rugged_owner(self);
Data_Get_Struct(rb_repo, git_repository, repo);
if (!rb_block_given_p())
return rb_funcall(self, rb_intern("to_enum"), 1, CSTR2SYM("each"));
payload.exception = 0;
payload.rb_data = rb_repo;
error = git_submodule_foreach(repo, &cb_submodule__each, &payload);
if (payload.exception)
rb_jump_tag(payload.exception);
rugged_exception_check(error);
return Qnil;
}
/*
* call-seq:
* submodules.setup_add(url, path[, options]) -> submodule
*
* Setup a new +submodule+ for checkout in +repository+.
*
* This does "git submodule add" up to the fetch and checkout of the
* submodule contents. It prepares a new submodule, creates an entry in
* +.gitmodules+ and creates an empty initialized repository either at the
* given +path+ in the working directory or in +.git/modules+ with a gitlink
* from the working directory to the new repository.
*
* To fully emulate "git submodule add" call this function, then open
* the submodule repository and perform the clone step as needed.
* Lastly, call Submodule#finalize_add to wrap up adding the new submodule and
* +.gitmodules+ to the index to be ready to commit.
*
* - +url+: URL for the submodule's remote
* - +path+: path at which the submodule should be created
*
* The following options can be passed in the +options+ Hash:
* :gitlink ::
* (defaults to +true+) should workdir contain a
* gitlink to the repository in +.git/modules+ vs. repository
* directly in workdir.
*
* Returns the newly created +submodule+
*/
static VALUE rb_git_submodule_setup_add(int argc, VALUE *argv, VALUE self)
{
git_submodule *submodule;
git_repository *repo;
int error;
int use_gitlink = 1;
VALUE rb_repo, rb_url, rb_path, rb_options;
rb_scan_args(argc, argv, "20:", &rb_url, &rb_path, &rb_options);
Check_Type(rb_url, T_STRING);
Check_Type(rb_path, T_STRING);
rb_repo = rugged_owner(self);
Data_Get_Struct(rb_repo, git_repository, repo);
if (!NIL_P(rb_options)) {
VALUE rb_val;
rb_val = rb_hash_aref(rb_options, CSTR2SYM("gitlink"));
use_gitlink = (rb_val != Qfalse);
}
error = git_submodule_add_setup(
&submodule,
repo,
StringValueCStr(rb_url),
StringValueCStr(rb_path),
use_gitlink
);
rugged_exception_check(error);
return rugged_submodule_new(rb_repo, submodule);
}
static git_submodule_ignore_t rb_git_subm_ignore_rule_toC(VALUE rb_ignore_rule)
{
ID id_ignore_rule;
Check_Type(rb_ignore_rule, T_SYMBOL);
id_ignore_rule = SYM2ID(rb_ignore_rule);
if (id_ignore_rule == rb_intern("none")) {
return GIT_SUBMODULE_IGNORE_NONE;
} else if (id_ignore_rule == rb_intern("untracked")) {
return GIT_SUBMODULE_IGNORE_UNTRACKED;
} else if (id_ignore_rule == rb_intern("dirty")) {
return GIT_SUBMODULE_IGNORE_DIRTY;
} else if (id_ignore_rule == rb_intern("all")) {
return GIT_SUBMODULE_IGNORE_ALL;
} else {
rb_raise(rb_eArgError, "Invalid submodule ignore rule type.");
}
}
static git_submodule_update_t rb_git_subm_update_rule_toC(VALUE rb_update_rule)
{
ID id_update_rule;
Check_Type(rb_update_rule, T_SYMBOL);
id_update_rule = SYM2ID(rb_update_rule);
if (id_update_rule == rb_intern("checkout")) {
return GIT_SUBMODULE_UPDATE_CHECKOUT;
} else if (id_update_rule == rb_intern("rebase")) {
return GIT_SUBMODULE_UPDATE_REBASE;
} else if (id_update_rule == rb_intern("merge")) {
return GIT_SUBMODULE_UPDATE_MERGE;
} else if (id_update_rule == rb_intern("none")) {
return GIT_SUBMODULE_UPDATE_NONE;
} else {
rb_raise(rb_eArgError, "Invalid submodule update rule type.");
}
}
/*
* call-seq:
* submodules.update(submodule, settings) -> nil
* submodules.update(name, settings) -> nil
*
* Update settings for the given submodule in the submodule config.
*
* Existing `Rugged::Submodule` instances are not updated, but can be
* reloaded by calling `#reload`.
*
* The following options can be passed in the +settings+ Hash:
*
* :url ::
* Updates the URL for the submodule.
*
* :ignore_rule ::
* See `Rugged::Submodule#ignore_rule` for a list of accepted rules.
*
* :update_rule ::
* See `Rugged::Submodule#update_rule` for a list of accepted rules.
*
* :fetch_recurse_submodules ::
* Updates the +fetchRecurseSubmodules+ rule.
*/
static VALUE rb_git_submodule_update(VALUE self, VALUE rb_name_or_submodule, VALUE rb_settings)
{
git_repository *repo;
git_submodule_ignore_t ignore_rule = GIT_SUBMODULE_IGNORE_UNSPECIFIED;
git_submodule_update_t update_rule = GIT_SUBMODULE_UPDATE_DEFAULT;
const char *submodule_name;
int fetch_recurse_submodules = 0;
VALUE rb_repo = rugged_owner(self);
VALUE rb_url, rb_fetch_recurse_submodules, rb_ignore_rule, rb_update_rule;
rugged_check_repo(rb_repo);
Data_Get_Struct(rb_repo, git_repository, repo);
if (rb_obj_is_kind_of(rb_name_or_submodule, rb_cRuggedSubmodule))
rb_name_or_submodule = rb_funcall(rb_name_or_submodule, rb_intern("name"), 0);
if (TYPE(rb_name_or_submodule) != T_STRING)
rb_raise(rb_eTypeError, "Expecting a String or Rugged::Submodule instance");
rb_url = rb_hash_aref(rb_settings, CSTR2SYM("url"));
rb_fetch_recurse_submodules = rb_hash_aref(rb_settings, CSTR2SYM("fetch_recurse_submodules"));
rb_ignore_rule = rb_hash_aref(rb_settings, CSTR2SYM("ignore_rule"));
rb_update_rule = rb_hash_aref(rb_settings, CSTR2SYM("update_rule"));
if (!NIL_P(rb_url)) {
Check_Type(rb_url, T_STRING);
}
if (!NIL_P(rb_fetch_recurse_submodules)) {
fetch_recurse_submodules = rugged_parse_bool(rb_fetch_recurse_submodules);
}
if (!NIL_P(rb_ignore_rule)) {
ignore_rule = rb_git_subm_ignore_rule_toC(rb_ignore_rule);
}
if (!NIL_P(rb_update_rule)) {
update_rule = rb_git_subm_update_rule_toC(rb_update_rule);
}
submodule_name = StringValueCStr(rb_name_or_submodule);
if (!NIL_P(rb_url)) {
rugged_exception_check(
git_submodule_set_url(repo,
submodule_name,
StringValueCStr(rb_url)
)
);
}
if (!NIL_P(rb_fetch_recurse_submodules)) {
rugged_exception_check(
git_submodule_set_fetch_recurse_submodules(repo,
submodule_name,
fetch_recurse_submodules
)
);
}
if (!NIL_P(rb_ignore_rule)) {
rugged_exception_check(
git_submodule_set_ignore(repo,
submodule_name,
ignore_rule
)
);
}
if (!NIL_P(rb_update_rule)) {
rugged_exception_check(
git_submodule_set_update(repo,
submodule_name,
update_rule
)
);
}
return Qnil;
}
void Init_rugged_submodule_collection(void)
{
rb_cRuggedSubmoduleCollection = rb_define_class_under(rb_mRugged, "SubmoduleCollection", rb_cObject);
rb_include_module(rb_cRuggedSubmoduleCollection, rb_mEnumerable);
rb_define_method(rb_cRuggedSubmoduleCollection, "initialize", rb_git_submodule_collection_initialize, 1);
rb_define_method(rb_cRuggedSubmoduleCollection, "[]", rb_git_submodule_collection_aref, 1);
rb_define_method(rb_cRuggedSubmoduleCollection, "each", rb_git_submodule_collection_each, 0);
rb_define_method(rb_cRuggedSubmoduleCollection, "update", rb_git_submodule_update, 2);
rb_define_method(rb_cRuggedSubmoduleCollection, "setup_add", rb_git_submodule_setup_add, -1);
}