Skip to content

Commit 63e95be

Browse files
stefanbellergitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ee30f17 commit 63e95be

File tree

3 files changed

+258
-78
lines changed

3 files changed

+258
-78
lines changed

builtin/submodule--helper.c

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,211 @@
99
#include "submodule-config.h"
1010
#include "string-list.h"
1111
#include "run-command.h"
12+
#include "remote.h"
13+
#include "refs.h"
14+
#include "connect.h"
15+
16+
static char *get_default_remote(void)
17+
{
18+
char *dest = NULL, *ret;
19+
unsigned char sha1[20];
20+
struct strbuf sb = STRBUF_INIT;
21+
const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
22+
23+
if (!refname)
24+
die(_("No such ref: %s"), "HEAD");
25+
26+
/* detached HEAD */
27+
if (!strcmp(refname, "HEAD"))
28+
return xstrdup("origin");
29+
30+
if (!skip_prefix(refname, "refs/heads/", &refname))
31+
die(_("Expecting a full ref name, got %s"), refname);
32+
33+
strbuf_addf(&sb, "branch.%s.remote", refname);
34+
if (git_config_get_string(sb.buf, &dest))
35+
ret = xstrdup("origin");
36+
else
37+
ret = dest;
38+
39+
strbuf_release(&sb);
40+
return ret;
41+
}
42+
43+
static int starts_with_dot_slash(const char *str)
44+
{
45+
return str[0] == '.' && is_dir_sep(str[1]);
46+
}
47+
48+
static int starts_with_dot_dot_slash(const char *str)
49+
{
50+
return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
51+
}
52+
53+
/*
54+
* Returns 1 if it was the last chop before ':'.
55+
*/
56+
static int chop_last_dir(char **remoteurl, int is_relative)
57+
{
58+
char *rfind = find_last_dir_sep(*remoteurl);
59+
if (rfind) {
60+
*rfind = '\0';
61+
return 0;
62+
}
63+
64+
rfind = strrchr(*remoteurl, ':');
65+
if (rfind) {
66+
*rfind = '\0';
67+
return 1;
68+
}
69+
70+
if (is_relative || !strcmp(".", *remoteurl))
71+
die(_("cannot strip one component off url '%s'"),
72+
*remoteurl);
73+
74+
free(*remoteurl);
75+
*remoteurl = xstrdup(".");
76+
return 0;
77+
}
78+
79+
/*
80+
* The `url` argument is the URL that navigates to the submodule origin
81+
* repo. When relative, this URL is relative to the superproject origin
82+
* URL repo. The `up_path` argument, if specified, is the relative
83+
* path that navigates from the submodule working tree to the superproject
84+
* working tree. Returns the origin URL of the submodule.
85+
*
86+
* Return either an absolute URL or filesystem path (if the superproject
87+
* origin URL is an absolute URL or filesystem path, respectively) or a
88+
* relative file system path (if the superproject origin URL is a relative
89+
* file system path).
90+
*
91+
* When the output is a relative file system path, the path is either
92+
* relative to the submodule working tree, if up_path is specified, or to
93+
* the superproject working tree otherwise.
94+
*
95+
* NEEDSWORK: This works incorrectly on the domain and protocol part.
96+
* remote_url url outcome expectation
97+
* http://a.com/b ../c http://a.com/c as is
98+
* http://a.com/b ../../c http://c error out
99+
* http://a.com/b ../../../c http:/c error out
100+
* http://a.com/b ../../../../c http:c error out
101+
* http://a.com/b ../../../../../c .:c error out
102+
* NEEDSWORK: Given how chop_last_dir() works, this function is broken
103+
* when a local part has a colon in its path component, too.
104+
*/
105+
static char *relative_url(const char *remote_url,
106+
const char *url,
107+
const char *up_path)
108+
{
109+
int is_relative = 0;
110+
int colonsep = 0;
111+
char *out;
112+
char *remoteurl = xstrdup(remote_url);
113+
struct strbuf sb = STRBUF_INIT;
114+
size_t len = strlen(remoteurl);
115+
116+
if (is_dir_sep(remoteurl[len]))
117+
remoteurl[len] = '\0';
118+
119+
if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
120+
is_relative = 0;
121+
else {
122+
is_relative = 1;
123+
/*
124+
* Prepend a './' to ensure all relative
125+
* remoteurls start with './' or '../'
126+
*/
127+
if (!starts_with_dot_slash(remoteurl) &&
128+
!starts_with_dot_dot_slash(remoteurl)) {
129+
strbuf_reset(&sb);
130+
strbuf_addf(&sb, "./%s", remoteurl);
131+
free(remoteurl);
132+
remoteurl = strbuf_detach(&sb, NULL);
133+
}
134+
}
135+
/*
136+
* When the url starts with '../', remove that and the
137+
* last directory in remoteurl.
138+
*/
139+
while (url) {
140+
if (starts_with_dot_dot_slash(url)) {
141+
url += 3;
142+
colonsep |= chop_last_dir(&remoteurl, is_relative);
143+
} else if (starts_with_dot_slash(url))
144+
url += 2;
145+
else
146+
break;
147+
}
148+
strbuf_reset(&sb);
149+
strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
150+
free(remoteurl);
151+
152+
if (starts_with_dot_slash(sb.buf))
153+
out = xstrdup(sb.buf + 2);
154+
else
155+
out = xstrdup(sb.buf);
156+
strbuf_reset(&sb);
157+
158+
if (!up_path || !is_relative)
159+
return out;
160+
161+
strbuf_addf(&sb, "%s%s", up_path, out);
162+
free(out);
163+
return strbuf_detach(&sb, NULL);
164+
}
165+
166+
static int resolve_relative_url(int argc, const char **argv, const char *prefix)
167+
{
168+
char *remoteurl = NULL;
169+
char *remote = get_default_remote();
170+
const char *up_path = NULL;
171+
char *res;
172+
const char *url;
173+
struct strbuf sb = STRBUF_INIT;
174+
175+
if (argc != 2 && argc != 3)
176+
die("resolve-relative-url only accepts one or two arguments");
177+
178+
url = argv[1];
179+
strbuf_addf(&sb, "remote.%s.url", remote);
180+
free(remote);
181+
182+
if (git_config_get_string(sb.buf, &remoteurl))
183+
/* the repository is its own authoritative upstream */
184+
remoteurl = xgetcwd();
185+
186+
if (argc == 3)
187+
up_path = argv[2];
188+
189+
res = relative_url(remoteurl, url, up_path);
190+
puts(res);
191+
free(res);
192+
free(remoteurl);
193+
return 0;
194+
}
195+
196+
static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
197+
{
198+
char *remoteurl, *res;
199+
const char *up_path, *url;
200+
201+
if (argc != 4)
202+
die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>");
203+
204+
up_path = argv[1];
205+
remoteurl = xstrdup(argv[2]);
206+
url = argv[3];
207+
208+
if (!strcmp(up_path, "(null)"))
209+
up_path = NULL;
210+
211+
res = relative_url(remoteurl, url, up_path);
212+
puts(res);
213+
free(res);
214+
free(remoteurl);
215+
return 0;
216+
}
12217

13218
struct module_list {
14219
const struct cache_entry **entries;
@@ -504,7 +709,9 @@ static struct cmd_struct commands[] = {
504709
{"list", module_list},
505710
{"name", module_name},
506711
{"clone", module_clone},
507-
{"update-clone", update_clone}
712+
{"update-clone", update_clone},
713+
{"resolve-relative-url", resolve_relative_url},
714+
{"resolve-relative-url-test", resolve_relative_url_test},
508715
};
509716

510717
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)

git-submodule.sh

Lines changed: 4 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -46,79 +46,6 @@ prefix=
4646
custom_name=
4747
depth=
4848

49-
# The function takes at most 2 arguments. The first argument is the
50-
# URL that navigates to the submodule origin repo. When relative, this URL
51-
# is relative to the superproject origin URL repo. The second up_path
52-
# argument, if specified, is the relative path that navigates
53-
# from the submodule working tree to the superproject working tree.
54-
#
55-
# The output of the function is the origin URL of the submodule.
56-
#
57-
# The output will either be an absolute URL or filesystem path (if the
58-
# superproject origin URL is an absolute URL or filesystem path,
59-
# respectively) or a relative file system path (if the superproject
60-
# origin URL is a relative file system path).
61-
#
62-
# When the output is a relative file system path, the path is either
63-
# relative to the submodule working tree, if up_path is specified, or to
64-
# the superproject working tree otherwise.
65-
resolve_relative_url ()
66-
{
67-
remote=$(get_default_remote)
68-
remoteurl=$(git config "remote.$remote.url") ||
69-
remoteurl=$(pwd) # the repository is its own authoritative upstream
70-
url="$1"
71-
remoteurl=${remoteurl%/}
72-
sep=/
73-
up_path="$2"
74-
75-
case "$remoteurl" in
76-
*:*|/*)
77-
is_relative=
78-
;;
79-
./*|../*)
80-
is_relative=t
81-
;;
82-
*)
83-
is_relative=t
84-
remoteurl="./$remoteurl"
85-
;;
86-
esac
87-
88-
while test -n "$url"
89-
do
90-
case "$url" in
91-
../*)
92-
url="${url#../}"
93-
case "$remoteurl" in
94-
*/*)
95-
remoteurl="${remoteurl%/*}"
96-
;;
97-
*:*)
98-
remoteurl="${remoteurl%:*}"
99-
sep=:
100-
;;
101-
*)
102-
if test -z "$is_relative" || test "." = "$remoteurl"
103-
then
104-
die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
105-
else
106-
remoteurl=.
107-
fi
108-
;;
109-
esac
110-
;;
111-
./*)
112-
url="${url#./}"
113-
;;
114-
*)
115-
break;;
116-
esac
117-
done
118-
remoteurl="$remoteurl$sep${url%/}"
119-
echo "${is_relative:+${up_path}}${remoteurl#./}"
120-
}
121-
12249
# Resolve a path to be relative to another path. This is intended for
12350
# converting submodule paths when git-submodule is run in a subdirectory
12451
# and only handles paths where the directory separator is '/'.
@@ -281,7 +208,7 @@ cmd_add()
281208
die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
282209

283210
# dereference source url relative to parent's url
284-
realrepo=$(resolve_relative_url "$repo") || exit
211+
realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
285212
;;
286213
*:*|/*)
287214
# absolute url
@@ -485,7 +412,7 @@ cmd_init()
485412
# Possibly a url relative to parent
486413
case "$url" in
487414
./*|../*)
488-
url=$(resolve_relative_url "$url") || exit
415+
url=$(git submodule--helper resolve-relative-url "$url") || exit
489416
;;
490417
esac
491418
git config submodule."$name".url "$url" ||
@@ -1202,9 +1129,9 @@ cmd_sync()
12021129
# guarantee a trailing /
12031130
up_path=${up_path%/}/ &&
12041131
# path from submodule work tree to submodule origin repo
1205-
sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
1132+
sub_origin_url=$(git submodule--helper resolve-relative-url "$url" "$up_path") &&
12061133
# path from superproject work tree to submodule origin repo
1207-
super_config_url=$(resolve_relative_url "$url") || exit
1134+
super_config_url=$(git submodule--helper resolve-relative-url "$url") || exit
12081135
;;
12091136
*)
12101137
sub_origin_url="$url"

t/t0060-path-utils.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ relative_path() {
1919
"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
2020
}
2121

22+
test_submodule_relative_url() {
23+
test_expect_success "test_submodule_relative_url: $1 $2 $3 => $4" "
24+
actual=\$(git submodule--helper resolve-relative-url-test '$1' '$2' '$3') &&
25+
test \"\$actual\" = '$4'
26+
"
27+
}
28+
2229
test_git_path() {
2330
test_expect_success "git-path $1 $2 => $3" "
2431
$1 git rev-parse --git-path $2 >actual &&
@@ -298,4 +305,43 @@ test_git_path GIT_COMMON_DIR=bar config bar/config
298305
test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
299306
test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
300307

308+
# In the tests below, the distinction between $PWD and $(pwd) is important:
309+
# on Windows, $PWD is POSIX style (/c/foo), $(pwd) has drive letter (c:/foo).
310+
311+
test_submodule_relative_url "../" "../foo" "../submodule" "../../submodule"
312+
test_submodule_relative_url "../" "../foo/bar" "../submodule" "../../foo/submodule"
313+
test_submodule_relative_url "../" "../foo/submodule" "../submodule" "../../foo/submodule"
314+
test_submodule_relative_url "../" "./foo" "../submodule" "../submodule"
315+
test_submodule_relative_url "../" "./foo/bar" "../submodule" "../foo/submodule"
316+
test_submodule_relative_url "../../../" "../foo/bar" "../sub/a/b/c" "../../../../foo/sub/a/b/c"
317+
test_submodule_relative_url "../" "$PWD/addtest" "../repo" "$(pwd)/repo"
318+
test_submodule_relative_url "../" "foo/bar" "../submodule" "../foo/submodule"
319+
test_submodule_relative_url "../" "foo" "../submodule" "../submodule"
320+
321+
test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c" "../foo/sub/a/b/c"
322+
test_submodule_relative_url "(null)" "../foo/bar" "../submodule" "../foo/submodule"
323+
test_submodule_relative_url "(null)" "../foo/submodule" "../submodule" "../foo/submodule"
324+
test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule"
325+
test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule"
326+
test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule"
327+
test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" "//somewhere else/subrepo"
328+
test_submodule_relative_url "(null)" "$PWD/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r"
329+
test_submodule_relative_url "(null)" "$PWD/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r"
330+
test_submodule_relative_url "(null)" "$PWD/." "../." "$(pwd)/."
331+
test_submodule_relative_url "(null)" "$PWD" "./." "$(pwd)/."
332+
test_submodule_relative_url "(null)" "$PWD/addtest" "../repo" "$(pwd)/repo"
333+
test_submodule_relative_url "(null)" "$PWD" "./å äö" "$(pwd)/å äö"
334+
test_submodule_relative_url "(null)" "$PWD/." "../submodule" "$(pwd)/submodule"
335+
test_submodule_relative_url "(null)" "$PWD/submodule" "../submodule" "$(pwd)/submodule"
336+
test_submodule_relative_url "(null)" "$PWD/home2/../remote" "../bundle1" "$(pwd)/home2/../bundle1"
337+
test_submodule_relative_url "(null)" "$PWD/submodule_update_repo" "./." "$(pwd)/submodule_update_repo/."
338+
test_submodule_relative_url "(null)" "file:///tmp/repo" "../subrepo" "file:///tmp/subrepo"
339+
test_submodule_relative_url "(null)" "foo/bar" "../submodule" "foo/submodule"
340+
test_submodule_relative_url "(null)" "foo" "../submodule" "submodule"
341+
test_submodule_relative_url "(null)" "helper:://hostname/repo" "../subrepo" "helper:://hostname/subrepo"
342+
test_submodule_relative_url "(null)" "ssh://hostname/repo" "../subrepo" "ssh://hostname/subrepo"
343+
test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh://hostname:22/subrepo"
344+
test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo"
345+
test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo"
346+
301347
test_done

0 commit comments

Comments
 (0)