threads / patch / 40963

patchsubmodule: Port resolve_relative_url from shell to C

Subject: [PATCH] submodule: Port resolve_relative_url from shell to C

## tl;dr

11 messages between Dec 10, 2015 and Jan 11, 2016. Diffs are folded; open one to read it.

replies: 10people: 5as markdown or json

Stefan Beller· Dec 10, 2015, 01:07 UTC · lore

This reimplements the helper function `resolve_relative_url` in shell in C. This functionality is needed in C for introducing the groups feature later on. When using groups, the user should not need to run `git submodule init`, but it should be implicit at all appropriate places, which are all in C code. As the we would not just call out to `git submodule init`, but do a more fine grained structure there, we actually need all the init functionality in C before attempting the groups feature. To get the init functionality in C, rewriting the resolve_relative_url subfunction is a major step.

This also improves the performance: (Best out of 3) time ./t7400-submodule-basic.sh Before: real 0m9.575s user 0m2.683s sys 0m6.773s After: real 0m9.293s user 0m2.691s sys 0m6.549s

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 This applies on origin/master, and I'd carry as its own feature branch
 as I am nowhere near done with the groups feature after reading Jens feedback.
 (It took me a while to identify this as a next best step.)
 
 Thanks,
 Stefan
 builtin/submodule--helper.c | 120 ++++++++++++++++++++++++++++++++++++++++++++
 git-submodule.sh            |  81 ++----------------------------
 2 files changed, 124 insertions(+), 77 deletions(-)
Show changes to 2 files +124 −77

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f4c3eff..f48b5b5 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -9,6 +9,125 @@
 #include "submodule-config.h"
 #include "string-list.h"
 #include "run-command.h"
+#include "remote.h"
+#include "refs.h"
+
+static const char *get_default_remote(void)
+{
+	char *dest = NULL;
+	unsigned char sha1[20];
+	int flag;
+	struct strbuf sb = STRBUF_INIT;
+	const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
+
+	if (!refname)
+		die("No such ref: HEAD");
+
+	refname = shorten_unambiguous_ref(refname, 0);
+	strbuf_addf(&sb, "branch.%s.remote", refname);
+	if (git_config_get_string(sb.buf, &dest))
+		return "origin";
+	else
+		return xstrdup(dest);
+}
+
+/*
+ * 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.
+ */
+static const char *relative_url(const char *url, const char *up_path)
+{
+	int is_relative = 0;
+	size_t len;
+	char *remoteurl = NULL;
+	char *sep = "/";
+	const char *out;
+	struct strbuf sb = STRBUF_INIT;
+	const char *remote = get_default_remote();
+	strbuf_addf(&sb, "remote.%s.url", remote);
+
+	if (git_config_get_string(sb.buf, &remoteurl))
+		/* the repository is its own authoritative upstream */
+		remoteurl = xgetcwd();
+
+	if (strip_suffix(remoteurl, "/", &len))
+		remoteurl[len] = '\0';
+
+	if (strchr(remoteurl, ':') || skip_prefix(remoteurl, "/", &out))
+		is_relative = 0;
+	else if (skip_prefix(remoteurl, "./", &out) ||
+		    skip_prefix(remoteurl, "../", &out))
+		is_relative = 1;
+	else {
+		is_relative = 1;
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "./%s", remoteurl);
+		remoteurl = strbuf_detach(&sb, NULL);
+	}
+
+	while (url) {
+		if (skip_prefix(url, "../", &out)) {
+			char *rfind;
+			url = out;
+
+			rfind = strrchr(remoteurl, '/');
+			if (rfind)
+				*rfind = '\0';
+			else {
+				rfind = strrchr(remoteurl, ':');
+				if (rfind) {
+					*rfind = '\0';
+					sep = ":";
+				} else {
+					if (is_relative || !strcmp(".", remoteurl))
+						die(N_("cannot strip one component off url '%s'"), remoteurl);
+					else
+						remoteurl = ".";
+				}
+			}
+		} else if (skip_prefix(url, "./", &out))
+			url = out;
+		else
+			break;
+	}
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
+
+	if (!skip_prefix(sb.buf, "./", &out))
+		out = sb.buf;
+	out = xstrdup(out);
+
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s%s", is_relative && up_path ? up_path : "", out);
+
+	free((char*)out);
+	return strbuf_detach(&sb, NULL);
+}
+
+static int resolve_relative_url(int argc, const char **argv, const char *prefix)
+{
+	if (argc == 2)
+		printf("%s\n", relative_url(argv[1], NULL));
+	else if (argc == 3)
+		printf("%s\n", relative_url(argv[1], argv[2]));
+	else
+		die("BUG: resolve_relative_url only accepts one or two arguments");
+	return 0;
+}
 
 struct module_list {
 	const struct cache_entry **entries;
@@ -264,6 +383,7 @@ static struct cmd_struct commands[] = {
 	{"list", module_list},
 	{"name", module_name},
 	{"clone", module_clone},
+	{"resolve_relative_url", resolve_relative_url},
 };
 
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --git a/git-submodule.sh b/git-submodule.sh
index 9bc5c5f..6a7a3e4 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -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" ||
@@ -1190,9 +1117,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"
-- 
2.6.3.470.g4b82c23.dirty
Johannes Sixt· Dec 10, 2015, 06:48 UTC · re: Stefan Beller · lore

Re: [PATCH] submodule: Port resolve_relative_url from shell to C

Am 10.12.2015 um 02:07 schrieb Stefan Beller:
Show 9 quoted lines
> This reimplements the helper function `resolve_relative_url` in shell
> in C. This functionality is needed in C for introducing the groups
> feature later on. When using groups, the user should not need to run
> `git submodule init`, but it should be implicit at all appropriate places,
> which are all in C code. As the we would not just call out to `git
> submodule init`, but do a more fine grained structure there, we actually
> need all the init functionality in C before attempting the groups
> feature. To get the init functionality in C, rewriting the
> resolve_relative_url subfunction is a major step.

I see lots of '/', but no is_dir_sep() in the C version. Did you consider that local URLs can use a backslash as path separator on Windows? In the shell version, this did not matter because bash converts the backslashes to forward slashes for us. But when rewritten in C, this does not happen.

Valid URLs are
   D:\foo\bar.git
   \\server\share\foo\bar
   ..\..\foo\bar
and all of them with some or all backslashes replaced by forward slashes.

See also connect.c:url_is_local_not_ssh, which ensures that the first example above is considered a local path with a drive letter, not a remote ssh path.

Show 279 quoted lines
>
> This also improves the performance:
> (Best out of 3) time ./t7400-submodule-basic.sh
> Before:
> real	0m9.575s
> user	0m2.683s
> sys	0m6.773s
> After:
> real	0m9.293s
> user	0m2.691s
> sys	0m6.549s
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
>   This applies on origin/master, and I'd carry as its own feature branch
>   as I am nowhere near done with the groups feature after reading Jens feedback.
>   (It took me a while to identify this as a next best step.)
>
>   Thanks,
>   Stefan
>
>   builtin/submodule--helper.c | 120 ++++++++++++++++++++++++++++++++++++++++++++
>   git-submodule.sh            |  81 ++----------------------------
>   2 files changed, 124 insertions(+), 77 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index f4c3eff..f48b5b5 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -9,6 +9,125 @@
>   #include "submodule-config.h"
>   #include "string-list.h"
>   #include "run-command.h"
> +#include "remote.h"
> +#include "refs.h"
> +
> +static const char *get_default_remote(void)
> +{
> +	char *dest = NULL;
> +	unsigned char sha1[20];
> +	int flag;
> +	struct strbuf sb = STRBUF_INIT;
> +	const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
> +
> +	if (!refname)
> +		die("No such ref: HEAD");
> +
> +	refname = shorten_unambiguous_ref(refname, 0);
> +	strbuf_addf(&sb, "branch.%s.remote", refname);
> +	if (git_config_get_string(sb.buf, &dest))
> +		return "origin";
> +	else
> +		return xstrdup(dest);
> +}
> +
> +/*
> + * 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.
> + */
> +static const char *relative_url(const char *url, const char *up_path)
> +{
> +	int is_relative = 0;
> +	size_t len;
> +	char *remoteurl = NULL;
> +	char *sep = "/";
> +	const char *out;
> +	struct strbuf sb = STRBUF_INIT;
> +	const char *remote = get_default_remote();
> +	strbuf_addf(&sb, "remote.%s.url", remote);
> +
> +	if (git_config_get_string(sb.buf, &remoteurl))
> +		/* the repository is its own authoritative upstream */
> +		remoteurl = xgetcwd();
> +
> +	if (strip_suffix(remoteurl, "/", &len))
> +		remoteurl[len] = '\0';
> +
> +	if (strchr(remoteurl, ':') || skip_prefix(remoteurl, "/", &out))
> +		is_relative = 0;
> +	else if (skip_prefix(remoteurl, "./", &out) ||
> +		    skip_prefix(remoteurl, "../", &out))
> +		is_relative = 1;
> +	else {
> +		is_relative = 1;
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "./%s", remoteurl);
> +		remoteurl = strbuf_detach(&sb, NULL);
> +	}
> +
> +	while (url) {
> +		if (skip_prefix(url, "../", &out)) {
> +			char *rfind;
> +			url = out;
> +
> +			rfind = strrchr(remoteurl, '/');
> +			if (rfind)
> +				*rfind = '\0';
> +			else {
> +				rfind = strrchr(remoteurl, ':');
> +				if (rfind) {
> +					*rfind = '\0';
> +					sep = ":";
> +				} else {
> +					if (is_relative || !strcmp(".", remoteurl))
> +						die(N_("cannot strip one component off url '%s'"), remoteurl);
> +					else
> +						remoteurl = ".";
> +				}
> +			}
> +		} else if (skip_prefix(url, "./", &out))
> +			url = out;
> +		else
> +			break;
> +	}
> +	strbuf_reset(&sb);
> +	strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
> +
> +	if (!skip_prefix(sb.buf, "./", &out))
> +		out = sb.buf;
> +	out = xstrdup(out);
> +
> +	strbuf_reset(&sb);
> +	strbuf_addf(&sb, "%s%s", is_relative && up_path ? up_path : "", out);
> +
> +	free((char*)out);
> +	return strbuf_detach(&sb, NULL);
> +}
> +
> +static int resolve_relative_url(int argc, const char **argv, const char *prefix)
> +{
> +	if (argc == 2)
> +		printf("%s\n", relative_url(argv[1], NULL));
> +	else if (argc == 3)
> +		printf("%s\n", relative_url(argv[1], argv[2]));
> +	else
> +		die("BUG: resolve_relative_url only accepts one or two arguments");
> +	return 0;
> +}
>
>   struct module_list {
>   	const struct cache_entry **entries;
> @@ -264,6 +383,7 @@ static struct cmd_struct commands[] = {
>   	{"list", module_list},
>   	{"name", module_name},
>   	{"clone", module_clone},
> +	{"resolve_relative_url", resolve_relative_url},
>   };
>
>   int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
> diff --git a/git-submodule.sh b/git-submodule.sh
> index 9bc5c5f..6a7a3e4 100755
> --- a/git-submodule.sh
> +++ b/git-submodule.sh
> @@ -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" ||
> @@ -1190,9 +1117,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"
>
Stefan Beller· Dec 16, 2015, 22:36 UTC · re: Johannes Sixt · lore

Re: [PATCH] submodule: Port resolve_relative_url from shell to C

On Wed, Dec 9, 2015 at 10:48 PM, Johannes Sixt <j6t@kdbg.org> wrote:
Show 17 quoted lines
> Am 10.12.2015 um 02:07 schrieb Stefan Beller:
>>
>> This reimplements the helper function `resolve_relative_url` in shell
>> in C. This functionality is needed in C for introducing the groups
>> feature later on. When using groups, the user should not need to run
>> `git submodule init`, but it should be implicit at all appropriate places,
>> which are all in C code. As the we would not just call out to `git
>> submodule init`, but do a more fine grained structure there, we actually
>> need all the init functionality in C before attempting the groups
>> feature. To get the init functionality in C, rewriting the
>> resolve_relative_url subfunction is a major step.
>
>
> I see lots of '/', but no is_dir_sep() in the C version. Did you consider
> that local URLs can use a backslash as path separator on Windows? In the
> shell version, this did not matter because bash converts the backslashes to
> forward slashes for us. But when rewritten in C, this does not happen.
I see. That's a pity. :(
Show 6 quoted lines
>
> Valid URLs are
>
>   D:\foo\bar.git
>   \\server\share\foo\bar
>   ..\..\foo\bar

I am staring at the code in desperation as backslashes in Linux are valid for file names, i.e.: "/tmp/testfile\" is a valid filename

Now look at the code where the first slash occurs, it's
    if (strip_suffix(remoteurl, "/", &len))
        remoteurl[len] = '\0';

The intention is to strip off the last character if it is a directory separator. So in the unix world we want to keep "/tmp/testfile\" as it is, whereas in Windows we want to chop off the backslash (because there is no file with a backslash allowed, it is the directory separator?)

So what I think I am going to do for next round is something like
    static int has_same_dir_prefix(const char *str, const char **out)
    {
    #ifdef GIT_WINDOWS_NATIVE
        return skip_prefix(str, "./", out)
           || skip_prefix(str, ".\\", out);
    #else
        return skip_prefix(str, "./", out);
    #endif
    }
    static int has_upper_dir_prefix(const char *str, const char **out)
    {
    #ifdef GIT_WINDOWS_NATIVE
        return skip_prefix(str, "../", out)
           || skip_prefix(str, "..\\", out);
    #else
        return skip_prefix(str, "../", out);
    #endif
    }

in the submodule helper function, or alternatively in the wrapper.c ? and then rely on these functions being accurate.

Would that approach make sense?

Thanks, Stefan

Show 310 quoted lines
>
> and all of them with some or all backslashes replaced by forward slashes.
>
> See also connect.c:url_is_local_not_ssh, which ensures that the first
> example above is considered a local path with a drive letter, not a remote
> ssh path.
>
>
>>
>> This also improves the performance:
>> (Best out of 3) time ./t7400-submodule-basic.sh
>> Before:
>> real    0m9.575s
>> user    0m2.683s
>> sys     0m6.773s
>> After:
>> real    0m9.293s
>> user    0m2.691s
>> sys     0m6.549s
>>
>> Signed-off-by: Stefan Beller <sbeller@google.com>
>> ---
>>
>>   This applies on origin/master, and I'd carry as its own feature branch
>>   as I am nowhere near done with the groups feature after reading Jens
>> feedback.
>>   (It took me a while to identify this as a next best step.)
>>
>>   Thanks,
>>   Stefan
>>
>>   builtin/submodule--helper.c | 120
>> ++++++++++++++++++++++++++++++++++++++++++++
>>   git-submodule.sh            |  81 ++----------------------------
>>   2 files changed, 124 insertions(+), 77 deletions(-)
>>
>> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
>> index f4c3eff..f48b5b5 100644
>> --- a/builtin/submodule--helper.c
>> +++ b/builtin/submodule--helper.c
>> @@ -9,6 +9,125 @@
>>   #include "submodule-config.h"
>>   #include "string-list.h"
>>   #include "run-command.h"
>> +#include "remote.h"
>> +#include "refs.h"
>> +
>> +static const char *get_default_remote(void)
>> +{
>> +       char *dest = NULL;
>> +       unsigned char sha1[20];
>> +       int flag;
>> +       struct strbuf sb = STRBUF_INIT;
>> +       const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
>> +
>> +       if (!refname)
>> +               die("No such ref: HEAD");
>> +
>> +       refname = shorten_unambiguous_ref(refname, 0);
>> +       strbuf_addf(&sb, "branch.%s.remote", refname);
>> +       if (git_config_get_string(sb.buf, &dest))
>> +               return "origin";
>> +       else
>> +               return xstrdup(dest);
>> +}
>> +
>> +/*
>> + * 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.
>> + */
>> +static const char *relative_url(const char *url, const char *up_path)
>> +{
>> +       int is_relative = 0;
>> +       size_t len;
>> +       char *remoteurl = NULL;
>> +       char *sep = "/";
>> +       const char *out;
>> +       struct strbuf sb = STRBUF_INIT;
>> +       const char *remote = get_default_remote();
>> +       strbuf_addf(&sb, "remote.%s.url", remote);
>> +
>> +       if (git_config_get_string(sb.buf, &remoteurl))
>> +               /* the repository is its own authoritative upstream */
>> +               remoteurl = xgetcwd();
>> +
>> +       if (strip_suffix(remoteurl, "/", &len))
>> +               remoteurl[len] = '\0';
>> +
>> +       if (strchr(remoteurl, ':') || skip_prefix(remoteurl, "/", &out))
>> +               is_relative = 0;
>> +       else if (skip_prefix(remoteurl, "./", &out) ||
>> +                   skip_prefix(remoteurl, "../", &out))
>> +               is_relative = 1;
>> +       else {
>> +               is_relative = 1;
>> +               strbuf_reset(&sb);
>> +               strbuf_addf(&sb, "./%s", remoteurl);
>> +               remoteurl = strbuf_detach(&sb, NULL);
>> +       }
>> +
>> +       while (url) {
>> +               if (skip_prefix(url, "../", &out)) {
>> +                       char *rfind;
>> +                       url = out;
>> +
>> +                       rfind = strrchr(remoteurl, '/');
>> +                       if (rfind)
>> +                               *rfind = '\0';
>> +                       else {
>> +                               rfind = strrchr(remoteurl, ':');
>> +                               if (rfind) {
>> +                                       *rfind = '\0';
>> +                                       sep = ":";
>> +                               } else {
>> +                                       if (is_relative || !strcmp(".",
>> remoteurl))
>> +                                               die(N_("cannot strip one
>> component off url '%s'"), remoteurl);
>> +                                       else
>> +                                               remoteurl = ".";
>> +                               }
>> +                       }
>> +               } else if (skip_prefix(url, "./", &out))
>> +                       url = out;
>> +               else
>> +                       break;
>> +       }
>> +       strbuf_reset(&sb);
>> +       strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
>> +
>> +       if (!skip_prefix(sb.buf, "./", &out))
>> +               out = sb.buf;
>> +       out = xstrdup(out);
>> +
>> +       strbuf_reset(&sb);
>> +       strbuf_addf(&sb, "%s%s", is_relative && up_path ? up_path : "",
>> out);
>> +
>> +       free((char*)out);
>> +       return strbuf_detach(&sb, NULL);
>> +}
>> +
>> +static int resolve_relative_url(int argc, const char **argv, const char
>> *prefix)
>> +{
>> +       if (argc == 2)
>> +               printf("%s\n", relative_url(argv[1], NULL));
>> +       else if (argc == 3)
>> +               printf("%s\n", relative_url(argv[1], argv[2]));
>> +       else
>> +               die("BUG: resolve_relative_url only accepts one or two
>> arguments");
>> +       return 0;
>> +}
>>
>>   struct module_list {
>>         const struct cache_entry **entries;
>> @@ -264,6 +383,7 @@ static struct cmd_struct commands[] = {
>>         {"list", module_list},
>>         {"name", module_name},
>>         {"clone", module_clone},
>> +       {"resolve_relative_url", resolve_relative_url},
>>   };
>>
>>   int cmd_submodule__helper(int argc, const char **argv, const char
>> *prefix)
>> diff --git a/git-submodule.sh b/git-submodule.sh
>> index 9bc5c5f..6a7a3e4 100755
>> --- a/git-submodule.sh
>> +++ b/git-submodule.sh
>> @@ -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" ||
>> @@ -1190,9 +1117,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"
>>
>
Stefan Beller· Dec 17, 2015, 00:26 UTC · re: Stefan Beller · lore

[PATCHv2] Porting resolve_relative_url from shell to C

A new version of the patch, which spells out more its intent and may actually work in Windows.

Any comment welcome, Thanks, Stefan

Stefan Beller (1):
  submodule: Port resolve_relative_url from shell to C
 builtin/submodule--helper.c | 151 ++++++++++++++++++++++++++++++++++++++++++++
 git-submodule.sh            |  81 ++----------------------
 2 files changed, 155 insertions(+), 77 deletions(-)
interdiff to previous version:
Show changes to builtin/submodule--helper.c +39 −8
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f48b5b5..b925bed 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -31,6 +31,36 @@ static const char *get_default_remote(void)
 		return xstrdup(dest);
 }
 
+static int has_same_dir_prefix(const char *str, const char **out)
+{
+#ifdef GIT_WINDOWS_NATIVE
+	return skip_prefix(str, "./", out)
+		|| skip_prefix(str, ".\\", out);
+#else
+	return skip_prefix(str, "./", out);
+#endif
+}
+
+static int has_upper_dir_prefix(const char *str, const char **out)
+{
+#ifdef GIT_WINDOWS_NATIVE
+	return skip_prefix(str, "../", out)
+		|| skip_prefix(str, "..\\", out);
+#else
+	return skip_prefix(str, "../", out);
+#endif
+}
+
+static char *last_dir_separator(const char *str)
+{
+#ifdef GIT_WINDOWS_NATIVE
+	return strrchr(str, "/")
+		|| strrchr(str, "\\");
+#else
+	return strrchr(str, '/');
+#endif
+}
+
 /*
  * The function takes at most 2 arguments. The first argument is the
  * URL that navigates to the submodule origin repo. When relative, this URL
@@ -64,13 +94,14 @@ static const char *relative_url(const char *url, const char *up_path)
 		/* the repository is its own authoritative upstream */
 		remoteurl = xgetcwd();
 
-	if (strip_suffix(remoteurl, "/", &len))
+	len = strlen(remoteurl);
+	if (is_dir_sep(remoteurl[len]))
 		remoteurl[len] = '\0';
 
-	if (strchr(remoteurl, ':') || skip_prefix(remoteurl, "/", &out))
+	if (strchr(remoteurl, ':') || is_dir_sep(*remoteurl))
 		is_relative = 0;
-	else if (skip_prefix(remoteurl, "./", &out) ||
-		    skip_prefix(remoteurl, "../", &out))
+	else if (has_same_dir_prefix(remoteurl, &out) ||
+		    has_upper_dir_prefix(remoteurl, &out))
 		is_relative = 1;
 	else {
 		is_relative = 1;
@@ -80,11 +111,11 @@ static const char *relative_url(const char *url, const char *up_path)
 	}
 
 	while (url) {
-		if (skip_prefix(url, "../", &out)) {
+		if (has_upper_dir_prefix(url, &out)) {
 			char *rfind;
 			url = out;
 
-			rfind = strrchr(remoteurl, '/');
+			rfind = last_dir_separator(remoteurl);
 			if (rfind)
 				*rfind = '\0';
 			else {
@@ -99,7 +130,7 @@ static const char *relative_url(const char *url, const char *up_path)
 						remoteurl = ".";
 				}
 			}
-		} else if (skip_prefix(url, "./", &out))
+		} else if (has_same_dir_prefix(url, &out))
 			url = out;
 		else
 			break;
@@ -107,7 +138,7 @@ static const char *relative_url(const char *url, const char *up_path)
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
 
-	if (!skip_prefix(sb.buf, "./", &out))
+	if (!has_same_dir_prefix(sb.buf, &out))
 		out = sb.buf;
 	out = xstrdup(out);
 
-- 
2.7.0.rc1.2.gfc39790.dirty
Stefan Beller· Dec 17, 2015, 00:26 UTC · re: Stefan Beller · lore

[PATCHv2] submodule: Port resolve_relative_url from shell to C

This reimplements the helper function `resolve_relative_url` in shell in C. This functionality is needed in C for introducing the groups feature later on. When using groups, the user should not need to run `git submodule init`, but it should be implicit at all appropriate places, which are all in C code. As the we would not just call out to `git submodule init`, but do a more fine grained structure there, we actually need all the init functionality in C before attempting the groups feature. To get the init functionality in C, rewriting the resolve_relative_url subfunction is a major step.

This also improves the performance: (Best out of 3) time ./t7400-submodule-basic.sh Before: real 0m9.575s user 0m2.683s sys 0m6.773s After: real 0m9.293s user 0m2.691s sys 0m6.549s

That's about 3%.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 builtin/submodule--helper.c | 151 ++++++++++++++++++++++++++++++++++++++++++++
 git-submodule.sh            |  81 ++----------------------
 2 files changed, 155 insertions(+), 77 deletions(-)
Show changes to 2 files +155 −77

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f4c3eff..b925bed 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -9,6 +9,156 @@
 #include "submodule-config.h"
 #include "string-list.h"
 #include "run-command.h"
+#include "remote.h"
+#include "refs.h"
+
+static const char *get_default_remote(void)
+{
+	char *dest = NULL;
+	unsigned char sha1[20];
+	int flag;
+	struct strbuf sb = STRBUF_INIT;
+	const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
+
+	if (!refname)
+		die("No such ref: HEAD");
+
+	refname = shorten_unambiguous_ref(refname, 0);
+	strbuf_addf(&sb, "branch.%s.remote", refname);
+	if (git_config_get_string(sb.buf, &dest))
+		return "origin";
+	else
+		return xstrdup(dest);
+}
+
+static int has_same_dir_prefix(const char *str, const char **out)
+{
+#ifdef GIT_WINDOWS_NATIVE
+	return skip_prefix(str, "./", out)
+		|| skip_prefix(str, ".\\", out);
+#else
+	return skip_prefix(str, "./", out);
+#endif
+}
+
+static int has_upper_dir_prefix(const char *str, const char **out)
+{
+#ifdef GIT_WINDOWS_NATIVE
+	return skip_prefix(str, "../", out)
+		|| skip_prefix(str, "..\\", out);
+#else
+	return skip_prefix(str, "../", out);
+#endif
+}
+
+static char *last_dir_separator(const char *str)
+{
+#ifdef GIT_WINDOWS_NATIVE
+	return strrchr(str, "/")
+		|| strrchr(str, "\\");
+#else
+	return strrchr(str, '/');
+#endif
+}
+
+/*
+ * 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.
+ */
+static const char *relative_url(const char *url, const char *up_path)
+{
+	int is_relative = 0;
+	size_t len;
+	char *remoteurl = NULL;
+	char *sep = "/";
+	const char *out;
+	struct strbuf sb = STRBUF_INIT;
+	const char *remote = get_default_remote();
+	strbuf_addf(&sb, "remote.%s.url", remote);
+
+	if (git_config_get_string(sb.buf, &remoteurl))
+		/* the repository is its own authoritative upstream */
+		remoteurl = xgetcwd();
+
+	len = strlen(remoteurl);
+	if (is_dir_sep(remoteurl[len]))
+		remoteurl[len] = '\0';
+
+	if (strchr(remoteurl, ':') || is_dir_sep(*remoteurl))
+		is_relative = 0;
+	else if (has_same_dir_prefix(remoteurl, &out) ||
+		    has_upper_dir_prefix(remoteurl, &out))
+		is_relative = 1;
+	else {
+		is_relative = 1;
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "./%s", remoteurl);
+		remoteurl = strbuf_detach(&sb, NULL);
+	}
+
+	while (url) {
+		if (has_upper_dir_prefix(url, &out)) {
+			char *rfind;
+			url = out;
+
+			rfind = last_dir_separator(remoteurl);
+			if (rfind)
+				*rfind = '\0';
+			else {
+				rfind = strrchr(remoteurl, ':');
+				if (rfind) {
+					*rfind = '\0';
+					sep = ":";
+				} else {
+					if (is_relative || !strcmp(".", remoteurl))
+						die(N_("cannot strip one component off url '%s'"), remoteurl);
+					else
+						remoteurl = ".";
+				}
+			}
+		} else if (has_same_dir_prefix(url, &out))
+			url = out;
+		else
+			break;
+	}
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
+
+	if (!has_same_dir_prefix(sb.buf, &out))
+		out = sb.buf;
+	out = xstrdup(out);
+
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s%s", is_relative && up_path ? up_path : "", out);
+
+	free((char*)out);
+	return strbuf_detach(&sb, NULL);
+}
+
+static int resolve_relative_url(int argc, const char **argv, const char *prefix)
+{
+	if (argc == 2)
+		printf("%s\n", relative_url(argv[1], NULL));
+	else if (argc == 3)
+		printf("%s\n", relative_url(argv[1], argv[2]));
+	else
+		die("BUG: resolve_relative_url only accepts one or two arguments");
+	return 0;
+}
 
 struct module_list {
 	const struct cache_entry **entries;
@@ -264,6 +414,7 @@ static struct cmd_struct commands[] = {
 	{"list", module_list},
 	{"name", module_name},
 	{"clone", module_clone},
+	{"resolve_relative_url", resolve_relative_url},
 };
 
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --git a/git-submodule.sh b/git-submodule.sh
index 9bc5c5f..6a7a3e4 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -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" ||
@@ -1190,9 +1117,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"
-- 
2.7.0.rc1.2.gfc39790.dirty
Eric Sunshine· Dec 17, 2015, 08:17 UTC · re: Stefan Beller · lore

Re: [PATCHv2] submodule: Port resolve_relative_url from shell to C

A rather superficial review...
On Wed, Dec 16, 2015 at 7:26 PM, Stefan Beller <sbeller@google.com> wrote:
> This reimplements the helper function `resolve_relative_url` in shell
s/This reimplements/Reimplement/
> in C. This functionality is needed in C for introducing the groups
> feature later on. When using groups, the user should not need to run
> `git submodule init`, but it should be implicit at all appropriate places,
> which are all in C code. As the we would not just call out to `git
I guess you mean "As then we..." or something?
Show 26 quoted lines
> submodule init`, but do a more fine grained structure there, we actually
> need all the init functionality in C before attempting the groups
> feature. To get the init functionality in C, rewriting the
> resolve_relative_url subfunction is a major step.
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> @@ -9,6 +9,156 @@
> +static const char *get_default_remote(void)
> +{
> +       char *dest = NULL;
> +       unsigned char sha1[20];
> +       int flag;
> +       struct strbuf sb = STRBUF_INIT;
> +       const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
> +
> +       if (!refname)
> +               die("No such ref: HEAD");
> +
> +       refname = shorten_unambiguous_ref(refname, 0);
> +       strbuf_addf(&sb, "branch.%s.remote", refname);
> +       if (git_config_get_string(sb.buf, &dest))
> +               return "origin";
> +       else
> +               return xstrdup(dest);
Leaking the strbuf at both returns.

And, leaking the strdup'd dest (in the caller), but I suppose that's intentional?

Show 31 quoted lines
> +}
> +
> +static int has_same_dir_prefix(const char *str, const char **out)
> +{
> +#ifdef GIT_WINDOWS_NATIVE
> +       return skip_prefix(str, "./", out)
> +               || skip_prefix(str, ".\\", out);
> +#else
> +       return skip_prefix(str, "./", out);
> +#endif
> +}
> +
> +static int has_upper_dir_prefix(const char *str, const char **out)
> +{
> +#ifdef GIT_WINDOWS_NATIVE
> +       return skip_prefix(str, "../", out)
> +               || skip_prefix(str, "..\\", out);
> +#else
> +       return skip_prefix(str, "../", out);
> +#endif
> +}
> +
> +static char *last_dir_separator(const char *str)
> +{
> +#ifdef GIT_WINDOWS_NATIVE
> +       return strrchr(str, "/")
> +               || strrchr(str, "\\");
> +#else
> +       return strrchr(str, '/');
> +#endif
> +}
Cleaner would be to move the #if's outside the functions:
    #ifdef GIT_WINDOWS_NATIVE
    /* Windows implementations */
    static int has_same_dir_prefix(...) {...}
    static int has_upper_dir_prefix(...) {...}
    static char *last_dir_separator(...) {...}
    #else
    /* POSIX implementations */
    static int has_same_dir_prefix(...) {...}
    static int has_upper_dir_prefix(...) {...}
    static char *last_dir_separator(...) {...}
    #endif
Show 6 quoted lines
> +static const char *relative_url(const char *url, const char *up_path)
> +{
> +       int is_relative = 0;
> +       size_t len;
> +       char *remoteurl = NULL;
> +       char *sep = "/";

'sep' only ever holds a single character, so why not declare it 'char' rather than 'char *'? (And, adjust the format string of strbuf_addf(), of course.)

Show 32 quoted lines
> +       const char *out;
> +       [...]
> +       while (url) {
> +               if (has_upper_dir_prefix(url, &out)) {
> +                       [...]
> +                       if (rfind)
> +                               *rfind = '\0';
> +                       else {
> +                               rfind = strrchr(remoteurl, ':');
> +                               if (rfind) {
> +                                       *rfind = '\0';
> +                                       sep = ":";
> +                               } else {
> +                                       [...]
> +                               }
> +                       }
> +               } else if (has_same_dir_prefix(url, &out))
> +                       url = out;
> +               else
> +                       break;
> +       }
> +       strbuf_reset(&sb);
> +       strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
> +
> +       if (!has_same_dir_prefix(sb.buf, &out))
> +               out = sb.buf;
> +       out = xstrdup(out);
> +
> +       strbuf_reset(&sb);
> +       strbuf_addf(&sb, "%s%s", is_relative && up_path ? up_path : "", out);
> +
> +       free((char*)out);
Why declare 'out' const if you know you will be freeing it?
Show 21 quoted lines
> +       return strbuf_detach(&sb, NULL);
> +}
> +
> +static int resolve_relative_url(int argc, const char **argv, const char *prefix)
> +{
> +       if (argc == 2)
> +               printf("%s\n", relative_url(argv[1], NULL));
> +       else if (argc == 3)
> +               printf("%s\n", relative_url(argv[1], argv[2]));
> +       else
> +               die("BUG: resolve_relative_url only accepts one or two arguments");
> +       return 0;
> +}
>
>  struct module_list {
>         const struct cache_entry **entries;
> @@ -264,6 +414,7 @@ static struct cmd_struct commands[] = {
>         {"list", module_list},
>         {"name", module_name},
>         {"clone", module_clone},
> +       {"resolve_relative_url", resolve_relative_url},

Can we please use hyphens rather than underscores, and name this "resolve-relative-url" instead? Quoting from a review[1] of an earlier version of git-submodule--helper:

    ... these subcommands would be better spelled with a hyphen than
    an underscore. If I recall correctly, the arguments for using
    underscore were (1) a less noisy diff, (2) these are internal
    commands nobody will be typing anyhow. However, (1) the diff
    noise will be the same with hyphens, and (2) people will want to
    test these commands manually anyhow, and its easier to type
    hyphens and easier to remember them since the precedent for
    hyphens in command-names is already well established.
    Also, the reason that the original shell code used underscores
    was because hyphens are not valid characters in shell function
    names, but that's an implementation detail which shouldn't be
    allowed to bleed over to user-facing interface design (and these
    subcommands are user-facing).
[1]: http://article.gmane.org/gmane.comp.version-control.git/276947
>  };
Johannes Sixt· Dec 17, 2015, 18:55 UTC · re: Stefan Beller · lore

Re: [PATCHv2] submodule: Port resolve_relative_url from shell to C

Am 17.12.2015 um 01:26 schrieb Stefan Beller:
Show 22 quoted lines
> This reimplements the helper function `resolve_relative_url` in shell
> in C. This functionality is needed in C for introducing the groups
> feature later on. When using groups, the user should not need to run
> `git submodule init`, but it should be implicit at all appropriate places,
> which are all in C code. As the we would not just call out to `git
> submodule init`, but do a more fine grained structure there, we actually
> need all the init functionality in C before attempting the groups
> feature. To get the init functionality in C, rewriting the
> resolve_relative_url subfunction is a major step.
>
> This also improves the performance:
> (Best out of 3) time ./t7400-submodule-basic.sh
> Before:
> real	0m9.575s
> user	0m2.683s
> sys	0m6.773s
> After:
> real	0m9.293s
> user	0m2.691s
> sys	0m6.549s
>
> That's about 3%.

I appreciate this effort as it should help us on Windows. Although the numbers (and my own timings) suggest that this is only a small step forward. That's not surprising as the patch removes only two forks.

As to the implementation, find a patch below that removes the ifdefs and a few other suggestions. It is a mechanical conversion without understanding what relative_url() does. I have the gut feeling that the two strbuf_addf towards the end of the function can be contracted and the temporarily allocate copy in 'out' can be removed.

If there were a few examples in the comment above the function, it would be much simpler to understand.

Show changes to builtin/submodule--helper.c +27 −34
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index b925bed..8ec0975 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -11,6 +11,7 @@
 #include "run-command.h"
 #include "remote.h"
 #include "refs.h"
+#include "connect.h"
 
 static const char *get_default_remote(void)
 {
@@ -31,34 +32,23 @@ static const char *get_default_remote(void)
 		return xstrdup(dest);
 }
 
-static int has_same_dir_prefix(const char *str, const char **out)
+static int starts_with_dot_slash(const char *str)
 {
-#ifdef GIT_WINDOWS_NATIVE
-	return skip_prefix(str, "./", out)
-		|| skip_prefix(str, ".\\", out);
-#else
-	return skip_prefix(str, "./", out);
-#endif
+	return str[0] == '.' && is_dir_sep(str[1]);
 }
 
-static int has_upper_dir_prefix(const char *str, const char **out)
+static int starts_with_dot_dot_slash(const char *str)
 {
-#ifdef GIT_WINDOWS_NATIVE
-	return skip_prefix(str, "../", out)
-		|| skip_prefix(str, "..\\", out);
-#else
-	return skip_prefix(str, "../", out);
-#endif
+	return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
 }
 
-static char *last_dir_separator(const char *str)
+static char *last_dir_separator(char *str)
 {
-#ifdef GIT_WINDOWS_NATIVE
-	return strrchr(str, "/")
-		|| strrchr(str, "\\");
-#else
-	return strrchr(str, '/');
-#endif
+	char* p = str + strlen(str);
+	while (p-- != str)
+		if (is_dir_sep(*p))
+			return p;
+	return NULL;
 }
 
 /*
@@ -85,9 +75,10 @@ static const char *relative_url(const char *url, const char *up_path)
 	size_t len;
 	char *remoteurl = NULL;
 	char *sep = "/";
-	const char *out;
+	char *out;
 	struct strbuf sb = STRBUF_INIT;
 	const char *remote = get_default_remote();
+
 	strbuf_addf(&sb, "remote.%s.url", remote);
 
 	if (git_config_get_string(sb.buf, &remoteurl))
@@ -98,22 +89,23 @@ static const char *relative_url(const char *url, const char *up_path)
 	if (is_dir_sep(remoteurl[len]))
 		remoteurl[len] = '\0';
 
-	if (strchr(remoteurl, ':') || is_dir_sep(*remoteurl))
+	if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
 		is_relative = 0;
-	else if (has_same_dir_prefix(remoteurl, &out) ||
-		    has_upper_dir_prefix(remoteurl, &out))
+	else if (starts_with_dot_slash(remoteurl) ||
+		    starts_with_dot_dot_slash(remoteurl))
 		is_relative = 1;
 	else {
 		is_relative = 1;
 		strbuf_reset(&sb);
 		strbuf_addf(&sb, "./%s", remoteurl);
+		free(remoteurl);
 		remoteurl = strbuf_detach(&sb, NULL);
 	}
 
 	while (url) {
-		if (has_upper_dir_prefix(url, &out)) {
+		if (starts_with_dot_dot_slash(url)) {
 			char *rfind;
-			url = out;
+			url += 3;
 
 			rfind = last_dir_separator(remoteurl);
 			if (rfind)
@@ -130,22 +122,23 @@ static const char *relative_url(const char *url, const char *up_path)
 						remoteurl = ".";
 				}
 			}
-		} else if (has_same_dir_prefix(url, &out))
-			url = out;
-		else
+		} else if (starts_with_dot_slash(url)) {
+			url += 2;
+		} else
 			break;
 	}
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s%s%s", remoteurl, sep, url);
 
-	if (!has_same_dir_prefix(sb.buf, &out))
-		out = sb.buf;
-	out = xstrdup(out);
+	if (starts_with_dot_slash(sb.buf))
+		out = strdup(sb.buf + 2);
+	else
+		out = xstrdup(sb.buf);
 
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s%s", is_relative && up_path ? up_path : "", out);
 
-	free((char*)out);
+	free(out);
 	return strbuf_detach(&sb, NULL);
 }
 
Johannes Sixt· Dec 17, 2015, 19:17 UTC · re: Johannes Sixt · lore

Re: [PATCHv2] submodule: Port resolve_relative_url from shell to C

Am 17.12.2015 um 19:55 schrieb Johannes Sixt:
> As to the implementation, find a patch below that removes the ifdefs
> and a few other suggestions.
The word "offers" is missing in this last half-sentence ;)
Jeff King· Dec 18, 2015, 03:27 UTC · re: Johannes Sixt · lore

Re: [PATCHv2] submodule: Port resolve_relative_url from shell to C

On Thu, Dec 17, 2015 at 07:55:43PM +0100, Johannes Sixt wrote:
Show 23 quoted lines
> -static int has_same_dir_prefix(const char *str, const char **out)
> +static int starts_with_dot_slash(const char *str)
>  {
> -#ifdef GIT_WINDOWS_NATIVE
> -	return skip_prefix(str, "./", out)
> -		|| skip_prefix(str, ".\\", out);
> -#else
> -	return skip_prefix(str, "./", out);
> -#endif
> +	return str[0] == '.' && is_dir_sep(str[1]);
>  }
>  
> -static int has_upper_dir_prefix(const char *str, const char **out)
> +static int starts_with_dot_dot_slash(const char *str)
>  {
> -#ifdef GIT_WINDOWS_NATIVE
> -	return skip_prefix(str, "../", out)
> -		|| skip_prefix(str, "..\\", out);
> -#else
> -	return skip_prefix(str, "../", out);
> -#endif
> +	return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
>  }

As the set of prefixes you are looking is probably bounded, it may not be worth generalizing this. But I wondered if something like:

  /*
   * Like skip_prefix, but consider any "/" in the prefix as a
   * directory separator for the platform.
   */
  int skip_prefix_fs(const char *str, const char *prefix, const char **out)
  {
	while (1) {
		if (!*prefix) {
			*out = str;
			return 1;
		} else if (*prefix == '/') {
			if (!is_dir_sep(*str))
				return 0;
		} else {
			if (*str != *prefix)
				return 0;
		}
		str++;
		prefix++;
	}
  }
  ...
  /* works on all platforms! */
  if (skip_prefix_fs(foo, "./", &out))
	...

would be helpful. I don't know if there are other opportunities in the code base that could make use of this. If it's just these two sites, it's probably not worth it.

-Peff
Stefan Beller· Jan 11, 2016, 20:17 UTC · re: Johannes Sixt · lore

Re: [PATCHv2] submodule: Port resolve_relative_url from shell to C

I forgot to send out the draft last year.
On Thu, Dec 17, 2015 at 10:55 AM, Johannes Sixt <j6t@kdbg.org> wrote:
Show 5 quoted lines
>> That's about 3%.
>
> I appreciate this effort as it should help us on Windows. Although the
> numbers (and my own timings) suggest that this is only a small step
> forward. That's not surprising as the patch removes only two forks.
ok. Probably the timings are not as important here anyways.
Show 6 quoted lines
>
> As to the implementation, find a patch below that removes the ifdefs
> and a few other suggestions. It is a mechanical conversion without
> understanding what relative_url() does. I have the gut feeling that the
> two strbuf_addf towards the end of the function can be contracted and
> the temporarily allocate copy in 'out' can be removed.
I have the gut feeling, too. But I could not quite write down the solution.
>
> If there were a few examples in the comment above the function, it
> would be much simpler to understand.
I agree. So rewording the comment in the next reroll.
Torsten Bögershausen· Dec 17, 2015, 07:47 UTC · re: Stefan Beller · lore

Re: [PATCHv2] Porting resolve_relative_url from shell to C

On 17.12.15 01:26, Stefan Beller wrote:
Show 27 quoted lines
> A new version of the patch, which spells out more its intent and
> may actually work in Windows.
> 
> Any comment welcome,
> Thanks,
> Stefan
> 
> Stefan Beller (1):
>   submodule: Port resolve_relative_url from shell to C
> 
>  builtin/submodule--helper.c | 151 ++++++++++++++++++++++++++++++++++++++++++++
>  git-submodule.sh            |  81 ++----------------------
>  2 files changed, 155 insertions(+), 77 deletions(-)
> 
> interdiff to previous version:
> 
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index f48b5b5..b925bed 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -31,6 +31,36 @@ static const char *get_default_remote(void)
>  		return xstrdup(dest);
>  }
>  
> +static int has_same_dir_prefix(const char *str, const char **out)
> +{
> +#ifdef GIT_WINDOWS_NATIVE

Should that be if defined(GIT_WINDOWS_NATIVE) || defined(__CYGWIN__)

← back to recent threads