rlsbl v0.40.1 /rlsbl.changelog.validate
On this page

Validates JSONL changelog entries against git history: hash resolution, range checking, commit coverage, orphan detection, and schema conformance.

#rlsbl.changelog.validate

#rlsbl.changelog.validate

Validates JSONL changelog entries against git history: hash resolution, range checking, commit coverage, orphan detection, and schema conformance.

#_get_batch_limits_config

python
def _get_batch_limits_config() -> dict

Return the resolved batch_limits config with defaults applied.

Reads the raw batch_limits section via :func:get_changelog_validation_config and guarantees that all three expected keys are present with sane types:

  • max_commits_per_entry (int)
  • max_entries_per_commit (int)
  • exclusions (list)

If any key is missing or has the wrong type, the default is used and a warning is emitted on stderr.

#_git_log_hashes

python
def _git_log_hashes(range_spec: str) -> list[str]

Get commit hashes from git log for a given range spec.

Returns a list of full 40-char SHAs, or empty list on error.

#_get_last_version_tag

python
def _get_last_version_tag(tag_glob: str | None=None) -> str | None

Get the most recent version tag (e.g., v0.25.2).

When tag_glob is set (monorepo mode), uses it directly as the --match pattern (e.g. mylib@v* or go/v*).

Returns the tag string or None if no version tags exist.

#_unreleased_range

python
def _unreleased_range(tag_glob: str | None=None) -> str

Return the git log range spec for unreleased commits.

Uses ..HEAD if a version tag exists, otherwise HEAD (all commits, for first release). Passes tag_glob through to _get_last_version_tag for monorepo-aware tag discovery.

#_git_head

python
def _git_head() -> str | None

Get the current HEAD commit hash.

#_is_changelog_only_commit

python
def _is_changelog_only_commit(sha: str) -> bool

Check if a commit only touches changelog/release infrastructure files.

Returns True when every file changed by the commit matches one of:

  • .rlsbl/changes/ (any depth)
  • .rlsbl/version
  • CHANGELOG.md

For monorepo support, paths prefixed with a subdirectory are also accepted (e.g. python/.rlsbl/changes/unreleased.jsonl).

Subprocess errors or empty file lists are treated conservatively (returns False).

#_is_changelog_path

python
def _is_changelog_path(path: str) -> bool

Return True if path is a changelog/release infrastructure file.

Recognised patterns (with optional leading directory prefix):

  • [prefix/].rlsbl/changes/...
  • [prefix/].rlsbl/version
  • [prefix/]CHANGELOG.md

#_is_autogenerated

python
def _is_autogenerated(sha: str) -> bool

Check if a commit has the Autogenerated: true trailer.

Returns True when the trailer is present with value true, False otherwise. Subprocess errors are treated as non-autogenerated.

#_is_ancestor

python
def _is_ancestor(ancestor: str, descendant: str) -> bool

Check if ancestor is an ancestor of descendant.

#_cache_path

python
def _cache_path(changes_dir: str) -> str

Return path to the .validated cache file.

#_read_cache

python
def _read_cache(changes_dir: str) -> str | None

Read the .validated file. Return the cached HEAD hash or None.

#_write_cache

python
def _write_cache(changes_dir: str) -> None

Write the current HEAD hash to the .validated cache file.

#_is_cache_valid

python
def _is_cache_valid(changes_dir: str) -> bool

Check if the validation cache is still valid.

Valid when:

  • .validated exists and contains a 40-char SHA
  • That SHA is an ancestor of (or equal to) HEAD
  • unreleased.jsonl's mtime is older than .validated's mtime

#check_hashes_resolve

python
def check_hashes_resolve(entries: list[ChangelogEntry]) -> tuple[bool, list[str]]

Check that every hash in every entry resolves via git rev-parse.

#check_in_range

python
def check_in_range(entries: list[ChangelogEntry], tag_glob: str | None=None) -> tuple[bool, list[str]]

Check that every resolved hash is in the unreleased range.

Unreleased range: commits since the last version tag (or all commits if no tags exist). When tag_glob is set, scopes to monorepo tags.

#check_coverage

python
def check_coverage(entries: list[ChangelogEntry], tag_glob: str | None=None) -> tuple[bool, list[str]]

Check that every unreleased commit appears in at least one entry.

Commits with the Autogenerated: true trailer are automatically exempted -- they are release infrastructure and don't need coverage. When tag_glob is set, scopes to monorepo tags.

#check_no_orphans

python
def check_no_orphans(entries: list[ChangelogEntry]) -> tuple[bool, list[str]]

Flag entries where ALL hashes are unresolvable (stale/rebased entries).

#check_schema

python
def check_schema(entries: list[ChangelogEntry]) -> tuple[bool, list[str]]

Check that every entry passes schema validation.

#check_batch_size_commits

python
def check_batch_size_commits(entries: list[ChangelogEntry], config: dict, version: str='unreleased') -> tuple[bool, list[str]]

Check that no entry has more commits than max_commits_per_entry.

config is the resolved batch_limits config (see :func:_get_batch_limits_config). Per-entry exclusions (matched by version + 1-based line number) silence the check for those entries.

#check_batch_size_entries

python
def check_batch_size_entries(entries_by_version: dict[str, list[ChangelogEntry]], config: dict) -> tuple[bool, list[str]]

Check that no commit appears in more than max_entries_per_commit entries.

entries_by_version maps version label (e.g. "unreleased" or "0.32.0") to its entry list, so the check spans across ALL JSONL files, not just unreleased. Per-commit exclusions in config silence specific hashes.

#_read_all_versioned_entries

python
def _read_all_versioned_entries(changes_dir: str) -> dict[str, list[ChangelogEntry]]

Read entries from unreleased.jsonl AND every x.y.z.jsonl in changes_dir.

Returns a mapping {version_label: entries} where version_label is "unreleased" or the bare semver string (e.g. "0.32.0"). Files that fail to parse are skipped silently -- other checks surface such errors separately.

#validate_unreleased

python
def validate_unreleased(changes_dir: str, tag_glob: str | None=None) -> dict

Run all 7 validation checks on unreleased.jsonl.

Returns a dict with:

  • check names as keys, (passed, details) tuples as values
  • "passed": overall bool (True only if all checks pass)

Uses validation cache: if the cache is valid and HEAD hasn't changed, skips full revalidation. When tag_glob is set, uses it directly as the glob pattern for monorepo tag discovery (e.g. mylib@v* or go/v*).

The two batch_limits checks (batch_size_commits and batch_size_entries) read configuration from .rlsbl/config.json via :func:_get_batch_limits_config. The cross-version batch_size_entries check reads every x.y.z.jsonl file in changes_dir so it can detect commits that appear in too many entries across versions.