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>maint
							parent
							
								
									ee30f17805
								
							
						
					
					
						commit
						63e95beb08
					
				|  | @ -9,6 +9,211 @@ | ||||||
| #include "submodule-config.h" | #include "submodule-config.h" | ||||||
| #include "string-list.h" | #include "string-list.h" | ||||||
| #include "run-command.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 { | struct module_list { | ||||||
| 	const struct cache_entry **entries; | 	const struct cache_entry **entries; | ||||||
|  | @ -504,7 +709,9 @@ static struct cmd_struct commands[] = { | ||||||
| 	{"list", module_list}, | 	{"list", module_list}, | ||||||
| 	{"name", module_name}, | 	{"name", module_name}, | ||||||
| 	{"clone", module_clone}, | 	{"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) | int cmd_submodule__helper(int argc, const char **argv, const char *prefix) | ||||||
|  |  | ||||||
|  | @ -46,79 +46,6 @@ prefix= | ||||||
| custom_name= | custom_name= | ||||||
| depth= | 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 | # Resolve a path to be relative to another path.  This is intended for | ||||||
| # converting submodule paths when git-submodule is run in a subdirectory | # converting submodule paths when git-submodule is run in a subdirectory | ||||||
| # and only handles paths where the directory separator is '/'. | # 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")" | 		die "$(gettext "Relative path can only be used from the toplevel of the working tree")" | ||||||
|  |  | ||||||
| 		# dereference source url relative to parent's url | 		# 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 | 		# absolute url | ||||||
|  | @ -485,7 +412,7 @@ cmd_init() | ||||||
| 			# Possibly a url relative to parent | 			# Possibly a url relative to parent | ||||||
| 			case "$url" in | 			case "$url" in | ||||||
| 			./*|../*) | 			./*|../*) | ||||||
| 				url=$(resolve_relative_url "$url") || exit | 				url=$(git submodule--helper resolve-relative-url "$url") || exit | ||||||
| 				;; | 				;; | ||||||
| 			esac | 			esac | ||||||
| 			git config submodule."$name".url "$url" || | 			git config submodule."$name".url "$url" || | ||||||
|  | @ -1202,9 +1129,9 @@ cmd_sync() | ||||||
| 			# guarantee a trailing / | 			# guarantee a trailing / | ||||||
| 			up_path=${up_path%/}/ && | 			up_path=${up_path%/}/ && | ||||||
| 			# path from submodule work tree to submodule origin repo | 			# 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 | 			# 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" | 			sub_origin_url="$url" | ||||||
|  |  | ||||||
|  | @ -19,6 +19,13 @@ relative_path() { | ||||||
| 	"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'" | 	"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_git_path() { | ||||||
| 	test_expect_success "git-path $1 $2 => $3" " | 	test_expect_success "git-path $1 $2 => $3" " | ||||||
| 		$1 git rev-parse --git-path $2 >actual && | 		$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 packed-refs              bar/packed-refs | ||||||
| test_git_path GIT_COMMON_DIR=bar shallow                  bar/shallow | 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 | test_done | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Stefan Beller
						Stefan Beller