On this page
The questions answered here alone: whether a path is rlsbl's own tool-owned bookkeeping, which workspace member owns it (most specific path wins), and what a releasable's changelog scope claims beyond its members' files.
#rlsbl.ownership
#rlsbl.ownership
File ownership: the tool-owned exempt set, single-owner attribution, and the scope claims a releasable makes on its own state directory.
The questions answered here, and nowhere else:
- Is this path tool-owned? :func:
is_tool_owned_pathdecides from static
path rules alone -- no config reads, no git calls, no workspace lookups. A tool-owned path is rlsbl's own bookkeeping (changelog state, release state, the workspace directory, the generated CI router), so it never needs a changelog owner and never participates in attribution.
- Which workspace member owns this path? :func:
owner_ofanswers with
exactly one member: the most specific declared member path wins, and the root member (path = ".") owns everything no other member claims.
- Is this path inside the scope I am asking about?
:class:OwnershipScope answers, and it is the only question a releasable participates in: a releasable is not a member, so it owns no file, but its scope claims its own state directory (.rlsbl-monorepo/releasables/<name>/) on top of its members' files.
Kept free of imports from git, workspace, targets and checks so every layer can depend on it -- :mod:rlsbl.workspace_types is the one exception, for the directory names, and it imports nothing but :mod:rlsbl.errors. Commit-level attribution (which needs git) is in :mod:rlsbl.git_util, which imports this module.
#OwnershipError
File attribution could not be performed.
#releasable_state_dir
def releasable_state_dir(releasable_name) -> strRepo-relative path of a releasable's own state directory.
.rlsbl-monorepo/releasables/<name> -- the changelog, the release archives, the version, the transition record and the config of one releasable. No member's declared path claims it (it is tool-owned, and a releasable is not a member), so it is the releasable itself that claims it, at the scope level: see :meth:OwnershipScope.for_releasable.
#state_dir_releasable
def state_dir_releasable(path) -> str | NoneThe releasable whose state directory path sits in, or None.
The inverse of :func:releasable_state_dir. A path under .rlsbl-monorepo/releasables/<name>/ belongs to no member -- it is tool-owned, and a releasable is not a member -- so this is the only way to answer "whose is this?" for it. Used by messages that have to name the owner of a file the asking scope does not claim.
#normalize_path
def normalize_path(path) -> strReturn path with \ separators folded and trailing slashes gone.
#is_root_path
def is_root_path(path) -> boolTrue when path is one of the spellings that mean the repository root.
The root member declares path = "."; "" and "./" are the other accepted spellings of the same territory. Anything that prefixes a member path has to ask, because prefixing the root produces a path with a leading ./ that is not the repository root's real spelling -- and for a git tag it is not even a legal ref name.
#tool_owned_rule
def tool_owned_rule(path) -> str | NoneReturn the static rule making path tool-owned, or None.
The returned string is the rule itself (".rlsbl/changes/**", "CHANGELOG.md"), suitable for putting in a message that has to explain why a path needs no owner. Root-relative rules answer only for a path at the repository root; the rest answer at any depth.
#is_tool_owned_path
def is_tool_owned_path(path) -> boolIs path rlsbl's own bookkeeping, and therefore exempt from ownership?
#member_path
def member_path(member) -> strReturn a member's declared path, normalized ("" for the root member).
#member_name
def member_name(member) -> strReturn a member's name.
#is_root_member
def is_root_member(member) -> boolDoes member declare the repository root as its territory?
#member_prefix
def member_prefix(member) -> strReturn the path prefix a member's files carry.
"" for the root member -- it claims every path -- and "pkg/" for a member at pkg. The root member's prefix used to be computed as "./", which matches no path git ever prints, so a root member owned nothing at all.
#find_root_member
def find_root_member(members)Return the root member of members, or None when there is none.
#owner_of
def owner_of(filepath, members)Return the single member owning filepath, or None.
None means one of two things, both of which the caller must be able to live with: the path is tool-owned (:func:is_tool_owned_path), or the member list has no root member and no declared path claims the file. A loaded workspace always has a root member, so within a real workspace only the first case occurs.
Most specific wins: with members at ., pkg and pkg/inner, the file pkg/inner/a.py belongs to pkg/inner alone.
#member_for_directory
def member_for_directory(dirpath, members, *, include_root)Return the member whose territory a directory falls in.
The same most-specific-path rule :func:owner_of uses, minus the tool-owned exclusion: a directory is a place to run a command from, not a file needing a changelog owner.
include_root decides whether the root member may answer. It is mandatory because the two questions it separates are genuinely different: file attribution always gives the root member the residual, while "which project am I standing in?" is a question several commands answer with "none of them, you are at the workspace root" and act on. Callers state which they are asking.
#_most_specific_claim
def _most_specific_claim(normalized, members)The most specific member path claiming normalized; root is the residual.
#owner_name_of
def owner_name_of(filepath, members) -> str | NoneName of the member owning filepath, or None (see :func:owner_of).
#owner_names_of_files
def owner_names_of_files(files, members) -> setReturn the set of member names owning any path in files.
#OwnershipScope
A question asked of attribution: which members' files am I after?
Attribution needs the whole member list to answer at all -- a file under pkg/inner belongs to pkg/inner even when the caller only cares about pkg, and a root file belongs to the root member even when the caller only cares about pkg. Handing a function the members it cares about and nothing else is what let a file be claimed by two members at once, so the two halves travel together: members is every member in the workspace, owned names the subset in scope.
state_dirs is the third half, and the one thing here that is not a member question. A releasable's own state directory -- its changelog, its release archives, its version -- belongs to no member: no declared path claims it, and inventing a phantom member for it would break the invariant that every file has exactly one member owner. A RELEASABLE's scope claims it directly instead, so archiving that releasable's release file and finalizing its changelog are commits inside that releasable's scope rather than outside every scope in the workspace.
#for_members
def for_members(cls, all_members, scope_members) -> 'OwnershipScope'Scope covering scope_members, claiming no state directory.
#for_releasable
def for_releasable(cls, all_members, scope_members, releasable_name) -> 'OwnershipScope'Scope covering a releasable: its members AND its state directory.
#for_member
def for_member(cls, all_members, one_member) -> 'OwnershipScope'Scope covering exactly one member.
#owner_name_of
def owner_name_of(self, filepath) -> str | NoneName of the member owning filepath (any member, in scope or not).
#claims_state_dir
def claims_state_dir(self, filepath) -> boolDoes filepath sit inside a state directory this scope claims?
#claims
def claims(self, filepath) -> boolIs filepath in scope -- owned by a member, or in a state directory?
The two answers cannot collide: a state directory is tool-owned, so it has no member owner to disagree with. Being claimed here says which releasable a path belongs to; it says nothing about whether a changelog entry is REQUIRED for it, which the tool-owned exempt set still answers with "no".
#claims_any
def claims_any(self, files) -> boolIs any path in files owned by a member in scope?
#owned_members
def owned_members(self) -> listThe in-scope members themselves, in workspace declaration order.
#describe
def describe(self) -> strA short human name for the scope, for error messages.
A releasable's state-directory claim is named alongside its members: the directory belongs to no member, so a description built from the member names alone reads as if it were outside the scope -- and the message that carries it would then contradict what :meth:claims answers.