| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,7 +12,6 @@ | |||
| 12 | 12 | import shlex | |
| 13 | 13 | import stat | |
| 14 | 14 | import sys | |
| 15 | - import urllib.parse | ||
| 16 | 15 | import uuid | |
| 17 | 16 | from io import BytesIO | |
| 18 | 17 | from pathlib import Path | |
@@ -57,8 +56,12 @@ | |||
| 57 | 56 | ||
| 58 | 57 | from .util import ( | |
| 59 | 58 | SubmoduleConfigParser, | |
| 59 | + _is_absolute_path, | ||
| 60 | + _url_is_local_not_ssh, | ||
| 60 | 61 | find_first_remote_branch, | |
| 62 | + is_relative_url, | ||
| 61 | 63 | mkhead, | |
| 64 | + resolve_relative_url, | ||
| 62 | 65 | sm_name, | |
| 63 | 66 | sm_section, | |
| 64 | 67 | ) | |
@@ -68,7 +71,7 @@ | |||
| 68 | 71 | if TYPE_CHECKING: | |
| 69 | 72 | from git.index import IndexFile | |
| 70 | 73 | from git.objects.commit import Commit | |
| 71 | - from git.refs import Head, RemoteReference | ||
| 74 | + from git.refs import Head | ||
| 72 | 75 | from git.repo import Repo | |
| 73 | 76 | ||
| 74 | 77 | # ----------------------------------------------------------------------------- | |
@@ -428,13 +431,8 @@ def _clone_repo( | |||
| 428 | 431 | if not osp.isdir(module_abspath_dir): | |
| 429 | 432 | os.makedirs(module_abspath_dir) | |
| 430 | 433 | ||
| 431 | - if url.startswith("../"): | ||
| 432 | - remote_name = cast("RemoteReference", repo.active_branch.tracking_branch()).remote_name | ||
| 433 | - repo_remote_url = repo.remote(remote_name).url | ||
| 434 | - url = os.path.join(repo_remote_url, url) | ||
| 435 | - | ||
| 436 | 434 | clone = git.Repo.clone_from( | |
| 437 | - url, | ||
| 435 | + cls._resolve_url(repo, url), | ||
| 438 | 436 | module_checkout_path, | |
| 439 | 437 | allow_unsafe_options=allow_unsafe_options, | |
| 440 | 438 | allow_unsafe_protocols=allow_unsafe_protocols, | |
@@ -746,8 +744,9 @@ def add( | |||
| 746 | 744 | ) | |
| 747 | 745 | # END verify url | |
| 748 | 746 | ||
| 749 | - ## See #525 for ensuring git URLs in config-files are valid under Windows. | ||
| 750 | - url = Git.polish_url(url, expand_vars=False) | ||
| 747 | + # Relative submodule URLs must keep their prefix to resolve against the parent remote. | ||
| 748 | + if not is_relative_url(url): | ||
| 749 | + url = Git.polish_url(url, expand_vars=False) | ||
| 751 | 750 | ||
| 752 | 751 | # It's important to add the URL to the parent config, to let `git submodule` know. | |
| 753 | 752 | # Otherwise there is a '-' character in front of the submodule listing: | |
@@ -982,13 +981,9 @@ def fetch_remotes(module_repo: "Repo") -> None: | |||
| 982 | 981 | if not dry_run: | |
| 983 | 982 | if no_fetch: | |
| 984 | 983 | raise ValueError("Missing module at %r but fetching is disabled" % self.path) from None | |
| 985 | - if self.url.startswith("."): | ||
| 986 | - url = urllib.parse.urljoin(self.repo.remotes.origin.url + "/", self.url) | ||
| 987 | - else: | ||
| 988 | - url = self.url | ||
| 989 | 984 | mrepo = self._clone_repo( | |
| 990 | 985 | self.repo, | |
| 991 | - url, | ||
| 986 | + self.url, | ||
| 992 | 987 | self.path, | |
| 993 | 988 | self.name, | |
| 994 | 989 | n=True, | |
@@ -1738,6 +1733,31 @@ def url(self) -> str: | |||
| 1738 | 1733 | """:return: The url to the repository our submodule's repository refers to""" | |
| 1739 | 1734 | return self._url | |
| 1740 | 1735 | ||
| 1736 | + @staticmethod | ||
| 1737 | + def _resolve_url(repo: "Repo", url: str) -> str: | ||
| 1738 | + """Resolve relative URLs against the tracking remote, then ``origin``, | ||
| 1739 | + falling back to the superproject's directory the way git does.""" | ||
| 1740 | + if not is_relative_url(url): | ||
| 1741 | + return url | ||
| 1742 | + remote_name = "origin" | ||
| 1743 | + if not repo.head.is_detached: | ||
| 1744 | + with repo.active_branch.config_reader() as reader: | ||
| 1745 | + remote_name = reader.get("remote", fallback="origin") | ||
| 1746 | + try: | ||
| 1747 | + remote_url = repo.remote(remote_name).url | ||
| 1748 | + except ValueError: | ||
| 1749 | + remote_url = os.fspath(repo.working_dir) | ||
| 1750 | + if remote_url == ".": | ||
| 1751 | + remote_url = os.fspath(repo.working_dir) | ||
| 1752 | + resolved = resolve_relative_url(remote_url, url) | ||
| 1753 | + if resolved and _url_is_local_not_ssh(resolved) and not _is_absolute_path(resolved): | ||
| 1754 | + resolved = os.path.join(repo.working_dir, resolved) | ||
| 1755 | + return resolved | ||
| 1756 | + | ||
| 1757 | + def _resolved_url(self) -> str: | ||
| 1758 | + """:return: :attr:`url` resolved against the superproject's default remote""" | ||
| 1759 | + return self._resolve_url(self.repo, self.url) | ||
| 1760 | + | ||
| 1741 | 1761 | @property | |
| 1742 | 1762 | def parent_commit(self) -> "Commit": | |
| 1743 | 1763 | """ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -264,10 +264,13 @@ def update( # type: ignore[override] | |||
| 264 | 264 | nn = "__new_origin__" | |
| 265 | 265 | smm = sm.module() | |
| 266 | 266 | rmts = smm.remotes | |
| 267 | + # Relative urls are resolved against the superproject's remote, | ||
| 268 | + # which is what the module's remotes hold. | ||
| 269 | + sm_url = sm._resolved_url() | ||
| 267 | 270 | ||
| 268 | 271 | # Don't do anything if we already have the url we search in | |
| 269 | 272 | # place. | |
| 270 | - if len([r for r in rmts if r.url == sm.url]) == 0: | ||
| 273 | + if len([r for r in rmts if r.url == sm_url]) == 0: | ||
| 271 | 274 | progress.update( | |
| 272 | 275 | BEGIN | URLCHANGE, | |
| 273 | 276 | i, | |
@@ -277,38 +280,31 @@ def update( # type: ignore[override] | |||
| 277 | 280 | ||
| 278 | 281 | if not dry_run: | |
| 279 | 282 | previous_remote = None | |
| 280 | - for remote in rmts: | ||
| 281 | - if remote.url == psm.url: | ||
| 282 | - previous_remote = remote | ||
| 283 | - break | ||
| 284 | - # END if urls match | ||
| 285 | - # END for each remote | ||
| 283 | + if len(rmts) == 1: | ||
| 284 | + previous_remote = rmts[0] | ||
| 285 | + else: | ||
| 286 | + psm_url = psm._resolved_url() | ||
| 287 | + for remote in rmts: | ||
| 288 | + if remote.url == psm_url: | ||
| 289 | + previous_remote = remote | ||
| 290 | + break | ||
| 291 | + # END if urls match | ||
| 292 | + # END for each remote | ||
| 286 | 293 | ||
| 287 | - # If we didn't find a matching remote, but have exactly | ||
| 288 | - # one, we can safely use this one. | ||
| 289 | 294 | if previous_remote is None: | |
| 290 | - if len(rmts) == 1: | ||
| 291 | - previous_remote = rmts[0] | ||
| 292 | - else: | ||
| 293 | - # If we have not found any remote with the | ||
| 294 | - # original URL we may not have a name. This is a | ||
| 295 | - # special case, and its okay to fail here. | ||
| 296 | - # Alternatively we could just generate a unique | ||
| 297 | - # name and leave all existing ones in place. | ||
| 298 | - raise InvalidGitRepositoryError( | ||
| 299 | - "Couldn't find original remote-repo at url %r" % psm.url | ||
| 300 | - ) | ||
| 301 | - # END handle one single remote | ||
| 295 | + raise InvalidGitRepositoryError( | ||
| 296 | + "Couldn't find original remote-repo at url %r" % psm.url | ||
| 297 | + ) | ||
| 302 | 298 | # END handle check we found a remote | |
| 303 | 299 | ||
| 304 | 300 | if no_fetch: | |
| 305 | 301 | # A new remote would have no cached refs. Preserve | |
| 306 | 302 | # the existing refs and tracking configuration for | |
| 307 | 303 | # offline updates instead of replacing the remote. | |
| 308 | - previous_remote.set_url(git.Git.polish_url(sm.url, expand_vars=False)) | ||
| 304 | + previous_remote.set_url(git.Git.polish_url(sm_url, expand_vars=False)) | ||
| 309 | 305 | else: | |
| 310 | 306 | assert nn not in [r.name for r in rmts] | |
| 311 | - smr = smm.create_remote(nn, sm.url) | ||
| 307 | + smr = smm.create_remote(nn, sm_url) | ||
| 312 | 308 | smr.fetch(progress=progress) | |
| 313 | 309 | ||
| 314 | 310 | # If we have a tracking branch, it should be available | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,10 +6,13 @@ | |||
| 6 | 6 | "sm_name", | |
| 7 | 7 | "mkhead", | |
| 8 | 8 | "find_first_remote_branch", | |
| 9 | + "is_relative_url", | ||
| 10 | + "resolve_relative_url", | ||
| 9 | 11 | "SubmoduleConfigParser", | |
| 10 | 12 | ] | |
| 11 | 13 | ||
| 12 | 14 | from io import BytesIO | |
| 15 | + import sys | ||
| 13 | 16 | import weakref | |
| 14 | 17 | ||
| 15 | 18 | import git | |
@@ -63,6 +66,97 @@ def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> " | |||
| 63 | 66 | raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name) | |
| 64 | 67 | ||
| 65 | 68 | ||
| 69 | + def _is_dir_sep(char: str) -> bool: | ||
| 70 | + return char == "/" or (sys.platform == "win32" and char == "\\") | ||
| 71 | + | ||
| 72 | + | ||
| 73 | + def _starts_with_dot_slash(url: str) -> bool: | ||
| 74 | + return len(url) >= 2 and url[0] == "." and _is_dir_sep(url[1]) | ||
| 75 | + | ||
| 76 | + | ||
| 77 | + def _starts_with_dot_dot_slash(url: str) -> bool: | ||
| 78 | + return len(url) >= 3 and url[0:2] == ".." and _is_dir_sep(url[2]) | ||
| 79 | + | ||
| 80 | + | ||
| 81 | + def _has_dos_drive_prefix(url: str) -> bool: | ||
| 82 | + return sys.platform == "win32" and len(url) >= 2 and url[0].isalpha() and url[1] == ":" | ||
| 83 | + | ||
| 84 | + | ||
| 85 | + def _is_absolute_path(url: str) -> bool: | ||
| 86 | + return (len(url) >= 1 and _is_dir_sep(url[0])) or _has_dos_drive_prefix(url) | ||
| 87 | + | ||
| 88 | + | ||
| 89 | + def _url_is_local_not_ssh(url: str) -> bool: | ||
| 90 | + colon = url.find(":") | ||
| 91 | + slash = url.find("/") | ||
| 92 | + return colon == -1 or (slash != -1 and slash < colon) or _has_dos_drive_prefix(url) | ||
| 93 | + | ||
| 94 | + | ||
| 95 | + def _find_last_dir_sep(url: str) -> int: | ||
| 96 | + for index in range(len(url) - 1, -1, -1): | ||
| 97 | + if _is_dir_sep(url[index]): | ||
| 98 | + return index | ||
| 99 | + return -1 | ||
| 100 | + | ||
| 101 | + | ||
| 102 | + def is_relative_url(url: str) -> bool: | ||
| 103 | + """:return: ``True`` if `url` starts with ``./`` or ``../``, which git resolves | ||
| 104 | + against the superproject's remote rather than treating as a path""" | ||
| 105 | + return _starts_with_dot_slash(url) or _starts_with_dot_dot_slash(url) | ||
| 106 | + | ||
| 107 | + | ||
| 108 | + def resolve_relative_url(remote_url: str, url: str) -> str: | ||
| 109 | + """Resolve a submodule URL that starts with ``./`` or ``../`` against the | ||
| 110 | + superproject's remote URL, the way git's ``relative_url`` does. | ||
| 111 | + | ||
| 112 | + Git treats a relative submodule URL like a relative directory: each leading | ||
| 113 | + ``../`` removes the last component of `remote_url`, whether that is a path | ||
| 114 | + component, the host of a URL, or the host of an scp-like ``host:path`` address. | ||
| 115 | + The remainder is appended with ``/``, or with ``:`` once an scp-like host was | ||
| 116 | + reached. A URL that is not relative is returned unchanged. | ||
| 117 | + | ||
| 118 | + :raise ValueError: | ||
| 119 | + If more components are stripped than `remote_url` has, which git rejects with | ||
| 120 | + "cannot strip one component off url". | ||
| 121 | + """ | ||
| 122 | + if not is_relative_url(url): | ||
| 123 | + return url | ||
| 124 | + | ||
| 125 | + remote = remote_url | ||
| 126 | + if remote and _is_dir_sep(remote[-1]): | ||
| 127 | + remote = remote[:-1] | ||
| 128 | + | ||
| 129 | + is_relative = _url_is_local_not_ssh(remote) and not _is_absolute_path(remote) | ||
| 130 | + if is_relative and not _starts_with_dot_slash(remote) and not _starts_with_dot_dot_slash(remote): | ||
| 131 | + remote = "./" + remote | ||
| 132 | + | ||
| 133 | + colonsep = False | ||
| 134 | + while url: | ||
| 135 | + if _starts_with_dot_dot_slash(url): | ||
| 136 | + url = url[3:] | ||
| 137 | + separator = _find_last_dir_sep(remote) | ||
| 138 | + if separator != -1: | ||
| 139 | + remote = remote[:separator] | ||
| 140 | + elif ":" in remote: | ||
| 141 | + remote = remote[: remote.rfind(":")] | ||
| 142 | + colonsep = True | ||
| 143 | + elif is_relative or remote == ".": | ||
| 144 | + raise ValueError("cannot strip one component off url %r" % remote) | ||
| 145 | + else: | ||
| 146 | + remote = "." | ||
| 147 | + elif _starts_with_dot_slash(url): | ||
| 148 | + url = url[2:] | ||
| 149 | + else: | ||
| 150 | + break | ||
| 151 | + | ||
| 152 | + resolved = remote + (":" if colonsep else "/") + url | ||
| 153 | + if url.endswith("/"): | ||
| 154 | + resolved = resolved[:-1] | ||
| 155 | + if _starts_with_dot_slash(resolved): | ||
| 156 | + resolved = resolved[2:] | ||
| 157 | + return resolved | ||
| 158 | + | ||
| 159 | + | ||
| 66 | 160 | # } END utilities | |
| 67 | 161 | ||
| 68 | 162 | # { Classes | |
| Back | FazBrowse Home | New Git URL |
0 commit comments