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 "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 New Issue
	
	 Stefan Beller
						Stefan Beller