Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/pr-bump-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: PR bump preview

on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]

permissions:
contents: read
pull-requests: write

jobs:
bump-preview:
# Skip drafts, and skip fork PRs entirely. `pull_request_target` runs with
# the base repo's GITHUB_TOKEN (write access to PR comments). `cz bump`
# can render Jinja templates from the checked-out workspace whenever
# `update_changelog_on_bump` is set in config, and the renderer is not
# sandboxed (FileSystemLoader('.')) — running it against fork-controlled
# files would risk RCE / token exfiltration. Same-repo PRs are written by
# collaborators who already have push access, so the same risk doesn't
# apply.
if: >
${{
github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name ==
github.event.pull_request.base.repo.full_name
}}
runs-on: ubuntu-latest
steps:
- name: Check out PR head
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
fetch-tags: true
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 52530b0: added persist-credentials: false on actions/checkout.

# Defense in depth: don't write the workflow token to .git/config.
persist-credentials: false

- name: Set up Commitizen
uses: commitizen-tools/setup-cz@main
with:
set-git-config: false

- name: Run cz bump --dry-run
id: dry-run
run: |
set +e
output="$(cz bump --dry-run --yes 2>&1)"
status=$?
set -e
{
echo "status=${status}"
echo "output<<__CZ_BUMP_PREVIEW__"
printf '%s\n' "${output}"
echo "__CZ_BUMP_PREVIEW__"
} >> "$GITHUB_OUTPUT"

- name: Build comment body
env:
STATUS: ${{ steps.dry-run.outputs.status }}
OUTPUT: ${{ steps.dry-run.outputs.output }}
run: |
{
echo "<!-- commitizen-bump-preview -->"
echo "## 🔍 Commitizen bump preview"
echo ""
case "${STATUS}" in
0)
echo "Merging this PR will produce the following bump:"
echo ""
echo '```'
printf '%s\n' "${OUTPUT}"
echo '```'
;;
21)
echo "No commits in this PR are eligible for a version bump."
;;
*)
echo "⚠️ \`cz bump --dry-run\` exited with status \`${STATUS}\`:"
echo ""
echo '```'
printf '%s\n' "${OUTPUT}"
echo '```'
;;
esac
} > comment.md

- name: Post or update PR comment
uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
body-path: comment.md
body-includes: "<!-- commitizen-bump-preview -->"
edit-mode: replace
121 changes: 121 additions & 0 deletions docs/tutorials/github_actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,127 @@ jobs:

You can find the complete workflow in our repository at [bumpversion.yml](https://github.com/commitizen-tools/commitizen/blob/master/.github/workflows/bumpversion.yml).

### Previewing the version bump on pull requests

To help reviewers spot unexpected version bumps before merging, you can run
`cz bump --dry-run` on every pull request and post (or update) a sticky
comment summarizing the would-be version bump.

Create `.github/workflows/pr-bump-preview.yml`:

```yaml title=".github/workflows/pr-bump-preview.yml"
name: PR bump preview

on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]

permissions:
contents: read
pull-requests: write

jobs:
bump-preview:
# Skip drafts and fork PRs (see "How it works" below).
if: >
${{
github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name ==
github.event.pull_request.base.repo.full_name
}}
runs-on: ubuntu-latest
steps:
- name: Check out PR head
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
fetch-tags: true
persist-credentials: false
- uses: commitizen-tools/setup-cz@main
with:
set-git-config: false
- name: Run cz bump --dry-run
id: dry-run
run: |
set +e
output="$(cz bump --dry-run --yes 2>&1)"
status=$?
set -e
{
echo "status=${status}"
echo "output<<__CZ_BUMP_PREVIEW__"
printf '%s\n' "${output}"
echo "__CZ_BUMP_PREVIEW__"
} >> "$GITHUB_OUTPUT"
- name: Build comment body
env:
STATUS: ${{ steps.dry-run.outputs.status }}
OUTPUT: ${{ steps.dry-run.outputs.output }}
run: |
{
echo "<!-- commitizen-bump-preview -->"
echo "## 🔍 Commitizen bump preview"
echo ""
case "${STATUS}" in
0)
echo "Merging this PR will produce the following bump:"
echo ""
echo '```'
printf '%s\n' "${OUTPUT}"
echo '```'
;;
21)
echo "No commits in this PR are eligible for a version bump."
;;
*)
echo "⚠️ \`cz bump --dry-run\` exited with status \`${STATUS}\`:"
echo ""
echo '```'
printf '%s\n' "${OUTPUT}"
echo '```'
;;
esac
} > comment.md
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
body-path: comment.md
body-includes: "<!-- commitizen-bump-preview -->"
edit-mode: replace
```

#### How it works

- **Trigger**: `pull_request_target` runs in the context of the base
repository, which gives the workflow `pull-requests: write` permission
even for PRs from forks. We deliberately gate the job to **same-repo PRs
only** (`head.repo == base.repo`); fork PRs are skipped. This is because
`cz bump` renders [Jinja templates from the working directory][jinja]
whenever [`update_changelog_on_bump`](../config/configuration_file.md) is
enabled, and the renderer is not sandboxed — running it against
fork-controlled files under a write token would risk arbitrary code
execution and token exfiltration. Same-repo PRs are written by
collaborators who already have push access, so the same risk doesn't
apply.
- **Setup**: [`commitizen-tools/setup-cz`](https://github.com/commitizen-tools/setup-cz)
Comment on lines +219 to +230
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 52530b0: rewrote the safety explanation in the How it works section. The job now gates to same-repo PRs only, and the docs spell out why (cz bump's non-sandboxed Jinja renderer + update_changelog_on_bump would otherwise let a fork PR run arbitrary code under the write token). Also added persist-credentials: false as defense in depth.

installs the Commitizen CLI; no language-specific build tooling is required.
- **Defense in depth**: `persist-credentials: false` on `actions/checkout`
keeps the workflow token out of the local git config.
- **Dry-run**: `cz bump --dry-run --yes` computes the next version (and, if
`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 `<!-- commitizen-bump-preview -->`
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.

[jinja]: https://github.com/commitizen-tools/commitizen/blob/master/commitizen/changelog.py

You can find the complete workflow in our repository at [pr-bump-preview.yml](https://github.com/commitizen-tools/commitizen/blob/master/.github/workflows/pr-bump-preview.yml).

### Publishing a Python package

After a new version tag is created by the bump workflow, you can automatically publish your package to PyPI.
Expand Down
Loading