Skip to content
Merged
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
32 changes: 32 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,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`).
Comment thread
woile marked this conversation as resolved.

### 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.
1 change: 0 additions & 1 deletion commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,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
62 changes: 57 additions & 5 deletions commitizen/commands/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@

from packaging.version import InvalidVersion

from commitizen import out
from commitizen import bump, factory, git, out
from commitizen.__version__ import __version__
from commitizen.config import BaseConfig
from commitizen.exceptions import NoVersionSpecifiedError, VersionSchemeUnknown
from commitizen.exceptions import (
NoCommitsFoundError,
NoPatternMapError,
NotAGitProjectError,
NoVersionSpecifiedError,
VersionSchemeUnknown,
)
from commitizen.providers import get_provider
from commitizen.tags import TagRules
from commitizen.version_increment import VersionIncrement
from commitizen.version_schemes import Increment, get_version_scheme
from commitizen.version_schemes import Increment, VersionProtocol, get_version_scheme


class VersionArgs(TypedDict, total=False):
Expand Down Expand Up @@ -63,6 +69,9 @@ def __call__(self) -> None:
or self.arguments.get("next")
or self.arguments.get("manual_version")
):
# TODO: once `cz --version` is implemented, move `self.cz` back to __init__
self.cz = factory.committer_factory(self.config)

version_str = self.arguments.get("manual_version")
if version_str is None:
try:
Expand All @@ -84,11 +93,15 @@ def __call__(self) -> None:

if next_increment_str := self.arguments.get("next"):
if next_increment_str == "USE_GIT_COMMITS":
# Only check under `git` to allow the user to do stuff like
# `cz version 1.2.3 --major`
if not git.is_git_project():
raise NotAGitProjectError()

# TODO: implement USE_GIT_COMMITS by deriving the increment from
# git history. This requires refactoring the bump logic out of
# `commitizen/commands/bump.py` so it can be reused here. See #1678.
out.error("--next USE_GIT_COMMITS is not implemented yet.")
return
version = self._get_next_git_version(version)

next_increment = VersionIncrement.from_value(next_increment_str)
increment: Increment | None
Expand All @@ -100,6 +113,9 @@ def __call__(self) -> None:
increment = "MINOR"
else:
increment = "MAJOR"

# TODO: Consider adding all the parameters `.bump` supports:
# prerelease, prerelease_offset,exact_increment, etc...
version = version.bump(increment=increment)

if self.arguments.get("major"):
Expand Down Expand Up @@ -139,3 +155,39 @@ def __call__(self) -> None:

# If no arguments are provided, just show the installed commitizen version
out.write(__version__)

def _get_next_git_version(
self, current_version: VersionProtocol
) -> VersionProtocol:
"""Calculate the next version based on commits."""
rules = TagRules.from_settings(self.config.settings)
current_tag = rules.find_tag_for(git.get_tags(), current_version)
commits = git.get_commits(current_tag.name if current_tag else None)

Comment thread
woile marked this conversation as resolved.
# No commits, there is no need to create an empty tag.
# Unless we previously had a prerelease.
if not commits and not current_version.is_prerelease:
raise NoCommitsFoundError("[NO_COMMITS_FOUND]\nNo new commits found.")
Comment thread
woile marked this conversation as resolved.

bump_map = (
self.cz.bump_map_major_version_zero
if self.config.settings["major_version_zero"]
else self.cz.bump_map
)
bump_pattern = self.cz.bump_pattern

if not bump_map or not bump_pattern:
raise NoPatternMapError(
f"'{self.config.settings['name']}' rule does not support bump"
)
increment = bump.find_increment(
commits, regex=bump_pattern, increments_map=bump_map
)

# TODO: Consider adding all the parameters `.bump` supports:
# prerelease, prerelease_offset,exact_increment, etc..
new_version = current_version.bump(
increment,
)

return new_version
14 changes: 12 additions & 2 deletions docs/commands/version.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@ Get the version of the installed Commitizen or the current project (default: ins

- **`--major`**, **`--minor`**, **`--patch`**: print only that component of the (possibly manual) project version. Requires `--project`, `--verbose`, or a manual version.
- **`--next` `[MAJOR|MINOR|PATCH|NONE]`**: print the version after applying that bump to the current project or manual version. `NONE` leaves the version unchanged.
- **`--next USE_GIT_COMMITS`**: derive the bump automatically from the commits since the tag matching the current version, using your commit rules' bump map (the same logic as `cz bump`). Passing `--next` with no value defaults to `USE_GIT_COMMITS`. If no tag matches the current version, all commits are considered. When there are no new commits (and the current version is not a pre-release), the command errors out. The functionality does not yet include advanced versioning like prereleases or exact_increment.
- **`--tag`**: print the version formatted with your `tag_format` (requires `--project` or `--verbose`).

`--next USE_GIT_COMMITS` is reserved for a future feature (derive the bump from git history) and is not implemented yet.

## Examples

```bash
cz version --project
cz version 2.0.0 --next MAJOR
cz version --project --major
cz version --verbose

# Derive the next version from the commits since the current version's tag
cz version --project --next USE_GIT_COMMITS
# Equivalent shorthand (--next defaults to USE_GIT_COMMITS)
cz version --project --next
# Retrieve just the next major part of the version
cz version --project --next --major
# Works with a manual version too
cz version 1.4.0 --next
# Combine with --tag to format the derived version using your tag_format
cz version --project --next --tag
```
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
usage: cz version [-h] [-r | -p | -c | -v]
[--major | --minor | --tag | --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[--major | --minor | --tag | --patch]
[--next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[MANUAL_VERSION]

Get the version of the installed commitizen or the current project (default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
usage: cz version [-h] [-r | -p | -c | -v]
[--major | --minor | --tag | --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[--major | --minor | --tag | --patch]
[--next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[MANUAL_VERSION]

Get the version of the installed commitizen or the current project (default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
usage: cz version [-h] [-r | -p | -c | -v]
[--major | --minor | --tag | --patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[--major | --minor | --tag | --patch]
[--next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[MANUAL_VERSION]

Get the version of the installed commitizen or the current project (default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag |
--patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
--patch] [--next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[MANUAL_VERSION]

Get the version of the installed commitizen or the current project (default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
usage: cz version [-h] [-r | -p | -c | -v] [--major | --minor | --tag |
--patch | --next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
--patch] [--next [{USE_GIT_COMMITS,NONE,PATCH,MINOR,MAJOR}]]
[MANUAL_VERSION]

Get the version of the installed commitizen or the current project (default:
Expand Down
Loading
Loading