Repository: gitpython-developers/GitPython

Issue:
Submodule from relative URL
Git submodules allow relative URLs which are based on the parent repo URL.

Right now this is not supported, ie. adding a submodule configuration in `.gitmodules` with a relative URL will fail when cloning in the `add` or `update` `Submodule`.

A sample stack trace when calling `submodule.update(init=True)`:

```
GitCommandError
Cmd('git') failed due to: exit code(128)
  cmdline: git clone -n --separate-git-dir=/opt/parent_project/.git/modules/submodule_project -v ../../my-company/submodule-project.git /opt/parent_project/submodule_project
  stderr: 'fatal: repository '../../my-company/submodule-project.git' does not exist
'
git/cmd.py in wait at line 291
git/util.py in finalize_process at line 341
git/repo/base.py in _clone at line 880
git/repo/base.py in clone_from at line 925
git/objects/submodule/base.py in _clone_repo at line 261
git/objects/submodule/base.py in update at line 524
```

GitPython==2.1.1
Still present in the current revision 8f76463221cf1c69046b27c07afde4f0442b75d5

Discussion:

Fix (patch):
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index fa60bcda..0e55b8fa 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -353,6 +353,11 @@ class Submodule(IndexObject, TraversableIterableObj):
                 os.makedirs(module_abspath_dir)
             module_checkout_path = osp.join(str(repo.working_tree_dir), path)
 
+        if url.startswith("../"):
+            remote_name = repo.active_branch.tracking_branch().remote_name
+            repo_remote_url = repo.remote(remote_name).url
+            url = os.path.join(repo_remote_url, url)
+
         clone = git.Repo.clone_from(
             url,
             module_checkout_path,


Test changes:
diff --git a/test/test_submodule.py b/test/test_submodule.py
index d88f9dab..f44f086c 100644
--- a/test/test_submodule.py
+++ b/test/test_submodule.py
@@ -753,6 +753,22 @@ class TestSubmodule(TestBase):
             )
         # END for each checkout mode
 
+    @with_rw_directory
+    @_patch_git_config("protocol.file.allow", "always")
+    def test_update_submodule_with_relative_path(self, rwdir):
+        repo_path = osp.join(rwdir, "parent")
+        repo = git.Repo.init(repo_path)
+        module_repo_path = osp.join(rwdir, "module")
+        module_repo = git.Repo.init(module_repo_path)
+        module_repo.git.commit(m="test", allow_empty=True)
+        repo.git.submodule("add", "../module", "module")
+        repo.index.commit("add submodule")
+
+        cloned_repo_path = osp.join(rwdir, "cloned_repo")
+        cloned_repo = git.Repo.clone_from(repo_path, cloned_repo_path)
+
+        cloned_repo.submodule_update(init=True, recursive=True)
+
     @with_rw_directory
     @_patch_git_config("protocol.file.allow", "always")
     def test_list_only_valid_submodules(self, rwdir):