fix: correct version tags, changelog, and release script recovery
CI / detect-changes (pull_request) Successful in 58s
CI / pr-review (pull_request) Successful in 1m2s
CI / quality (pull_request) Failing after 1m32s
CI / auto-merge (pull_request) Has been skipped
CI / release-dry-run (pull_request) Has been skipped

Three issues fixed:

1. **Version tags**: No tags existed despite 6 release commits. Created
   proper tags (v0.1.0 through v0.4.0) at the correct release commits.
   Without tags, git-cliff always returned the initial version (0.1.0),
   so every release commit said "v0.1.0" and the version never advanced.

2. **cliff.toml preprocessor bug**: The pattern `^DEVX-\d+\s+` didn't
   match the actual commit format `DEVX-N: feat: ...` (missing colon).
   Fixed to `^DEVX-\d+:\s+`. Also added `refactor_always_bump_patch`
   so structural refactors that touch src/ or pyproject.toml trigger
   a patch release.

3. **Release script recovery**: If HEAD is a release commit but the tag
   doesn't exist (e.g., tag push failed in CI), the script now creates
   and pushes the tag instead of skipping. This recovers from the common
   failure mode where the release commit was pushed but the tag was not.

Also fixed __init__.py version to 0.4.0 and regenerated CHANGELOG.md
with correct version sections.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devx-ci-bot
2026-06-22 22:12:29 +02:00
committed by emil
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 8810a0337b
commit 0c59166f4c
6 changed files with 81 additions and 29 deletions
+1 -1
View File
@@ -1 +1 @@
DEVX-5
DEVX-6
+25 -18
View File
@@ -2,32 +2,39 @@
All notable changes to this project will be documented in this file.
## [0.1.0] - 2026-06-22
## [0.4.0] - 2026-06-22
## [0.1.0] - 2026-06-22
### Features
## [0.1.0] - 2026-06-22
- Add DEFAULT_INFRASTRUCTURE and configurable task prefix
## [0.3.0] - 2026-06-22
## [0.1.0] - 2026-06-22
### Features
- Add --no-ansible-collections option to setup tool
## [0.2.0] - 2026-06-22
### Features
- Pluggable change classification framework
## [0.1.2] - 2026-06-22
### Bug Fixes
- Make sync-wiki and vikunja depend on release
## [0.1.1] - 2026-06-22
### Bug Fixes
- Disable push whitelist, allow direct pushes to master
## [0.1.0] - 2026-06-22
### Features
- Extract reusable dev/CI tools from GRM into devx package
## [unreleased]
### Features
- Extract reusable development and CI/CD tools from GRM into a standalone Python package
- Port core modules: config, exceptions, i18n, api_clients, gitea_cli
- Port 14 CI scripts: auto_merge, check_translations, classify_changes, detect_release_commit, discover_runners, doc_coverage, notify_failure, post_merge, pr_review, publish, push_badges, release, sync_wiki, validate_commit_msg
- Port 6 dev tools: check_test_speed, generate_badges, install_checkmake, install_tools, setup, configure_repo
- Port 5 molecule tools as optional extra: platforms, distribute_molecule, discover_runners, molecule_ci_guard, molecule_all
- Add CLI entry point with subcommands: devx ci, devx tools, devx molecule
- Add Gitea PyPI registry publishing support in publish.py
- Add configurable workflow-only patterns in classify_changes.py
- Add configurable version file path in release.py
- Replicate GRM's automated workflow: CI, auto-merge, post-merge, release, badges, wiki sync, Vikunja
### Bug Fixes
- Use python3 and venv python in workflows and Makefile
- Fix post-merge job failures (configure-repo, badges, notify-failure)
- Allow release bot to push to protected master
+5 -2
View File
@@ -39,8 +39,8 @@ sort_commits = "oldest"
recurse_submodules = false
commit_preprocessors = [
# Strip DEVX-N task ID prefix from merge commits so git-cliff sees conventional commits
{ pattern = "^DEVX-\\d+\\s+", replace = "" },
# Strip DEVX-N: task ID prefix from squash-merge commits so git-cliff sees conventional commits
{ pattern = "^DEVX-\\d+:\\s+", replace = "" },
]
commit_parsers = [
@@ -66,3 +66,6 @@ commit_parsers = [
features_always_bump_minor = true
breaking_always_bump_major = false
initial_tag = "0.1.0"
# Refactor commits bump patch — structural changes to src/ or pyproject.toml
# affect users even though no new feature was added.
refactor_always_bump_patch = true
+1 -1
View File
@@ -1,3 +1,3 @@
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
__version__ = "0.1.0"
__version__ = "0.4.0"
+22 -4
View File
@@ -271,16 +271,34 @@ def main(dry_run: bool, skip_tests: bool) -> None:
)
)
# Release lock: if HEAD is already a release commit, another release
# run is in progress (or already completed). Skip to prevent duplicate tags.
# Release lock: if HEAD is already a release commit, check if the tag
# exists. If the tag is missing (e.g., tag push failed in a previous run),
# create and push it instead of skipping — this recovers from the
# common failure mode where the commit was pushed but the tag was not.
head_msg = run_cmd(["git", "log", "-1", "--pretty=%s"]).stdout.strip()
if re.match(r"^release: v\d+\.\d+\.\d+", head_msg):
release_match = re.match(r"^release: v(\d+\.\d+\.\d+)", head_msg)
if release_match:
release_version = release_match.group(1)
release_tag = f"v{release_version}"
if tag_exists(release_tag):
click.echo(
_(
"HEAD is already a release commit ('{msg}') and tag {tag} exists. Skipping.",
msg=head_msg,
tag=release_tag,
)
)
return
# Tag is missing — recover by creating and pushing it
click.echo(
_(
"HEAD is already a release commit ('{msg}'). Another release may have just completed. Skipping.",
"HEAD is a release commit ('{msg}') but tag {tag} is missing. Recovering by creating tag.",
msg=head_msg,
tag=release_tag,
)
)
changelog = get_changelog(release_version)
create_and_push_tag(release_version, changelog, dry_run)
return
# Check if any user-facing files changed since the last tag.
+27 -3
View File
@@ -318,12 +318,15 @@ class TestMain:
@patch.dict("os.environ", {})
@patch("devx.ci.release.has_user_facing_changes", return_value=True)
@patch("devx.ci.release.run_cmd")
def test_release_lock_skips_when_head_is_release_commit(self, mock_run_cmd: MagicMock, mock_uf: MagicMock) -> None:
"""If HEAD is already a release commit, should skip to prevent duplicate releases."""
# First call: git rev-parse (master), second: git log -1 (release commit)
def test_release_lock_skips_when_head_is_release_commit_and_tag_exists(
self, mock_run_cmd: MagicMock, mock_uf: MagicMock
) -> None:
"""If HEAD is a release commit and the tag exists, skip."""
# git rev-parse, git log -1, git tag -l (tag exists)
mock_run_cmd.side_effect = [
MagicMock(returncode=0, stdout="master\n", stderr=""),
MagicMock(returncode=0, stdout="release: v0.5.0\n", stderr=""),
MagicMock(returncode=0, stdout="v0.5.0\n", stderr=""), # tag -l finds tag
]
runner = CliRunner()
result = runner.invoke(main, [])
@@ -331,6 +334,27 @@ class TestMain:
assert "already a release commit" in result.output
assert "Skipping" in result.output
@patch.dict("os.environ", {})
@patch("devx.ci.release.get_changelog", return_value="## changelog")
@patch("devx.ci.release.create_and_push_tag", return_value=True)
@patch("devx.ci.release.run_cmd")
def test_release_lock_recovers_when_tag_missing(
self, mock_run_cmd: MagicMock, mock_create_tag: MagicMock, mock_changelog: MagicMock
) -> None:
"""If HEAD is a release commit but the tag is missing, create the tag."""
# git rev-parse, git log -1, git tag -l (tag NOT found)
mock_run_cmd.side_effect = [
MagicMock(returncode=0, stdout="master\n", stderr=""),
MagicMock(returncode=0, stdout="release: v0.5.0\n", stderr=""),
MagicMock(returncode=0, stdout="", stderr=""), # tag -l finds nothing
]
runner = CliRunner()
result = runner.invoke(main, [])
assert result.exit_code == 0
assert "tag v0.5.0 is missing" in result.output
assert "Recovering" in result.output
mock_create_tag.assert_called_once_with("0.5.0", "## changelog", False)
@patch.dict("os.environ", {})
@patch("devx.ci.release.has_user_facing_changes", return_value=True)
@patch("devx.ci.release.has_unreleased_changes", return_value=False)