Public Access
fix: correct version tags, changelog, and release script recovery
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:
committed by
emil
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
8810a0337b
commit
0c59166f4c
+25
-18
@@ -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
@@ -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,3 +1,3 @@
|
||||
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__version__ = "0.4.0"
|
||||
|
||||
+22
-4
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user