Commit 0c601229 by Carlos Martín Nieto

Merge commit 'refs/pull/2632/head' of github.com:libgit2/libgit2

parents c4a2fd5c 9d1f97df
......@@ -172,3 +172,7 @@ v0.21 + 1
* The local transport now generates textual progress output like
git-upload-pack does ("counting objects").
* The git_submodule_update function was renamed to
git_submodule_update_strategy. git_submodule_update is now used to
provide functionalty similar to "git submodule update".
......@@ -10,6 +10,8 @@
#include "common.h"
#include "types.h"
#include "oid.h"
#include "remote.h"
#include "checkout.h"
/**
* @file git2/submodule.h
......@@ -106,6 +108,68 @@ typedef enum {
GIT_SUBMODULE_STATUS_WD_UNTRACKED)) != 0)
/**
* Submodule update options structure
*
* Use the GIT_SUBMODULE_UPDATE_OPTIONS_INIT to get the default settings, like this:
*
* git_submodule_update_options opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
*/
typedef struct git_submodule_update_options {
unsigned int version;
/**
* These options are passed to the checkout step. To disable
* checkout, set the `checkout_strategy` to
* `GIT_CHECKOUT_NONE`. Generally you will want the use
* GIT_CHECKOUT_SAFE to update files in the working
* directory. Use the `clone_checkout_strategy` field
* to set the checkout strategy that will be used in
* the case where update needs to clone the repository.
*/
git_checkout_options checkout_opts;
/**
* Callbacks to use for reporting fetch progress, and for acquiring
* credentials in the event they are needed.
*/
git_remote_callbacks remote_callbacks;
/**
* The checkout strategy to use when the sub repository needs to
* be cloned. Use GIT_CHECKOUT_SAFE_CREATE to create all files
* in the working directory for the newly cloned repository.
*/
unsigned int clone_checkout_strategy;
/**
* The identity used when updating the reflog. NULL means to
* use the default signature using the config.
*/
git_signature *signature;
} git_submodule_update_options;
#define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1
#define GIT_SUBMODULE_UPDATE_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION, {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE}, GIT_REMOTE_CALLBACKS_INIT, GIT_CHECKOUT_SAFE_CREATE}
/**
* Update a submodule. This will clone a missing submodule and
* checkout the subrepository to the commit specified in the index of
* containing repository.
*
* @param submodule Submodule object
* @param init If the submodule is not initialized, setting this flag to true
* will initialize the submodule before updating. Otherwise, this will
* return an error if attempting to update an uninitialzed repository.
* but setting this to true forces them to be updated.
* @param options configuration options for the update. If NULL, the
* function works as though GIT_SUBMODULE_UPDATE_OPTIONS_INIT was passed.
* @return 0 on success, any non-zero return value from a callback
* function, or a negative value to indicate an error (use
* `giterr_last` for a detailed error message).
*/
GIT_EXTERN(int) git_submodule_update(git_submodule *submodule, int init, git_submodule_update_options *options);
/**
* Lookup submodule information by name or path.
*
* Given either the submodule name or path (they are usually the same), this
......@@ -403,7 +467,7 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore(
* @return The current git_submodule_update_t value that will be used
* for this submodule.
*/
GIT_EXTERN(git_submodule_update_t) git_submodule_update(
GIT_EXTERN(git_submodule_update_t) git_submodule_update_strategy(
git_submodule *submodule);
/**
......
......@@ -381,10 +381,6 @@ int git_submodule_add_setup(
return GIT_EEXISTS;
}
/* resolve parameters */
if ((error = git_submodule_resolve_url(&real_url, repo, url)) < 0)
goto cleanup;
/* validate and normalize path */
if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
......@@ -409,7 +405,7 @@ int git_submodule_add_setup(
goto cleanup;
if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
(error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)
(error = git_config_file_set_string(mods, name.ptr, url)) < 0)
goto cleanup;
git_buf_clear(&name);
......@@ -425,6 +421,11 @@ int git_submodule_add_setup(
*/
if (!(git_path_exists(name.ptr) &&
git_path_contains(&name, DOT_GIT))) {
/* resolve the actual URL to use */
if ((error = git_submodule_resolve_url(&real_url, repo, url)) < 0)
goto cleanup;
if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0)
goto cleanup;
}
......@@ -466,13 +467,24 @@ int git_submodule_repo_init(
{
int error;
git_repository *sub_repo = NULL;
const char *configured_url;
git_config *cfg = NULL;
git_buf buf = GIT_BUF_INIT;
assert(out && sm);
error = submodule_repo_init(&sub_repo, sm->repo, sm->path, sm->url, use_gitlink);
/* get the configured remote url of the submodule */
if ((error = git_buf_printf(&buf, "submodule.%s.url", sm->name)) < 0 ||
(error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 ||
(error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 ||
(error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0)
goto done;
*out = sub_repo;
done:
git_config_free(cfg);
git_buf_free(&buf);
return error;
}
......@@ -778,7 +790,7 @@ git_submodule_ignore_t git_submodule_set_ignore(
return old;
}
git_submodule_update_t git_submodule_update(git_submodule *submodule)
git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule)
{
assert(submodule);
return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ?
......@@ -823,11 +835,183 @@ git_submodule_recurse_t git_submodule_set_fetch_recurse_submodules(
return old;
}
static int submodule_repo_create(
git_repository **out,
git_repository *parent_repo,
const char *path)
{
int error = 0;
git_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT;
git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
git_repository *subrepo = NULL;
initopt.flags =
GIT_REPOSITORY_INIT_MKPATH |
GIT_REPOSITORY_INIT_NO_REINIT |
GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
/* Workdir: path to sub-repo working directory */
error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
if (error < 0)
goto cleanup;
initopt.workdir_path = workdir.ptr;
/**
* Repodir: path to the sub-repo. sub-repo goes in:
* <repo-dir>/modules/<name>/ with a gitlink in the
* sub-repo workdir directory to that repository.
*/
error = git_buf_join3(
&repodir, '/', git_repository_path(parent_repo), "modules", path);
if (error < 0)
goto cleanup;
error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
cleanup:
git_buf_free(&workdir);
git_buf_free(&repodir);
*out = subrepo;
return error;
}
/**
* Callback to override sub-repository creation when
* cloning a sub-repository.
*/
static int git_submodule_update_repo_init_cb(
git_repository **out,
const char *path,
int bare,
void *payload)
{
GIT_UNUSED(bare);
git_submodule *sm = payload;
return submodule_repo_create(out, sm->repo, path);
}
int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options)
{
int error;
unsigned int submodule_status;
git_config *config = NULL;
const char *submodule_url;
git_repository *sub_repo = NULL;
git_remote *remote = NULL;
git_object *target_commit = NULL;
git_buf buf = GIT_BUF_INIT;
git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT;
assert(sm);
if (_update_options)
memcpy(&update_options, _update_options, sizeof(git_submodule_update_options));
GITERR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options");
/* Copy over the remote callbacks */
clone_options.remote_callbacks = update_options.remote_callbacks;
clone_options.signature = update_options.signature;
/* Get the status of the submodule to determine if it is already initialized */
if ((error = git_submodule_status(&submodule_status, sm)) < 0)
goto done;
/*
* If submodule work dir is not already initialized, check to see
* what we need to do (initialize, clone, return error...)
*/
if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) {
/*
* Work dir is not initialized, check to see if the submodule
* info has been copied into .git/config
*/
if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
(error = git_buf_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0)
goto done;
if ((error = git_config_get_string(&submodule_url, config, git_buf_cstr(&buf))) < 0) {
/*
* If the error is not "not found" or if it is "not found" and we are not
* initializing the submodule, then return error.
*/
if (error != GIT_ENOTFOUND)
goto done;
if (error == GIT_ENOTFOUND && !init) {
giterr_set(GITERR_SUBMODULE, "Submodule is not initialized.");
error = GIT_ERROR;
goto done;
}
/* The submodule has not been initialized yet - initialize it now.*/
if ((error = git_submodule_init(sm, 0)) < 0)
goto done;
git_config_free(config);
config = NULL;
if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
(error = git_config_get_string(&submodule_url, config, git_buf_cstr(&buf))) < 0)
goto done;
}
/** submodule is initialized - now clone it **/
/* override repo creation */
clone_options.repository_cb = git_submodule_update_repo_init_cb;
clone_options.repository_cb_payload = sm;
/*
* Do not perform checkout as part of clone, instead we
* will checkout the specific commit manually.
*/
clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;
update_options.checkout_opts.checkout_strategy = update_options.clone_checkout_strategy;
if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 ||
(error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm), update_options.signature, NULL)) < 0 ||
(error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0)
goto done;
} else {
/**
* Work dir is initialized - look up the commit in the parent repository's index,
* update the workdir contents of the subrepository, and set the subrepository's
* head to the new commit.
*/
if ((error = git_submodule_open(&sub_repo, sm)) < 0 ||
(error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0 ||
(error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 ||
(error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm), update_options.signature, NULL)) < 0)
goto done;
/* Invalidate the wd flags as the workdir has been updated. */
sm->flags = sm->flags &
~(GIT_SUBMODULE_STATUS_IN_WD |
GIT_SUBMODULE_STATUS__WD_OID_VALID |
GIT_SUBMODULE_STATUS__WD_SCANNED);
}
done:
git_buf_free(&buf);
git_config_free(config);
git_object_free(target_commit);
git_remote_free(remote);
git_repository_free(sub_repo);
return error;
}
int git_submodule_init(git_submodule *sm, int overwrite)
{
int error;
const char *val;
git_buf key = GIT_BUF_INIT;
git_buf key = GIT_BUF_INIT, effective_submodule_url = GIT_BUF_INIT;
git_config *cfg = NULL;
if (!sm->url) {
......@@ -841,9 +1025,10 @@ int git_submodule_init(git_submodule *sm, int overwrite)
/* write "submodule.NAME.url" */
if ((error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
if ((error = git_submodule_resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 ||
(error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
(error = git_config__update_entry(
cfg, key.ptr, sm->url, overwrite != 0, false)) < 0)
cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0)
goto cleanup;
/* write "submodule.NAME.update" if not default */
......@@ -861,6 +1046,7 @@ int git_submodule_init(git_submodule *sm, int overwrite)
cleanup:
git_config_free(cfg);
git_buf_free(&key);
git_buf_free(&effective_submodule_url);
return error;
}
......@@ -1853,16 +2039,31 @@ static int lookup_head_remote_key(git_buf *remote_name, git_repository *repo)
if ((error = git_repository_head(&head, repo)) < 0)
return error;
/**
* If head does not refer to a branch, then return
* GIT_ENOTFOUND to indicate that we could not find
* a remote key for the local tracking branch HEAD points to.
**/
if (!git_reference_is_branch(head)) {
giterr_set(GITERR_INVALID,
"HEAD does not refer to a branch.");
error = GIT_ENOTFOUND;
goto done;
}
/* lookup remote tracking branch of HEAD */
if (!(error = git_branch_upstream_name(
&upstream_name, repo, git_reference_name(head))))
{
if ((error = git_branch_upstream_name(
&upstream_name,
repo,
git_reference_name(head))) < 0)
goto done;
/* lookup remote of remote tracking branch */
error = git_branch_remote_name(remote_name, repo, upstream_name.ptr);
if ((error = git_branch_remote_name(remote_name, repo, upstream_name.ptr)) < 0)
goto done;
done:
git_buf_free(&upstream_name);
}
git_reference_free(head);
return error;
......
[submodule "testrepo"]
path = testrepo
url = ../testrepo.git
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
Unnamed repository; edit this file 'description' to name the repository.
# pack-refs with: peeled fully-peeled
229cea838964f435d4fc2c11561ddb7447003609 refs/remotes/origin/alternate_1
a8575e6aaececba78823993e4f11abbc6172aabd refs/remotes/origin/master
a8575e6aaececba78823993e4f11abbc6172aabd
#include "clar_libgit2.h"
#include "posix.h"
#include "path.h"
#include "submodule_helpers.h"
#include "fileops.h"
static git_repository *g_repo = NULL;
void test_submodule_init__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_submodule_init__absolute_url(void)
{
git_submodule *sm;
git_config *cfg;
git_buf absolute_url = GIT_BUF_INIT;
const char *config_url;
g_repo = setup_fixture_submodule_simple();
cl_assert(git_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0);
cl_git_pass(git_buf_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
/* write the absolute url to the .gitmodules file*/
cl_git_pass(git_submodule_set_url(sm, absolute_url.ptr));
/* verify that the .gitmodules is set with an absolute path*/
cl_assert_equal_s(absolute_url.ptr, git_submodule_url(sm));
/* init and verify that absolute path is written to .git/config */
cl_git_pass(git_submodule_init(sm, false));
cl_git_pass(git_repository_config(&cfg, g_repo));
git_config_get_string(&config_url, cfg, "submodule.testrepo.url");
cl_assert_equal_s(absolute_url.ptr, config_url);
git_buf_free(&absolute_url);
git_config_free(cfg);
}
void test_submodule_init__relative_url(void)
{
git_submodule *sm;
git_config *cfg;
git_buf absolute_url = GIT_BUF_INIT;
const char *config_url;
g_repo = setup_fixture_submodule_simple();
cl_assert(git_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0);
cl_git_pass(git_buf_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
/* verify that the .gitmodules is set with an absolute path*/
cl_assert_equal_s("../testrepo.git", git_submodule_url(sm));
/* init and verify that absolute path is written to .git/config */
cl_git_pass(git_submodule_init(sm, false));
cl_git_pass(git_repository_config(&cfg, g_repo));
git_config_get_string(&config_url, cfg, "submodule.testrepo.url");
cl_assert_equal_s(absolute_url.ptr, config_url);
git_buf_free(&absolute_url);
git_config_free(cfg);
}
void test_submodule_init__relative_url_detached_head(void)
{
git_submodule *sm;
git_config *cfg;
git_buf absolute_url = GIT_BUF_INIT;
const char *config_url;
git_reference *head_ref = NULL;
git_object *head_commit = NULL;
g_repo = setup_fixture_submodule_simple();
/* Put the parent repository into a detached head state. */
cl_git_pass(git_repository_head(&head_ref, g_repo));
cl_git_pass(git_reference_peel(&head_commit, head_ref, GIT_OBJ_COMMIT));
cl_git_pass(git_repository_set_head_detached(g_repo, git_commit_id((git_commit *)head_commit), NULL, NULL));
cl_assert(git_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0);
cl_git_pass(git_buf_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
/* verify that the .gitmodules is set with an absolute path*/
cl_assert_equal_s("../testrepo.git", git_submodule_url(sm));
/* init and verify that absolute path is written to .git/config */
cl_git_pass(git_submodule_init(sm, false));
cl_git_pass(git_repository_config(&cfg, g_repo));
git_config_get_string(&config_url, cfg, "submodule.testrepo.url");
cl_assert_equal_s(absolute_url.ptr, config_url);
git_buf_free(&absolute_url);
git_config_free(cfg);
}
......@@ -49,7 +49,7 @@ void test_submodule_lookup__accessors(void)
cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0);
cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE);
cl_assert(git_submodule_update(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT);
cl_assert(git_submodule_update_strategy(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT);
git_submodule_free(sm);
......
......@@ -160,7 +160,7 @@ void test_submodule_modify__edit_and_save(void)
cl_assert_equal_i(
GIT_SUBMODULE_IGNORE_UNTRACKED, git_submodule_ignore(sm1));
cl_assert_equal_i(
GIT_SUBMODULE_UPDATE_REBASE, git_submodule_update(sm1));
GIT_SUBMODULE_UPDATE_REBASE, git_submodule_update_strategy(sm1));
cl_assert_equal_i(
GIT_SUBMODULE_RECURSE_YES, git_submodule_fetch_recurse_submodules(sm1));
......@@ -179,7 +179,7 @@ void test_submodule_modify__edit_and_save(void)
/* check that revert was successful */
cl_assert_equal_s(old_url, git_submodule_url(sm1));
cl_assert_equal_i((int)old_ignore, (int)git_submodule_ignore(sm1));
cl_assert_equal_i((int)old_update, (int)git_submodule_update(sm1));
cl_assert_equal_i((int)old_update, (int)git_submodule_update_strategy(sm1));
cl_assert_equal_i(
old_fetchrecurse, git_submodule_fetch_recurse_submodules(sm1));
......@@ -202,7 +202,7 @@ void test_submodule_modify__edit_and_save(void)
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update_strategy(sm1));
cl_assert_equal_i(GIT_SUBMODULE_RECURSE_YES, git_submodule_fetch_recurse_submodules(sm1));
/* call reload and check that the new values are loaded */
......@@ -212,7 +212,7 @@ void test_submodule_modify__edit_and_save(void)
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update_strategy(sm1));
cl_assert_equal_i(GIT_SUBMODULE_RECURSE_YES, git_submodule_fetch_recurse_submodules(sm1));
/* open a second copy of the repo and compare submodule */
......@@ -223,7 +223,7 @@ void test_submodule_modify__edit_and_save(void)
cl_assert_equal_i(
GIT_SUBMODULE_IGNORE_UNTRACKED, git_submodule_ignore(sm2));
cl_assert_equal_i(
GIT_SUBMODULE_UPDATE_REBASE, git_submodule_update(sm2));
GIT_SUBMODULE_UPDATE_REBASE, git_submodule_update_strategy(sm2));
cl_assert_equal_i(
GIT_SUBMODULE_RECURSE_NO, git_submodule_fetch_recurse_submodules(sm2));
......
......@@ -17,6 +17,7 @@ void test_submodule_repository_init__basic(void)
g_repo = setup_fixture_submod2();
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only"));
cl_git_pass(git_submodule_init(sm, 0));
cl_git_pass(git_submodule_repo_init(&repo, sm, 1));
/* Verify worktree */
......
......@@ -126,6 +126,20 @@ git_repository *setup_fixture_submod2(void)
return repo;
}
git_repository *setup_fixture_submodule_simple(void)
{
git_repository *repo = cl_git_sandbox_init("submodule_simple");
cl_fixture_sandbox("testrepo.git");
p_mkdir("submodule_simple/testrepo", 0777);
cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git");
cl_git_pass(git_repository_reinit_filesystem(repo, 1));
return repo;
}
void assert__submodule_exists(
git_repository *repo, const char *name,
const char *msg, const char *file, int line)
......
......@@ -3,6 +3,7 @@ extern void rewrite_gitmodules(const char *workdir);
/* these will automatically set a cleanup callback */
extern git_repository *setup_fixture_submodules(void);
extern git_repository *setup_fixture_submod2(void);
extern git_repository *setup_fixture_submodule_simple(void);
extern unsigned int get_submodule_status(git_repository *, const char *);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment