Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions dvc/repo/open_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,22 @@ def _clone_default_branch(url, rev):


def _pull(git: "Git", unshallow: bool = False):
from scmrepo.exceptions import AuthError

from dvc.repo.experiments.utils import fetch_all_exps
from dvc.scm import GitAuthError

try:
git.fetch(unshallow=unshallow)
except AuthError as exc:
raise GitAuthError(str(exc)) from exc

git.fetch(unshallow=unshallow)
_merge_upstream(git)
fetch_all_exps(git, "origin")

try:
fetch_all_exps(git, "origin")
except AuthError as exc:
raise GitAuthError(str(exc)) from exc


def _merge_upstream(git: "Git"):
Expand Down
3 changes: 3 additions & 0 deletions dvc/scm.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def update_git(self, event: "GitProgressEvent") -> None:


def clone(url: str, to_path: str, **kwargs):
from scmrepo.exceptions import AuthError
from scmrepo.exceptions import CloneError as InternalCloneError

from dvc.repo.experiments.utils import fetch_all_exps
Expand All @@ -154,6 +155,8 @@ def clone(url: str, to_path: str, **kwargs):
if "shallow_branch" not in kwargs:
fetch_all_exps(git, url, progress=pbar.update_git)
return git
except AuthError as exc:
raise GitAuthError(str(exc)) from exc
except InternalCloneError as exc:
raise CloneError("SCM error") from exc

Expand Down
79 changes: 79 additions & 0 deletions test_auth_error_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
Test that git authentication errors are properly reported in DVC fetch/import operations.
"""

from unittest.mock import MagicMock, patch

import pytest
from scmrepo.exceptions import AuthError

from dvc.repo.open_repo import _pull, clone
from dvc.scm import GitAuthError


def test_pull_auth_error_propagation():
"""Test that _pull properly converts AuthError to GitAuthError."""
mock_git = MagicMock()
mock_git.fetch.side_effect = AuthError("Authentication failed")

with pytest.raises(GitAuthError) as exc_info:
_pull(mock_git)

assert "Authentication failed" in str(exc_info.value)
assert "See https://dvc.org/doc/user-guide/troubleshooting#git-auth" in str(
exc_info.value
)


def test_pull_fetch_all_exps_auth_error():
"""Test that _pull handles AuthError from fetch_all_exps."""
mock_git = MagicMock()
mock_git.fetch.return_value = None # fetch succeeds

with patch("dvc.repo.open_repo.fetch_all_exps") as mock_fetch_all_exps:
mock_fetch_all_exps.side_effect = AuthError(
"Authentication failed for experiments"
)

with pytest.raises(GitAuthError) as exc_info:
_pull(mock_git)

assert "Authentication failed for experiments" in str(exc_info.value)


def test_clone_auth_error_propagation():
"""Test that clone properly converts AuthError to GitAuthError."""
with patch("dvc.scm.Git.clone") as mock_git_clone:
mock_git_clone.side_effect = AuthError("Bad PAT token")

with pytest.raises(GitAuthError) as exc_info:
clone("https://github.com/test/repo.git", "/tmp/test")

assert "Bad PAT token" in str(exc_info.value)
assert "See https://dvc.org/doc/user-guide/troubleshooting#git-auth" in str(
exc_info.value
)


def test_clone_fetch_all_exps_auth_error():
"""Test that clone handles AuthError from fetch_all_exps."""
mock_git = MagicMock()

with patch("dvc.scm.Git.clone", return_value=mock_git):
with patch("dvc.repo.experiments.utils.fetch_all_exps") as mock_fetch_all_exps:
mock_fetch_all_exps.side_effect = AuthError("Experiments fetch auth failed")

with pytest.raises(GitAuthError) as exc_info:
clone("https://github.com/test/repo.git", "/tmp/test")

assert "Experiments fetch auth failed" in str(exc_info.value)


if __name__ == "__main__":
# Run basic tests
test_pull_auth_error_propagation()
test_pull_fetch_all_exps_auth_error()
test_clone_auth_error_propagation()
test_clone_fetch_all_exps_auth_error()
print("✅ All tests passed!")
Loading