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>

Stefan Beller committed Apr 15, 2016 at 17:50 UTC 63e95beb085ce8ae359d728ec0a54c3dcfe741f5
3 files changed +258 -78
builtin/submodule--helper.c
+208 -1
@@ -9,6 +9,211 @@
9 #include "submodule-config.h"
10 #include "string-list.h"
11 #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 +}
217
218 struct module_list {
219 const struct cache_entry **entries;
@@ -504,7 +709,9 @@ static struct cmd_struct commands[] = {
709 {"list", module_list},
710 {"name", module_name},
711 {"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},
715 };
716
717 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
git-submodule.sh
+4 -77
@@ -46,79 +46,6 @@ prefix=
46 custom_name=
47 depth=
48
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 -
49 # Resolve a path to be relative to another path. This is intended for
50 # converting submodule paths when git-submodule is run in a subdirectory
51 # and only handles paths where the directory separator is '/'.
@@ -281,7 +208,7 @@ cmd_add()
208 die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
209
210 # 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
212 ;;
213 *:*|/*)
214 # absolute url
@@ -485,7 +412,7 @@ cmd_init()
412 # Possibly a url relative to parent
413 case "$url" in
414 ./*|../*)
488 - url=$(resolve_relative_url "$url") || exit
415 + url=$(git submodule--helper resolve-relative-url "$url") || exit
416 ;;
417 esac
418 git config submodule."$name".url "$url" ||
@@ -1202,9 +1129,9 @@ cmd_sync()
1129 # guarantee a trailing /
1130 up_path=${up_path%/}/ &&
1131 # 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") &&
1133 # 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
1135 ;;
1136 *)
1137 sub_origin_url="$url"
t/t0060-path-utils.sh
+46
@@ -19,6 +19,13 @@ relative_path() {
19 "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
20 }
21
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 +
29 test_git_path() {
30 test_expect_success "git-path $1 $2 => $3" "
31 $1 git rev-parse --git-path $2 >actual &&
@@ -298,4 +305,43 @@ test_git_path GIT_COMMON_DIR=bar config bar/config
305 test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
306 test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
307
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 +
347 test_done