Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c0ca27d
fix: workspace member with fixed version key
vemilano Jun 15, 2026
9277972
feat: inheriting workspace members with path, package and version att…
vemilano Jun 19, 2026
8600f81
ci(docspublish): make cli_interactive gif regeneration deterministic …
bearomorphism Jun 21, 2026
8bd1417
ci: make bump preview comment sticky (#2008)
bearomorphism Jun 21, 2026
2394ddb
ci(deps): bump codecov/codecov-action from 6 to 7 (#2013)
dependabot[bot] Jun 21, 2026
2b37564
docs(cli/screenshots): update CLI screenshots
github-actions[bot] Jun 21, 2026
de8a33d
ci(deps): bump peter-evans/find-comment from 3 to 4 (#2020)
dependabot[bot] Jun 22, 2026
5b49851
fix(providers/cargo): don't crash on workspace member with fixed version
bearomorphism May 25, 2026
8e6c95a
bump: version 4.16.3 → 4.16.4
commitizenbot[bot] Jun 22, 2026
1b133ac
docs(cli/screenshots): update CLI screenshots
github-actions[bot] Jun 22, 2026
ee4554d
build(deps): update argcomplete requirement from <3.7,>=1.12.1 to >=1…
dependabot[bot] Jul 1, 2026
6da74da
ci(deps): bump dawidd6/action-homebrew-bump-formula from 7 to 8 (#2022)
dependabot[bot] Jul 3, 2026
93a05d4
ci(deps): bump soupsieve from 2.8.3 to 2.8.4 (#2024)
dependabot[bot] Jul 10, 2026
427b1dc
fix: exclude star-history API from lychee link checker (#2029)
Copilot Jul 16, 2026
84480c3
bump: version 4.16.4 → 4.16.5
commitizenbot[bot] Jul 16, 2026
fa98440
docs(cli/screenshots): update CLI screenshots
github-actions[bot] Jul 16, 2026
fb8b249
ci(deps): bump actions/setup-go from 6 to 7 (#2032)
dependabot[bot] Jul 17, 2026
73dbe61
ci(deps): bump actions/labeler from 6 to 7 (#2034)
dependabot[bot] Jul 22, 2026
c985d57
ci(deps): bump gitpython from 3.1.50 to 3.1.52 (#2035)
dependabot[bot] Jul 22, 2026
5b291b4
ci(deps): bump pymdown-extensions from 10.21.3 to 11.0 (#2037)
dependabot[bot] Jul 28, 2026
617dd0f
ci(deps): bump gitpython from 3.1.52 to 3.1.54 (#2038)
dependabot[bot] Jul 28, 2026
e72bfd0
build(deps-dev): update uv-build requirement from <0.12,>=0.9.17 to >…
dependabot[bot] Jul 29, 2026
a4e410d
fix(bump): use correct type for calling changelog
woile Jul 24, 2026
0c0751a
fix(version): remove next from exclusive group
woile Jul 24, 2026
50f0dcb
fix: robuster agents
woile Jul 24, 2026
7586ec3
feat(cmd/version): add support for `--next USE_GIT_COMMITS`
woile Jul 24, 2026
b4fd8a3
bump: version 4.16.5 → 4.17.0
commitizenbot[bot] Jul 29, 2026
4c3bd92
docs(cli/screenshots): update CLI screenshots
github-actions[bot] Jul 29, 2026
5529ed4
docs: remove star history (#2043)
bearomorphism Jul 29, 2026
51ab90c
docs(tutorial): add Releasing with the SCM Version Provider
woile Jul 29, 2026
3490872
build(deps): bump gitpython from 3.1.54 to 3.1.57 (#2050)
dependabot[bot] Aug 5, 2026
44234a0
build(deps): bump gitpython from 3.1.57 to 3.1.58 (#2058)
dependabot[bot] Aug 10, 2026
85ac073
build(deps): bump pymdown-extensions from 11.0 to 11.0.1 (#2059)
dependabot[bot] Aug 10, 2026
b3b009f
fix(init): use prek when installing hooks if pre-commit is missing
DSeaStar Aug 14, 2026
6931803
fix(init): skip hook question when no installer is present
DSeaStar Aug 14, 2026
0c73c4e
test(init): cover installer missing at install time
DSeaStar Aug 14, 2026
b0f522f
bump: version 4.17.0 → 4.17.1
commitizenbot[bot] Aug 17, 2026
9351e01
docs(cli/screenshots): update CLI screenshots
github-actions[bot] Aug 17, 2026
4ce8164
feat(commit): add a tag(--body-length-limit) and a function for comma…
yjaw Aug 19, 2026
cc45233
bump: version 4.17.1 → 4.18.0
commitizenbot[bot] Aug 19, 2026
6e013c1
docs(cli/screenshots): update CLI screenshots
github-actions[bot] Aug 19, 2026
16117a8
docs(tutorial): fix setup-cz link
bearomorphism Aug 20, 2026
196973f
docs: restore Star History section in README (#2054)
Mubelotix Aug 20, 2026
0bc70e8
ci: restore changelog updates (#2074)
bearomorphism Aug 20, 2026
d5c90a1
ci(links): avoid duplicate broken link reports (#2073)
bearomorphism Aug 21, 2026
7efe21a
fix: workspace member with fixed version key
vemilano Jun 15, 2026
45945bf
feat: inheriting workspace members with path, package and version att…
vemilano Jun 19, 2026
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
33 changes: 31 additions & 2 deletions .github/workflows/docspublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,50 @@ jobs:
- name: Set up Python
uses: astral-sh/setup-uv@v7
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version: '1.25.8'
- name: Set up VHS
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
uv sync --frozen --group base --group script
- 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]"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/homebrewpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
run: |
echo "project_version=$(cz version --project)" >> $GITHUB_ENV
- name: Update Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@v7
uses: dawidd6/action-homebrew-bump-formula@v8
with:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
formula: commitizen
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/label_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
sparse-checkout: |
.github/labeler.yml
sparse-checkout-cone-mode: false
- uses: actions/labeler@v6
- uses: actions/labeler@v7
with:
configuration-path: .github/labeler.yml
- name: Label based on PR title
Expand Down
21 changes: 18 additions & 3 deletions .github/workflows/links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,29 @@ jobs:
with:
script: |
const fs = require('fs');
const reportTitle = 'Broken Links Report';
const openIssues = await github.paginate(
github.rest.issues.listForRepo,
{
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
}
);

if (openIssues.some(issue =>
!issue.pull_request && issue.title === reportTitle
)) {
core.info(`An open "${reportTitle}" issue already exists; skipping.`);
return;
}

// Read the markdown file
// Ensure the path is correct relative to the workspace root
const reportBody = fs.readFileSync('./lychee/out.md', 'utf8');

await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Broken Links Report',
title: reportTitle,
body: reportBody
});
18 changes: 17 additions & 1 deletion .github/workflows/pr-bump-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<!-- commitizen-bump-preview -->"

- 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: "<!-- commitizen-bump-preview -->"
edit-mode: replace
4 changes: 2 additions & 2 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ repos:
- tomli

- repo: https://github.com/commitizen-tools/commitizen
rev: v4.16.3 # automatically updated by Commitizen
rev: v4.18.0 # automatically updated by Commitizen
hooks:
- id: commitizen
- id: commitizen-branch
Expand Down
47 changes: 47 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ Follow these instructions in addition to any higher-level system or tool rules.
- **Commit messages** must follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by commitizen itself).
- **Pull requests** must follow the [Pull Request Guidelines](docs/contributing/pull_request.md) and the template in `.github/pull_request_template.md`.

### Commit and Pull Request Types

Choose the Conventional Commit type based on the change's release impact, not
the files or subsystem it touches. The type controls automated version bumps:

- `feat` triggers a minor release.
- `fix`, `perf`, and `refactor` trigger a patch release.
- `ci`, `docs`, `test`, `build`, and `chore` do not trigger a release.
- A breaking-change marker triggers a major release.

Use the same rule for pull request titles because squash merges use the title as
the resulting commit message. In particular, use `ci:` for changes limited to
workflows or release automation. A scope does not change release impact:
`fix(ci): ...` still triggers a patch release.

## Setup and Validation

> Full contributor guidelines (prerequisites, workflow, PR process): [`docs/contributing/contributing.md`](docs/contributing/contributing.md).
Expand Down Expand Up @@ -76,8 +91,40 @@ Always run at least `uv run ruff check --fix . && uv run ruff format .` before p
- **Types**: Preserve or improve existing type hints.
- **Errors**: Prefer `commitizen/exceptions.py` error types; keep messages clear for CLI users.
- **Output**: Use `commitizen/out.py`; do not add noisy logging.
- **Testing**: Follow the Arrange, Act, Assert (AAA) pattern. Visually separate these phases with blank lines or comments.

## When Unsure

- Prefer **reading tests and documentation first** to understand the expected behavior.
- When behavior is ambiguous, **assume backward compatibility** with current tests and docs is required.

## Documentation Guidelines

- 100% Coverage: Every new module, class, method, and function MUST have a docstring. No exceptions
- Even simple functions or internal helpers require documentation to explain their context

### Docstring Format: Modified Google Style

Use Google Docstring Style with these major modifications:

1. **NEVER include type hints in the docstring.** We rely exclusively on Python's PEP 484 type signatures.
2. **Classes MUST include an example.** Any class documentation must contain a brief usage example formatted in Markdown.
3. **Class Attributes MUST be documented.** The class docstring must document the instance variables initialized in `__init__` within an `Attributes:` section.

**Format Rules:**

- `Args:`, `Returns:`, and `Attributes:` sections must describe the _semantic meaning_ and _constraints_ of the variables, not their types.
- Omit the type in the lists (e.g., use `user_id: The ID of the user`, NOT `user_id (int): The ID of the user`).

### Content Focus: The "What" and "Why"

Code explains _how_. Your docstrings must explain _what_ and _why_.

When documenting, you may include:

1. **The Core Intent** What business or technical rule is this solving?
2. **Considerations:** Architectural choices. Why was this approach chosen over the obvious alternative? Why the complexity (if introduced)?
3. **Discarded Approaches:** If a simpler method wasn't used (e.g., avoiding an ORM feature for raw SQL, or caching strategies), explain what was discarded and why.
4. **Edge Cases & Gotchas:** What weird scenarios does this code handle?

In the end, the reader must understand the intention behind the decisions, to avoid making the same mistakes.
89 changes: 89 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,92 @@
## Unreleased

## v4.18.0 (2026-08-19)

### Feat

- **commit**: add a tag(--body-length-limit) and a function for command commit (#1849)

## v4.17.1 (2026-08-17)

### Fix

- **init**: skip hook question when no installer is present
- **init**: use prek when installing hooks if pre-commit is missing

## v4.17.0 (2026-07-29)

### Feat

- **cmd/version**: add support for `--next USE_GIT_COMMITS`

### Fix

- robuster agents
- **version**: remove next from exclusive group
- **bump**: use correct type for calling changelog

## v4.16.5 (2026-07-16)

### Fix

- exclude star-history API from lychee link checker (#2029)

## v4.16.4 (2026-06-22)

### Fix

- **providers/cargo**: don't crash on workspace member with fixed version

## v4.16.3 (2026-05-30)

### Fix

- **check**: expand env vars in --rev-range (#2005)

## v4.16.2 (2026-05-15)

### Fix

- **tags**: widen prerelease and devrelease tag regexes for SemVer2 (#1972)

## v4.16.1 (2026-05-15)

### Fix

- **cz_customize**: derive bump_map_major_version_zero from bump_map (#1977)

## v4.16.0 (2026-05-12)

### Feat

- **hooks**: support interactive hooks scripts

## v4.15.1 (2026-05-06)

### Fix

- **security**: prevent command injection via shell=True (CWE-78) (#1941)

## v4.15.0 (2026-05-03)

### Feat

- **version**: add MANUAL_VERSION, --next and --patch to version command (#1724)

## v4.14.0 (2026-05-03)

### Feat

- add `--allow-no-commit` to `changelog` command (#1868)

## v4.13.10 (2026-04-11)

### Fix

- **ci**: use commitizen bot to push tags and commits
- **init**: set semver2 as default if not python
- change deprecated value and bump versions

## v4.13.9 (2026-02-25)

### Fix
Expand Down
2 changes: 1 addition & 1 deletion commitizen/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.16.3"
__version__ = "4.18.0"
6 changes: 5 additions & 1 deletion commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ def __call__(
"type": int,
"help": "Set the length limit of the commit message; 0 for no limit.",
},
{
"name": ["--body-length-limit"],
"type": int,
"help": "Set the length limit of the commit body. Commit message in body will be rewrapped to this length; 0 for no limit.",
},
{
"name": ["--"],
"action": "store_true",
Expand Down Expand Up @@ -583,7 +588,6 @@ def __call__(
"const": "USE_GIT_COMMITS",
"choices": ["USE_GIT_COMMITS"]
+ [str(increment) for increment in VersionIncrement],
"exclusive_group": "group2",
},
{
"name": "manual_version",
Expand Down
2 changes: 1 addition & 1 deletion commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ChangelogArgs(TypedDict, total=False):
change_type_order: list[str]
current_version: str
dry_run: bool
file_name: str
file_name: str | None
incremental: bool
merge_prerelease: bool
rev_range: str
Expand Down
26 changes: 26 additions & 0 deletions commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import shutil
import subprocess
import tempfile
import textwrap
from itertools import chain
from pathlib import Path
from typing import TYPE_CHECKING, TypedDict

Expand Down Expand Up @@ -36,6 +38,7 @@ class CommitArgs(TypedDict, total=False):
edit: bool
extra_cli_args: list[str]
message_length_limit: int
body_length_limit: int
no_retry: bool
signoff: bool
write_message_to_file: Path | None
Expand Down Expand Up @@ -82,6 +85,7 @@ def _get_message_by_prompt_commit_questions(self) -> str:

message = self.cz.message(answers)
self._validate_subject_length(message)
message = self._wrap_body(message)
return message

def _validate_subject_length(self, message: str) -> None:
Expand All @@ -100,6 +104,28 @@ def _validate_subject_length(self, message: str) -> None:
f"Length of commit message exceeds limit ({len(subject)}/{message_length_limit}), subject: '{subject}'"
)

def _wrap_body(self, message: str) -> str:
"""
Wrap the body of the commit message to the --body-length-limit length.
"""

body_length_limit = self.arguments.get(
"body_length_limit", self.config.settings["body_length_limit"]
)
# By the contract, body_length_limit is set to 0 for no limit
if not body_length_limit or body_length_limit <= 0:
return message

lines = message.split("\n")
if len(lines) < 3:
return message

# First line is subject, second is blank line, rest are body lines
wrapped_body_lines = chain.from_iterable(
textwrap.wrap(line, width=body_length_limit) for line in lines[2:]
)
return "\n".join(chain(lines[:2], wrapped_body_lines))

def manual_edit(self, message: str) -> str:
editor = git.get_core_editor()
if editor is None:
Expand Down
Loading