Commit 4c958046 by Vicent Marti

Merge pull request #2530 from libgit2/jamill/relative_gitlink

Teach repository to use relative paths for git symbolic links
parents eb939c57 bc737620
......@@ -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;
/**
......
......@@ -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
......
......@@ -750,6 +750,61 @@ int git_path_cmp(
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
int git_path_make_relative(git_buf *path, const char *parent)
{
const char *p, *q, *p_dirsep, *q_dirsep;
size_t plen = path->size, newlen, depth = 1, i;
for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
if (*p == '/' && *q == '/') {
p_dirsep = p;
q_dirsep = q;
}
else if (*p != *q)
break;
}
/* need at least 1 common path segment */
if ((p_dirsep == path->ptr || q_dirsep == parent) &&
(*p_dirsep != '/' || *q_dirsep != '/')) {
giterr_set(GITERR_INVALID,
"%s is not a parent of %s", parent, path->ptr);
return GIT_ENOTFOUND;
}
if (*p == '/' && !*q)
p++;
else if (!*p && *q == '/')
q++;
else if (!*p && !*q)
return git_buf_clear(path), 0;
else {
p = p_dirsep + 1;
q = q_dirsep + 1;
}
plen -= (p - path->ptr);
if (!*q)
return git_buf_set(path, p, plen);
for (; (q = strchr(q, '/')) && *(q + 1); q++)
depth++;
newlen = (depth * 3) + plen;
if (git_buf_try_grow(path, newlen + 1, 1, 0) < 0)
return -1;
memmove(path->ptr + (depth * 3), p, plen + 1);
for (i = 0; i < depth; i++)
memcpy(path->ptr + (i * 3), "../", 3);
path->size = newlen;
return 0;
}
bool git_path_has_non_ascii(const char *path, size_t pathlen)
{
const uint8_t *scan = (const uint8_t *)path, *end;
......
......@@ -197,6 +197,17 @@ extern bool git_path_contains(git_buf *dir, const char *item);
extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
/**
* Make the path relative to the given parent path.
*
* @param path The path to make relative
* @param parent The parent path to make path relative to
* @return 0 if path was made relative, GIT_ENOTFOUND
* if there was not common root between the paths,
* or <0.
*/
extern int git_path_make_relative(git_buf *path, const char *parent);
/**
* Check if the given path contains the given file.
*
* @param dir Directory path that might contain file
......
......@@ -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)
......
......@@ -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,
......
#include "clar_libgit2.h"
#include "path.h"
static void test_make_relative(
const char *expected_path,
const char *path,
const char *parent,
int expected_status)
{
git_buf buf = GIT_BUF_INIT;
git_buf_puts(&buf, path);
cl_assert_equal_i(expected_status, git_path_make_relative(&buf, parent));
cl_assert_equal_s(expected_path, buf.ptr);
git_buf_free(&buf);
}
void test_path_core__make_relative(void)
{
git_buf buf = GIT_BUF_INIT;
test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0);
test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0);
test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0);
test_make_relative("", "/path/to", "/path/to", 0);
test_make_relative("", "/path/to", "/path/to/", 0);
test_make_relative("../", "/path/to", "/path/to/foo", 0);
test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0);
test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0);
test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0);
test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0);
test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0);
test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0);
test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0);
test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0);
test_make_relative("../foo", "/foo", "/bar", 0);
test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0);
test_make_relative("../foo", "path/to/foo", "path/to/bar", 0);
test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND);
test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND);
test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND);
test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND);
test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND);
test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND);
}
......@@ -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(
......
......@@ -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(
......
#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);
}
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