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