diff --git a/.github/workflows/bumpversion.yml b/.github/workflows/bumpversion.yml index f4068daf5..aab3b0ab4 100644 --- a/.github/workflows/bumpversion.yml +++ b/.github/workflows/bumpversion.yml @@ -20,7 +20,7 @@ jobs: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.PRIVATE_KEY }} - name: Check out - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: token: ${{ steps.app-token.outputs.token }} fetch-depth: 0 diff --git a/.github/workflows/docspublish.yml b/.github/workflows/docspublish.yml index c572d2d3f..76cfc5975 100644 --- a/.github/workflows/docspublish.yml +++ b/.github/workflows/docspublish.yml @@ -11,7 +11,7 @@ jobs: if: ${{ github.repository == 'commitizen-tools/commitizen' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 @@ -25,7 +25,9 @@ jobs: run: | sudo apt update sudo apt install -y ffmpeg ttyd - go install github.com/charmbracelet/vhs@latest + # Pin VHS to a known version so gif rendering is deterministic across runs. + # Bump this version intentionally when you want to refresh all interactive screenshots. + go install github.com/charmbracelet/vhs@v0.11.0 - name: Install dependencies run: | uv --version @@ -33,6 +35,33 @@ jobs: - name: Update CLI screenshots run: | uv run --no-sync poe doc:screenshots + - name: Discard cli_interactive regeneration when render inputs are unchanged + # VHS gif output drifts by a few bytes between runs even when sources are + # identical, which used to produce a noisy stream of `docs(cli/screenshots)` + # commits. Keep regenerated gifs only when something that can actually + # change the rendered content has changed in this push (the tape sources, + # or any commitizen source code the tapes execute), or when manually + # dispatched. + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "Manual run; keeping any regenerated cli_interactive gifs." + exit 0 + fi + before="${{ github.event.before }}" + if [[ -z "$before" || "$before" == "0000000000000000000000000000000000000000" ]]; then + echo "No previous SHA available (first push or new branch); keeping any regenerated cli_interactive gifs." + exit 0 + fi + if ! git rev-parse --verify "$before^{commit}" >/dev/null 2>&1; then + echo "Previous SHA $before is unreachable; keeping any regenerated cli_interactive gifs." + exit 0 + fi + if git diff --quiet "$before" HEAD -- docs/images/'*.tape' docs/images/shared/ commitizen/; then + echo "Tape sources and commitizen/ unchanged since $before; discarding cli_interactive gif regeneration to avoid byte-level churn." + git checkout -- docs/images/cli_interactive/ + else + echo "Render inputs changed since $before (tapes or commitizen/); keeping regenerated cli_interactive gifs." + fi - name: Commit and push updated CLI screenshots run: | git config --global user.name "github-actions[bot]" @@ -52,7 +81,7 @@ jobs: runs-on: ubuntu-latest needs: update-cli-screenshots steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}" fetch-depth: 0 diff --git a/.github/workflows/homebrewpublish.yml b/.github/workflows/homebrewpublish.yml index 3a4d2cd3d..476a8c40e 100644 --- a/.github/workflows/homebrewpublish.yml +++ b/.github/workflows/homebrewpublish.yml @@ -12,7 +12,7 @@ jobs: if: ${{ github.repository == 'commitizen-tools/commitizen' && github.event.workflow_run.conclusion == 'success' }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up Python uses: astral-sh/setup-uv@v7 - name: Install dependencies diff --git a/.github/workflows/label_pr.yml b/.github/workflows/label_pr.yml index 168b322ff..f01afa765 100644 --- a/.github/workflows/label_pr.yml +++ b/.github/workflows/label_pr.yml @@ -9,7 +9,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: sparse-checkout: | .github/labeler.yml diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 28345c464..b541439f0 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -13,7 +13,7 @@ jobs: permissions: issues: write # required for Broken Links Report steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Link Checker id: lychee diff --git a/.github/workflows/pr-bump-preview.yml b/.github/workflows/pr-bump-preview.yml index 1eab96774..9ed2a335d 100644 --- a/.github/workflows/pr-bump-preview.yml +++ b/.github/workflows/pr-bump-preview.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out PR head - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -84,11 +84,27 @@ jobs: esac } > comment.md + # Look up an existing preview comment so the next step edits it in + # place instead of appending a new one on every run. + # `peter-evans/create-or-update-comment` does not search by body + # itself -- it only creates new comments, or updates a specific + # `comment-id`. + - name: Find existing preview comment + id: find-comment + uses: peter-evans/find-comment@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "" + - name: Post or update PR comment uses: peter-evans/create-or-update-comment@v5 with: token: ${{ secrets.GITHUB_TOKEN }} + # When empty (no prior comment found) a new one is created; + # otherwise the existing comment is edited in place. + comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body-path: comment.md - body-includes: "" edit-mode: replace diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 0667e255c..5b01beb8e 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -13,7 +13,7 @@ jobs: outputs: relevant: ${{ steps.filter.outputs.relevant }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - id: filter @@ -36,7 +36,7 @@ jobs: if: ${{ needs.detect_changes.outputs.relevant != 'true' }} run: | echo "No relevant file changes; skipping tests and linters." - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 if: ${{ needs.detect_changes.outputs.relevant == 'true' }} with: fetch-depth: 0 @@ -58,12 +58,12 @@ jobs: uv run --no-sync poe ci shell: bash - name: Upload coverage to Codecov - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 if: ${{ needs.detect_changes.outputs.relevant == 'true' }} with: token: ${{ secrets.CODECOV_TOKEN }} - name: Upload test results to Codecov - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 if: ${{ needs.detect_changes.outputs.relevant == 'true' && !cancelled() }} with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index 238bc4db4..0903f11f4 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -32,7 +32,7 @@ jobs: echo "::error::Dispatch ref '${TAG}' is not an existing tag" exit 1 fi - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', github.event.inputs.ref) || github.ref }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c9d1086f..7b9f6ee05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,7 +56,7 @@ repos: - tomli - repo: https://github.com/commitizen-tools/commitizen - rev: v4.16.3 # automatically updated by Commitizen + rev: v4.16.4 # automatically updated by Commitizen hooks: - id: commitizen - id: commitizen-branch diff --git a/commitizen/__version__.py b/commitizen/__version__.py index b2da05759..d3c4e81b6 100644 --- a/commitizen/__version__.py +++ b/commitizen/__version__.py @@ -1 +1 @@ -__version__ = "4.16.3" +__version__ = "4.16.4" diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py index 235d4a110..5152bc74c 100644 --- a/commitizen/providers/cargo_provider.py +++ b/commitizen/providers/cargo_provider.py @@ -82,15 +82,15 @@ def set_lock_version(self, version: str) -> None: ).get("package", {}) if TYPE_CHECKING: assert isinstance(package_content, dict) - try: - version_workspace = package_content["version"]["workspace"] - if version_workspace is True: - package_name = package_content["name"] - if TYPE_CHECKING: - assert isinstance(package_name, str) - members_inheriting.append(package_name) - except NonExistentKey: - pass + version_field = package_content.get("version") + if ( + isinstance(version_field, dict) + and version_field.get("workspace") is True + ): + package_name = package_content["name"] + if TYPE_CHECKING: + assert isinstance(package_name, str) + members_inheriting.append(package_name) for i, package in enumerate(packages): if package["name"] in members_inheriting: diff --git a/docs/images/bump.tape b/docs/images/bump.tape index ef4a427c9..03b81fdb7 100644 --- a/docs/images/bump.tape +++ b/docs/images/bump.tape @@ -57,12 +57,17 @@ Type "cz bump" Sleep 500ms Enter -# Wait for the "Is this the first tag created?" prompt -Sleep 2s +# Wait for the "Is this the first tag created?" prompt to render +Wait+Screen /Is this the first tag/ + +# Small visual pause so the prompt is readable before the answer +Sleep 500ms # Answer Yes to "Is this the first tag created?" (default is Yes, just press Enter) Enter -Sleep 3s + +# Wait for cz bump to finish (the "Done!" success line is printed last) +Wait+Screen /Done!/ # Step 3: Show new version after bump Type "cz version --project" diff --git a/docs/images/cli_interactive/bump.gif b/docs/images/cli_interactive/bump.gif index fdf09310d..3d482720f 100644 Binary files a/docs/images/cli_interactive/bump.gif and b/docs/images/cli_interactive/bump.gif differ diff --git a/docs/images/cli_interactive/commit.gif b/docs/images/cli_interactive/commit.gif index 35392de01..b50bbfdeb 100644 Binary files a/docs/images/cli_interactive/commit.gif and b/docs/images/cli_interactive/commit.gif differ diff --git a/docs/images/cli_interactive/init.gif b/docs/images/cli_interactive/init.gif index 96504321b..faa852940 100644 Binary files a/docs/images/cli_interactive/init.gif and b/docs/images/cli_interactive/init.gif differ diff --git a/docs/images/cli_interactive/shortcut_custom.gif b/docs/images/cli_interactive/shortcut_custom.gif index 69562f396..27e9587e2 100644 Binary files a/docs/images/cli_interactive/shortcut_custom.gif and b/docs/images/cli_interactive/shortcut_custom.gif differ diff --git a/docs/images/cli_interactive/shortcut_default.gif b/docs/images/cli_interactive/shortcut_default.gif index 359943130..9f54b17e7 100644 Binary files a/docs/images/cli_interactive/shortcut_default.gif and b/docs/images/cli_interactive/shortcut_default.gif differ diff --git a/docs/images/commit.tape b/docs/images/commit.tape index e93a38ae6..e60b27ae3 100644 --- a/docs/images/commit.tape +++ b/docs/images/commit.tape @@ -29,38 +29,41 @@ Type "cz commit" Sleep 500ms Enter -# Wait for first prompt to appear -Sleep 1s - -# Question 1: Select the type of change (move down to "feat") +# Q1: prefix -- select the type of change (move down to "feat") +Wait+Screen /Select the type of change/ +Sleep 500ms Down Sleep 500ms Enter -Sleep 1s -# Question 2: Scope (optional, skip) +# Q2: scope (optional, skip) +Wait+Screen /What is the scope/ +Sleep 500ms Enter -Sleep 1s -# Question 3: Subject +# Q3: subject +Wait+Screen /imperative summary/ +Sleep 500ms Type "awesome new feature" Sleep 500ms Enter -Sleep 1s -# Question 4: Is this a BREAKING CHANGE? (No) +# Q4: body (optional, skip) +Wait+Screen /additional contextual information/ +Sleep 500ms Enter -Sleep 1s -# Question 5: Body (optional, skip) +# Q5: is_breaking_change (No) +Wait+Screen /BREAKING CHANGE\?/ +Sleep 500ms Enter -Sleep 1s -# Question 6: Footer (optional, skip) +# Q6: footer (optional, skip) +Wait+Screen /Footer\. Information/ +Sleep 500ms Enter -Sleep 1s # Wait for commit success message -Sleep 1s +Sleep 2s Source shared/cleanup.tape diff --git a/docs/images/init.tape b/docs/images/init.tape index aad833530..6edbb9e6e 100644 --- a/docs/images/init.tape +++ b/docs/images/init.tape @@ -15,51 +15,55 @@ Type "cz init" Sleep 500ms Enter -# Wait for welcome message and first prompt -Sleep 500ms -Sleep 1s - -# Question 1: Please choose a supported config file +# Q1: Please choose a supported config file # Default is .cz.toml, just press Enter +Wait+Screen /Please choose a supported config/ +Sleep 500ms Enter -Sleep 1s -# Question 2: Please choose a cz (commit rule) +# Q2: Please choose a cz (commit rule) # Default is cz_conventional_commits, just press Enter +Wait+Screen /Please choose a cz/ +Sleep 500ms Enter -Sleep 1s -# Question 3: Choose the source of the version -# Default is "commitizen: Fetch and set version in commitizen config, just press Enter" +# Q3: Choose the source of the version +# Default is "commitizen", just press Enter +Wait+Screen /Choose the source of the version/ +Sleep 500ms Enter -Sleep 1s -# Question 4: Choose version scheme +# Q4: Choose version scheme # Default is semver, just press Enter +Wait+Screen /Choose version scheme/ +Sleep 500ms Enter -Sleep 1s -# Question 5: Please enter the correct version format +# Q5: Please enter the correct version format # Default is "$version", just press Enter +Wait+Screen /Please enter the correct version format/ +Sleep 500ms Enter -Sleep 1s -# Question 6: Create changelog automatically on bump +# Q6: Create changelog automatically on bump # Default is Yes, just press Enter +Wait+Screen /Create changelog automatically on bump/ +Sleep 500ms Enter -Sleep 1s -# Question 7: Keep major version zero (0.x) during breaking changes +# Q7: Keep major version zero (0.x) during breaking changes # Default is Yes, just press Enter +Wait+Screen /Keep major version zero/ +Sleep 500ms Enter -Sleep 1s -# Question 8: What types of pre-commit hook you want to install? -# Default is [commit-msg], just press Enter to accept +# Q8: What types of pre-commit hook you want to install? +# Default is [], just press Enter to accept +Wait+Screen /types of pre-commit hook/ +Sleep 500ms Enter -Sleep 1s # Wait for completion message -Sleep 1s +Sleep 2s Source shared/cleanup.tape diff --git a/docs/images/shortcut_custom.tape b/docs/images/shortcut_custom.tape index 54362b4bb..710ebbec7 100644 --- a/docs/images/shortcut_custom.tape +++ b/docs/images/shortcut_custom.tape @@ -83,23 +83,21 @@ Type "cz commit" Sleep 500ms Enter -# Wait for first prompt to appear -Sleep 2s - -# Question 1: Select the type of change (press d to "docs") +# Q1: prefix -- press "d" shortcut for "docs" +Wait+Screen /Select the type of change/ Sleep 1s Type "d" Sleep 2s Enter -Sleep 1s -# Question 2: Commit body +# Q2: Commit body +Wait+Screen /Commit body/ +Sleep 500ms Type "demo with custom keys" Sleep 500ms Enter -Sleep 500ms # Wait for commit success message -Sleep 1s +Sleep 2s Source shared/cleanup.tape diff --git a/docs/images/shortcut_default.tape b/docs/images/shortcut_default.tape index c7d12111c..244f66e5b 100644 --- a/docs/images/shortcut_default.tape +++ b/docs/images/shortcut_default.tape @@ -42,39 +42,41 @@ Type "cz commit" Sleep 500ms Enter -# Wait for first prompt to appear -Sleep 2s - -# Question 1: Select the type of change (press d to "docs") +# Q1: prefix -- press "d" shortcut for "docs" +Wait+Screen /Select the type of change/ Sleep 1s Type "d" Sleep 2s Enter -Sleep 1s -# Question 2: Scope (optional, skip) +# Q2: scope (optional, skip) +Wait+Screen /What is the scope/ +Sleep 500ms Enter -Sleep 1s -# Question 3: Subject +# Q3: subject +Wait+Screen /imperative summary/ +Sleep 500ms Type "demo with custom keys" Sleep 500ms Enter -Sleep 500ms -# Question 4: Is this a BREAKING CHANGE? (No) -Enter +# Q4: body (optional, skip) +Wait+Screen /additional contextual information/ Sleep 500ms - -# Question 5: Body (optional, skip) Enter -Sleep 500ms -# Question 6: Footer (optional, skip) +# Q5: is_breaking_change (No) +Wait+Screen /BREAKING CHANGE\?/ +Sleep 500ms Enter + +# Q6: footer (optional, skip) +Wait+Screen /Footer\. Information/ Sleep 500ms +Enter # Wait for commit success message -Sleep 1s +Sleep 2s Source shared/cleanup.tape diff --git a/docs/tutorials/github_actions.md b/docs/tutorials/github_actions.md index 7717bdbf5..f0b216072 100644 --- a/docs/tutorials/github_actions.md +++ b/docs/tutorials/github_actions.md @@ -205,12 +205,20 @@ jobs: ;; esac } > comment.md - - uses: peter-evans/create-or-update-comment@v4 + - name: Find existing preview comment + id: find-comment + uses: peter-evans/find-comment@v3 with: token: ${{ secrets.GITHUB_TOKEN }} issue-number: ${{ github.event.pull_request.number }} - body-path: comment.md + comment-author: "github-actions[bot]" body-includes: "" + - uses: peter-evans/create-or-update-comment@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: comment.md edit-mode: replace ``` @@ -235,10 +243,13 @@ jobs: `update_changelog_on_bump` is set in your config, also the changelog entries that would be produced). Exit code `21` (`NoneIncrementExit`) is treated as "no eligible bump" rather than a failure. -- **Sticky comment**: The hidden HTML marker `` - lets [`peter-evans/create-or-update-comment`](https://github.com/peter-evans/create-or-update-comment) - find and replace the previous preview on every push, instead of leaving a - growing trail of comments. +- **Sticky comment**: [`peter-evans/find-comment`](https://github.com/peter-evans/find-comment) + looks up an existing comment by the hidden HTML marker + `` and bot author, then + [`peter-evans/create-or-update-comment`](https://github.com/peter-evans/create-or-update-comment) + edits it in place (or creates a new one on the first run when the + marker is not yet present), instead of leaving a growing trail of + comments. [jinja]: https://github.com/commitizen-tools/commitizen/blob/master/commitizen/changelog.py diff --git a/pyproject.toml b/pyproject.toml index 3fcb69437..37c1ac045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "commitizen" -version = "4.16.3" +version = "4.16.4" description = "Python commitizen client tool" authors = [{ name = "Santiago Fraire", email = "santiwilly@gmail.com" }] maintainers = [ diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index 63f143b29..023e34b21 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -451,3 +451,80 @@ def test_cargo_provider_workspace_member_without_workspace_key( assert file.read_text() == dedent(expected_workspace_toml) # The lock file should remain unchanged since the member doesn't inherit workspace version assert lock_file.read_text() == dedent(expected_lock_content) + + +def test_cargo_provider_workspace_member_with_fixed_version( + config: BaseConfig, + chdir: Path, +): + """Regression test for https://github.com/commitizen-tools/commitizen/issues/2001. + + A workspace member with a hardcoded ``version = "x.y.z"`` string (rather + than ``version.workspace = true``) used to crash ``set_lock_version`` with + ``TypeError: string indices must be integers, not 'str'`` because the code + unconditionally subscripted ``package["version"]["workspace"]``. + """ + workspace_toml = """\ +[workspace] +members = ["member_with_fixed_version"] + +[workspace.package] +version = "0.1.0" +""" + + # Real Cargo syntax for a workspace member that opts out of the + # workspace-inherited version by pinning its own. + member_content = """\ +[package] +name = "member_with_fixed_version" +version = "0.9.0" +""" + + lock_content = """\ +[[package]] +name = "member_with_fixed_version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + expected_workspace_toml = """\ +[workspace] +members = ["member_with_fixed_version"] + +[workspace.package] +version = "42.1" +""" + + # The pinned member must not be bumped because it does not inherit the + # workspace version. + expected_lock_content = """\ +[[package]] +name = "member_with_fixed_version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123abc" +""" + + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(workspace_toml)) + + os.mkdir(chdir / "member_with_fixed_version") + member_file = chdir / "member_with_fixed_version" / "Cargo.toml" + member_file.write_text(dedent(member_content)) + + lock_filename = CargoProvider.lock_filename + lock_file = chdir / lock_filename + lock_file.write_text(dedent(lock_content)) + + config.settings["version_provider"] = "cargo" + + provider = get_provider(config) + assert isinstance(provider, CargoProvider) + assert provider.get_version() == "0.1.0" + + # Must not raise TypeError: string indices must be integers, not 'str'. + provider.set_version("42.1") + assert file.read_text() == dedent(expected_workspace_toml) + assert lock_file.read_text() == dedent(expected_lock_content) diff --git a/uv.lock b/uv.lock index e07a6fb8b..4f67292ce 100644 --- a/uv.lock +++ b/uv.lock @@ -216,7 +216,7 @@ wheels = [ [[package]] name = "commitizen" -version = "4.16.3" +version = "4.16.4" source = { editable = "." } dependencies = [ { name = "argcomplete" },