On this page
Rename a releasable group across workspace.toml, state directories, and publish gate prefixes, with optional alias tag creation.
#rlsbl.commands.monorepo.releasable_rename
#rlsbl.commands.monorepo.releasable_rename
Rename a releasable group: rlsbl monorepo rename-releasable <old> <new>.
Renaming a releasable is a coordinated, idempotent operation:
- Rewrite
workspace.tomlin place -- the[[releasables]]table's
name field and every member project's releasable field -- preserving comments and key order.
- Move the releasable's state directory
(.rlsbl-monorepo/releasables/<old> -> <new>).
- Delete the moved
changes/.validatedcache (the tag glob changes, so the
validation cache is stale).
- Re-run
monorepo syncto regenerate the publish gate prefixes and CI
router.
- Commit all of the above as a single commit.
- Record the rename itself as a
releasable-renameevent in the
REPOSITORY-scoped transition record, and commit that. It is a tag-SPELLING fact -- the releasable's future tags are spelled with the new name and nothing a consumer resolves by changed -- so it never makes rlsbl release reconcile refuse to repair a ref released under the old spelling. It goes in the repository's record rather than the releasable's own because after the rename the releasable's state directory exists only under the NEW name, while the old name is the spelling a reader with an old tag in hand has.
Then, last, when the releasable's tag_format contains {name} (so the tag prefix actually changes), a boundary alias tag for the current version is created at the commit the old current-version tag points to, RECORDED as a boundary-alias event in the releasable's transition record, and pushed. The push is the single sanctioned remote action. Historical releases stay under the old prefix and are no longer managed by rlsbl release edit/deprecate/yank.
The transition record is what makes the alias discoverable: expected_refs (the single authority for a version's full ref set) reads recorded aliases from there, and it is the same event kind a conversion writes for the same fact.
The flow is idempotent: a crash between the local commit and the tag push is healed by re-running the command, which detects the already-renamed state and finishes the tag step. The transition record append is idempotent by content, so a re-run never duplicates the record.
#validate_releasable_name
def validate_releasable_name(name)Validate a releasable name against the [a-z][a-z0-9-]* charset.
Raises WorkspaceError when the name is not a string, is empty, or contains characters outside the allowed set (lowercase letters, digits, hyphens, starting with a letter).
#_is_clean_tree
def _is_clean_tree(root)Return True when the git working tree at root is clean.
#_tag_exists_local
def _tag_exists_local(root, tag)Return True when tag exists as a local git tag.
#_tag_exists_remote
def _tag_exists_remote(root, remote, tag)Return True when tag exists on remote.
#_resolve_tag_commit
def _resolve_tag_commit(root, tag)Resolve tag to the commit SHA it points to, or None if absent.
#_saferm_file
def _saferm_file(path)Delete a stale cache file via saferm (audit trail; -f skips if missing).
#_apply_workspace_rename
def _apply_workspace_rename(root, old, new)Rewrite workspace.toml in place: releasable name + member fields.
Locates the [[releasables]] table whose name equals old and rewrites only its name field, then rewrites every [[projects]] table whose releasable equals old to new. All other content -- comments, key order, unrelated tables -- is preserved byte-for-byte.
Returns True when the file was changed, False when it was already renamed (idempotent no-op).
#_apply_local_rename
def _apply_local_rename(root, old, new)Perform the local mutations (steps 1-5) and commit them as one commit.
Structured as a standalone callable so tests can invoke it (simulating a crash right after the commit, before the tag push) and then run the full command to verify the tag step is healed on re-run.
#_push_timeout_for
def _push_timeout_for(root, name)Resolve the push timeout from the renamed releasable's config.
The workspace root has no per-package .rlsbl/config.json in a monorepo, so this reads the releasable-level config and lets the standard push_timeout key (or its default) apply.
#_record_alias_in_transition_record
def _record_alias_in_transition_record(root, releasable_name, alias_tag, aliased_tag, commit)Append the boundary alias to the releasable's transition record and commit it.
An alias tag created here and a boundary alias recorded by a conversion are the same kind of fact -- a pre-rename version made addressable under the post-rename naming -- so they are recorded in the same place. expected_refs reads that one place; an alias that exists only as a git ref would be a second, undiscoverable source for the same question.
Idempotent by CONTENT: an alias already recorded under this alias_tag is not appended again, so every crash window (and a plain re-run) heals without duplicating the record.
#_record_rename_in_transition_record
def _record_rename_in_transition_record(root, old_name, new_name)Append the rename itself to the REPOSITORY-scoped transition record.
The boundary alias records one tag standing for another; this records the fact that produced it -- the releasable's whole tag prefix changed, so every historical tag spelled with the old name belongs to this releasable too. It is a tag-SPELLING fact, never an identity change: rlsbl release reconcile still repairs the refs of releases made under the old spelling, which is exactly what an identity-transition would forbid.
It goes in the repository-scoped record rather than the releasable's own, because after the rename the releasable's state directory exists only under the NEW name -- and the old name is the spelling a reader with an old tag in hand will be looking up. That is also where rlsbl transition record --releasable-rename writes it, so the fact has one home whichever wrote it.
Idempotent by CONTENT: the same rename is not appended twice, so the command's own re-run path (a crash between the local commit and the tag push) heals without duplicating the record.
#_finish_alias_tag
def _finish_alias_tag(root, old_tag, new_tag, remote, *, push_timeout, releasable_name=None)Create the boundary alias tag, record it, and push it, idempotently.
Returns a status dict describing what was (or would have been) done.
#_unmanaged_history_note
def _unmanaged_history_note(old_prefix, new_prefix)Return the note printed after a prefix-changing rename.
#_check_no_inflight
def _check_no_inflight(root, releasables)Hard-error when a release is in flight (workspace or member state).
#_announce_rename
def _announce_rename(old, new, alias_tag)Print what the rename is about to do, before any mutation.
An announcement, not the consent step. Consent belongs to the framework: monorepo rename-releasable declares itself consequential, so a real run is confirmed before it starts. This print exists so that confirmation is an informed one -- it names the local rewrite and, when there is one, the alias tag that will be pushed to the remote.
#rename_releasable
def rename_releasable(workspace_root, old_name, new_name, *, dry_run=False, remote='origin')Rename releasable old_name to new_name in a monorepo workspace.
See the module docstring for the full ordered flow. Returns a result dict describing what was done (or, in dry-run, what would be done).
Raises WorkspaceError on any preflight failure.