Repository: gitpython-developers/GitPython

Issue:
submodule update doesn't work with relative submodule paths (in GitPython 2.1.14)
When using the git.objects.submodules.base.Submodule.update method (https://gitpython.readthedocs.io/en/stable/reference.html#git.objects.submodule.base.Submodule.update), the git python module seems to fake a `git submodule` call with `git clone`. It executes ```git clone -n --separate-git-dir=<workspace>/.git/modules/<submodule_path> -v <submodule_url> <workspace>/<submodule_path>```. This command doesn't work when a submodule is specified as a relative path in the `.gitmodules` path. For example:
```
[submodule "main_project/submodule"]
  path = main_project/submodule
  url = ../../other_project/some_module.git
```
The `git.objects.submodules.base.Submodule.update` then fails with `repository '../../other_project/some_module.git' does not exist.

The regular `git submodule` command is able to resolve the paths.



Discussion:
Thanks for posting. Which version of GitPython are you using?

Even though the submodule implementation may not be up to date with what `git` produces, I remember that some improvements were made in the past which might resolve this issue.
Thanks for the feedback, I am using version `2.1.14` due to external python2 requirement. The issue is indeed resolved when using version `3.0.3`. 
Thanks for your reply! I am closing this issue as it is already resolved.

Interestingly, in the last released version of GitPython with Python 2 compatibility, the submodule improvements should already be present. If you can spot the difference, I would be happy to receive a PR against 2.1.14 that fixes the issue there as well and release 2.1.15.
Sorry, I made a mistake in my setup while I was testing it with python3 and git python version `3.0.3`. On version `3.0.3` the relative submodule paths also does not work
This bug is still present in `3.0.5` any chance to increase priority?
I am afraid there is no priority beyond what people decide to contribute. GitPython is in maintenance mode.
When looking trough other issues, I think this issue is the same as [issue 730](https://github.com/gitpython-developers/GitPython/issues/730). Is there any reason why gitpython doesn't use the "native" `git submodule` calls? 
If memory serves, back in the days I wanted to have 'smart' submodules, that are easier to handle and to update. For that, a lot of additional logic was added to what constitutes a very own implementation, compatible to git-submodules. In retrospect that turned out to be coming with maintenance costs that are now unaffordable.

Luckily, users can always workaround themselves by using `repo.git.submodule(…)`, which may be enough for most usecases.
I have same trouble.
I written the 'relatives' class, and this class can supply full URL, from origin + relative path to submodule.
But how i can replace path to submodule in Repo object directly? 
> But how i can replace path to submodule in Repo object directly? 

Due to its nature, it is probably read-only. More details could be provided if there is more information on what's you are trying to do and how it's done.
**My Workaround**

I was able to update my relative submodules with:

`repo.git.execute(command=['git', 'submodule', 'update', '--init', '--recursive'])`

Docs: https://gitpython.readthedocs.io/en/stable/reference.html#git.cmd.Git.execute

GitPython 3.1.26, Python 3.8.10
Thanks for sharing. This can probably be rewritten to `repo.git.submodule('update', init=True, recursive=True)`, giving it a bit of an edge over calling `popen` entirely by hand.
I wasn't able to make it work neither with
`repo.submodule_update(init=True, recursive=True)`
nor
`repo.git.submodule('update', init=True, recursive=True)`
The latter is due to derivation to wrong git command `cmdline: git submodule --init --recursive update`

https://github.com/gitpython-developers/GitPython/issues/944#issuecomment-1043410572 solution worked for me.

**Git**: 2.25.1
**GitPython**: 3.1.27
**Python**: 3.8.10
Thanks for letting us know.

I think the takeaway here is that one should avoid `GitPython` submodules in favor of using `reop.git.submodule(…)`, which uses the `git` program as one would on the command-line.



Patch:
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index 0e55b8fa9..5031a2e71 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -11,6 +11,7 @@
 import stat
 import sys
 import uuid
+import urllib
 
 import git
 from git.cmd import Git
@@ -799,9 +800,13 @@ def update(
                     + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name),
                 )
                 if not dry_run:
+                    if self.url.startswith("."):
+                        url = urllib.parse.urljoin(self.repo.remotes.origin.url + "/", self.url)
+                    else:
+                        url = self.url
                     mrepo = self._clone_repo(
                         self.repo,
-                        self.url,
+                        url,
                         self.path,
                         self.name,
                         n=True,


Test patch:
diff --git a/test/test_submodule.py b/test/test_submodule.py
index f44f086c2..4a248eb60 100644
--- a/test/test_submodule.py
+++ b/test/test_submodule.py
@@ -1350,3 +1350,23 @@ def test_submodule_update_unsafe_options_allowed(self, rw_repo):
             for unsafe_option in unsafe_options:
                 with self.assertRaises(GitCommandError):
                     submodule.update(clone_multi_options=[unsafe_option], allow_unsafe_options=True)
+
+    @with_rw_directory
+    @_patch_git_config("protocol.file.allow", "always")
+    def test_submodule_update_relative_url(self, rwdir):
+        parent_path = osp.join(rwdir, "parent")
+        parent_repo = git.Repo.init(parent_path)
+        submodule_path = osp.join(rwdir, "module")
+        submodule_repo = git.Repo.init(submodule_path)
+        submodule_repo.git.commit(m="initial commit", allow_empty=True)
+
+        parent_repo.git.submodule("add", "../module", "module")
+        parent_repo.index.commit("add submodule with relative URL")
+
+        cloned_path = osp.join(rwdir, "cloned_repo")
+        cloned_repo = git.Repo.clone_from(parent_path, cloned_path)
+
+        cloned_repo.submodule_update(init=True, recursive=True)
+
+        has_module = any(sm.name == "module" for sm in cloned_repo.submodules)
+        assert has_module, "Relative submodule was not updated properly"