Compare commits

..
4 Commits
Author SHA1 Message Date
devx-ci-bot 40a94df029 release: v0.35.7 [skip ci] 2026-07-06 13:22:27 +00:00
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> f50c4c1e00 DEVX-118: fix: use Gitea wiki dash-marker filename convention
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 11s
Post-merge / vikunja (push) Successful in 24s
Post-merge / configure-repo (push) Successful in 16s
Post-merge / sync-wiki (push) Successful in 35s
Post-merge / release (push) Successful in 44s
Post-merge / publish (push) Successful in 22s
Post-merge / badges (push) Failing after 30s
Gitea appends a ".-" suffix before ".md" for wiki page titles that
contain dashes, to distinguish literal dashes from space-to-dash
conversions. For example, "Getting-Started" becomes
"Getting-Started.-.md", while "Architecture" becomes "Architecture.md".

Previously the code wrote "Getting-Started.md" which Gitea couldn't
recognize as a valid wiki page, causing verification to fail with
"page not found" for 15 of 21 pages.

Also force-push to handle concurrent CI runs that may have pushed to
the wiki repo between our clone and push.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-06 15:21:22 +02:00
devx-ci-bot bbb264efc9 release: v0.35.6 [skip ci] 2026-07-06 13:00:59 +00:00
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> c97b249935 DEVX-118: fix: add delay before wiki verification to avoid race condition
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 9s
Post-merge / configure-repo (push) Successful in 15s
Post-merge / vikunja (push) Successful in 20s
Post-merge / sync-wiki (push) Successful in 29s
Post-merge / release (push) Successful in 39s
Post-merge / publish (push) Successful in 25s
Post-merge / badges (push) Failing after 38s
Gitea needs a few seconds to process pushed wiki commits before a
re-clone will see them. Add a 5s sleep after a successful push before
verification re-clones the wiki.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-06 15:00:02 +02:00
7 changed files with 80 additions and 18 deletions
+12
View File
@@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.35.7] - 2026-07-06
### Bug Fixes
- Use Gitea wiki dash-marker filename convention
## [0.35.6] - 2026-07-06
### Bug Fixes
- Add delay before wiki verification to avoid race condition
## [0.35.5] - 2026-07-06 ## [0.35.5] - 2026-07-06
### Bug Fixes ### Bug Fixes
+3 -3
View File
@@ -87,7 +87,7 @@ extra index and list devx in your dependencies:
```toml ```toml
[project] [project]
dependencies = [ dependencies = [
"devx>=0.35.5", "devx>=0.35.7",
] ]
[tool.pip] [tool.pip]
@@ -101,8 +101,8 @@ pip install -e .
``` ```
> **Note:** If your project requires a specific devx version, pin it in > **Note:** If your project requires a specific devx version, pin it in
> `dependencies` (for example, `"devx==0.35.5"`) or use a version constraint > `dependencies` (for example, `"devx==0.35.7"`) or use a version constraint
> (for example, `"devx>=0.35.5,<0.36"`). > (for example, `"devx>=0.35.7,<0.36"`).
### Optional extras ### Optional extras
+2 -2
View File
@@ -74,14 +74,14 @@ Add devx to your `pyproject.toml` dependencies and configure the registry:
```toml ```toml
[project] [project]
dependencies = [ dependencies = [
"devx>=0.35.5", "devx>=0.35.7",
] ]
[tool.pip] [tool.pip]
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple" extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
``` ```
Pin a specific version if needed: `"devx==0.35.5"` or `"devx>=0.35.5,<0.36"`. Pin a specific version if needed: `"devx==0.35.7"` or `"devx>=0.35.7,<0.36"`.
### Optional extras ### Optional extras
+2 -2
View File
@@ -48,12 +48,12 @@ Add devx to your `pyproject.toml`:
```toml ```toml
[project] [project]
dependencies = [ dependencies = [
"devx>=0.35.5", "devx>=0.35.7",
] ]
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"devx>=0.35.5", "devx>=0.35.7",
] ]
``` ```
+1 -1
View File
@@ -1,3 +1,3 @@
"""devx — reusable development and CI/CD tools for oblachno-oss projects.""" """devx — reusable development and CI/CD tools for oblachno-oss projects."""
__version__ = "0.35.5" __version__ = "0.35.7"
+30 -10
View File
@@ -31,8 +31,9 @@ import os
import re import re
import subprocess # nosec B404 import subprocess # nosec B404
import tempfile import tempfile
import time
from pathlib import Path from pathlib import Path
from urllib.parse import urlparse from urllib.parse import quote, urlparse
import click import click
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType] from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
@@ -49,6 +50,23 @@ MAPPING_FILE = DOCS_DIR / "mapping.json"
_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)") _LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
def wiki_filename(page_title: str) -> str:
"""Convert a wiki page title to its Gitea wiki filename.
Gitea uses a "dash marker" (``.-``) suffix to distinguish literal dashes
from space-to-dash conversions. See Gitea's ``services/wiki/wiki_path.go``.
- "Architecture" (no dashes) → ``Architecture.md``
- "Getting-Started" (has dashes) → ``Getting-Started.-.md``
- "Home" (no dashes) → ``Home.md``
"""
name = page_title.replace(" ", "-")
if "-" in name:
name += ".-"
name += ".md"
return quote(name, safe="")
def load_mapping() -> dict[str, str]: def load_mapping() -> dict[str, str]:
"""Load the file-to-wiki-page mapping from mapping.json.""" """Load the file-to-wiki-page mapping from mapping.json."""
with open(MAPPING_FILE, encoding="utf-8") as f: with open(MAPPING_FILE, encoding="utf-8") as f:
@@ -170,16 +188,15 @@ def sync_files(
# Transform links # Transform links
transformed = transform_links(content) transformed = transform_links(content)
# Wiki filename: use the page title with spaces → underscores # Wiki filename: Gitea uses a dash-marker convention for titles with dashes
# Gitea wiki uses the page title as filename (spaces become dashes) fname = wiki_filename(page_title)
wiki_filename = page_title.replace(" ", "-") + ".md" expected_files.add(fname)
expected_files.add(wiki_filename)
if not dry_run: if not dry_run:
dest = wiki_dir / wiki_filename dest = wiki_dir / fname
dest.write_text(transformed, encoding="utf-8") dest.write_text(transformed, encoding="utf-8")
synced += 1 synced += 1
click.echo(_(" Synced: {title}{file}", title=page_title, file=wiki_filename)) click.echo(_(" Synced: {title}{file}", title=page_title, file=fname))
# Prune stale pages (in wiki but not in mapping) # Prune stale pages (in wiki but not in mapping)
pruned = 0 pruned = 0
@@ -234,7 +251,7 @@ def commit_and_push(wiki_dir: Path, wiki_url: str, dry_run: bool) -> bool:
# Push # Push
result = subprocess.run( # nosec result = subprocess.run( # nosec
["git", "push", wiki_url, "HEAD:master"], ["git", "push", "--force", wiki_url, "HEAD:master"],
cwd=wiki_dir, cwd=wiki_dir,
capture_output=True, capture_output=True,
text=True, text=True,
@@ -309,6 +326,9 @@ def main(dry_run: bool, repo: str | None, verify: bool) -> None:
# Verification # Verification
if verify and not dry_run: if verify and not dry_run:
if pushed:
click.echo(_("Waiting 5s for Gitea to process pushed commits..."))
time.sleep(5)
click.echo(_("\nVerifying wiki pages...")) click.echo(_("\nVerifying wiki pages..."))
# Re-clone to verify # Re-clone to verify
verify_dir = Path(tmpdir) / "verify" verify_dir = Path(tmpdir) / "verify"
@@ -317,8 +337,8 @@ def main(dry_run: bool, repo: str | None, verify: bool) -> None:
raise click.ClickException(_("Wiki verification failed — could not clone wiki")) raise click.ClickException(_("Wiki verification failed — could not clone wiki"))
failures = 0 failures = 0
for _file_path, page_title in sorted(mapping.items()): for _file_path, page_title in sorted(mapping.items()):
wiki_filename = page_title.replace(" ", "-") + ".md" fname = wiki_filename(page_title)
if (verify_dir / wiki_filename).exists(): if (verify_dir / fname).exists():
click.echo(_(" OK: {title}", title=page_title)) click.echo(_(" OK: {title}", title=page_title))
else: else:
click.echo(_(" FAIL: {title} — page not found in wiki!", title=page_title)) click.echo(_(" FAIL: {title} — page not found in wiki!", title=page_title))
+30
View File
@@ -19,6 +19,7 @@ from devx.ci.sync_wiki import (
main, main,
sync_files, sync_files,
transform_links, transform_links,
wiki_filename,
) )
@@ -96,6 +97,29 @@ class TestGetWikiCloneUrl:
assert "owner/repo.wiki.git" in url assert "owner/repo.wiki.git" in url
class TestWikiFilename:
def test_no_dashes(self) -> None:
assert wiki_filename("Home") == "Home.md"
def test_single_word(self) -> None:
assert wiki_filename("Architecture") == "Architecture.md"
def test_with_dashes_adds_marker(self) -> None:
assert wiki_filename("Getting-Started") == "Getting-Started.-.md"
def test_spaces_become_dashes_with_marker(self) -> None:
# "Getting Started" → "Getting-Started" (has dash) → marker added
assert wiki_filename("Getting Started") == "Getting-Started.-.md"
def test_spaces_no_dashes_no_marker(self) -> None:
# "Foo Bar" → "Foo-Bar" (has dash) → marker added
assert wiki_filename("Foo Bar") == "Foo-Bar.-.md"
def test_single_word_with_spaces_no_dash(self) -> None:
# No dash at all after conversion → no marker
assert wiki_filename("HelloWorld") == "HelloWorld.md"
class TestCloneWiki: class TestCloneWiki:
@patch("devx.ci.sync_wiki.subprocess.run") @patch("devx.ci.sync_wiki.subprocess.run")
def test_clone_success(self, mock_run: MagicMock, tmp_path: Path) -> None: def test_clone_success(self, mock_run: MagicMock, tmp_path: Path) -> None:
@@ -323,6 +347,7 @@ class TestMain:
assert result.exit_code == 0 assert result.exit_code == 0
mock_init.assert_called_once() mock_init.assert_called_once()
@patch("devx.ci.sync_wiki.time.sleep")
@patch("devx.ci.sync_wiki.clone_wiki") @patch("devx.ci.sync_wiki.clone_wiki")
@patch("devx.ci.sync_wiki.commit_and_push", return_value=True) @patch("devx.ci.sync_wiki.commit_and_push", return_value=True)
@patch("devx.ci.sync_wiki.sync_files", return_value=(1, 0)) @patch("devx.ci.sync_wiki.sync_files", return_value=(1, 0))
@@ -331,6 +356,7 @@ class TestMain:
mock_sync: MagicMock, mock_sync: MagicMock,
mock_push: MagicMock, mock_push: MagicMock,
mock_clone: MagicMock, mock_clone: MagicMock,
mock_sleep: MagicMock,
tmp_path: Path, tmp_path: Path,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
@@ -380,6 +406,7 @@ class TestMain:
assert result.exit_code == 0 assert result.exit_code == 0
assert "No push needed" in result.output assert "No push needed" in result.output
@patch("devx.ci.sync_wiki.time.sleep")
@patch("devx.ci.sync_wiki.clone_wiki", side_effect=[True, False]) @patch("devx.ci.sync_wiki.clone_wiki", side_effect=[True, False])
@patch("devx.ci.sync_wiki.commit_and_push", return_value=True) @patch("devx.ci.sync_wiki.commit_and_push", return_value=True)
@patch("devx.ci.sync_wiki.sync_files", return_value=(1, 0)) @patch("devx.ci.sync_wiki.sync_files", return_value=(1, 0))
@@ -388,6 +415,7 @@ class TestMain:
mock_sync: MagicMock, mock_sync: MagicMock,
mock_push: MagicMock, mock_push: MagicMock,
mock_clone: MagicMock, mock_clone: MagicMock,
mock_sleep: MagicMock,
tmp_path: Path, tmp_path: Path,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
@@ -404,6 +432,7 @@ class TestMain:
assert result.exit_code != 0 assert result.exit_code != 0
assert "could not clone" in result.output assert "could not clone" in result.output
@patch("devx.ci.sync_wiki.time.sleep")
@patch("devx.ci.sync_wiki.clone_wiki") @patch("devx.ci.sync_wiki.clone_wiki")
@patch("devx.ci.sync_wiki.commit_and_push", return_value=True) @patch("devx.ci.sync_wiki.commit_and_push", return_value=True)
@patch("devx.ci.sync_wiki.sync_files", return_value=(1, 0)) @patch("devx.ci.sync_wiki.sync_files", return_value=(1, 0))
@@ -412,6 +441,7 @@ class TestMain:
mock_sync: MagicMock, mock_sync: MagicMock,
mock_push: MagicMock, mock_push: MagicMock,
mock_clone: MagicMock, mock_clone: MagicMock,
mock_sleep: MagicMock,
tmp_path: Path, tmp_path: Path,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None: