On this page
Undo command that reverts a release by deleting the GitHub Release, removing the git tag, and reverting the version bump commit. Supports older versions.
#rlsbl.commands.undo
#rlsbl.commands.undo
Undo command that reverts a release.
The undo flow is plan-driven: an :class:UndoPlan is computed UPFRONT (for both the latest-release path and the --version non-latest path) before anything is mutated. The plan enumerates the release commits to revert (found by walking git history from the release tag toward the pre-release boundary), companion tags to delete, whether a GitHub Release exists, and the registry evidence verdict. --dry-run prints the plan and exits without touching anything; a real run consumes the identical plan object.
#UndoPlan
A fully-computed description of what an undo will do.
Computed once, before any mutation. --dry-run prints it and exits; a real run executes it. revert_shas is a list of (sha, subject) tuples ordered newest-first (the order they must be reverted in so each revert applies cleanly).
#_resolve_undo_changelog_paths
def _resolve_undo_changelog_paths(project_path, ws_root, releasable_name)Resolve the changes dir, regenerate callable, and git-add paths for changelog restoration -- releasable-aware.
In explicit releasable mode the finalized JSONL lives in the releasable's changes dir and the canonical CHANGELOG.md in the releasable dir (plus the combined root CHANGELOG.md); otherwise everything is per-project.
#_resolve_undo_releases_dir
def _resolve_undo_releases_dir(project_path, ws_root, releasable_name)Resolve the releases dir holding the release-file family -- releasable-aware.
In explicit releasable mode the archived v{x}.toml (and unreleased.toml) live under the releasable's own releases dir; otherwise per-project.
#_clear_release_state
def _clear_release_state(project_path, ws_root)Clear any in-progress release state after a successful rollback.
A rolled-back release's preserved in-progress.json is meaningless and would hard-block the next rlsbl release run.
#_print_summary
def _print_summary(results)Print a summary table of step results. Only called when at least one step failed.
#_resolve_context
def _resolve_context(ctx)Detect the monorepo / releasable context shared by both undo paths.
#_find_latest_tag
def _find_latest_tag(uc)Resolve the latest release tag (scoped to releasable/project in monorepos).
#_version_and_msg
def _version_and_msg(uc, tag)Return (bare_version, expected_version_bump_message) for a tag.
#_build_tag_from_version
def _build_tag_from_version(uc, version)Build the release tag string for a given version (non-latest path).
#_classify_release_commit
def _classify_release_commit(subject, expected_msg)Return the shape of a release commit, or None if it is not one.
#_walk_release_commits
def _walk_release_commits(tag, expected_msg)Walk history from the tag's commit toward the pre-release boundary.
Collects contiguous release-shaped commits newest-first. Message-shape matching only VALIDATES what the walk finds; the walk itself (not the shape of the current HEAD) drives iteration, so it is immune to the "revert changes HEAD's subject" bug. Stops at the first non-release-shaped commit (the boundary) or after collecting the version-bump commit (the oldest commit of a release -- this keeps back-to-back releases from bleeding into each other).
Returns (revert_shas, captured_finalize_changelog, captured_finalize_release_file).
#_plan_companion_tags
def _plan_companion_tags(uc, tag, version)Enumerate companion tags to delete (both paths, releasable mode only).
#_plan_gate
def _plan_gate(uc, ctx, version)Run the registry evidence gate for the release (both paths).
Resolution goes through MemberContext so targets/paths inherit releasable-level config. resolved_targets is available on the context for future per-pipeline evidence checks, but the evidence gate currently operates per-target-object only.
#_build_plan
def _build_plan(uc, flags, ctx)Compute the full UndoPlan for either path, before any mutation.
#_enforce_gate
def _enforce_gate(plan)Refuse the undo when the evidence gate did not clear.
PUBLISHED -> route to release yank / release deprecate (no bypass). INCONCLUSIVE / all-inconclusive -> hard block (same semantics as the non-latest path). Applies to BOTH the latest and non-latest paths.
#_restore_changelog
def _restore_changelog(plan, uc, results)Reconcile CHANGELOG.md with the restored changelog state.
Runs on BOTH paths. It first un-finalizes the version's JSONL (renaming {version}.jsonl back to unreleased.jsonl; a no-op when a revert already restored it, and the repair when it didn't -- e.g. the non-latest path or a finalize commit outside the collected release commits). It then ALWAYS regenerates CHANGELOG.md so the generated file matches the restored unreleased.jsonl -- reverting the finalize commit restores the JSONL but leaves CHANGELOG.md showing the released version rather than ## Unreleased. Commits only when git reports an actual change, so it is idempotent.
#_restore_release_file
def _restore_release_file(plan, uc, results)Repair the release file when its finalize commit was NOT reverted.
unfinalize_release_file is a no-op when the file is already restored (e.g. the finalize commit was reverted), so this is safe on both paths.
#_execute_plan
def _execute_plan(plan, uc, flags, ctx)Execute a computed UndoPlan. Writes the audit journal BEFORE any deletion.
#run_cmd
def run_cmd(registry, args, flags, *, ctx)