You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/08/11 17:38:20 UTC
(tooling-trusted-releases) branch sbp updated: Authenticate the SVN export and list reads
This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/sbp by this push:
new 4c91cafb Authenticate the SVN export and list reads
4c91cafb is described below
commit 4c91cafbeb5e8661ae3a38dcedd5a11a9088d767
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Tue Aug 11 18:37:50 2026 +0100
Authenticate the SVN export and list reads
---
atr/svn/__init__.py | 16 ++++++++++--
tests/unit/test_svn_export.py | 61 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 74 insertions(+), 3 deletions(-)
diff --git a/atr/svn/__init__.py b/atr/svn/__init__.py
index 34fcec64..f9ea542f 100644
--- a/atr/svn/__init__.py
+++ b/atr/svn/__init__.py
@@ -181,14 +181,16 @@ async def export(
destination: pathlib.Path,
timeout_seconds: float = EXPORT_TIMEOUT_SECONDS,
) -> None:
+ stdin_bytes = _authentication(url)
arguments = ["export", "--non-interactive", "--ignore-externals", "--ignore-keywords"]
if revision is not None:
arguments.extend(["-r", str(revision)])
pegged_url = f"{url}@{revision}"
else:
pegged_url = f"{url}@"
+ arguments.extend(["--username", ASF_TOOL, "--password-from-stdin"])
arguments.extend(["--", pegged_url, str(destination)])
- await run_command("svn", *arguments, timeout_seconds=timeout_seconds)
+ await run_command("svn", *arguments, timeout_seconds=timeout_seconds, stdin_bytes=stdin_bytes)
async def get_diff(path: pathlib.Path, revision: int) -> str:
@@ -245,7 +247,17 @@ async def info_authenticated(url: str) -> str:
async def list_files(url: str) -> list[str]:
"""List every file below a URL, as paths relative to it. Directories are left out."""
- output = await _run_svn_command("list", url, "--recursive", timeout_seconds=LIST_TIMEOUT_SECONDS)
+ output = await _run_svn_command(
+ "list",
+ url,
+ "--recursive",
+ "--username",
+ ASF_TOOL,
+ "--password-from-stdin",
+ "--non-interactive",
+ timeout_seconds=LIST_TIMEOUT_SECONDS,
+ stdin_bytes=_authentication(url),
+ )
return [line for line in output.splitlines() if line and (not line.endswith("/"))]
diff --git a/tests/unit/test_svn_export.py b/tests/unit/test_svn_export.py
index 1622f605..439fa6a7 100644
--- a/tests/unit/test_svn_export.py
+++ b/tests/unit/test_svn_export.py
@@ -21,6 +21,7 @@ import unittest.mock as mock
import pytest
+import atr.config as config
import atr.svn as svn
requires_svn = pytest.mark.skipif(
@@ -30,8 +31,10 @@ requires_svn = pytest.mark.skipif(
@requires_svn
-async def test_export_pinned_revision_of_deleted_path(tmp_path: pathlib.Path) -> None:
+async def test_export_pinned_revision_of_deleted_path(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(config.get(), "SVN_TOKEN", "dummy", raising=False)
repository = tmp_path / "repository"
+ monkeypatch.setattr(config.get(), "SVN_PUBLISH_URL", f"file://{repository}", raising=False)
await svn.run_command("svnadmin", "create", str(repository))
url = f"file://{repository}/release"
source = tmp_path / "source"
@@ -47,6 +50,8 @@ async def test_export_pinned_revision_of_deleted_path(tmp_path: pathlib.Path) ->
async def test_export_pins_revision(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(config.get(), "SVN_TOKEN", "dummy", raising=False)
+ monkeypatch.setattr(config.get(), "SVN_PUBLISH_URL", "file:///repo", raising=False)
run_command = mock.AsyncMock(return_value="")
monkeypatch.setattr(svn, "run_command", run_command)
destination = tmp_path / "dest"
@@ -61,14 +66,31 @@ async def test_export_pins_revision(tmp_path: pathlib.Path, monkeypatch: pytest.
"--ignore-keywords",
"-r",
"42",
+ "--username",
+ svn.ASF_TOOL,
+ "--password-from-stdin",
"--",
"file:///repo/example@42",
str(destination),
timeout_seconds=svn.EXPORT_TIMEOUT_SECONDS,
+ stdin_bytes=b"dummy",
)
+async def test_export_requires_token(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(config.get(), "SVN_TOKEN", None, raising=False)
+ run_command = mock.AsyncMock(return_value="")
+ monkeypatch.setattr(svn, "run_command", run_command)
+
+ with pytest.raises(ValueError, match="SVN_TOKEN"):
+ await svn.export("file:///repo/example", 1, tmp_path / "dest")
+
+ run_command.assert_not_awaited()
+
+
async def test_export_without_revision(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(config.get(), "SVN_TOKEN", "dummy", raising=False)
+ monkeypatch.setattr(config.get(), "SVN_PUBLISH_URL", "file:///repo", raising=False)
run_command = mock.AsyncMock(return_value="")
monkeypatch.setattr(svn, "run_command", run_command)
destination = tmp_path / "dest"
@@ -81,8 +103,45 @@ async def test_export_without_revision(tmp_path: pathlib.Path, monkeypatch: pyte
"--non-interactive",
"--ignore-externals",
"--ignore-keywords",
+ "--username",
+ svn.ASF_TOOL,
+ "--password-from-stdin",
"--",
"file:///repo/example@",
str(destination),
timeout_seconds=10.0,
+ stdin_bytes=b"dummy",
)
+
+
+async def test_list_files_authenticates(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(config.get(), "SVN_TOKEN", "dummy", raising=False)
+ monkeypatch.setattr(config.get(), "SVN_PUBLISH_URL", "file:///repo", raising=False)
+ run_command = mock.AsyncMock(return_value="a.tar.gz\nsub/\nsub/b.tar.gz\n")
+ monkeypatch.setattr(svn, "run_command", run_command)
+
+ assert await svn.list_files("file:///repo/example") == ["a.tar.gz", "sub/b.tar.gz"]
+
+ run_command.assert_awaited_once_with(
+ "svn",
+ "list",
+ "--recursive",
+ "--username",
+ svn.ASF_TOOL,
+ "--password-from-stdin",
+ "--non-interactive",
+ "file:///repo/example",
+ timeout_seconds=svn.LIST_TIMEOUT_SECONDS,
+ stdin_bytes=b"dummy",
+ )
+
+
+async def test_list_files_requires_token(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(config.get(), "SVN_TOKEN", None, raising=False)
+ run_command = mock.AsyncMock(return_value="")
+ monkeypatch.setattr(svn, "run_command", run_command)
+
+ with pytest.raises(ValueError, match="SVN_TOKEN"):
+ await svn.list_files("file:///repo/example")
+
+ run_command.assert_not_awaited()
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org