Skip to content

Commit f481f43

Browse files
committed
get_remote_url now redirects forks to upstream
(DryRunBackend)
1 parent a0d7070 commit f481f43

File tree

2 files changed

+93
-22
lines changed

2 files changed

+93
-22
lines changed

conda_forge_tick/git_utils.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,8 @@ def does_repository_exist(self, owner: str, repo_name: str) -> bool:
543543
"""
544544
pass
545545

546-
@staticmethod
547546
def get_remote_url(
547+
self,
548548
owner: str,
549549
repo_name: str,
550550
connection_mode: GitConnectionMode = GitConnectionMode.HTTPS,
@@ -558,6 +558,8 @@ def get_remote_url(
558558
:param token: A token to use for authentication. If falsy, no token is used. Use get_authenticated_remote_url
559559
instead if you want to use the token of the current user.
560560
:raises ValueError: If the connection mode is not supported.
561+
:raises RepositoryNotFoundError: If the repository does not exist. This is only raised if the backend relies on
562+
the repository existing to generate the URL.
561563
"""
562564
# Currently we don't need any abstraction for other platforms than GitHub, so we don't build such abstractions.
563565
match connection_mode:
@@ -937,7 +939,12 @@ class DryRunBackend(GitPlatformBackend):
937939

938940
def __init__(self):
939941
super().__init__(GitCli())
940-
self._repos: set[str] = set()
942+
self._repos: dict[str, str] = {}
943+
"""
944+
_repos maps from repository name to the owner of the upstream repository.
945+
If a remote URL of a fork is requested with get_remote_url, _USER (the virtual current user) is
946+
replaced by the owner of the upstream repository. This allows cloning the forked repository.
947+
"""
941948

942949
def get_api_requests_left(self) -> Bound:
943950
return Bound.INFINITY
@@ -951,6 +958,26 @@ def does_repository_exist(self, owner: str, repo_name: str) -> bool:
951958
self.get_remote_url(owner, repo_name, GitConnectionMode.HTTPS)
952959
)
953960

961+
def get_remote_url(
962+
self,
963+
owner: str,
964+
repo_name: str,
965+
connection_mode: GitConnectionMode = GitConnectionMode.HTTPS,
966+
token: str | None = None,
967+
) -> str:
968+
if owner != self._USER:
969+
return super().get_remote_url(owner, repo_name, connection_mode, token)
970+
# redirect to the upstream repository
971+
try:
972+
upstream_owner = self._repos[repo_name]
973+
except KeyError:
974+
raise RepositoryNotFoundError(
975+
f"Repository {owner}/{repo_name} appears to be a virtual fork but does not exist. Note that dry-run "
976+
"forks are persistent only for the duration of the backend instance."
977+
)
978+
979+
return super().get_remote_url(upstream_owner, repo_name, connection_mode, token)
980+
954981
def push_to_repository(
955982
self, owner: str, repo_name: str, git_dir: Path, branch: str
956983
):
@@ -967,7 +994,7 @@ def fork(self, owner: str, repo_name: str):
967994
logger.debug(
968995
f"Dry Run: Creating fork of {owner}/{repo_name} for user {self._USER}."
969996
)
970-
self._repos.add(repo_name)
997+
self._repos[repo_name] = owner
971998

972999
def _sync_default_branch(self, upstream_owner: str, upstream_repo: str):
9731000
logger.debug(

tests/test_git_utils.py

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,25 +1044,6 @@ def test_git_cli_clone_fork_and_branch_non_existing_remote_existing_target_dir(c
10441044
assert "trying to reset hard" in caplog.text
10451045

10461046

1047-
def test_git_platform_backend_get_remote_url_https():
1048-
owner = "OWNER"
1049-
repo = "REPO"
1050-
1051-
url = GitPlatformBackend.get_remote_url(owner, repo, GitConnectionMode.HTTPS)
1052-
1053-
assert url == f"https://github.com/{owner}/{repo}.git"
1054-
1055-
1056-
def test_git_platform_backend_get_remote_url_token():
1057-
owner = "OWNER"
1058-
repo = "REPO"
1059-
token = "TOKEN"
1060-
1061-
url = GitPlatformBackend.get_remote_url(owner, repo, GitConnectionMode.HTTPS, token)
1062-
1063-
assert url == f"https://{token}@github.com/{owner}/{repo}.git"
1064-
1065-
10661047
def test_github_backend_from_token():
10671048
token = "TOKEN"
10681049

@@ -1106,6 +1087,27 @@ def test_github_backend_does_repository_exist(does_exist: bool):
11061087
github3_client.repository.assert_called_once_with("OWNER", "REPO")
11071088

11081089

1090+
def test_github_backend_get_remote_url_https():
1091+
owner = "OWNER"
1092+
repo = "REPO"
1093+
backend = GitHubBackend(MagicMock(), MagicMock(), "")
1094+
1095+
url = backend.get_remote_url(owner, repo, GitConnectionMode.HTTPS)
1096+
1097+
assert url == f"https://github.com/{owner}/{repo}.git"
1098+
1099+
1100+
def test_github_backend_get_remote_url_token():
1101+
owner = "OWNER"
1102+
repo = "REPO"
1103+
token = "TOKEN"
1104+
backend = GitHubBackend(MagicMock(), MagicMock(), "")
1105+
1106+
url = backend.get_remote_url(owner, repo, GitConnectionMode.HTTPS, token)
1107+
1108+
assert url == f"https://{token}@github.com/{owner}/{repo}.git"
1109+
1110+
11091111
@mock.patch("time.sleep", return_value=None)
11101112
@mock.patch(
11111113
"conda_forge_tick.git_utils.GitHubBackend.user", new_callable=mock.PropertyMock
@@ -1609,6 +1611,48 @@ def test_dry_run_backend_does_repository_exist_other_repo():
16091611
)
16101612

16111613

1614+
@pytest.mark.parametrize("token", [None, "TOKEN"])
1615+
def test_dry_run_backend_get_remote_url_non_fork(token: str | None):
1616+
backend = DryRunBackend()
1617+
1618+
url = backend.get_remote_url("OWNER", "REPO", GitConnectionMode.HTTPS, token)
1619+
1620+
if token is None:
1621+
assert url == "https://github.com/OWNER/REPO.git"
1622+
else:
1623+
assert url == "https://[email protected]/OWNER/REPO.git"
1624+
1625+
1626+
@pytest.mark.parametrize("token", [None, "TOKEN"])
1627+
def test_dry_run_backend_get_remote_url_non_existing_fork(token: str | None):
1628+
backend = DryRunBackend()
1629+
1630+
with pytest.raises(RepositoryNotFoundError, match="does not exist"):
1631+
backend.get_remote_url(backend.user, "REPO", GitConnectionMode.HTTPS, token)
1632+
1633+
backend.fork("UPSTREAM_OWNER", "REPO2")
1634+
1635+
with pytest.raises(RepositoryNotFoundError, match="does not exist"):
1636+
backend.get_remote_url(backend.user, "REPO", GitConnectionMode.HTTPS, token)
1637+
1638+
1639+
@pytest.mark.parametrize("token", [None, "TOKEN"])
1640+
def test_dry_run_backend_get_remote_url_existing_fork(token: str | None):
1641+
backend = DryRunBackend()
1642+
1643+
backend.fork("UPSTREAM_OWNER", "pytest-feedstock")
1644+
1645+
url = backend.get_remote_url(
1646+
backend.user, "pytest-feedstock", GitConnectionMode.HTTPS, token
1647+
)
1648+
1649+
# note that the URL does not indicate anymore that it is a fork
1650+
assert (
1651+
url
1652+
== f"https://{f'{token}@' if token else ''}github.com/UPSTREAM_OWNER/pytest-feedstock.git"
1653+
)
1654+
1655+
16121656
def test_dry_run_backend_push_to_repository(caplog):
16131657
caplog.set_level(logging.DEBUG)
16141658

0 commit comments

Comments
 (0)