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 <sbel...@google.com>
Signed-off-by: Junio C Hamano <gits...@pobox.com>
---
 builtin/submodule--helper.c | 208 +++++++++++++++++++++++++++++++++++++++++++-
 git-submodule.sh            |  81 +----------------
 t/t0060-path-utils.sh       |  42 +++++++++
 3 files changed, 253 insertions(+), 78 deletions(-)

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 65bdc14..d1e9118 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -9,6 +9,210 @@
 #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];
+       int flag = 0;
+       struct strbuf sb = STRBUF_INIT;
+       const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
+
+       if (!refname)
+               die("No such ref: 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          correct
+ * http://a.com/b  ../c             http://a.com/c   yes
+ * http://a.com/b  ../../c          http://c         no (domain should be kept)
+ * http://a.com/b  ../../../c       http:/c          no
+ * http://a.com/b  ../../../../c    http:c           no
+ * http://a.com/b  ../../../../../c    .:c           no
+ */
+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);
+
+       if (starts_with_dot_slash(sb.buf))
+               out = xstrdup(sb.buf + 2);
+       else
+               out = xstrdup(sb.buf);
+       strbuf_reset(&sb);
+
+       free(remoteurl);
+       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;
@@ -502,7 +706,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)
diff --git a/git-submodule.sh b/git-submodule.sh
index 10c5af9..615ef9b 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" ||
@@ -1176,9 +1103,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"
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index f0152a7..0d2176b 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -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 &&
@@ -289,4 +296,39 @@ 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
 
+test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c" 
"../foo/sub/a/b/c"
+test_submodule_relative_url "../../../" "../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 "../" "../foo/bar" "../submodule" 
"../../foo/submodule"
+test_submodule_relative_url "(null)" "../foo/submodule" "../submodule" 
"../foo/submodule"
+test_submodule_relative_url "../" "../foo/submodule" "../submodule" 
"../../foo/submodule"
+test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule"
+test_submodule_relative_url "../" "../foo" "../submodule" "../../submodule"
+test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule"
+test_submodule_relative_url "../" "./foo/bar" "../submodule" "../foo/submodule"
+test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule"
+test_submodule_relative_url "../" "./foo" "../submodule" "../submodule"
+test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" 
"//somewhere else/subrepo"
+test_submodule_relative_url "(null)" "/u//trash 
directory.t7406-submodule-update/subsuper_update_r" "../subsubsuper_update_r" 
"/u//trash directory.t7406-submodule-update/subsubsuper_update_r"
+test_submodule_relative_url "(null)" "/u//trash 
directory.t7406-submodule-update/super_update_r2" "../subsuper_update_r" 
"/u//trash directory.t7406-submodule-update/subsuper_update_r"
+test_submodule_relative_url "(null)" "/u/trash directory.t3600-rm/." "../." 
"/u/trash directory.t3600-rm/."
+test_submodule_relative_url "(null)" "/u/trash directory.t3600-rm" "./." 
"/u/trash directory.t3600-rm/."
+test_submodule_relative_url "(null)" "/u/trash 
directory.t7400-submodule-basic/addtest" "../repo" "/u/trash 
directory.t7400-submodule-basic/repo"
+test_submodule_relative_url "../" "/u/trash 
directory.t7400-submodule-basic/addtest" "../repo" "/u/trash 
directory.t7400-submodule-basic/repo"
+test_submodule_relative_url "(null)" "/u/trash 
directory.t7400-submodule-basic" "./å äö" "/u/trash 
directory.t7400-submodule-basic/å äö"
+test_submodule_relative_url "(null)" "/u/trash 
directory.t7403-submodule-sync/." "../submodule" "/u/trash 
directory.t7403-submodule-sync/submodule"
+test_submodule_relative_url "(null)" "/u/trash 
directory.t7407-submodule-foreach/submodule" "../submodule" "/u/trash 
directory.t7407-submodule-foreach/submodule"
+test_submodule_relative_url "(null)" "/u/trash 
directory.t7409-submodule-detached-worktree/home2/../remote" "../bundle1" 
"/u/trash directory.t7409-submodule-detached-worktree/home2/../bundle1"
+test_submodule_relative_url "(null)" "/u/trash 
directory.t7613-merge-submodule/submodule_update_repo" "./." "/u/trash 
directory.t7613-merge-submodule/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 "../" "foo/bar" "../submodule" "../foo/submodule"
+test_submodule_relative_url "(null)" "foo" "../submodule" "submodule"
+test_submodule_relative_url "../" "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
-- 
2.7.1.292.g18a4ced.dirty

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to