Documentation
¶
Overview ¶
Package overlay implements the overlay operations behind `bentoo overlay`: the git workflow (status, add, commit with a generated message, push, pull), version renames, Manifest regeneration, pruning of superseded ebuilds, and compare, which reads each package against a baseline repository, classifies how it diverges and, with a model available, reviews the divergence.
Index ¶
- Constants
- Variables
- func AnnotateAuthorship(report *CompareReport, prov provider.Provider, opts CompareOptions)
- func AnnotateBaseline(report *CompareReport, prov provider.Provider, opts CompareOptions)
- func AnnotateRealignVerdicts(ctx context.Context, report *CompareReport, rev RealignReviewer, ...)
- func AnnotateReviews(ctx context.Context, report *CompareReport, reviewer DivergenceReviewer, ...)
- func CandidateDeclarations(axes []AxisFinding, declared []DeclaredDivergence) []string
- func CandidateDeclarationsWithVerdict(r CompareResult) []string
- func Commit(ctx context.Context, cfg *config.Config, message string) error
- func CommitWithExecutor(ctx context.Context, cfg *config.Config, message string, ...) error
- func EstablishFindings(report *CompareReport)
- func EvaluateDropWhen(condition, gentooTree, atom string) (met, checkable bool)
- func FormatManifestResult(result *ManifestResult, dryRun bool) string
- func FormatRenamePreview(result *RenameResult, isGlobalSearch bool) string
- func FormatRenameResult(result *RenameResult, dryRun bool) string
- func FormatStatus(statuses []PackageStatus) string
- func FormatValidationError(result *ValidationResult, path string) string
- func GenerateCommitMessage(changes []Change, files []RepoFileChange) string
- func GenerateMessage(changes []Change) string
- func HasEbuildChanges(changes []Change) bool
- func LocateBaselineTree(candidate string) (string, error)
- func MarkBaselineSkipped(report *CompareReport, lookedFor string)
- func PushDryRun(ctx context.Context, cfg *config.Config) (string, error)
- func ReduceDiff(our, baseline string, move *VersionMove) (Classified, []Hunk)
- func ShouldBlockForVersionFiles(versionFiles []VersionFile, force bool) bool
- func StatusLabel(code string) string
- type AddResult
- type Authorship
- type AxisFinding
- type Baseline
- type CategoryNotFoundError
- type Change
- type ChangeType
- type Classified
- type Collision
- type CollisionError
- type CompareOptions
- type CompareReport
- type CompareResult
- type CompareStatus
- type Conflict
- type ConflictError
- type Content
- type ContentResult
- type DeclaredDivergence
- type Divergence
- type DivergenceReviewer
- type EbuildMatcher
- type Effect
- type EffectSource
- type FileChange
- type FileKind
- type FileType
- type Finding
- type FindingKind
- type Hunk
- type LocalRepo
- type LookupCause
- type ManifestOptions
- type ManifestResult
- type ManifestScope
- type ManifestUpdate
- type MatchResult
- type OtherRepo
- type PackageStatus
- func GroupStatusEntries(entries []git.StatusEntry) []PackageStatus
- func StagedStatus(ctx context.Context, cfg *config.Config) ([]PackageStatus, error)
- func StagedStatusWithExecutor(ctx context.Context, executor git.GitExecutor) ([]PackageStatus, error)
- func Status(ctx context.Context, cfg *config.Config) ([]PackageStatus, error)
- func StatusWithExecutor(ctx context.Context, executor git.GitExecutor) ([]PackageStatus, error)
- type PatternValidator
- type PruneBatch
- type PruneClass
- type PruneOptions
- type PrunePlan
- type PruneResult
- type PullMode
- type PullResult
- type PushResult
- type Reading
- type RealignNote
- type RealignRequest
- type RealignReviewer
- type RenameError
- type RenameMatch
- type RenameOptions
- type RenameResult
- type RenameSpec
- type RepoFileChange
- type ReviewFailure
- type ReviewNote
- type ReviewOrigin
- type ReviewRequest
- type ValidationError
- type ValidationResult
- type Verdict
- type Verification
- type VersionFile
- type VersionFilesBlockError
- type VersionFilesDetector
- type VersionMove
Constants ¶
const DefaultCompareConcurrency = 10
DefaultCompareConcurrency is the number of packages CompareWithProvider processes in parallel when CompareOptions.Concurrency is not set (<= 0).
const DefaultDistfilesCache = distfiles.DefaultCache
DefaultDistfilesCache is the system path queried, when readable, to skip re-downloading distfiles already present in the portage cache. Used as the default for ManifestOptions.DistfilesCache.
The value lives in internal/common/distfiles alongside the resolution logic that consumes it; it is re-exported here under the name the CLI has always used for the --distfiles-cache flag default.
const DefaultManifestJobs = 10
DefaultManifestJobs is the default number of pkgdev workers run in parallel when ManifestOptions.Jobs is not set (or set to a non-positive value).
Variables ¶
var ( ErrPkgdevNotFound = errors.New("pkgdev not found; install dev-util/pkgdev") ErrManifestNoTargets = errors.New("no packages found to update") ErrManifestInvalidScope = errors.New("invalid manifest scope") )
Errors for manifest operations.
var ( // ErrPullConflict indicates merge conflicts occurred during pull ErrPullConflict = errors.New("merge conflicts detected") // ErrNoRemote indicates no remote is configured ErrNoRemote = errors.New("no remote configured") // ErrDirtyWorktree indicates uncommitted changes block the integration step ErrDirtyWorktree = errors.New("uncommitted changes in the overlay") )
var ( // ErrReviewTimedOut: the reviewer's own budget elapsed. ErrReviewTimedOut = errors.New("review timed out") // ErrReviewCouldNotStart: the reviewer's process never started. ErrReviewCouldNotStart = errors.New("review could not start") // ErrReviewExitedNonZero: the reviewer's process exited with a failure status. ErrReviewExitedNonZero = errors.New("review exited non-zero") // ErrReviewUnusableReply: the reviewer answered with nothing usable. ErrReviewUnusableReply = errors.New("review reply unusable") )
The review-outcome sentinels a DivergenceReviewer (or RealignReviewer) wraps to say why it failed. They are declared here, not in the package that runs the model, so this package reads no client's errors (TestOverlayImportsNoAutoupdate); cmd/ translates the client's outcome into these.
var ErrNoBaselineTree = errors.New("no ::gentoo tree to compare against")
ErrNoBaselineTree is the run-level outcome: there is no ::gentoo tree to read, so NOTHING was examined and the review could not do its job.
It is a SENTINEL because the caller branches on it. This is the one condition the command exits non-zero for, and an exit code cannot be derived by matching on a message. Everything else this package knows about a baseline is a per-package VALUE and never an error — Baseline.Found is false for the 84 packages ::gentoo does not carry, Baseline.Unexamined says why a baseline that exists would not read — so no per-package non-answer can reach the exit code through here. The two states are returned through different channels on purpose; collapsing them would make one unreadable ebuild fail the whole run.
var (
ErrOverlayPathNotSet = errors.New("overlay path is not configured")
)
Errors for rename operations
var ( // ErrUpToDate indicates the repository is already up-to-date with remote ErrUpToDate = errors.New("everything is up-to-date") )
Functions ¶
func AnnotateAuthorship ¶
func AnnotateAuthorship(report *CompareReport, prov provider.Provider, opts CompareOptions)
AnnotateAuthorship records, for every package whose two ebuilds were compared and found to differ, whether the overlay's own content proves the difference originates here — and which file proves it.
It runs ONLY over Verified == VerifiedDiffers: an identical or never-compared package holds no difference to attribute, so every other result keeps the zero Authorship — "the report cannot tell" — as an API-only run does.
It returns nothing, deliberately. Every way this can fail (an unreadable ebuild, an upstream tree that would not resolve) is a way of not knowing and reads as unproved, never an error — verifyAgainstLocalContent already reads an unreadable file as "nothing is known", and one unreadable file must not mean two different things in two neighbouring functions.
It runs AFTER CompareWithProvider returns, so nothing here is concurrent and the report is already sorted. Nothing it writes can change a Verdict: it adds two fields to a finished result and edits none.
func AnnotateBaseline ¶
func AnnotateBaseline(report *CompareReport, prov provider.Provider, opts CompareOptions)
AnnotateBaseline measures every compared package against the ::gentoo ebuild it should be compared with, and writes the answers onto the finished report: which baseline was used, how the two ebuilds differ structurally, what our ebuild declares about those differences, and what the three-way reduction made of the diff. It follows AnnotateAuthorship and AnnotateReviews.
It runs ONLY when the review was requested: FormatReport never learns the flags, so every field written here renders nothing at its zero value, and nothing may call this from CompareWithProvider — that keeps `overlay compare` without `--realign` byte-identical. It runs after the sorted comparison and changes no Verdict, Status, count or ordering.
It ends by rebuilding report.Findings with EstablishFindings; rebuilding rather than appending keeps a second call from doubling the list. A per-package failure is a way of NOT KNOWING and leaves the zero value; the run-level failure (no ::gentoo tree) is recorded by MarkBaselineSkipped, and the caller derives the exit code from it. It reads only files in the two trees: no command, no host, no git (the local ::gentoo is a shallow clone).
func AnnotateRealignVerdicts ¶
func AnnotateRealignVerdicts(ctx context.Context, report *CompareReport, rev RealignReviewer, prov provider.Provider, opts CompareOptions)
AnnotateRealignVerdicts attaches a model's judgement to every undeclared or expired divergence: whether it is still justified and why, and the ::gentoo text that would replace it when it is not. A nil reviewer returns at once.
It returns NO ERROR: an unreachable model is exit 0, because the deterministic half of the report is complete without one, and formatRealignSummary says the verdicts are missing. Every failure is a way of HAVING NO VERDICT, left EMPTY and counted, never read as "justified". Like AnnotateBaseline, it ends by rebuilding report.Findings.
Cost (237 packages and zero declarations on the first run) is bounded by ONE model call per package, the cache keyed on the two files' content, and the number printed BEFORE the pass starts. It is SEQUENTIAL: inside the comparison it would inherit ten-way concurrency — ten concurrent `claude` processes and a report order that depends on which finished first.
It runs AnnotateBaseline first when the report is not annotated yet. It writes NO FILE — the overlay auto-commits, so the baseline text is a proposal — and decides nothing: RealignVerdict per result plus two run counters.
func AnnotateReviews ¶
func AnnotateReviews(ctx context.Context, report *CompareReport, reviewer DivergenceReviewer, prov provider.Provider, opts CompareOptions)
AnnotateReviews attaches a model's reading to every undeclared divergence the report holds — where the difference came from, what it does, and, when it is ours, the `patched` text that would declare it.
A NIL REVIEWER RETURNS IMMEDIATELY: `--no-review` and "no `claude` on PATH" then share one no-op path instead of two conditions in cmd/ that could disagree. It runs AFTER CompareWithProvider and SEQUENTIALLY, so commentary order never depends on which of ten concurrent `claude` processes finished first; if that grows too slow, the answer is a bounded worker pool.
It returns NOTHING. Every failure is a way of having no reading: the row records ReadingFailed, the deterministic report goes out unchanged, and the exit status does not move. It warns about no review outcome — each is a fact on the row already — only about the cache having nowhere to live, a fact about this machine that belongs to no row.
It writes only Review and Reading, so it cannot change a Verdict, a count or the grouping. It writes NO FILE: the declaration is a PROPOSAL, because the overlay repository auto-commits and pushes within minutes.
func CandidateDeclarations ¶
func CandidateDeclarations(axes []AxisFinding, declared []DeclaredDivergence) []string
CandidateDeclarations proposes one `# BENTOO-DIVERGENCE:` block per structural difference that no declaration covers.
It returns text and writes nothing: the overlay auto-commits and pushes within minutes, so a declaration written here would be PUBLISHED unread. A maintainer accepts or edits the candidates.
No model, reviewer or provider is a parameter, so none can be reached: under `--no-review` there is no model description at all, and a candidate needing one would be unsatisfiable on the run that most needs it. The block therefore states WHAT diverges ("ours passes 85 build options, ::gentoo's passes 2"), and the reason is the maintainer's to write.
One candidate per axis, because two divergent axes are two decisions and one blanket declaration would retire both the day either expires. A covered axis produces nothing — re-proposing it trains a maintainer to skip the section — unless its declaration EXPIRED, which earns a fresh candidate. The axis is matched with strings.EqualFold: DeclaredDivergence.Axis keeps the maintainer's spelling verbatim.
func CandidateDeclarationsWithVerdict ¶
func CandidateDeclarationsWithVerdict(r CompareResult) []string
CandidateDeclarationsWithVerdict is CandidateDeclarations enriched with the model's reading, finishing the candidate proposal where the model already is.
CandidateDeclarations builds a block from the axis and the deterministic finding ALONE — no model in its signature — because under `--no-review` no reading exists, and a candidate that needed one would be unsatisfiable on the run that most needs it. This adds to that; called on a result with no verdict it returns exactly what CandidateDeclarations returned.
The block says WHAT diverges; the reason that is acceptable is what a maintainer has to write, and the model's reading is a first draft of it, labelled by realignCandidateReadingLead because the candidate is text somebody may paste into an ebuild as the recorded reason.
It takes the whole result so a renderer cannot pair one package's axes with another's verdict. It writes NOTHING; like everything else in this file it returns text.
func CommitWithExecutor ¶
func CommitWithExecutor(ctx context.Context, cfg *config.Config, message string, executor git.GitExecutor) error
CommitWithExecutor executes a git commit using the provided GitExecutor. This function is useful for testing with mock implementations.
func EstablishFindings ¶
func EstablishFindings(report *CompareReport)
EstablishFindings rebuilds report.Findings from the report, and is what a caller runs after the annotation passes have written back onto it.
The annotation passes (AnnotateAuthorship, AnnotateReviews, AnnotateBaseline, AnnotateRealignVerdicts) run after CompareWithProvider and write facts an earlier finding could not have known; the findings are a pure function of the report, so rebuilding is the whole of keeping up.
It is IDEMPOTENT: the field is replaced, never appended to, and a nil report is a no-op. It is the ONE place report.Findings is written — `overlay compare` calls it again after all passes, so anything a producer appended would be silently discarded; every producer is a pure function this composes instead.
Order: the run-level outcome first, since "nothing was compared against ::gentoo" qualifies everything under it; then the comparison's findings per package; then the baseline review's. Nothing here sorts: Results is already sorted, and a second arrangement is how two renderers come to disagree.
func EvaluateDropWhen ¶
EvaluateDropWhen answers a declaration's exit condition against the local ::gentoo tree — and says whether it answered it at all.
met=false with checkable=true: evaluated, not met. checkable=false: NOT EVALUATED — outside the vocabulary, or the tree could not answer. It is a return value rather than an error because a caller that dropped it would collapse "a human must check this" into "we checked, and no". Read as unmet, a prose condition keeps its divergence alive forever; read as met, it retires deliberate work with no cause at all.
It reads one directory listing and at most one ebuild inside gentooTree, starts no process, resolves no host and never consults git: the local /var/db/repos/gentoo is a shallow clone with no history to read.
func FormatManifestResult ¶
func FormatManifestResult(result *ManifestResult, dryRun bool) string
FormatManifestResult renders a ManifestResult for display.
It has had NO production caller since `overlay manifest` moved onto the report envelope: the counts are values the run hands back (ManifestResult.Ok/Failed) and the rendering moved to internal/common/report. Nothing outside its own three tests calls it.
It stays because four comments elsewhere cite what it DOES as the canonical reading: rename.go on ManifestUpdate.Error (the atom this formatter prints before it); cmd/bentoo/overlay_manifest.go (logger.Info over this function as the sentence that was replaced); and cmd/bentoo/overlay_manifest_report.go on buildManifestReport, twice — the dry-run branch answered BEFORE any count is taken, and the "No packages processed" reading of a nil result. Removing it is a change to those four comments first and to this function second.
func FormatRenamePreview ¶
func FormatRenamePreview(result *RenameResult, isGlobalSearch bool) string
FormatRenamePreview formats the preview for display before confirmation.
func FormatRenameResult ¶
func FormatRenameResult(result *RenameResult, dryRun bool) string
FormatRenameResult formats the rename result for display.
func FormatStatus ¶
func FormatStatus(statuses []PackageStatus) string
FormatStatus renders package statuses as the lines a caller prints, and chooses NO APPEARANCE.
A colour is a decision only a terminal can use — the same status could not then be written to a Markdown file, exported as JSON, diffed or counted — so the library no longer makes it, and whoever is printing does. Composing the facts into a line (category joined to package, status in brackets) is what this function is for, so that stayed.
The result is byte-identical to what the command printed OFF A TTY, since fatih/color returns bare text there — so every pipe, log and CI run reads exactly what it read before. The CLI's presentation is restyled through the report renderers; reintroducing colour here would put the decision back in the library that just gave it up.
func FormatValidationError ¶
func FormatValidationError(result *ValidationResult, path string) string
FormatValidationError formats a validation result into a user-friendly error message
func GenerateCommitMessage ¶
func GenerateCommitMessage(changes []Change, files []RepoFileChange) string
GenerateCommitMessage generates a commit message from both package (ebuild) changes and non-ebuild file changes. Parts are joined with ", " in the order add, del, mod, up, down — packages first within each action, then files.
func GenerateMessage ¶
GenerateMessage generates a commit message from a list of package changes. Kept for backward compatibility; see GenerateCommitMessage for the combined form that also renders non-ebuild file changes.
func HasEbuildChanges ¶
HasEbuildChanges checks if there are any ebuild changes in the list
func LocateBaselineTree ¶
LocateBaselineTree resolves candidate to the local ::gentoo repository, or reports that there is none to compare against.
A tree is RECOGNISED and never assumed: the directory has to carry Portage's own repository marker, which tells a synced repository from an empty directory standing where one is meant to be. What it accepts it returns AS GIVEN — cleaned, never replaced — because the report names the tree the review actually read.
The refusal wraps ErrNoBaselineTree and NAMES the path and the marker: a mistyped path and a repository that was never synced need opposite fixes, and the message says which one it found.
It stats two paths and reads nothing at all: no command, no host, no git, exactly as ResolveBaseline does and for the same reason.
func MarkBaselineSkipped ¶
func MarkBaselineSkipped(report *CompareReport, lookedFor string)
MarkBaselineSkipped records on the report that the review was SKIPPED because no ::gentoo tree could be located, naming what was looked for.
It is a function rather than a field the caller assigns so the wording is written once, beside the marker it names. It never records a per-package state: an unreadable baseline ebuild is Baseline.Unexamined on that package, or else a run that examined 320 packages would report having examined none.
It leaves the outcome as a FINDING as well as a field: a consumer walking report.Findings — an export, a count, a second renderer — would otherwise be told nothing about a run that compared nothing. It asks EstablishFindings to rebuild the list rather than appending, because that is the ONE place report.Findings is written and `overlay compare` calls it again later, which would discard an appended entry. Rebuilding is idempotent, which makes it safe here AND in AnnotateBaseline, which calls both.
func PushDryRun ¶
PushDryRun shows what would be pushed without actually pushing
func ReduceDiff ¶
func ReduceDiff(our, baseline string, move *VersionMove) (Classified, []Hunk)
ReduceDiff compares our ebuild against its baseline and says, difference by difference, what caused each one — so that the model is asked only what deterministic work could not answer. our and baseline are PATHS, as in CompareAxes, because the versions are in the filenames.
- It diffs the FULL CONTENT whether or not the versions match; everything below only labels the differences, never decides whether they are sought.
- Same version: no version move exists, so every difference is ours by construction (the nodejs case: 492 differing lines, all ours).
- Different versions with a usable third point: a hunk of ours that matches a hunk of the move is that move showing through; what survives is ours.
- Different versions, no usable third point: the whole diff is unclassified and the report says it was unreduced.
A third point wider than the baseline-to-us distance would classify deliberate work as noise, so it subtracts NOTHING (Reduced=false). Matching is by exact hunk text and errs toward ours: under-subtraction costs a question, over-subtraction deletes a decision. No error return: an unreadable ebuild has nothing to classify; Baseline.Unexamined already reports it.
func ShouldBlockForVersionFiles ¶
func ShouldBlockForVersionFiles(versionFiles []VersionFile, force bool) bool
ShouldBlockForVersionFiles determines if the operation should be blocked due to version files being detected. Returns true if operation should abort, false if it should proceed.
func StatusLabel ¶
StatusLabel returns a human-readable label for a git status code
Types ¶
type AddResult ¶
type AddResult struct {
Added []string // Successfully added files
Errors []error // Errors encountered during add
}
AddResult contains the result of an Add operation
func AddFiles ¶
AddFiles stages files for commit in the overlay repository. If no paths are provided, it defaults to adding all changes (equivalent to "git add ."). When paths are provided, only those specific paths are staged. Returns a structured result with added files and any errors encountered.
type Authorship ¶
type Authorship int
Authorship is what the two package directories prove about WHO WROTE the difference — a different question from Verification, which only says that a difference exists. Verification compares two symmetric files and so can never answer it; the overlay's files/ tree sometimes can.
The vocabulary lives here with the rest of the compare types, exactly as Verification does, while the pass that fills it is wired in separately (AnnotateAuthorship, authorship.go).
const ( // AuthorshipUnproved means nothing in the content proves where the // difference came from. It is the ZERO VALUE by construction, so every // result nobody examined — the identical ones, every API-only run — reads as // "cannot tell" rather than as a claim about anybody. It is NEVER "the // change is upstream's". AuthorshipUnproved Authorship = iota // AuthorshipOverlay means our ebuild references a file under ${FILESDIR} // that ::gentoo does not provide for that package. A reference to a patch // upstream never had cannot have been inherited from upstream, which is the // one thing two ebuilds and two directories can prove between them. AuthorshipOverlay )
type AxisFinding ¶
type AxisFinding struct {
// Axis is the property that differs: inherit, options, iuse or deps.
Axis string
// Detail is what the difference IS, always stated in the same direction —
// what we added on top of ::gentoo, and what ::gentoo has that we dropped.
// A bare "the inherit lines differ" would not have told anyone which 85
// options they had just taken over by hand.
Detail string
}
AxisFinding is one structural difference between our ebuild and the ::gentoo baseline, on one of the four structural axes.
It renders NOTHING at its zero value, and a nil slice of them is the "nothing to say" state rather than a missing answer. That is what lets the field ride on an existing result without changing a byte of what `overlay compare` prints for a run that did not ask for a baseline review.
func CompareAxes ¶
func CompareAxes(ourPath, baselinePath string) ([]AxisFinding, error)
CompareAxes compares the four structural axes of two ebuilds and returns one finding per axis that differs.
ourPath and baselinePath are the two EBUILD FILES, not their package directories: an axis is a property of one ebuild's text, and a directory would force this to guess which version it was asked about.
No model, no network: it opens two files and nothing else: no process, no host, no git, no reviewer. A provider is not a parameter, so the signature is the guarantee — with `overlay compare --no-review` these findings still answer, and the axis that would have caught issue #33 (see axisInherit) is the cheapest one.
A set difference is ONE finding naming what moved, not one per element: four "we now inherit X" lines are one sentence read four times and bury the name that matters. The size of each difference is carried in the detail.
The error return is for a file that will not read, and for nothing else: differing axes are findings with a nil error.
type Baseline ¶
type Baseline struct {
// Repo names the repository the baseline was read from.
Repo string
// Version is the version that repository carries: OUR version when it
// carries it, and the nearest one it does carry otherwise.
Version string
// Path is the ebuild the comparison will actually read, so that "which file
// was this measured against" is answered by the report instead of being
// re-derived by whoever reads it.
Path string
// Distance is how far Version is from ours, in release steps — see
// versionDistance, which states the unit. It is 0 exactly when the baseline
// IS our version.
//
// It is reported because it bounds how much the comparison is worth: a
// baseline one patch release away and a baseline three series away are not
// the same kind of evidence, and against the second one most of the
// difference may be nothing but the version move between the two. The
// report must not let them look alike — a difference measured against
// a far baseline is a question, not a finding.
Distance int
// Found reports whether ::gentoo carries the package at all.
Found bool
// Unexamined is non-empty when a baseline could not be read, and says why.
//
// That is a per-package state and not a run failure, following
// verifyAgainstLocalContent, which already reads an unreadable ebuild as
// NotVerified and exits 0 on the principle that absence of evidence is not
// evidence. Reported as nothing at all, such a package would be
// indistinguishable from one that matches ::gentoo exactly.
Unexamined string
}
Baseline is the ::gentoo ebuild that one of our ebuilds is measured against.
Its zero value is "::gentoo does not carry this package": Found is false and nothing is named, because there is nothing to name. No realignment is ever proposed from a baseline in that state — 84 of the overlay's 321 packages are in it, and they are Bentoo's own work rather than a divergence from anyone's.
Every field is comparable, so the struct is: it is carried by value on a result, and "is this still the zero value" is how a run that requested no review proves it changed nothing.
func ResolveBaseline ¶
ResolveBaseline names the ::gentoo ebuild that atom's `version` in our overlay should be compared against, reading nothing but the tree it is given.
The rule is two lines. If ::gentoo carries our exact version, that ebuild is the baseline and the distance is 0 — the same version is not a candidate among others, it is the answer, so it is matched before any proximity is computed. Otherwise the nearest version ::gentoo does carry is the baseline, and the distance says how far away it is. If ::gentoo carries no version of the package, nothing is named and Found stays false.
It reads one directory listing and one ebuild inside gentooTree — no command, no host, never git: the local /var/db/repos/gentoo is a shallow clone whose history is absent by construction, so the answer is in the tree's CONTENT.
The error return is for a request that cannot be answered AS ASKED — a malformed atom, no tree, no version of ours — and nothing else. Every state of the tree is a value (Found=false, Unexamined), because the caller derives a non-zero exit from an error, and the 84 packages with no counterpart would otherwise turn every review into a failed run.
type CategoryNotFoundError ¶
type CategoryNotFoundError struct {
Category string
}
CategoryNotFoundError indicates that the specified category does not exist.
func (*CategoryNotFoundError) Error ¶
func (e *CategoryNotFoundError) Error() string
Error returns the error message.
type Change ¶
type Change struct {
Type ChangeType
Category string
Package string
Version string
OldVersion string // for up/down
}
Change represents a single package change
func AnalyzeChanges ¶
func AnalyzeChanges(entries []git.StatusEntry) []Change
AnalyzeChanges analyzes git status entries and returns a sorted list of changes. It detects version bumps by finding paired add/delete operations.
type ChangeType ¶
type ChangeType string
ChangeType represents the type of change detected
const ( Add ChangeType = "add" Del ChangeType = "del" Mod ChangeType = "mod" Up ChangeType = "up" Down ChangeType = "down" )
type Classified ¶
type Classified struct {
// VersionMove, Ours and Unclassified are the per-class counts. They always
// sum to the number of hunks returned beside them.
VersionMove, Ours, Unclassified int
// Reduced reports that the classification is an attributed one rather than a
// shrug: either a usable third point was accepted (one of the first two
// preferences for choosing it), or the baseline is our own version and no version noise was
// possible in the first place.
Reduced bool
// Span is the version distance the third point covers, in the release steps
// versionDistance measures — the same unit as Baseline.Distance, because the
// bound compares the two. It is 0 when no third point was offered, and it is
// reported even when the third point was refused: the refusal is only legible
// if the width that caused it is printed beside it.
Span int
}
Classified is what the reduction concluded about one package's differences: how many fell into each class, and how much evidence it had to work with.
The counts are per DIFFERENCE (per hunk), not per line. The size of the difference in lines is already reported beside them, by CompareResult's DiffAdded and DiffRemoved, and a second line count here would be a second answer to a question that already has one.
Reduced and Span are reported together because neither reads correctly alone. Reduced=false with Span=0 is "no third point existed"; Reduced=false with a large Span is "a third point existed and was REFUSED for being too wide", and those are different facts about the same package. A classification whose reach is invisible is indistinguishable from a guess.
It renders nothing at its zero value, which is what lets it ride on an existing result without changing a byte of what a run that asked for no baseline review prints.
type Collision ¶
type Collision struct {
Target string // Full path both sources would be renamed to
Sources []RenameMatch // Every match mapping to Target, in match order
}
Collision represents one target path that two or more matches map to — for example foo-1.0.ebuild and foo-1.0-r1.ebuild both becoming foo-1.1.ebuild, because the matcher strips the revision.
type CollisionError ¶
type CollisionError struct {
Collisions []Collision
}
CollisionError indicates that two or more matched ebuilds map to the same target filename. Renaming them would move each onto the next, so the rename is refused before any file moves, and --force does not override it: unlike a Conflict, there is no pre-existing file the operator could choose to replace — one of the sources itself would be destroyed.
func (*CollisionError) Error ¶
func (e *CollisionError) Error() string
Error implements the error interface. It names, per collision, the category/package, the target filename and every source filename.
type CompareOptions ¶
type CompareOptions struct {
// OnlyOutdated filters results to only show outdated packages
OnlyOutdated bool
// IncludeSynced includes packages that have the same version (up-to-date)
// When true, StatusUpToDate packages are included in results
// This is independent of OnlyOutdated - both can be combined
IncludeSynced bool
// IncludeNotInRemote includes packages that don't exist in remote
IncludeNotInRemote bool
// ProgressCallback, when non-nil, is invoked once per package as that
// package's comparison completes. done is the cumulative count of packages
// finished so far and total is the number of packages in the batch.
// Because CompareWithProvider runs packages concurrently, the callback may
// fire from multiple goroutines and the per-invocation order is not
// deterministic; done is sourced from an atomic counter, so the value
// observed by any single invocation is monotone non-decreasing.
ProgressCallback func(done, total uint64)
// Concurrency bounds the number of packages CompareWithProvider processes
// in parallel. A value <= 0 is treated as DefaultCompareConcurrency.
Concurrency int
// Divergence is what the caller knows about each package, keyed by the bare
// "category/package" atom. The caller builds it once, before the comparison
// starts, and the comparison only reads it — so it is shared across the
// per-package goroutines without a lock.
//
// Absence from the map IS the unknown state: nothing is recorded about that
// package, which is not the same as "recorded as not patched". A nil map
// therefore says nothing is known about anything, which is exactly the
// degraded mode when the registry cannot be read — and costs no special case.
Divergence map[string]Divergence
// OverlayPath is the root of the overlay the compared packages were scanned
// from. PackageInfo carries only Category/Package/Versions/LatestVersion, so
// a check that must open the local ebuild gets its overlay-side root here.
OverlayPath string
// Redact lists values removed from any recorded failure text with
// secrets.Scrub, so a token echoed inside an error reaches no report. nil
// redacts nothing; an empty entry is ignored.
Redact []string
// Logger receives the diagnostics of the passes that take these options —
// the review, realignment and baseline annotations and the review cache
// they open. nil discards them.
Logger *slog.Logger
}
CompareOptions configures the comparison behavior
type CompareReport ¶
type CompareReport struct {
TotalPackages int
ComparedPackages int
OutdatedCount int
NewerCount int
UpToDateCount int
NotInRemoteCount int
ErrorCount int
Results []CompareResult
// Findings is everything the run established for the operator, as VALUES
// rather than composed, coloured lines. One entry per compared package says
// how its two versions relate; a package that also carries a divergence, a
// stale declaration or a registry declaration carries a second entry.
//
// It is a MATERIALISED VIEW of Results: compareFindings is a pure function of
// them, refreshed by CompareWithProvider once sorted and by EstablishFindings
// after the annotation passes have run — a list built earlier would report a
// divergence as unproved after the content had proved it ours.
//
// The ORDER is Results' (category/package), so no renderer invents its own.
// Narrowing Results for display (--only-redundant, --only-patched) narrows the
// TABLE and nothing this field says, as it narrows none of the counts above.
Findings []Finding
// Per-Verdict counts sit ALONGSIDE the per-Status counts above and never
// replace or restate them: the two answer different questions ("how do the
// versions relate?" vs "what should be done about it?"), so every compared
// package is counted exactly once on each axis. Deriving either from the
// other would silently change what the existing summary lines mean.
VerdictKeepCount int
VerdictRedundantCount int
VerdictNeedsRebaseCount int
VerdictUnknownCount int
// BaselineSkipped is non-empty when the review could not run at all: no
// ::gentoo tree could be located, so NOTHING was examined. The text names
// what was looked for, and MarkBaselineSkipped is what writes it.
//
// It is the report's only RUN-level state, and it is deliberately not the
// per-package one. A baseline that exists but will not read is
// Baseline.Unexamined on that package alone and never reaches here:
// collapsing the two would have one unreadable ebuild declare that a run
// which examined 320 packages examined none.
//
// Its ZERO VALUE renders nothing (formatBaselineSkipped), which is what keeps
// a run that requested no review printing exactly what it printed before.
// Without it the same run prints "All packages are up-to-date!" over
// a comparison that never happened.
BaselineSkipped string
// NoBaselineCount is how many results ::gentoo carries no version of at all
// — 84 of 321 packages on the measured overlay. It is written by
// AnnotateBaseline and is, by construction, the number of Results whose
// Baseline.Found is false: any second definition would be a second answer to
// one question.
//
// It is a RUN-level count because the per-package fact cannot be printed. A
// package ::gentoo does not carry has the ZERO Baseline, which is also what
// a package no review examined has, so a per-package line would say "no
// baseline" over every row of a plain `overlay compare`. Counted here it is
// reported once, with its denominator, and renders nothing at 0.
NoBaselineCount int
// RealignAsked and RealignNoVerdict are the realignment review's own
// arithmetic: how many divergences were put to the model, and how many of
// those came back with no verdict. Both are written by
// AnnotateRealignVerdicts and by nothing else.
//
// The second exists because an unreachable model is EXIT 0 and leaves an
// EMPTY RealignVerdict on every package it could not judge — which renders as
// silence, and silence reads as "every divergence was judged and none
// objected". This is the number that says otherwise, and the first is the
// denominator without which it cannot be read: 12 unjudged out of 12 and 12
// out of 237 are different reports.
//
// They are RUN-level for the reason NoBaselineCount is: the per-package fact
// is an absent field, indistinguishable from the same absent field on every
// row of a plain `overlay compare`. Counted here they are stated once and
// render nothing at 0.
RealignAsked int
RealignNoVerdict int
// RealignNoVerdictBy splits RealignNoVerdict by why each divergence came
// back with no verdict, in the divergence review's words (ReviewFailure).
// Its values sum to RealignNoVerdict. It is nil when nothing went
// unanswered, and like the two counts above it is written only by
// AnnotateRealignVerdicts.
RealignNoVerdictBy map[ReviewFailure]int
// Interrupted reports that the run stopped dispatching before it reached the
// end of the package list it was handed: the context fired mid-scan, so some
// packages were never compared at all.
//
// It is the one RUN-level fact CompareWithProvider writes itself: only the
// dispatch loop sees the difference between "the list ended" and "we
// stopped".
//
// Complete is !Interrupted, NEVER NotEvaluated == 0: a run cut short just
// after its LAST package lost nothing, yet was still cut short, and the
// operator who pressed ctrl+c must not read that it ended normally.
//
// The unreached gap is exactly TotalPackages - ComparedPackages, because
// ComparedPackages++ runs for every result reaching the collector — every
// status, filtered or not — so only a worker that never ran is missing.
//
// The field is not named Complete so that false, the zero value of a
// hand-built report, means "not interrupted" rather than "cut short".
Interrupted bool
}
CompareReport contains the full comparison report
func Compare ¶
func Compare(ctx context.Context, localPackages []repo.PackageInfo, client *github.Client, opts CompareOptions) (*CompareReport, error)
Compare compares local packages against a remote GitHub repository
func CompareWithProvider ¶
func CompareWithProvider(ctx context.Context, localPackages []repo.PackageInfo, prov provider.Provider, opts CompareOptions) (*CompareReport, error)
CompareWithProvider compares local packages against an upstream repository using any Provider.
Packages are compared concurrently, bounded by opts.Concurrency (a value <= 0 is treated as DefaultCompareConcurrency). The semaphore is acquired with a context-cancellable select: when ctx is cancelled the remaining packages are not dispatched and the comparison returns the partial report together with the context error, so a SIGINT aborts a long scan. That stop is also recorded on the report itself, as Interrupted, so a caller reading the report alone can tell a partial scan from a complete one. All writes to the shared report from the worker goroutines are mutex-guarded, and results are sorted by category/package before returning so the output is deterministic regardless of completion order.
ctx must be non-nil. Every provider.GetPackageVersions call receives ctx itself, so a lookup ends when the caller's context does.
type CompareResult ¶
type CompareResult struct {
Category string
Package string
LocalVersion string // Version in Bentoo overlay
RemoteVersion string // Version in Gentoo repository
Status CompareStatus
// Verdict is the recommendation for this package.
Verdict Verdict
// Patched reports that a registry entry declares a divergence worth keeping.
Patched bool
// PatchedBy names the registry key that declared it, so a package with
// several entries says which one is speaking.
PatchedBy string
// PatchedReason is the declared text. It is rendered on the declaration line
// beneath the section (formatVerificationFindings' third case), capped at
// patchedReasonCap — unlike PatchedBy, which is printed whole.
PatchedReason string
// Verified is the outcome of comparing the two ebuilds' bytes. It stays
// NotVerified unless a content check ran.
Verified Verification
// DiffAdded and DiffRemoved are the line counts of that difference, ours
// against upstream's: added is what our ebuild has and ::gentoo's does not.
// They are meaningful ONLY when Verified == VerifiedDiffers and are zero
// otherwise — a check that did not run has no magnitude to report, and a
// byte-identical pair has none to find.
//
// They exist because the bare fact "differs" cannot be acted on. A one-line
// difference is almost always ::gentoo revising an ebuild in place, without a
// revbump, after we copied it; a four-hundred-line one is work of our own. The
// report cannot tell those two apart — see formatVerificationFindings — but
// the operator can, at a glance, once the size is on the line.
DiffAdded int
DiffRemoved int
// Authorship is what the overlay's own CONTENT proves about where the
// difference came from. The two ebuilds' bytes cannot say it — they are
// symmetric — but the files/ tree beside them can, so this is filled by
// AnnotateAuthorship (authorship.go) after the comparison, never by it.
//
// AuthorshipUnproved, the zero value, means THE REPORT CANNOT TELL. It is
// never a finding of "the change is upstream's": ::gentoo revises ebuilds in
// place without a revbump, so a difference nothing here proves may still be
// entirely ours and merely unprovable from the files. Reading unproved as
// "not ours" would authorise exactly the removal this check exists to stop.
Authorship Authorship
// ProvedBy names the file that proves it, relative to the package directory
// ("files/<name>"), so the operator can confirm the claim by looking instead
// of re-deriving the path from what ${FILESDIR} expands to. It is
// empty whenever Authorship is unproved: there is then no file to name.
ProvedBy string
// Review is a MODEL's reading of an undeclared divergence: printed beside the
// finding and never an input to anything this report decides. It sits
// on the result for the reason Authorship does — the pass that fills it
// (AnnotateReviews, review.go) runs after the comparison and writes back onto
// the report the caller is holding — and it is deliberately a THIRD field
// beside Authorship rather than a widening of it: Authorship is what the
// overlay's content PROVES, this is what a model SAYS, and a type that could
// hold either would let one be read as the other.
//
// The zero ReviewNote means NOTHING WAS SAID, exactly as AuthorshipUnproved
// does: every result no review reached — every `--no-review` run, every run
// with no `claude` on PATH, every package whose reviewer errored, and every
// finding that is not an undeclared divergence — carries it, and the renderer
// prints nothing for it.
Review ReviewNote
// Reading is WHETHER ANYBODY READ this result, and when nobody did, why
// not. It is a FOURTH field rather than something derived from the three
// above for the reason the doc on Review just gave: the zero ReviewNote
// covers four unrelated causes at once, so "is Review empty?" answers "no
// review reached this" and cannot answer "did anybody read this?" — a
// package nobody asked about and a package whose reviewer was killed both
// carry the zero note, and only this field tells them apart.
//
// It is filled by AnnotateReviews (review.go), the same pass that fills
// Review. Its zero value, ReadingNotRequested, is what every
// result that pass never reached already carries, so a run with no review
// is correct without anything being set.
Reading Reading
// LookupCause is why the upstream lookup failed. It is meaningful only when
// Status == StatusError and is LookupCauseNone otherwise: a lookup that
// succeeded has no cause to report.
LookupCause LookupCause
// ReviewFailure is why the review of this result did not come back. It is
// meaningful only when Reading == ReadingFailed and is ReviewFailureNone
// otherwise; Reading keeps its four values. Filled by AnnotateReviews.
ReviewFailure ReviewFailure
// FailureText is the full Error() text of whatever failed on this row —
// the upstream lookup when Status == StatusError, the review when Reading
// == ReadingFailed — after every CompareOptions.Redact value has been
// scrubbed from it. It is empty on a row with no failure. It is kept whole
// here; only a renderer's table cell may cut it.
FailureText string
// Baseline is the ::gentoo ebuild this package was measured against.
// Its zero value is "no review ran, or ::gentoo does not carry this
// package"; the run-level NoBaselineCount below is what turns the second of
// those into a number, because the two are indistinguishable per package by
// construction.
Baseline Baseline
// Axes are the structural differences between our ebuild and that baseline
// — inherit, options, IUSE, dependencies. A nil slice means nothing
// was compared or nothing differs, which is the same thing to a report that
// prints only what it found.
Axes []AxisFinding
// Classified is what the three-way reduction concluded about the
// differences: how many fell into each class and how much evidence it had.
Classified Classified
// Declarations are the `# BENTOO-DIVERGENCE:` tags our ebuild carries, with
// Expired decided against the ::gentoo tree. They are the
// EBUILD axis and never CompareOptions.Divergence, which is the registry
// one.
Declarations []DeclaredDivergence
// RealignVerdict is a MODEL's opinion on whether an undeclared divergence is
// still justified, and why. It is written by the realignment reviewer and
// never by AnnotateBaseline: a verdict nobody produced is
// worse than none, so an unreachable model leaves it empty.
//
// It is COMMENTARY. Nothing that decides a Verdict or an exit code may read
// it, on the same fence DiffAdded/DiffRemoved already sit behind.
RealignVerdict string
// Others are the repositories other than ::gentoo that carry this package,
// for the 84 of 321 packages ::gentoo does not. They are INFORMATIVE
// ONLY and no realignment is ever proposed from one: a repository
// outside ::gentoo has not been through the same review.
//
// They are filled by OtherRepositories, which needs the list of
// locally available repositories — something AnnotateBaseline is not given
// and deliberately does not go looking for.
Others []OtherRepo
}
CompareResult represents the result of comparing a package between overlays
type CompareStatus ¶
type CompareStatus int
CompareStatus indicates the comparison result
const ( // StatusUpToDate means local version equals remote version StatusUpToDate CompareStatus = iota // StatusOutdated means local version is older than remote StatusOutdated // StatusNewer means local version is newer than remote StatusNewer // StatusNotInRemote means package doesn't exist in remote StatusNotInRemote // StatusError means an error occurred during comparison StatusError )
func (CompareStatus) String ¶
func (s CompareStatus) String() string
String returns a human-readable status
type Conflict ¶
type Conflict struct {
Match RenameMatch
Existing string // Path to existing file
}
Conflict represents a target file that already exists.
type ConflictError ¶
type ConflictError struct {
Conflicts []Conflict
}
ConflictError indicates that target files already exist.
func (*ConflictError) Error ¶
func (e *ConflictError) Error() string
Error implements the error interface.
type Content ¶
type Content struct {
// Result is the finding. Only ContentIdentical may authorise a removal.
Result ContentResult
// Reason says why the result is ContentUnverifiable, or what differs when it
// is ContentDiffers. It is empty when the two copies are identical, since
// there is nothing to explain. When it names a difference it names the
// VERSION or the PATH, never just the package: "zed differs" sends the
// operator to diff a directory, "1.0 differs" sends them to a file.
Reason string
// Checked lists the versions actually compared byte for byte, for the plan to
// print. On a difference the list stops at the version that differed, because
// the comparison stops there too and claiming to have examined the rest would
// be a false report.
Checked []string
}
Content is what one package's content comparison found.
func PruneVerification ¶
PruneVerification reports whether the overlay's copy of pkg carries anything its upstream counterpart does not, comparing the two package directories byte for byte. It answers the only question that may authorise a removal, and it answers it about the WHOLE package: every version the two trees share plus the files/ tree, recursively.
ourDir and theirDir are PACKAGE directories the caller already resolved. This function joins no category or package name of its own — pkg only recognises the "<pkg>-<pv>.ebuild" shape — so registry-sourced traversal is absent by STRUCTURE, not by SplitPackageKey's check. Keep it that way.
Manifest and metadata.xml are never read: distfile hashes vary by revision and the maintainer differs on all 314 packages, so comparing either would mark the whole overlay divergent. verifyAgainstLocalContent (compare.go) compares ONE ebuild for a report; this compares the whole package to decide a deletion, and both are bytes.Equal wherever they overlap, so they agree.
It returns no error by design: every failure is ContentUnverifiable with a Reason, because "no answer" must never read as "identical".
type ContentResult ¶
type ContentResult int
ContentResult is the answer to one question about one package: does deleting our copy of it lose anything?
It is a third axis, separate from both CompareStatus and Verdict, because a Verdict is a statement about VERSIONS ("::gentoo ships the same or more") and this is a statement about BYTES. The two disagree in practice: measured on the live overlay on 2026-08-05, `overlay compare` calls 74 packages redundant and 8 of them carry real local changes — kwin, plasma-desktop and nodejs among them. A removal driven by the verdict alone deletes those changes, which is why only content may ever authorise one.
const ( // ContentUnverifiable means no comparison could be made, so nothing is known // in either direction. It is the ZERO VALUE deliberately: a Content that was // never filled in must not read as permission to delete, and ContentIdentical // is the only value that grants one. NotVerified is the zero Verification for // the same reason. ContentUnverifiable ContentResult = iota // ContentIdentical means every version the two trees share is byte-identical // and their files/ trees match, so our copy holds nothing that is ours. ContentIdentical // ContentDiffers means at least one shared version, or one path under files/, // is not the same on both sides. Content.Reason names which one. ContentDiffers )
func (ContentResult) String ¶
func (r ContentResult) String() string
String returns the plan's word for the result.
type DeclaredDivergence ¶
type DeclaredDivergence struct {
// Axis is the property the divergence is about, AS THE MAINTAINER WROTE IT.
// The tag's documentation spells it upper-case (`INHERIT:`) and the axis
// names are lower-case (`inherit`); neither side is authoritative, so nothing is
// normalised here and every consumer compares it with strings.EqualFold.
// Rewriting the word would also cost the report the maintainer's own
// spelling for the sake of a comparison that is one call away.
Axis string
// Reason is the maintainer's words, verbatim and uncapped. A declaration
// without one is what declarations exist to abolish, so an empty Reason never
// reaches this field: the tag is reported as malformed instead.
Reason string
// DropWhen is the condition from the `drop-when:` continuation line, empty
// when the declaration states none. Empty is the ordinary case — most
// divergences have no end in sight — and it is NOT an error: inventing a
// condition would retire a divergence nobody retired.
DropWhen string
// Expired reports that DropWhen has been checked against the ::gentoo tree
// and is met. It is written by EvaluateDeclarations, never by the parser:
// expiry is a question about the tree, and reading it off the ebuild's own
// text would answer it from the wrong source.
Expired bool
}
DeclaredDivergence is one `# BENTOO-DIVERGENCE:` tag read out of an ebuild's text: the reason a particular change exists, and when it stops applying.
It is NOT overlay.Divergence ¶
Divergence (compare.go) is the REGISTRY axis — Patched, Reason, Entry — supplied by the caller from packages.toml, one answer per package, feeding the `redundant` verdict. This one is the EBUILD axis: one answer per hunk, living beside the code it describes so it travels when the ebuild is copied to a new version, which is exactly when a divergence is silently inherited. Two names because they answer two questions; merging them would make a registry entry and a maintainer's note mean the same thing.
func EvaluateDeclarations ¶
func EvaluateDeclarations(declared []DeclaredDivergence, gentooTree, atom string) []DeclaredDivergence
EvaluateDeclarations returns the declarations with Expired decided against the local ::gentoo tree.
A declaration is EXPIRED when its condition was evaluated and is met, and in no other case. An unmet condition leaves the divergence declared, because it still earns its place; a condition the vocabulary does not cover leaves it declared too, because nothing was evaluated that could retire it.
Expiry is what puts a divergence back in front of the model — an expired declaration is treated as UNDECLARED from then on, where a declared and unexpired one is never sent — so the field is written here, in production. Left to each reader to derive from `met && checkable`, the one reader that got it wrong would silence a divergence or resurrect one, and the struct would carry a field nothing ever set.
Everything else comes back VERBATIM: the axis, the reason and the condition are the maintainer's words, and this pass reads the tree and reports rather than editing anybody's ebuild. The input slice is not modified.
func ParseDivergences ¶
func ParseDivergences(ebuildPath string) ([]DeclaredDivergence, error)
ParseDivergences reads one ebuild and returns the divergences it declares, in this shape:
# BENTOO-DIVERGENCE: INHERIT - gstreamer-meson does not handle the qt6 option list # drop-when: gentoo-version >= 1.29
(`INHERIT: <reason>` is read too.) The optional second line must immediately follow the first. A parsed declaration is questioned no further.
The tag is recognised only on a line that is WHOLLY a comment, outside any quoted string or heredoc body: an ebuild that echoes its own documentation writes the tag as data, and since declared divergences are quiet, a phantom tag would buy a package permanent silence on an axis nobody declared. Nothing is expanded, evaluated or executed.
A malformed tag comes back as an error naming the file, line and text, while the well-formed declarations beside it are still returned (as in buildDivergenceMap): dropping it silently would read as "never declared" and ask the maintainer to declare it again on every run, forever.
type Divergence ¶
type Divergence struct {
// Patched reports that at least one registry entry for this atom declares
// a divergence that must survive a bump.
Patched bool
// Reason is the declared text. It reaches the report through
// CompareResult.PatchedReason and is capped there, not here: this type
// carries what the registry said, and the width of a terminal is not its
// concern.
Reason string
// Entry names the registry key that declared it, so a package with several
// entries says which one is speaking.
Entry string
}
Divergence is what the caller knows about one atom's relationship to the upstream repository. It is supplied by the caller: this package never reads packages.toml, so internal/overlay keeps zero import edges to internal/autoupdate in either direction.
There is deliberately no Known field. Absence from the caller's map IS the unknown state, and it reaches deriveVerdict as its known parameter — a boolean stored beside the map would be a second copy of what the lookup already reports, and a second thing to desynchronise.
type DivergenceReviewer ¶
type DivergenceReviewer interface {
ReviewDivergence(ctx context.Context, req ReviewRequest) (ReviewNote, error)
}
DivergenceReviewer is the seam through which a model's reading of one divergence reaches this package: all internal/overlay knows about an LLM.
It is declared HERE, in the consumer, rather than as a method on autoupdate.LLMProvider: a method there would force openai.go and ollama.go to implement a review they never serve, and holding one would make this package import autoupdate — an edge kept absent in BOTH directions (review_test.go fences it). The production adapter lives in cmd/, which imports both halves.
The CONTEXT is the caller's, so a cancelled compare aborts the review rather than holding the run open. A nil DivergenceReviewer is not an error anywhere: it is how `--no-review` and an absent `claude` CLI reach one no-op path.
An implementation that knows WHY a review failed says so by wrapping one of ErrReviewTimedOut, ErrReviewCouldNotStart, ErrReviewExitedNonZero or ErrReviewUnusableReply, or context.Canceled for a run that was stopped, in the error it returns; anything else is recorded as ReviewOther. The sentinels are this package's own so it never has to import the client that fails.
type EbuildMatcher ¶
type EbuildMatcher struct {
// contains filtered or unexported fields
}
EbuildMatcher finds ebuilds matching a rename specification.
func NewEbuildMatcher ¶
func NewEbuildMatcher(overlayPath string) *EbuildMatcher
NewEbuildMatcher creates a new EbuildMatcher for the given overlay path.
func (*EbuildMatcher) Match ¶
func (m *EbuildMatcher) Match(spec *RenameSpec) (*MatchResult, error)
Match finds all ebuilds matching the specification. It searches within the specified category (or all categories if "*") and matches both package name pattern and exact old version. Scan errors are collected as warnings in the result rather than aborting.
type Effect ¶
type Effect struct {
// Text is the sentence, at full length and with its internal whitespace
// collapsed to single spaces.
//
// The collapse is SANITISATION, not layout. Some of this text is a language
// model's, and the report's structure is its lines — "⚠ " opens a finding
// this tool stands behind — so a newline inside a summary would forge a
// second finding line about a package that need not exist. That is true of
// every line-oriented consumer, including one reading the JSON export, so it
// is done once here rather than hoped for at each renderer. What is NOT done
// here is capping: a width is a terminal's business and the export has none.
Text string
// Source is where the sentence came from, and so how far it may be trusted.
Source EffectSource
}
Effect is what a divergence DOES, together with who says so.
Why the two are separate values ¶
An operator reading a divergence finding is deciding whether to keep the overlay's copy or let `prune` delete it. "ours: files/nodejs-26.7.0-gcc17.patch" does not support that decision; "slots the install under /usr/lib/node-${SLOT} and wires it to app-eselect/eselect-nodejs, so several majors coexist" does. The first is a filename, the second is the effect — and they are not interchangeable.
The effect alone is still not enough, because the same sentence can arrive from sources an operator must weigh differently: a maintainer's own declaration is a commitment, and a language model's reading is a guess that has been wrong before. Composed into one string they become indistinguishable, and the operator acts on the guess as though it were the commitment. Held apart, a renderer can label the guess — which is what the two `↳` leads in compare.go have always done, and this is those leads expressed as data.
type EffectSource ¶
type EffectSource int
EffectSource ranks the three ways the report can come to know what a divergence does, most trusted first.
The order is a statement about EVIDENCE and not about presentation: a maintainer wrote the declaration and stands behind it, a model produced a reading and stands behind nothing, and an absent answer is an absent answer. A renderer is free to show them however it likes, and is not free to present the second as the first.
const ( // EffectUnstated means nobody said what the divergence does. It is the ZERO // value, so a finding whose effect nobody established says "not known" // rather than accidentally claiming the empty string is the answer. // // It is not a dead end for the operator: the identifiers needed to find out // — the atom and the version — are on the finding, and the renderer is what // names the command that would show the difference. That sentence names a // CLI, and a library that named one would be deciding what its caller is. EffectUnstated EffectSource = iota // EffectDeclared is the maintainer's own reason — the registry's `patched` // text, or the ebuild's own `# BENTOO-DIVERGENCE:` tag, which is the same // commitment written in the other file. It is the most trusted source there // is, because somebody committed it on purpose. EffectDeclared // EffectReviewed is a MODEL's reading of the two ebuilds — a reading, never a // proof, and never an input to anything this report decides. A // renderer that drops the distinction invites the operator to act on a guess. EffectReviewed )
type FileChange ¶
type FileChange struct {
Type FileType // ebuild, manifest, metadata, files, other
Name string // filename
Status string // Added, Modified, Deleted, Renamed, Untracked
}
FileChange represents a single file change within a package
type FileKind ¶
type FileKind string
FileKind classifies a non-ebuild overlay file by its top-level directory.
func ClassifyFile ¶
ClassifyFile returns the FileKind for a path relative to the overlay root. Classification is based solely on the top-level directory; paths that live inside a category (category/pkg/...) return KindOther because ebuild-aware logic handles them elsewhere.
type FileType ¶
type FileType string
FileType represents the type of file in an overlay
func DetectFileType ¶
DetectFileType determines the type of file based on its path
type Finding ¶
type Finding struct {
// Kind names WHICH finding this is, so a renderer can group, count or
// suppress a class of them without pattern-matching the prose. It is the
// one field every producer sets.
Kind FindingKind
// Atom is what the finding is about: "<category>/<package>", exactly as an
// operator types it.
//
// It is a FIELD and never a fragment of Detail: an identifier formatted into
// a sentence cannot be counted, filtered on, used as a lookup key or
// re-emitted as a JSON field — it can only be printed.
//
// It is empty on a RUN-scoped finding, and nowhere else. Exactly one kind is
// about the run: FindingBaselineSkipped, which says no ::gentoo tree was
// reached. Naming a package would assert something the run never established
// about it, and repeating the sentence on all 321 packages would bury the
// per-package findings. That is NOT a licence for a blank atom elsewhere:
// renderCompareFindings' section caveat is not a Finding for the same reason
// (it is about a subset the CALLER chose), and a package-scoped finding with
// no atom is a bug the tests assert as one.
Atom string
// Detail is what was found, in one sentence the producer wrote and NOBODY
// decorated: no colour, no escape sequence, no leading glyph, no padding, no
// trailing newline. The "⚠ " that opens a warning today is the renderer's,
// and so is the atom in front of the colon.
//
// It overlaps the fields below on purpose, as report.ManifestTarget.Error
// does beside its Package: the fields are the FACTS, and this is the
// producer's own sentence over them, kept verbatim so today's rendered line
// survives unchanged and a later rewrite has a before to diff against.
//
// A divergence's EFFECT and its provenance are NOT flattened in here; they
// are Effect below. "ours: files/nodejs-26.7.0-gcc17.patch" tells an operator
// deciding whether to keep or delete a copy nothing; what must be headlined
// is what the divergence DOES and who says so, and composed into this string
// it could never be re-headlined.
Detail string
// Version is the overlay's version the finding was established at, and
// Upstream is ::gentoo's, both empty when the finding is not about a version
// pair.
//
// Two fields rather than one joined string because they are compared, not
// concatenated: a consumer asking "did the two sides agree?" would otherwise
// have to split a sentence apart to find out.
Version string
Upstream string
// Entry is the registry key that declared the divergence — the identifier an
// operator greps packages.toml for — and empty when nothing declared one.
//
// It is printed VERBATIM wherever it is printed: a shortened key looks usable
// and is not, which is worse than printing none. See patchedReasonCap, which
// makes the opposite call about the reason for the opposite reason.
Entry string
// Added and Removed are the size of the difference between our ebuild and
// ::gentoo's, ours against upstream's, and are zero on every finding that has
// no difference to measure.
//
// They DESCRIBE and never decide: a large diff authorises nothing and
// a small one forbids nothing. compare_diff_counts_fence_test.go holds that
// mechanically over CompareResult's own DiffAdded/DiffRemoved, and these two
// are named differently precisely so the fence keeps watching the source of
// truth rather than a copy of it — a copy nobody may compute on either, but
// one whose misuse the fence was never written to catch.
Added, Removed int
// Classified is how the three-way reduction divided ONE package's
// differences — how many were attributed to the version move, how many to
// us, how many to neither — together with how far the attribution reached.
// Its zero value means NOTHING WAS CLASSIFIED, which is every package of
// every run that asked for no baseline review, and is the same predicate
// (`== (Classified{})`) that keeps those runs' rendering unchanged.
//
// It is the STRUCT and not a sentence, for the reason Atom is a field: "17
// differences against the baseline, 4 of them attributed to nobody" cannot be
// summed across a run, key a consumer's threshold, or reach a JSON export
// without its digits being parsed back out. Detail is the sentence over it.
//
// It DESCRIBES and never decides, like Added and Removed: a large
// unclassified count authorises no realignment and forbids none. Reduced and
// Span are carried with the three counts because they are read TOGETHER — a
// third point refused for being too wide and one never offered both
// attribute nothing, and only the span tells them apart.
Classified Classified
// Authorship is what the overlay's own CONTENT proved about where a
// difference came from. Its zero, AuthorshipUnproved, means THE REPORT CANNOT
// TELL and is never "the change is upstream's" — which is exactly the
// "nothing was established" zero every optional field here needs.
Authorship Authorship
// ProvedBy names the file that proves it, package-relative ("files/<name>"),
// so the claim is confirmed with one `ls`. It is empty whenever Authorship is
// unproved: there is then no file to name.
//
// It is SECONDARY EVIDENCE and not the headline. A filename proves authorship
// and says nothing about what the divergence does, which is what an operator
// deciding to keep or delete needs — see Effect.
ProvedBy string
// Effect is what the divergence DOES and whose sentence that is, held as two
// values so a renderer can say which it is reading.
//
// This is the field the standing knowledge about `overlay compare` asks for
// by name. Its zero — no text, EffectUnstated — is an honest "not known", and
// a renderer meeting it says so rather than filling the silence.
Effect Effect
// Origin is which side a MODEL read the divergence as coming from. Its zero,
// OriginUnknown, means nothing was said, which is every run no review
// reached.
//
// It sits beside Effect rather than inside it because they are not the same
// claim: Effect.Text is what the difference does, Origin is where it came
// from, and a model can be useful about one while saying nothing about the
// other. It is COMMENTARY in both cases — nothing that decides a verdict or
// an exit code may read it.
Origin ReviewOrigin
// Proposal is the `patched` declaration a model offered for a divergence it
// read as ours, empty otherwise.
//
// It is carried AT FULL LENGTH. The rendered line caps it at
// patchedReasonCap so one model's essay cannot decide the width of the
// report, and that cap is the RENDERER's: a value truncated on the way into
// the report is truncated in the JSON export too, where there is no width to
// respect and nothing to un-truncate it from.
Proposal string
}
Finding is one thing a pass under internal/overlay established for the operator, held as a VALUE instead of as a line this package composed and coloured.
A fact held only as a printed line — colour picked, width applied, magnitude interpolated — cannot be exported to JSON, re-laid-out for Markdown or a log, counted or filtered by the command that received it, or tested without capturing stdout. Returning the facts is what lets `overlay compare` move into the report envelope.
The shape is deliberately flat, with optional fields: every producer in this package answers the same two questions — WHAT is this about, and WHAT was found — so a renderer walks one list rather than type-switching over a union, as CompareResult already carries its four annotation passes.
Every optional field's zero value means NOTHING WAS ESTABLISHED, never a claim; a field whose zero would assert something (a CompareStatus, whose zero is "up-to-date") is deliberately NOT here — see Status's absence, below.
type FindingKind ¶
type FindingKind int
FindingKind is which finding this is: the closed vocabulary a renderer groups, counts and suppresses by.
The four values below are `overlay compare`'s. The later producers add their own to the same type rather than to types of their own, so that one renderer can walk a mixed list — which is what a report assembling several passes into one document does.
const ( // FindingCompared is the row-level statement: how the overlay's version of // one package relates to ::gentoo's, and what is recommended about it. It is // established for EVERY compared package, which is what makes the finding // list a complete account of a run rather than a list of its exceptions. // // It is the zero value because it is the finding that claims the least: a // producer that forgot to set a Kind reports "here is a package the run // looked at", not "here is a warning". FindingCompared FindingKind = iota // FindingStaleDeclaration is a registry entry describing a divergence that no // longer exists — the two ebuilds are byte-identical — so it is suppressing a // removal recommendation for nothing. FindingStaleDeclaration // FindingUndeclaredDivergence is the loud one: our ebuild is not the one // ::gentoo ships and no entry says why, on a package the report is about to // list as a removal candidate. Authorship and ProvedBy say whether the // content settled who wrote the difference; unproved is NOT a finding that it // is ::gentoo's. FindingUndeclaredDivergence // FindingDeclaredDivergence is the declaration itself, stated wherever it has // not already been contradicted. It is what makes a patched package // visible at all on an API-only run, where no content check can run and a // patched package would otherwise print exactly like an unpatched one. FindingDeclaredDivergence // FindingBaseline names the ::gentoo ebuild one package was measured // against: which version, how far it is from ours, and the path, so // "what was this compared with" is answered by the report rather than // re-derived by whoever reads it. Upstream carries the baseline's version // beside Version, which is ours. FindingBaseline // FindingBaselineUnexamined is the per-package "we could not look": ::gentoo // carries the package but the ebuild would not read, or a baseline was named // and could not be opened. It is stated rather than left silent // because a package nobody could measure is otherwise indistinguishable from // one that matched ::gentoo exactly. FindingBaselineUnexamined // FindingBaselineSkipped is the RUN-level version of the same absence: no // ::gentoo tree was reached at all, so nothing was compared against ::gentoo. // It is the one kind with no Atom — see the field's own doc for why it may // not borrow one — and the one the caller derives an exit code from. FindingBaselineSkipped // FindingAxisDivergence is one structural axis on which our ebuild differs // from the baseline: inherit, options, iuse or deps. The DETAIL // is what makes it actionable — "the inherit lines differ" never told anyone // which eclass ::gentoo delegates the option list to — so the difference is // stated and not merely counted. FindingAxisDivergence // FindingEbuildDeclaration is a `# BENTOO-DIVERGENCE:` tag our own ebuild // carries: the maintainer's account, in the ebuild, of why a // difference is there. It is NOT FindingDeclaredDivergence, which is the // registry's `patched` entry — two different files, written and edited by // different acts, and one kind for both would leave a consumer unable to say // which of them it had read. FindingEbuildDeclaration // FindingExpiredDeclaration is such a declaration whose `drop-when:` // condition has been evaluated against the tree and is MET: the // divergence it was protecting is back in front of the review. It is the // loud one and is its own kind so a renderer can keep it loud — read as an // ordinary declaration it would stay quiet forever. FindingExpiredDeclaration // FindingClassification is what the three-way reduction made of one // package's differences. Classified carries the counts and // the reach as numbers; Detail is the sentence over them. FindingClassification // FindingOtherRepo is a repository other than ::gentoo that was consulted // about this package. It is INFORMATIVE ONLY — a // repository outside ::gentoo has not been through the same review, and // nothing proposes a realignment from one. FindingOtherRepo // FindingRealignVerdict is a MODEL's reading of whether a divergence still // earns its place. Its Effect carries the model's own sentence // with EffectReviewed on it, so a renderer can say whose words these are; it // is commentary and nothing that decides a verdict or an exit code may read // it. FindingRealignVerdict )
func (FindingKind) String ¶
func (k FindingKind) String() string
String returns the finding's stable word, used in a report and in an export.
The words are hyphen-free and lowercase because they are a machine vocabulary first — a `kind` in an exported document, which a consumer greps — and a label second.
type Hunk ¶
type Hunk struct {
// Class is version-move, ours or unclassified, and is never empty on a hunk
// this package returns: every difference lands in exactly one class, which
// is what makes the three counts add up to the number of differences.
Class string
// Start and End are the half-open line range the hunk replaces in the
// baseline, 1-based.
Start, End int
// Text is the hunk in both hands: every baseline line prefixed "-", then
// every line of ours prefixed "+". It carries NO line numbers, and that is
// what makes it comparable across two different diffs — a version move
// shifts every line below it, so a hunk that matched by position would stop
// matching as soon as anything above it changed size.
Text string
}
Hunk is one contiguous difference between the baseline ebuild and ours, together with what the reduction concluded caused it.
The line range is measured in the BASELINE, which is the side both the range and the diff are oriented from: this file compares baseline -> ours, the same direction diffLineCounts already reports DiffAdded/DiffRemoved in. Start and End are 1-based and the range is HALF-OPEN, [Start, End), so a pure insertion — a hunk that removes nothing — is the empty range Start == End at the line it was inserted before, exactly as a unified diff's `@@ -5,0 +6,2 @@` says it.
type LocalRepo ¶
type LocalRepo struct {
// Name is the repository as the registry names it — "guru", not "::guru";
// the report adds the "::" it prints.
Name string
// Path is the root of its tree, the directory <Path>/<category>/<package>
// would be found under. It is read only when Available is true.
Path string
// Available reports that the repository's CONTENTS are on disk at Path.
//
// "Registered", "resolvable by name" and "configured with a URL" are none of
// them available: nothing on this machine holds the tree, and the only true
// answer the report can give about such a repository is that it was not
// checked.
Available bool
}
LocalRepo is a repository the caller is prepared to have the report speak about: what it is called, where its tree is, and whether its CONTENTS are on this machine.
AVAILABLE IS THE CALLER'S ANSWER, NOT A GUESS MADE HERE. The ~428 repositories of the Gentoo ecosystem are resolvable BY NAME through the registry, and almost none of them are on disk; a list built from that registry alone would describe 428 trees nobody has. So the caller passes the repositories it means the report to speak about and says, for each, whether its contents are here — and a repository marked unavailable is never touched. That is what keeps this pass offline: the alternative, resolving the registry for 84 packages across every registered repository, is thousands of network lookups in a stage meant to make none.
type LookupCause ¶
type LookupCause int
LookupCause is why an upstream lookup failed: a closed vocabulary, so an operator can tell "wait for the rate limit" from "fix the token" from "check the network" without reading the error text.
const ( // LookupCauseNone is the zero value: no lookup failed. LookupCauseNone LookupCause = iota // LookupRateLimited: the host refused for rate limiting (provider.ErrRateLimit). LookupRateLimited // LookupAuth: the host rejected the credentials (provider.ErrUnauthorized). LookupAuth // LookupNetwork: the request never got an HTTP answer (a net.Error). LookupNetwork // LookupNotFound: the package is absent upstream (provider.ErrNotFound). // comparePackageVersions turns that into StatusNotInRemote first, so it is // reachable only through classifyLookupError itself. LookupNotFound // LookupOther: any failure none of the above matches. LookupOther )
func (LookupCause) String ¶
func (c LookupCause) String() string
String returns the cause's word; see lookupCauseWord.
type ManifestOptions ¶
type ManifestOptions struct {
// Keep, if true, leaves the existing Manifest in place and lets pkgdev
// reconcile it. By default, the existing Manifest is moved to a backup
// before regeneration and restored only on failure (clean regen).
Keep bool
// DryRun, if true, lists the packages that would be processed without
// running pkgdev or touching files.
DryRun bool
// Distdir, when non-empty, is used as pkgdev's --distdir. The path is
// expanded (~ and relative paths) and created if missing, and is
// preserved across runs as a persistent download cache. When empty,
// a temporary directory is created under os.TempDir() and removed
// when the run completes.
Distdir string
// Jobs is the maximum number of pkgdev invocations to run in parallel.
// Values <= 0 fall back to DefaultManifestJobs. Internally clamped to
// the number of targets so we never spin idle workers.
Jobs int
// DistfilesCache, when non-empty, points to a read-only distfiles cache
// (typically /var/cache/distfiles) that is consulted before each pkgdev
// invocation. For every DIST entry listed in the package's existing
// Manifest, if a file with the same name exists in this cache, a symlink
// is created in the working distdir so pkgdev can reuse it instead of
// downloading. Empty string disables the optimization entirely. The
// cache is never written to.
DistfilesCache string
// Reporter receives lifecycle events as workers process targets.
// Nil means silent (no progress output) — it is normalized to tui.Noop().
// The CLI typically wires a live TUI or plain reporter here.
Reporter tui.Reporter
// Summary composes the sentence the run's batch closes on, from the result
// the caller is about to receive. Nil closes the batch with no wording at
// all, which is what `overlay autoupdate` already passes today.
//
// It is a function because the words are the CALLER's and the numbers are
// the run's: nothing is known to summarize until the last worker has
// finished, inside this package, so the run supplies the facts when it has
// them and the caller supplies the wording it chose.
//
// The library still relays the call because BatchStart is emitted inside
// the run: a batch opened here and closed by the caller would stay open on
// every path a caller forgets, and the Reporter's consumers would need a
// case for a run that never ends.
//
// The four paths that return before the worker loop — an empty selection, a
// preview, and the two pre-flight refusals — open no batch, so they never
// reach this at all; a --dry-run emits no lifecycle event of any kind.
Summary func(ManifestResult) string
}
ManifestOptions controls Manifest regeneration behavior.
type ManifestResult ¶
type ManifestResult struct {
// Updates is one entry per target the run FINISHED EVALUATING, in the order
// the targets were given.
//
// On a run that reached the end of its list that is every target it was
// handed. On an interrupted one it is short, and the two fields below are
// what account for the difference — see Interrupted for why the missing
// targets are absent rather than present and marked.
Updates []ManifestUpdate
// NotEvaluated is how many of the targets the run was handed it never
// established an outcome for: the ones it never started, and the ones whose
// pkgdev was still running when the run was cancelled.
//
// It is a fact about the RUN, which is why it is a field beside Updates
// rather than a method over it: nothing in the slice can say how long the
// slice was supposed to be. Its zero value is the honest answer for every
// run that finished, and for a ManifestResult a caller assembled by hand.
//
// The report envelope carries the same number under the same name
// (report.Run.NotEvaluated) so the mapping is a copy rather than a
// translation — one less place for the two to drift apart.
NotEvaluated int
// Interrupted reports that the run stopped before reaching the end of its
// target list.
//
// # It is separate from NotEvaluated, and both are needed
//
// A run cancelled in the instant after its LAST target completed lost
// nothing: NotEvaluated is zero and the run was still cut short. Deriving
// "was it interrupted" from "is the gap zero" would report that run as a
// complete one, and the operator who pressed ctrl+c would be told their run
// finished normally.
//
// # False is the safe zero value, which is why the field is not named Complete
//
// A ManifestResult built by hand — in a test, or by a caller wrapping a
// slice — gets false, and false here means "not interrupted". A Complete
// bool would default to "this run was cut short" and quietly draw the
// interrupted block over every such report.
Interrupted bool
}
ManifestResult collects per-package results of a regeneration run, and is the value a caller asks how the run went.
Ok and Failed are methods over Updates rather than stored fields: two copies of one fact drift (a target appended without the counter makes the summary disagree with the list under it), and derived they cannot. They exist as values because a "%d ok, %d failed" sentence pushed through the Reporter cannot be counted, exported, or rendered again in another mode; the sentence is composed FROM them now.
NotEvaluated and Interrupted ARE stored, and the asymmetry is the point: they are questions about the RUN, which no row can answer — a target the run never reached contributes nothing to look at. So the run sets them, once, where it knows them.
func RegenerateManifests ¶
func RegenerateManifests(ctx context.Context, overlayPath string, targets []ManifestUpdate, opts *ManifestOptions) ManifestResult
RegenerateManifests regenerates Manifest files for the given packages using pkgdev. Workers are dispatched in parallel up to opts.Jobs (default DefaultManifestJobs); each pkgdev process runs against its own package directory and shares the resolved distdir as a download cache.
By default, the existing Manifest is moved aside before pkgdev runs so a fresh file is produced (clean regeneration). The backup is restored on failure. opts.Keep skips this step.
pkgdev output is surfaced through opts.Reporter as TaskStart/TaskLine/TaskDone events, bracketed by BatchStart/BatchDone; a nil Reporter is silent at no cost, because every fact about the run is in the returned ManifestResult. The returned Updates preserve the order of the input targets.
Cancelling ctx stops the run and does not fail what it never reached: workers stop PULLING from the queue, a target still running contributes no row, both are counted in NotEvaluated, and Interrupted is set even when that count is zero. Draining the queue against a dead context would report failures the operator caused by asking the run to stop.
func RegenerateManifestsForScope ¶
func RegenerateManifestsForScope(ctx context.Context, cfg *config.Config, scope ManifestScope, opts *ManifestOptions) (*ManifestResult, error)
RegenerateManifestsForScope is a convenience wrapper that resolves a scope and runs RegenerateManifests.
func (ManifestResult) Failed ¶
func (r ManifestResult) Failed() int
Failed is how many targets did not succeed.
It is the COMPLEMENT of Ok rather than a second loop with the condition inverted, so the two can never both skip a target or both claim one: a column defined as "the rest" cannot drift from the column it is the rest of. Every target therefore lands in exactly one, and Ok()+Failed() is always len(Updates) — which is what makes the pair safe to print as a summary line and safe to sum inside a report that also lists the rows.
A target the run never evaluated is in NEITHER column, because it is not in Updates at all. Counting it as failed would be this package inventing a failure out of the operator's own ctrl+c: nothing was learned about that package, and "we did not get to it" is not "it did not work". The count of those targets is NotEvaluated, a statement about the RUN rather than about any target, and it travels to the report envelope (report.Run.Complete / NotEvaluated) which already says it once for every kind of batch instead of once per domain.
func (ManifestResult) Ok ¶
func (r ManifestResult) Ok() int
Ok is how many targets the run regenerated successfully.
type ManifestScope ¶
ManifestScope identifies one or more packages to regenerate Manifests for.
Resolution rules:
- Empty Category and Package: every package in the overlay.
- Non-empty Category, empty Package: every package in that category.
- Both set: that single package.
func ParseManifestScope ¶
func ParseManifestScope(arg string) (ManifestScope, error)
ParseManifestScope parses a single CLI argument into a ManifestScope.
Accepted forms:
- "" -> whole overlay
- "<category>" -> all packages in category
- "<category>/<package>" -> single package
type ManifestUpdate ¶
type ManifestUpdate struct {
Category string
Package string
Success bool
// Error is the failure as the SENTENCE the formatters print, and it
// deliberately carries no atom: FormatManifestResult and FormatRenameResult
// both write "<category>/<package>: " immediately before it, so a copy of
// the atom in here would be printed twice on every failure line.
Error string
// Err is that same failure as a VALUE — the cause wrapped with %w and with
// the category/package that produced it.
//
// It exists beside Error rather than replacing it because a string is a
// dead end: it cannot be unwrapped, it cannot be matched with errors.Is,
// and a caller that needs to know WHICH failure it holds is reduced to
// searching text for a phrase. It carries the atom although Error does not,
// because an error travels on its own — into a log line, into another wrap,
// into a report row assembled long after the loop that produced it — and an
// error that has left the row naming its package cannot be reproduced.
//
// Nil on success, and nil only on success: "did this target fail?" is
// answered by Success and Err agreeing, never by whether a field happens to
// have been populated.
Err error
// Output is what the failing command printed — pkgdev's own diagnostic,
// verbatim, including the "[bentoo] reused N distfile(s)" line the run
// injects into the same stream. It is the text the operator acts on, and by
// the time a report is rendered the terminal that streamed it live is gone.
//
// It is populated ONLY on failure. The capture is a verbatim, unbounded copy
// of every byte the child wrote — a package fetching a large distfile can
// emit megabytes of progress output — and a whole-overlay run would hold one
// per target for as long as the caller holds the slice. A successful
// target's output has no reader to justify that: it was already streamed
// through the Reporter while the target ran.
Output string
// Reused is the number of distfiles symlinked from the distfiles cache
// (e.g. /var/cache/distfiles) into the working distdir, sparing pkgdev
// from re-downloading them. Zero when no cache is configured or no
// matching files were found.
Reused int
}
ManifestUpdate represents a Manifest update operation.
func ResolveManifestTargets ¶
func ResolveManifestTargets(overlayPath string, scope ManifestScope) ([]ManifestUpdate, error)
ResolveManifestTargets expands a scope into the concrete list of packages (category/package pairs) present in the overlay.
type MatchResult ¶
type MatchResult struct {
Matches []RenameMatch
Warnings []string
}
MatchResult holds the results of a Match() call, including any non-fatal scan warnings.
type OtherRepo ¶
type OtherRepo struct {
// Name is the repository as the registry names it — "guru", not "::guru";
// the report adds the "::" it prints.
Name string
// Version is the version that repository carries, empty when it carries
// none. It is meaningless unless Checked is true.
Version string
// Checked reports that the repository's contents were actually read. False
// means NOT CHECKED — the repository is registered but not available
// locally — and never "it does not carry the package".
Checked bool
}
OtherRepo is a repository other than ::gentoo that carries a package ::gentoo does not — one row of the answer for the 84 of 321 overlay packages that have no ::gentoo counterpart at all.
CHECKED IS THE FIELD THE TYPE EXISTS FOR. Under a two-way answer — carries it, or does not — a repository nobody consulted reports exactly like one that was consulted and came back empty. The ~428 registered repositories are resolvable BY NAME, but nothing on disk holds most of them, and asking about 84 packages across all of them would be thousands of network lookups in a stage that is meant to be offline.
Whatever it reports is INFORMATIVE ONLY and never a baseline: a repository outside ::gentoo has not been through the same review, its quality varies, and where several carry a package the report names each and chooses none.
Every field is comparable, so a nil slice of them is "nothing to say" and renders nothing — the same terms every field of the baseline review rides on.
func OtherRepositories ¶
OtherRepositories reports which of the given repositories carry atom — one row per repository, in the order they were given.
It is the answer for the 84 of the overlay's 321 packages ::gentoo carries no version of. Knowing that GURU already packages something we package alone is worth reporting; it is also the whole of what this function claims.
A repository whose contents are not here is NOT CHECKED, never one that does not carry the package: collapsing the two would let the report assert, on the strength of repositories nobody consulted, that a package is Bentoo's alone. Such a repository is not stat'ed, listed or resolved.
Where several carry the package each gets a row and none is chosen: a winner would quietly create a second baseline. Every row is INFORMATIVE ONLY, held structurally — needsRealignVerdict reads Baseline.Found and never these rows.
Per available repository it stats one directory and lists one more, and recognises ebuilds by NAME; it reads no ebuild text, because printing another repository's ebuild would be offering it as a replacement.
type PackageStatus ¶
type PackageStatus struct {
Category string
Package string
Changes []FileChange
}
PackageStatus represents the status of changes for a single package
func GroupStatusEntries ¶
func GroupStatusEntries(entries []git.StatusEntry) []PackageStatus
GroupStatusEntries groups git status entries by category/package
func StagedStatus ¶
StagedStatus retrieves and groups only the changes staged in the index, i.e. exactly what a commit would include. Unlike Status, it ignores files that are merely modified in the worktree but not staged.
func StagedStatusWithExecutor ¶
func StagedStatusWithExecutor(ctx context.Context, executor git.GitExecutor) ([]PackageStatus, error)
StagedStatusWithExecutor retrieves and groups the staged git status using the provided GitExecutor. This function is useful for testing with mocks.
func StatusWithExecutor ¶
func StatusWithExecutor(ctx context.Context, executor git.GitExecutor) ([]PackageStatus, error)
StatusWithExecutor retrieves and groups the current git status using the provided GitExecutor. This function is useful for testing with mock implementations.
type PatternValidator ¶
type PatternValidator struct{}
PatternValidator validates package patterns for safety. It ensures patterns are not too broad and have sufficient specificity to prevent accidental bulk operations on unintended packages.
func NewPatternValidator ¶
func NewPatternValidator() *PatternValidator
NewPatternValidator creates a new PatternValidator instance.
func (*PatternValidator) Validate ¶
func (v *PatternValidator) Validate(pattern string) error
Validate checks if a package pattern is safe to use. It returns nil if the pattern is valid, or a ValidationError if invalid.
Validation rules:
- Patterns without wildcards are always valid
- Patterns with wildcards must have at least 3 characters before the first wildcard
- Patterns with wildcards must have at least one complete token before the wildcard
- A bare "*" pattern is rejected as too broad
Examples of valid patterns: "gst-*", "python-*", "lib*", "gst-plugins-*", "mypackage" Examples of invalid patterns: "*", "g*", "gs*", "a-*"
type PruneBatch ¶
type PruneBatch struct {
// Identical is the default batch — redundant by version, undeclared, and
// byte-identical. Every plan in it is Eligible.
Identical []PrunePlan
// Diverging holds the packages that carry something of ours, declared or not.
// They are planned and printed either way; they are Eligible only under
// --include-patched.
Diverging []PrunePlan
// Refused holds everything no flag may act on, each with its reason.
Refused []PrunePlan
}
PruneBatch is the plan for a whole run, split by what may be done about each package.
The split is why Refused carries a reason per package: a plan that silently omitted the packages it will not touch reads as "these do not exist".
func PlanPrune ¶
func PlanPrune(results []CompareResult, prov provider.Provider, opts PruneOptions) PruneBatch
PlanPrune turns the compare's results into three buckets, each package carrying the reason it is in the one it is in.
The filter order — verdict, then declaration, then content — is the contract:
- A verdict other than redundant refuses the package and its content is NEVER compared: a `keep` package is carried on purpose, and skipping 240 of 314 packages is also what keeps the scan cheap.
- A `patched` declaration moves the package out of the removable batch REGARDLESS of the bytes: a stale declaration is compare's to report and the operator's to clear, not ours to overrule irreversibly.
- Only then does content decide — and where the content also PROVES the difference is ours, no flag may act on it.
Every step only moves a package AWAY from removal. "Without --apply, remove nothing" is structural: the planner cannot delete, and only reads the two trees. It returns no error, like PruneVerification: a failed read must read as "nothing was examined", not as an empty batch.
type PruneClass ¶
type PruneClass int
PruneClass is what the EVIDENCE says about one package.
It is deliberately not "what this run will do about it" — that is PrunePlan.Eligible, and the two are kept apart so the plan can print a diverging package, with its reason, on a run that will not touch it. The operator's next question about a package missing from the output is always "why not this one", and a report that answers it is the whole point of having three buckets instead of one list.
const ( // PruneRefused means no run of this command may remove the package: its // verdict is not redundant, its content could not be compared at all, or the // overlay's own content PROVES the difference originates here. It is // the ZERO VALUE for the same reason ContentUnverifiable is: a PrunePlan // nobody filled in must not read as permission to delete. PruneRefused PruneClass = iota // PruneIdentical means the verdict is redundant, no registry entry declares a // patch, and every byte the two trees share matches. It is the only class a // default run removes. PruneIdentical // PruneDiverging means the package is redundant BY VERSION but something of // ours is in it — either a declared `patched` entry or an undeclared // difference the byte comparison found — AND nothing proves who wrote that // difference. Only --include-patched may act on it; a difference the // content proves is ours is refused outright instead, because the flag // says "I accept discarding local work" and cannot say it about work the // report has just named the file for. PruneDiverging )
func (PruneClass) String ¶
func (c PruneClass) String() string
String returns the plan's word for the class.
type PruneOptions ¶
type PruneOptions struct {
// OverlayPath is the root of the overlay being pruned. Our side of every
// comparison is OverlayPath/category/package.
OverlayPath string
// IncludePatched is the --include-patched flag: the operator saying out loud
// that they accept discarding local work. It moves nothing between
// classes — it only decides whether the diverging batch is Eligible.
IncludePatched bool
// RegistryKeys maps "category/package" to every registry key for that atom.
// It is SUPPLIED by the caller and never looked up here, which is what keeps
// this package free of an import edge to internal/autoupdate.
//
// Nothing is ever joined from one of these keys into a filesystem path.
// SplitPackageKey now refuses a "." or ".." half, but the structure is what
// keeps traversal absent here, not that check.
RegistryKeys map[string][]string
}
PruneOptions is what the caller knows that this package cannot work out for itself.
type PrunePlan ¶
type PrunePlan struct {
// Category and Package come from the compare result, which comes from the
// overlay scan. They are the only two names any path here is built from.
Category string
Package string
// Versions is every version in OUR directory — not the versions that were
// compared. Removing the directory removes all of them, including the ones
// ::gentoo never carried, and a plan listing only the compared ones would
// understate the removal.
Versions []string
// Files is every file under the package directory, as a path RELATIVE to it:
// the plan prints category/package as its heading, so repeating it on every
// line is noise.
//
// Manifest and metadata.xml ARE listed even though the comparison ignores
// them. They are not evidence about whether the copy is ours, but the
// removal deletes them all the same, and a plan that hid them would understate
// what it is about to do.
//
// Directories are not listed as entries of their own: the overlay is a git
// tree, git cannot carry an empty directory, so every directory the removal
// takes has at least one file listed under it.
Files []string
// RegistryKeys are the registry entries that would be deleted with the
// package, exactly as the caller supplied them — every one of them, since 90
// of 321 atoms carry more than one entry and leaving one behind orphans it.
// This package never looks them up: internal/overlay does not know what TOML
// is, which is the layering rule.
RegistryKeys []string
// Eligible is what THIS RUN may do about the package: true only when the
// class, the flags and the evidence all allow a removal. A false Eligible on a
// planned package is not an error — it is the plan saying "here it is, and
// here is why I am leaving it alone".
Eligible bool
// Class is what the evidence says. See PruneClass.
Class PruneClass
// Reason says why the package was refused, or what diverges. It is empty only
// for PruneIdentical, where there is nothing to explain; every Refused and
// every Diverging plan carries one.
Reason string
}
PrunePlan is everything the report, the confirmation and the executor need about one package: what would go, and what this run may do about it.
type PruneResult ¶
type PruneResult struct {
// Plan is the plan this result is about, carried whole so the caller can name
// the package, and reach its RegistryKeys, without keeping its own index into
// the batch it handed over.
Plan PrunePlan
// Removed is true only when os.RemoveAll returned without error. It is the
// caller's authority to delete the package's registry entries:
// the entry goes only for a package whose directory actually went.
Removed bool
// Err is why the removal did not happen — a refusal by the guard, or what
// os.RemoveAll returned.
//
// Err is non-nil EXACTLY when Removed is false. The two are one fact seen from
// two sides, and a result where they disagree leaves the caller unable to say
// whether the package is still on disk — while its next act is deleting that
// package's registry entries. A "removed with an error" would orphan the
// directory; a "not removed, no error" would orphan the entry.
Err error
}
PruneResult is what happened to one plan the executor attempted.
func ExecutePrune ¶
func ExecutePrune(batch PruneBatch, opts PruneOptions) []PruneResult
ExecutePrune carries out the removals the plan authorised, and no others.
It takes the WHOLE batch, Refused included. Eligible is the authorisation; the BUCKET IS NOT — a loop removing by bucket would delete exactly the packages the run refused, the worst bug this file can have.
A plan that is not Eligible produces NO result: results record ATTEMPTS, and the CLI turns any Err into a non-zero exit, so a refusal reported as a failure would fail every run that merely SAW a non-redundant package.
One failure records and continues: a batch aborting halfway leaves the overlay in a state no plan described. It returns no error of its own, because the answer is per package and one error would hide the successes. It does not touch packages.toml: the CLI edits the registry afterwards, only for the atoms whose directory actually went, since internal/overlay does not know TOML.
type PullMode ¶
type PullMode int
PullMode selects how fetched commits are integrated into the current branch.
const ( // PullFFOnly integrates only when the branch can fast-forward. It is the // default: an overlay that has diverged from its upstream is something the // operator should see, not something a merge commit should paper over. PullFFOnly PullMode = iota // PullMerge merges the upstream, writing a merge commit when the branches // have diverged. PullMerge // PullRebase replays local commits on top of the upstream. PullRebase )
type PullResult ¶
type PullResult struct {
Success bool // True if the pull completed without conflicts
UpToDate bool // True if the branch was already current with upstream
CommitsPulled int // Number of commits the upstream was ahead by
Branch string // Branch that was pulled into
Upstream string // Upstream ref the commits came from (e.g. "origin/master")
Conflicts []string // List of conflicting file paths
Message string // Human-readable status message
}
PullResult contains pull operation results
func Pull ¶
Pull fetches the configured remote and integrates the current branch's upstream into it.
func PullWithRunner ¶
func PullWithRunner(ctx context.Context, runner git.GitExecutor, remote string, mode PullMode, dryRun bool) (*PullResult, error)
PullWithRunner performs the pull using a provided GitExecutor. This allows for testing with mock implementations.
The upstream is resolved from the branch that is actually checked out, not from the remote's default branch: merging origin/HEAD into whatever happens to be checked out silently drags the default branch into unrelated work.
type PushResult ¶
type PushResult struct {
UpToDate bool // True if nothing was pushed (already up-to-date)
Message string // Status message from git
}
PushResult contains the result of a Push operation
func Push ¶
Push pushes committed changes to the remote repository Returns ErrUpToDate if there's nothing to push
func PushWithExecutor ¶
func PushWithExecutor(ctx context.Context, executor git.GitExecutor) (*PushResult, error)
PushWithExecutor pushes committed changes using the provided GitExecutor. This function is useful for testing with mock implementations.
type Reading ¶
type Reading int
Reading is WHETHER ANYBODY EXPLAINED the difference — a third question again, and separate from both its neighbours above on purpose. Verification says whether the two ebuilds DIFFER; Authorship says what the content proves about who wrote the difference; Reading says whether a reading of it was even attempted, and when it was not, why not.
It is a type of its own because CompareResult.Review cannot answer that question. The zero ReviewNote covers four unrelated causes at once — a `--no-review` run, no `claude` on PATH, a reviewer that errored, and a finding that is not an undeclared divergence — so reading "Review is empty" as "nobody looked" reports the same thing for a package nobody was ever asked about and for one whose review was killed mid-flight. That conflation is the defect this vocabulary exists to remove, and collapsing Reading back into Verification or into Review would reproduce it exactly.
The vocabulary lives here with the rest of the compare types, exactly as Verification and Authorship do, while the pass that fills it is wired in separately (AnnotateReviews, review.go).
const ( // ReadingNotRequested means no reading was ever asked for: no reviewer was // configured, or the run passed `--no-review`. // // It is the ZERO VALUE by construction, the same way AuthorshipUnproved is, // so every result no annotation pass reached reads as "not requested" // without anyone setting it. That is what keeps "nobody asked" to ONE // spelling: were it placed anywhere but first, an untouched result and an // annotated one would disagree about the same fact, and a consumer counting // unread packages would be right about some of them and silently wrong // about the rest. ReadingNotRequested Reading = iota // ReadingNotComparable means the content check refused the pair, so there // was never a difference to hand a reader in the first place — one of the // causes NotVerified above lists. It is NOT a failure: nothing was asked of // anybody, and nobody let anybody down. ReadingNotComparable // ReadingFailed means a reading WAS attempted and did not come back — the // reviewer errored, timed out, or was killed. // // Holding it apart from ReadingNotComparable is the point of the type. Both // end with no note on the result, so anything that reports only that // absence says the same thing about a pair nothing could compare and about // a review that died mid-flight — and only the second is a run worth // repeating. ReadingFailed // ReadingDone means a reading returned a usable note. ReadingDone )
type RealignNote ¶
type RealignNote struct {
// Justified is the model's answer: does the divergence still earn its place?
//
// It is a plain bool with no third state, and Why below is what keeps that
// honest: a note with no reason says nothing at all (realignNoteSpeaks), so
// "false" can never arrive by default and be read as a judgement.
Justified bool `json:"justified"`
// Why is the model's reason, in its own words. A verdict without one
// is an instruction rather than an input — "not justified" with nothing
// behind it cannot be checked, argued with, or acted on — so a note that
// carries no Why is treated as no note.
Why string `json:"why"`
// BaselineText is the ::gentoo text that would replace the divergence when
// the model judges it unjustified. It is a PROPOSAL printed on a
// terminal: nothing here writes it into an ebuild, because the overlay
// auto-commits and a write would be a publish.
//
// It is empty for a divergence the model considers justified — there is then
// nothing to replace — and may also be empty on an unjustified one, which
// the report says rather than hides.
BaselineText string `json:"baseline_text"`
}
RealignNote is a model's judgement of one undeclared divergence: whether it is still justified and why, and the baseline text that would replace it when it is not.
It is COMMENTARY and nothing else. Nothing that decides a Verdict or an exit code may read what this becomes on the result, which is the fence realign_reviewer_test.go holds over RealignVerdict — one mechanism decides, the other comments, and a disagreement between them stays legible only while that holds.
The JSON tags exist because the verdict cache persists this type (review_cache.go). They are the same lowercase names the CLI adapter in cmd/ decodes from the model's reply, so one spelling serves both.
type RealignRequest ¶
type RealignRequest struct {
// Category, Package and Version identify the package for the reviewer's
// prose. They are NOT part of the cache's index: the verdict is a reading of
// the two files, and the same two files are the same question.
Category, Package, Version string
// Ours is our overlay ebuild at Version; Baseline is the ::gentoo ebuild it
// was measured against. The ORDER is meaningful — the answer is about what
// OURS carries on top of the baseline — so a request with the two swapped
// would invert the judgement.
Ours, Baseline []byte
}
RealignRequest is one undeclared divergence put to the reviewing model: which package, at which version of ours, and the two ebuilds that differ.
It carries the two files' BYTES and no measurement of them, exactly as ReviewRequest does: a prompt assembled from the SIZE of a difference would be a computation on that size, which is forbidden (compare_diff_counts_fence_test.go).
The BASELINE side is ::gentoo's ebuild as ResolveBaseline named it, which need not be at our version: unlike the content check, the baseline comparison happens regardless of the version relationship. Version is OURS; how far the baseline is from us is already known (Baseline.Distance) and printed beside the verdict, so the operator reads it from the report rather than from a model.
The two byte slices are also what the verdict cache keys on, so the request holds precisely what the answer depends on and nothing else.
type RealignReviewer ¶
type RealignReviewer interface {
ReviewRealignment(ctx context.Context, req RealignRequest) (RealignNote, error)
}
RealignReviewer is the seam through which a model's judgement of one divergence reaches this package. Like DivergenceReviewer next door it is the whole of what internal/overlay knows about an LLM: a function that takes two ebuilds and returns a bool and two strings.
It is declared HERE, in the consumer, for the reason DivergenceReviewer is: holding an autoupdate type would create an import edge kept absent in both directions (review_test.go fences it). The only production implementation is an adapter over the `claude` CLI in cmd/, which already imports both halves.
It is a SECOND interface rather than a method on DivergenceReviewer because `--no-review` turns off the commentary and `--realign` turns on the judgement; one interface would make an implementation of either promise both.
The CONTEXT is the caller's, so a cancelled compare aborts an in-flight review; the per-invocation TIMEOUT belongs to the adapter. A nil RealignReviewer is not an error anywhere: a run that asked for no verdicts and a machine with no `claude` on PATH reach one no-op path.
type RenameError ¶
type RenameError struct {
Match RenameMatch
Message string
}
RenameError represents a failed rename operation.
type RenameMatch ¶
type RenameMatch struct {
Category string // e.g., "media-plugins"
Package string // e.g., "gst-plugins-base"
OldFilename string // e.g., "gst-plugins-base-1.24.11-r1.ebuild"
NewFilename string // e.g., "gst-plugins-base-1.26.10.ebuild"
OldPath string // Full path to old file
NewPath string // Full path to new file
HasRevision bool // True if old filename had -rN suffix
}
RenameMatch represents a single ebuild to be renamed.
type RenameOptions ¶
type RenameOptions struct {
DryRun bool // Simulate without executing
SkipPrompt bool // Skip confirmation prompts
NoManifest bool // Skip Manifest updates
Force bool // Proceed despite warnings
}
RenameOptions controls rename behavior.
type RenameResult ¶
type RenameResult struct {
Matches []RenameMatch // All found matches
Renamed []RenameMatch // Successfully renamed
Failed []RenameError // Failed operations
VersionFiles []VersionFile // Version-specific files detected
Conflicts []Conflict // Target files that already exist
Collisions []Collision // Targets shared by two or more matches
ManifestUpdates []ManifestUpdate // Manifest update results
Warnings []string // Non-fatal scan warnings
}
RenameResult contains the outcome of a rename operation.
func Rename ¶
func Rename(ctx context.Context, cfg *config.Config, spec *RenameSpec, opts *RenameOptions) (*RenameResult, error)
Rename performs bulk ebuild renaming. It validates the pattern, finds matching ebuilds, detects version files, and performs the rename operation (or simulates it in dry-run mode).
func RenamePreview ¶
func RenamePreview(cfg *config.Config, spec *RenameSpec) (*RenameResult, error)
RenamePreview finds matching ebuilds and detects potential issues without executing. Used to show a preview before confirmation.
type RenameSpec ¶
type RenameSpec struct {
Category string // "*" for all categories, or specific category
PackagePattern string // Glob pattern for package names
OldVersion string // Exact old version to match
NewVersion string // New version to rename to
}
RenameSpec specifies what to rename.
type RepoFileChange ¶
type RepoFileChange struct {
Type ChangeType // Add, Del, Mod
Kind FileKind
Path string // path relative to overlay root (e.g. "eclass/rpm.eclass")
Name string // filename only (e.g. "rpm.eclass")
}
RepoFileChange represents a change to a non-ebuild file in the overlay.
func AnalyzeRepoFileChanges ¶
func AnalyzeRepoFileChanges(entries []git.StatusEntry) []RepoFileChange
AnalyzeRepoFileChanges analyzes git status entries and returns a sorted list of changes to non-ebuild files (eclasses, profiles, licenses, metadata, arbitrary repo files). Entries that correspond to a parseable ebuild are ignored here — use AnalyzeChanges for those. Non-ebuild files that live inside a package directory (Manifest, metadata.xml, files/*, patches) are also dropped: they are implementation details of the surrounding ebuild changes and would clutter every commit message.
type ReviewFailure ¶
type ReviewFailure int
ReviewFailure is why a review did not come back. It sits BESIDE Reading == ReadingFailed and never splits that state. Its zero value, ReviewFailureNone, is every result whose review did not fail.
const ( // ReviewFailureNone: no review failed. ReviewFailureNone ReviewFailure = iota // ReviewTimedOut: ErrReviewTimedOut. ReviewTimedOut // ReviewCouldNotStart: ErrReviewCouldNotStart. ReviewCouldNotStart // ReviewExitedNonZero: ErrReviewExitedNonZero. ReviewExitedNonZero // ReviewUnusableReply: ErrReviewUnusableReply, or a note that says nothing. ReviewUnusableReply // ReviewCancelled: the run was cancelled (context.Canceled, or the pass's // context was done before the review was asked). ReviewCancelled // ReviewEbuildUnreadable: the two ebuilds could not be re-read. ReviewEbuildUnreadable // ReviewOther: an error none of the above matches. ReviewOther )
func (ReviewFailure) String ¶
func (f ReviewFailure) String() string
String spells the cause in the report's vocabulary; the zero value spells nothing.
type ReviewNote ¶
type ReviewNote struct {
// Origin is where the divergence came from, as the model read it.
Origin ReviewOrigin `json:"origin"`
// Summary is one line saying what the divergence does.
Summary string `json:"summary"`
// Declaration is proposed `patched` text for the registry. It is
// empty unless Origin is OriginOverlay: there is nothing of ours to declare
// about a change that is not ours. It is a PROPOSAL — text on a terminal,
// never a file.
Declaration string `json:"declaration"`
}
ReviewNote is a model's reading of one divergence: COMMENTARY, printed beside a finding, and never an input to anything the report decides. The same package grouping, the same Verdicts and the same removal recommendations hold whether or not a review ran.
Its two prose fields are operator-facing text a MODEL produced. They are printed as ARGUMENTS and never as a format string — like every other piece of report text — they reach no shell and no command, and Declaration is capped on the line the way PatchedReason is (patchedReasonCap).
Nothing here is ever written to a file. The review PROPOSES declaration text and the operator applies it: the overlay repository auto-commits and pushes within minutes, so a declaration written by this program would be published before anyone could read it.
The JSON tags exist because the review cache persists this type (review_cache.go), and they are the same three lowercase names the CLI adapter in cmd/ decodes from the model's reply, so one spelling serves both.
type ReviewOrigin ¶
type ReviewOrigin int
ReviewOrigin is which side a divergence came from, as the model classified it. It is the model's reading and not a finding of this package's own: Authorship, next door, is what the overlay's CONTENT proves, and the two are deliberately different types so a classification can never be mistaken for a proof.
const ( // OriginUnknown is the ZERO VALUE, and it says nothing about anybody. Every // note nobody filled — every result no review reached, every run with // `--no-review`, every package whose reviewer errored — carries it, and it // reads as "nothing was said" rather than as a classification. // // This is the same argument AuthorshipUnproved makes as its own zero value, // and here it has a second edge: the review attaches a proposed `patched` // declaration to an OVERLAY-origin note. Had OriginOverlay been the zero // value, every un-reviewed finding would both accuse us of the change and // invite a declaration of it. OriginUnknown ReviewOrigin = iota // OriginOverlay means the divergence is work of ours, carried on top of // ::gentoo's ebuild. This is the classification a declaration is proposed // for. OriginOverlay // OriginUpstream means ::gentoo moved and our copy did not — the in-place // revision, with no revbump, that the undeclared-divergence caveat warns a // small diff is usually caused by. OriginUpstream // OriginBoth means each side changed: our copy carries work of ours AND has // fallen behind an upstream revision. OriginBoth )
func (ReviewOrigin) MarshalText ¶
func (o ReviewOrigin) MarshalText() ([]byte, error)
MarshalText encodes an origin as its WORD, so encoding/json stores "overlay" rather than 1.
That follows directly from the review cache having no expiry: the key IS the two files' content, so an entry stays reachable for as long as neither file changes, and there is no TTL that would ever retire it. An integer encoding would silently change meaning the day a constant is inserted into the block above — a note that said "upstream" would come back reading "both", years later, with nothing to flush it. A word survives a reordering.
func (ReviewOrigin) String ¶
func (o ReviewOrigin) String() string
String returns the report's word for an origin.
The four words are this feature's vocabulary in both directions: the cache stores one (see MarshalText) and the CLI adapter in cmd/ decodes one from the model's reply. A renderer wanting different prose — "::gentoo" for upstream, say — builds it from these, rather than these being built for one renderer.
func (*ReviewOrigin) UnmarshalText ¶
func (o *ReviewOrigin) UnmarshalText(text []byte) error
UnmarshalText decodes the word MarshalText wrote.
An unrecognised word is an ERROR rather than a quiet fall back to OriginUnknown. The only thing that decodes one is the review cache, which reads a failed parse as a corrupt cache: warn once, treat every lookup as a miss, ask again. That costs one request. Degrading to OriginUnknown would instead keep a note whose classification has been erased — and, with no expiry, keep it forever — while printing no proposed declaration for a divergence that may well be ours.
type ReviewRequest ¶
type ReviewRequest struct {
// Category, Package and Version identify the package for the reviewer's
// prose. They are NOT part of the cache's index: the classification is a
// reading of the two files, keyed on their content alone.
Category, Package, Version string
// Ours and Theirs are the two ebuilds at Version — ours from the overlay,
// theirs from ::gentoo — read through resolvePackagePaths, the one path
// construction in this package. The ORDER is meaningful: Origin below names a
// side, so a request with the two swapped would invert the answer.
Ours, Theirs []byte
}
ReviewRequest is one divergence put to a reviewer: which package, at which version, and the two ebuilds that differ.
It carries the two files' BYTES and never the line counts beside them, held at the type: a prompt assembled from the SIZE of a difference would be a computation on that size, which is exactly what the counts may never feed — see compare_diff_counts_fence_test.go, which fences DiffAdded/DiffRemoved to the one function that fills them and the one that prints them.
The bytes are also what the review cache keys on (review_cache.go), so the request holds precisely what the answer depends on and nothing else.
type ValidationError ¶
type ValidationError struct {
Pattern string // The pattern that failed validation
Reason string // Human-readable explanation of why validation failed
}
ValidationError represents a pattern validation failure.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements the error interface for ValidationError.
type ValidationResult ¶
type ValidationResult struct {
Valid bool // True if overlay structure is valid
Errors []string // Critical issues that prevent operation
Warnings []string // Non-critical issues
}
ValidationResult contains overlay validation results
func ValidateOverlay ¶
func ValidateOverlay(path string) (*ValidationResult, error)
ValidateOverlay checks if a path is a valid Gentoo overlay. A valid overlay must have: - profiles/ directory - metadata/ directory
type Verdict ¶
type Verdict int
Verdict is the recommendation for one overlay package: not what its versions are (that is CompareStatus) but what should be done about it. The two are separate axes — a package can be up-to-date with ::gentoo and still worth keeping, precisely because it carries a divergence.
const ( // VerdictKeep means the overlay's copy earns its place. VerdictKeep Verdict = iota // VerdictRedundant means ::gentoo delivers the same or more and we changed // nothing, so the overlay copy is a removal candidate. VerdictRedundant // VerdictNeedsRebase means ::gentoo moved ahead and we carry changes that // must be re-applied on top of the newer ebuild. VerdictNeedsRebase // VerdictUnknown means nothing is recorded about this package, or the // comparison itself failed. Either way no recommendation is supported. VerdictUnknown )
type Verification ¶
type Verification int
Verification is the tri-state a content check needs: a Verdict confirmed against the two ebuilds' bytes reads differently from one taken on trust. The check that produces it is wired in separately; the vocabulary lives here with the rest of the compare types.
const ( // NotVerified means no content was compared: no local copy, no upstream // copy at that version, an unreadable file, or versions that differ. NotVerified Verification = iota // VerifiedIdentical means the two ebuilds are byte-for-byte equal. VerifiedIdentical // VerifiedDiffers means the two ebuilds differ. VerifiedDiffers )
type VersionFile ¶
VersionFile represents a file with version in its name.
type VersionFilesBlockError ¶
type VersionFilesBlockError struct {
Files []VersionFile
}
VersionFilesBlockError indicates that version-specific files were detected and the operation was blocked because --force was not specified.
func (*VersionFilesBlockError) Error ¶
func (e *VersionFilesBlockError) Error() string
Error implements the error interface.
type VersionFilesDetector ¶
type VersionFilesDetector struct {
// contains filtered or unexported fields
}
VersionFilesDetector finds files with version numbers in their names. It scans the files/ subdirectory of packages for version-specific files that may need manual attention during rename operations.
func NewVersionFilesDetector ¶
func NewVersionFilesDetector(overlayPath string) *VersionFilesDetector
NewVersionFilesDetector creates a new VersionFilesDetector for the given overlay path.
func (*VersionFilesDetector) Detect ¶
func (d *VersionFilesDetector) Detect(matches []RenameMatch, oldVersion string) []VersionFile
Detect scans for version-specific files in package directories. It checks the files/ subdirectory of each unique category/package in the matches and returns all files containing the old version string.
type VersionMove ¶
type VersionMove struct{ From, To string }
VersionMove is the pair of ebuilds whose difference IS a version move: the third point the reduction subtracts.
It carries the already-chosen pair rather than a bare third path because the two ways of choosing a third point diff different pairs — `diff(baseline, gentoo_newer)` when ::gentoo carries two versions spanning ours, `diff(our_previous, ours)` when we carry the previous version — and a callee handed one lone path could not tell which of the two roles it was being given. Choosing is the caller's job; measuring and subtracting is this file's.
Both fields are PATHS to ebuild files, like ReduceDiff's own two, because the span is a distance between two VERSIONS and the version is in the filename.