mirror of
https://github.com/git/git
synced 2024-08-26 19:19:18 +00:00
submodule: port resolve_relative_url from shell to C
Later on we want to automatically call `git submodule init` from other commands, such that the users don't have to initialize the submodule themselves. As these other commands are written in C already, we'd need the init functionality in C, too. The `resolve_relative_url` function is a large part of that init functionality, so start by porting this function to C. To create the tests in t0060, the function `resolve_relative_url` was temporarily enhanced to write all inputs and output to disk when running the test suite. The added tests in this patch are a small selection thereof. Signed-off-by: Stefan Beller <sbeller@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
ee30f17805
commit
63e95beb08
|
@ -9,6 +9,211 @@
|
|||
#include "submodule-config.h"
|
||||
#include "string-list.h"
|
||||
#include "run-command.h"
|
||||
#include "remote.h"
|
||||
#include "refs.h"
|
||||
#include "connect.h"
|
||||
|
||||
static char *get_default_remote(void)
|
||||
{
|
||||
char *dest = NULL, *ret;
|
||||
unsigned char sha1[20];
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
|
||||
|
||||
if (!refname)
|
||||
die(_("No such ref: %s"), "HEAD");
|
||||
|
||||
/* detached HEAD */
|
||||
if (!strcmp(refname, "HEAD"))
|
||||
return xstrdup("origin");
|
||||
|
||||
if (!skip_prefix(refname, "refs/heads/", &refname))
|
||||
die(_("Expecting a full ref name, got %s"), refname);
|
||||
|
||||
strbuf_addf(&sb, "branch.%s.remote", refname);
|
||||
if (git_config_get_string(sb.buf, &dest))
|
||||
ret = xstrdup("origin");
|
||||
else
|
||||
ret = dest;
|
||||
|
||||
strbuf_release(&sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int starts_with_dot_slash(const char *str)
|
||||
{
|
||||
return str[0] == '.' && is_dir_sep(str[1]);
|
||||
}
|
||||
|
||||
static int starts_with_dot_dot_slash(const char *str)
|
||||
{
|
||||
return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 1 if it was the last chop before ':'.
|
||||
*/
|
||||
static int chop_last_dir(char **remoteurl, int is_relative)
|
||||
{
|
||||
char *rfind = find_last_dir_sep(*remoteurl);
|
||||
if (rfind) {
|
||||
*rfind = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
rfind = strrchr(*remoteurl, ':');
|
||||
if (rfind) {
|
||||
*rfind = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is_relative || !strcmp(".", *remoteurl))
|
||||
die(_("cannot strip one component off url '%s'"),
|
||||
*remoteurl);
|
||||
|
||||
free(*remoteurl);
|
||||
*remoteurl = xstrdup(".");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `url` argument is the URL that navigates to the submodule origin
|
||||
* repo. When relative, this URL is relative to the superproject origin
|
||||
* URL repo. The `up_path` argument, if specified, is the relative
|
||||
* path that navigates from the submodule working tree to the superproject
|
||||
* working tree. Returns the origin URL of the submodule.
|
||||
*
|
||||
* Return either an absolute URL or filesystem path (if the superproject
|
||||
* origin URL is an absolute URL or filesystem path, respectively) or a
|
||||
* relative file system path (if the superproject origin URL is a relative
|
||||
* file system path).
|
||||
*
|
||||
* When the output is a relative file system path, the path is either
|
||||
* relative to the submodule working tree, if up_path is specified, or to
|
||||
* the superproject working tree otherwise.
|
||||
*
|
||||
* NEEDSWORK: This works incorrectly on the domain and protocol part.
|
||||
* remote_url url outcome expectation
|
||||
* http://a.com/b ../c http://a.com/c as is
|
||||
* http://a.com/b ../../c http://c error out
|
||||
* http://a.com/b ../../../c http:/c error out
|
||||
* http://a.com/b ../../../../c http:c error out
|
||||
* http://a.com/b ../../../../../c .:c error out
|
||||
* NEEDSWORK: Given how chop_last_dir() works, this function is broken
|
||||
* when a local part has a colon in its path component, too.
|
||||
*/
|
||||
static char *relative_url(const char *remote_url,
|
||||
const char *url,
|
||||
const char *up_path)
|
||||
{
|
||||
int is_relative = 0;
|
||||
int colonsep = 0;
|
||||
char *out;
|
||||
char *remoteurl = xstrdup(remote_url);
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
size_t len = strlen(remoteurl);
|
||||
|
||||
if (is_dir_sep(remoteurl[len]))
|
||||
remoteurl[len] = '\0';
|
||||
|
||||
if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
|
||||
is_relative = 0;
|
||||
else {
|
||||
is_relative = 1;
|
||||
/*
|
||||
* Prepend a './' to ensure all relative
|
||||
* remoteurls start with './' or '../'
|
||||
*/
|
||||
if (!starts_with_dot_slash(remoteurl) &&
|
||||
!starts_with_dot_dot_slash(remoteurl)) {
|
||||
strbuf_reset(&sb);
|
||||
strbuf_addf(&sb, "./%s", remoteurl);
|
||||
free(remoteurl);
|
||||
remoteurl = strbuf_detach(&sb, NULL);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* When the url starts with '../', remove that and the
|
||||
* last directory in remoteurl.
|
||||
*/
|
||||
while (url) {
|
||||
if (starts_with_dot_dot_slash(url)) {
|
||||
url += 3;
|
||||
colonsep |= chop_last_dir(&remoteurl, is_relative);
|
||||
} else if (starts_with_dot_slash(url))
|
||||
url += 2;
|
||||
else
|
||||
break;
|
||||
}
|
||||
strbuf_reset(&sb);
|
||||
strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
|
||||
free(remoteurl);
|
||||
|
||||
if (starts_with_dot_slash(sb.buf))
|
||||
out = xstrdup(sb.buf + 2);
|
||||
else
|
||||
out = xstrdup(sb.buf);
|
||||
strbuf_reset(&sb);
|
||||
|
||||
if (!up_path || !is_relative)
|
||||
return out;
|
||||
|
||||
strbuf_addf(&sb, "%s%s", up_path, out);
|
||||
free(out);
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
static int resolve_relative_url(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
char *remoteurl = NULL;
|
||||
char *remote = get_default_remote();
|
||||
const char *up_path = NULL;
|
||||
char *res;
|
||||
const char *url;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
if (argc != 2 && argc != 3)
|
||||
die("resolve-relative-url only accepts one or two arguments");
|
||||
|
||||
url = argv[1];
|
||||
strbuf_addf(&sb, "remote.%s.url", remote);
|
||||
free(remote);
|
||||
|
||||
if (git_config_get_string(sb.buf, &remoteurl))
|
||||
/* the repository is its own authoritative upstream */
|
||||
remoteurl = xgetcwd();
|
||||
|
||||
if (argc == 3)
|
||||
up_path = argv[2];
|
||||
|
||||
res = relative_url(remoteurl, url, up_path);
|
||||
puts(res);
|
||||
free(res);
|
||||
free(remoteurl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
char *remoteurl, *res;
|
||||
const char *up_path, *url;
|
||||
|
||||
if (argc != 4)
|
||||
die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>");
|
||||
|
||||
up_path = argv[1];
|
||||
remoteurl = xstrdup(argv[2]);
|
||||
url = argv[3];
|
||||
|
||||
if (!strcmp(up_path, "(null)"))
|
||||
up_path = NULL;
|
||||
|
||||
res = relative_url(remoteurl, url, up_path);
|
||||
puts(res);
|
||||
free(res);
|
||||
free(remoteurl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct module_list {
|
||||
const struct cache_entry **entries;
|
||||
|
@ -504,7 +709,9 @@ static struct cmd_struct commands[] = {
|
|||
{"list", module_list},
|
||||
{"name", module_name},
|
||||
{"clone", module_clone},
|
||||
{"update-clone", update_clone}
|
||||
{"update-clone", update_clone},
|
||||
{"resolve-relative-url", resolve_relative_url},
|
||||
{"resolve-relative-url-test", resolve_relative_url_test},
|
||||
};
|
||||
|
||||
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
|
||||
|
|
|
@ -46,79 +46,6 @@ prefix=
|
|||
custom_name=
|
||||
depth=
|
||||
|
||||
# The function takes at most 2 arguments. The first argument is the
|
||||
# URL that navigates to the submodule origin repo. When relative, this URL
|
||||
# is relative to the superproject origin URL repo. The second up_path
|
||||
# argument, if specified, is the relative path that navigates
|
||||
# from the submodule working tree to the superproject working tree.
|
||||
#
|
||||
# The output of the function is the origin URL of the submodule.
|
||||
#
|
||||
# The output will either be an absolute URL or filesystem path (if the
|
||||
# superproject origin URL is an absolute URL or filesystem path,
|
||||
# respectively) or a relative file system path (if the superproject
|
||||
# origin URL is a relative file system path).
|
||||
#
|
||||
# When the output is a relative file system path, the path is either
|
||||
# relative to the submodule working tree, if up_path is specified, or to
|
||||
# the superproject working tree otherwise.
|
||||
resolve_relative_url ()
|
||||
{
|
||||
remote=$(get_default_remote)
|
||||
remoteurl=$(git config "remote.$remote.url") ||
|
||||
remoteurl=$(pwd) # the repository is its own authoritative upstream
|
||||
url="$1"
|
||||
remoteurl=${remoteurl%/}
|
||||
sep=/
|
||||
up_path="$2"
|
||||
|
||||
case "$remoteurl" in
|
||||
*:*|/*)
|
||||
is_relative=
|
||||
;;
|
||||
./*|../*)
|
||||
is_relative=t
|
||||
;;
|
||||
*)
|
||||
is_relative=t
|
||||
remoteurl="./$remoteurl"
|
||||
;;
|
||||
esac
|
||||
|
||||
while test -n "$url"
|
||||
do
|
||||
case "$url" in
|
||||
../*)
|
||||
url="${url#../}"
|
||||
case "$remoteurl" in
|
||||
*/*)
|
||||
remoteurl="${remoteurl%/*}"
|
||||
;;
|
||||
*:*)
|
||||
remoteurl="${remoteurl%:*}"
|
||||
sep=:
|
||||
;;
|
||||
*)
|
||||
if test -z "$is_relative" || test "." = "$remoteurl"
|
||||
then
|
||||
die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
|
||||
else
|
||||
remoteurl=.
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
./*)
|
||||
url="${url#./}"
|
||||
;;
|
||||
*)
|
||||
break;;
|
||||
esac
|
||||
done
|
||||
remoteurl="$remoteurl$sep${url%/}"
|
||||
echo "${is_relative:+${up_path}}${remoteurl#./}"
|
||||
}
|
||||
|
||||
# Resolve a path to be relative to another path. This is intended for
|
||||
# converting submodule paths when git-submodule is run in a subdirectory
|
||||
# and only handles paths where the directory separator is '/'.
|
||||
|
@ -281,7 +208,7 @@ cmd_add()
|
|||
die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
|
||||
|
||||
# dereference source url relative to parent's url
|
||||
realrepo=$(resolve_relative_url "$repo") || exit
|
||||
realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
|
||||
;;
|
||||
*:*|/*)
|
||||
# absolute url
|
||||
|
@ -485,7 +412,7 @@ cmd_init()
|
|||
# Possibly a url relative to parent
|
||||
case "$url" in
|
||||
./*|../*)
|
||||
url=$(resolve_relative_url "$url") || exit
|
||||
url=$(git submodule--helper resolve-relative-url "$url") || exit
|
||||
;;
|
||||
esac
|
||||
git config submodule."$name".url "$url" ||
|
||||
|
@ -1202,9 +1129,9 @@ cmd_sync()
|
|||
# guarantee a trailing /
|
||||
up_path=${up_path%/}/ &&
|
||||
# path from submodule work tree to submodule origin repo
|
||||
sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
|
||||
sub_origin_url=$(git submodule--helper resolve-relative-url "$url" "$up_path") &&
|
||||
# path from superproject work tree to submodule origin repo
|
||||
super_config_url=$(resolve_relative_url "$url") || exit
|
||||
super_config_url=$(git submodule--helper resolve-relative-url "$url") || exit
|
||||
;;
|
||||
*)
|
||||
sub_origin_url="$url"
|
||||
|
|
|
@ -19,6 +19,13 @@ relative_path() {
|
|||
"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
|
||||
}
|
||||
|
||||
test_submodule_relative_url() {
|
||||
test_expect_success "test_submodule_relative_url: $1 $2 $3 => $4" "
|
||||
actual=\$(git submodule--helper resolve-relative-url-test '$1' '$2' '$3') &&
|
||||
test \"\$actual\" = '$4'
|
||||
"
|
||||
}
|
||||
|
||||
test_git_path() {
|
||||
test_expect_success "git-path $1 $2 => $3" "
|
||||
$1 git rev-parse --git-path $2 >actual &&
|
||||
|
@ -298,4 +305,43 @@ test_git_path GIT_COMMON_DIR=bar config bar/config
|
|||
test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
|
||||
test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
|
||||
|
||||
# In the tests below, the distinction between $PWD and $(pwd) is important:
|
||||
# on Windows, $PWD is POSIX style (/c/foo), $(pwd) has drive letter (c:/foo).
|
||||
|
||||
test_submodule_relative_url "../" "../foo" "../submodule" "../../submodule"
|
||||
test_submodule_relative_url "../" "../foo/bar" "../submodule" "../../foo/submodule"
|
||||
test_submodule_relative_url "../" "../foo/submodule" "../submodule" "../../foo/submodule"
|
||||
test_submodule_relative_url "../" "./foo" "../submodule" "../submodule"
|
||||
test_submodule_relative_url "../" "./foo/bar" "../submodule" "../foo/submodule"
|
||||
test_submodule_relative_url "../../../" "../foo/bar" "../sub/a/b/c" "../../../../foo/sub/a/b/c"
|
||||
test_submodule_relative_url "../" "$PWD/addtest" "../repo" "$(pwd)/repo"
|
||||
test_submodule_relative_url "../" "foo/bar" "../submodule" "../foo/submodule"
|
||||
test_submodule_relative_url "../" "foo" "../submodule" "../submodule"
|
||||
|
||||
test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c" "../foo/sub/a/b/c"
|
||||
test_submodule_relative_url "(null)" "../foo/bar" "../submodule" "../foo/submodule"
|
||||
test_submodule_relative_url "(null)" "../foo/submodule" "../submodule" "../foo/submodule"
|
||||
test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule"
|
||||
test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule"
|
||||
test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule"
|
||||
test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" "//somewhere else/subrepo"
|
||||
test_submodule_relative_url "(null)" "$PWD/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r"
|
||||
test_submodule_relative_url "(null)" "$PWD/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r"
|
||||
test_submodule_relative_url "(null)" "$PWD/." "../." "$(pwd)/."
|
||||
test_submodule_relative_url "(null)" "$PWD" "./." "$(pwd)/."
|
||||
test_submodule_relative_url "(null)" "$PWD/addtest" "../repo" "$(pwd)/repo"
|
||||
test_submodule_relative_url "(null)" "$PWD" "./å äö" "$(pwd)/å äö"
|
||||
test_submodule_relative_url "(null)" "$PWD/." "../submodule" "$(pwd)/submodule"
|
||||
test_submodule_relative_url "(null)" "$PWD/submodule" "../submodule" "$(pwd)/submodule"
|
||||
test_submodule_relative_url "(null)" "$PWD/home2/../remote" "../bundle1" "$(pwd)/home2/../bundle1"
|
||||
test_submodule_relative_url "(null)" "$PWD/submodule_update_repo" "./." "$(pwd)/submodule_update_repo/."
|
||||
test_submodule_relative_url "(null)" "file:///tmp/repo" "../subrepo" "file:///tmp/subrepo"
|
||||
test_submodule_relative_url "(null)" "foo/bar" "../submodule" "foo/submodule"
|
||||
test_submodule_relative_url "(null)" "foo" "../submodule" "submodule"
|
||||
test_submodule_relative_url "(null)" "helper:://hostname/repo" "../subrepo" "helper:://hostname/subrepo"
|
||||
test_submodule_relative_url "(null)" "ssh://hostname/repo" "../subrepo" "ssh://hostname/subrepo"
|
||||
test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh://hostname:22/subrepo"
|
||||
test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo"
|
||||
test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo"
|
||||
|
||||
test_done
|
||||
|
|
Loading…
Reference in a new issue