diff options
-rw-r--r-- | include/git2/repository.h | 3 | ||||
-rw-r--r-- | include/git2/submodule.h | 18 | ||||
-rw-r--r-- | src/repository.c | 32 | ||||
-rw-r--r-- | src/submodule.c | 106 | ||||
-rw-r--r-- | tests/repo/init.c | 78 | ||||
-rw-r--r-- | tests/submodule/add.c | 20 | ||||
-rw-r--r-- | tests/submodule/repository_init.c | 40 |
7 files changed, 256 insertions, 41 deletions
diff --git a/include/git2/repository.h b/include/git2/repository.h index 18e515cb0..268782648 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -196,6 +196,8 @@ GIT_EXTERN(int) git_repository_init( * looking the "template_path" from the options if set, or the * `init.templatedir` global config if not, or falling back on * "/usr/share/git-core/templates" if it exists. + * * GIT_REPOSITORY_INIT_RELATIVE_GITLINK - If an alternate workdir is + * specified, use relative paths for the gitdir and core.worktree. */ typedef enum { GIT_REPOSITORY_INIT_BARE = (1u << 0), @@ -204,6 +206,7 @@ typedef enum { GIT_REPOSITORY_INIT_MKDIR = (1u << 3), GIT_REPOSITORY_INIT_MKPATH = (1u << 4), GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1u << 5), + GIT_REPOSITORY_INIT_RELATIVE_GITLINK = (1u << 6), } git_repository_init_flag_t; /** diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 864d1c58c..616890df6 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -471,6 +471,24 @@ GIT_EXTERN(git_submodule_recurse_t) git_submodule_set_fetch_recurse_submodules( GIT_EXTERN(int) git_submodule_init(git_submodule *submodule, int overwrite); /** + * Set up the subrepository for a submodule in preparation for clone. + * + * This function can be called to init and set up a submodule + * repository from a submodule in preparation to clone it from + * its remote. + * + * @param out Output pointer to the created git repository. + * @param sm The submodule to create a new subrepository from. + * @param use_gitlink Should the workdir contain a gitlink to + * the repo in .git/modules vs. repo directly in workdir. + * @return 0 on success, <0 on failure. + */ +GIT_EXTERN(int) git_submodule_repo_init( + git_repository **out, + const git_submodule *sm, + int use_gitlink); + +/** * Copy submodule remote info into submodule repo. * * This copies the information about the submodules URL into the checked out diff --git a/src/repository.c b/src/repository.c index 4f7a5feab..d86d8905a 100644 --- a/src/repository.c +++ b/src/repository.c @@ -994,7 +994,7 @@ static int repo_init_config( uint32_t mode) { int error = 0; - git_buf cfg_path = GIT_BUF_INIT; + git_buf cfg_path = GIT_BUF_INIT, worktree_path = GIT_BUF_INIT; git_config *config = NULL; bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); @@ -1019,9 +1019,16 @@ static int repo_init_config( if (!is_bare) { SET_REPO_CONFIG(bool, "core.logallrefupdates", true); - if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) - SET_REPO_CONFIG(string, "core.worktree", work_dir); - else if (is_reinit) { + if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { + if ((error = git_buf_sets(&worktree_path, work_dir)) < 0) + goto cleanup; + + if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK)) + if ((error = git_path_make_relative(&worktree_path, repo_dir)) < 0) + goto cleanup; + + SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr); + } else if (is_reinit) { if (git_config_delete_entry(config, "core.worktree") < 0) giterr_clear(); } @@ -1038,6 +1045,7 @@ static int repo_init_config( cleanup: git_buf_free(&cfg_path); + git_buf_free(&worktree_path); git_config_free(config); return error; @@ -1126,10 +1134,11 @@ static int repo_write_template( } static int repo_write_gitlink( - const char *in_dir, const char *to_repo) + const char *in_dir, const char *to_repo, bool use_relative_path) { int error; git_buf buf = GIT_BUF_INIT; + git_buf path_to_repo = GIT_BUF_INIT; struct stat st; git_path_dirname_r(&buf, to_repo); @@ -1157,13 +1166,20 @@ static int repo_write_gitlink( git_buf_clear(&buf); - error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo); + error = git_buf_sets(&path_to_repo, to_repo); + + if (!error && use_relative_path) + error = git_path_make_relative(&path_to_repo, in_dir); + + if (!error) + error = git_buf_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); if (!error) error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); cleanup: git_buf_free(&buf); + git_buf_free(&path_to_repo); return error; } @@ -1207,7 +1223,7 @@ static int repo_init_structure( if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0) { - if (repo_write_gitlink(work_dir, repo_dir) < 0) + if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0) return -1; } @@ -1635,7 +1651,7 @@ int git_repository_set_workdir( if (git_repository_config__weakptr(&config, repo) < 0) return -1; - error = repo_write_gitlink(path.ptr, git_repository_path(repo)); + error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); /* passthrough error means gitlink is unnecessary */ if (error == GIT_PASSTHROUGH) diff --git a/src/submodule.c b/src/submodule.c index b1291df8e..ccc8ad117 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -306,6 +306,56 @@ void git_submodule_cache_free(git_repository *repo) submodule_cache_free(cache); } +static int submodule_repo_init( + git_repository **out, + git_repository *parent_repo, + const char *path, + const char *url, + bool use_gitlink) +{ + 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; + + error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path); + if (error < 0) + goto cleanup; + + initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT; + initopt.origin_url = url; + + /* init submodule repository and add origin remote as needed */ + + /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a + * gitlink in the sub-repo workdir directory to that repository + * + * Old style: sub-repo goes directly into repo/<name>/.git/ + */ + if (use_gitlink) { + error = git_buf_join3( + &repodir, '/', git_repository_path(parent_repo), "modules", path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = workdir.ptr; + initopt.flags |= + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + } else + error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt); + +cleanup: + git_buf_free(&workdir); + git_buf_free(&repodir); + + *out = subrepo; + + return error; +} + int git_submodule_add_setup( git_submodule **out, git_repository *repo, @@ -317,7 +367,6 @@ int git_submodule_add_setup( git_config_backend *mods = NULL; git_submodule *sm = NULL; git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT; - git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; git_repository *subrepo = NULL; assert(repo && url && path); @@ -371,41 +420,14 @@ int git_submodule_add_setup( if (error < 0) goto cleanup; - /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a - * gitlink in the sub-repo workdir directory to that repository - * - * Old style: sub-repo goes directly into repo/<name>/.git/ + /* if the repo does not already exist, then init a new repo and add it. + * Otherwise, just add the existing repo. */ - - initopt.flags = GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_NO_REINIT; - initopt.origin_url = real_url.ptr; - - if (git_path_exists(name.ptr) && - git_path_contains(&name, DOT_GIT)) - { - /* repo appears to already exist - reinit? */ - } - else if (use_gitlink) { - git_buf repodir = GIT_BUF_INIT; - - error = git_buf_join3( - &repodir, '/', git_repository_path(repo), "modules", path); - if (error < 0) + if (!(git_path_exists(name.ptr) && + git_path_contains(&name, DOT_GIT))) { + if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0) goto cleanup; - - initopt.workdir_path = name.ptr; - initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; - - error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); - - git_buf_free(&repodir); } - else { - error = git_repository_init_ext(&subrepo, name.ptr, &initopt); - } - if (error < 0) - goto cleanup; /* add submodule to hash and "reload" it */ @@ -437,6 +459,23 @@ cleanup: return error; } +int git_submodule_repo_init( + git_repository **out, + const git_submodule *sm, + int use_gitlink) +{ + int error; + git_repository *sub_repo = NULL; + + assert(out && sm); + + error = submodule_repo_init(&sub_repo, sm->repo, sm->path, sm->url, use_gitlink); + + *out = sub_repo; + + return error; +} + int git_submodule_add_finalize(git_submodule *sm) { int error; @@ -1897,6 +1936,7 @@ static void submodule_get_index_status(unsigned int *status, git_submodule *sm) *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; } + static void submodule_get_wd_status( unsigned int *status, git_submodule *sm, diff --git a/tests/repo/init.c b/tests/repo/init.c index aea383c64..999afd625 100644 --- a/tests/repo/init.c +++ b/tests/repo/init.c @@ -367,6 +367,84 @@ void test_repo_init__extended_1(void) cl_fixture_cleanup("root"); } +void test_repo_init__relative_gitdir(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_config *cfg; + const char *worktree_path; + git_buf dot_git_content = GIT_BUF_INIT; + + opts.workdir_path = "../c_wd"; + opts.flags = + GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + + /* make the directory first, then it should succeed */ + cl_git_pass(git_repository_init_ext(&_repo, "root/b/my_repository", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "root/b/c_wd/")); + cl_assert(!git__suffixcmp(git_repository_path(_repo), "root/b/my_repository/")); + cl_assert(!git_repository_is_bare(_repo)); + cl_assert(git_repository_is_empty(_repo)); + + /* Verify that the gitlink and worktree entries are relative */ + + /* Verify worktree */ + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_get_string(&worktree_path, cfg, "core.worktree")); + cl_assert_equal_s("../c_wd/", worktree_path); + + /* Verify gitlink */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); + cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); + + git_buf_free(&dot_git_content); + git_config_free(cfg); + cleanup_repository("root"); +} + +void test_repo_init__relative_gitdir_2(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_config *cfg; + const char *worktree_path; + git_buf dot_git_content = GIT_BUF_INIT; + git_buf full_path = GIT_BUF_INIT; + + cl_git_pass(git_path_prettify(&full_path, ".", NULL)); + cl_git_pass(git_buf_joinpath(&full_path, full_path.ptr, "root/b/c_wd")); + + opts.workdir_path = full_path.ptr; + opts.flags = + GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + + /* make the directory first, then it should succeed */ + cl_git_pass(git_repository_init_ext(&_repo, "root/b/my_repository", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "root/b/c_wd/")); + cl_assert(!git__suffixcmp(git_repository_path(_repo), "root/b/my_repository/")); + cl_assert(!git_repository_is_bare(_repo)); + cl_assert(git_repository_is_empty(_repo)); + + /* Verify that the gitlink and worktree entries are relative */ + + /* Verify worktree */ + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_get_string(&worktree_path, cfg, "core.worktree")); + cl_assert_equal_s("../c_wd/", worktree_path); + + /* Verify gitlink */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); + cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); + + git_buf_free(&dot_git_content); + git_config_free(cfg); + cleanup_repository("root"); +} + #define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) static void assert_hooks_match( diff --git a/tests/submodule/add.c b/tests/submodule/add.c index 9fdc7cc57..10717809e 100644 --- a/tests/submodule/add.c +++ b/tests/submodule/add.c @@ -2,6 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" +#include "fileops.h" static git_repository *g_repo = NULL; @@ -29,6 +30,10 @@ static void assert_submodule_url(const char* name, const char *url) void test_submodule_add__url_absolute(void) { git_submodule *sm; + git_config *cfg; + git_repository *repo; + const char *worktree_path; + git_buf dot_git_content = GIT_BUF_INIT; g_repo = setup_fixture_submod2(); @@ -51,6 +56,21 @@ void test_submodule_add__url_absolute(void) cl_assert(git_path_isfile("submod2/.git/modules/" "sm_libgit2" "/HEAD")); assert_submodule_url("sm_libgit2", "https://github.com/libgit2/libgit2.git"); + cl_git_pass(git_repository_open(&repo, "submod2/" "sm_libgit2")); + + /* Verify worktree path is relative */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_get_string(&worktree_path, cfg, "core.worktree")); + cl_assert_equal_s("../../../sm_libgit2/", worktree_path); + + /* Verify gitdir path is relative */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_libgit2" "/.git")); + cl_assert_equal_s("gitdir: ../.git/modules/sm_libgit2/", dot_git_content.ptr); + + git_config_free(cfg); + git_repository_free(repo); + git_buf_free(&dot_git_content); + /* add a submodule not using a gitlink */ cl_git_pass( diff --git a/tests/submodule/repository_init.c b/tests/submodule/repository_init.c new file mode 100644 index 000000000..24b47aff8 --- /dev/null +++ b/tests/submodule/repository_init.c @@ -0,0 +1,40 @@ +#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_repository_init__basic(void) +{ + git_submodule *sm; + git_repository *repo; + git_config *cfg; + const char *worktree_path; + git_buf dot_git_content = GIT_BUF_INIT; + + g_repo = setup_fixture_submod2(); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_git_pass(git_submodule_repo_init(&repo, sm, 1)); + + /* Verify worktree */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_get_string(&worktree_path, cfg, "core.worktree")); + cl_assert_equal_s("../../../sm_gitmodules_only/", worktree_path); + + /* Verify gitlink */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_gitmodules_only" "/.git")); + cl_assert_equal_s("gitdir: ../.git/modules/sm_gitmodules_only/", dot_git_content.ptr); + + cl_assert(git_path_isfile("submod2/" "sm_gitmodules_only" "/.git")); + + cl_assert(git_path_isdir("submod2/.git/modules")); + cl_assert(git_path_isdir("submod2/.git/modules/" "sm_gitmodules_only")); + cl_assert(git_path_isfile("submod2/.git/modules/" "sm_gitmodules_only" "/HEAD")); + + git_config_free(cfg); + git_repository_free(repo); + git_buf_free(&dot_git_content); +} |