apply

package
v0.1.0-rc.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package apply executes a plan.Plan. It is the only code in labelsync that writes to GitHub.

The mode decides whether deleting is allowed at all

Apply creates missing configured labels, updates existing ones, and recolours displaced squatters. Under plan.ModeAppend that is all it does: a delete action is refused before the first write rather than executed. Under plan.ModePrune deletes execute, and the plan handed over is expected to carry only the candidates a user chose — the selection happens in the command, ahead of this package, because a prompt is not something the executor should own.

The order is a crash-consistency guarantee

Actions go out in exactly the order the planner emitted them: renames, then squatter recolours, then creates, then updates, then deletes. That order is what makes every intermediate state coherent, so a run killed halfway through a repository leaves it consistent rather than with a configured label sharing a colour with a squatter that was supposed to have moved off it. Deletes are last for the same reason turned up a notch: they are the one step nothing recovers from, so every recoverable action for the repository has already been attempted by the time one goes out. Nothing here reorders for throughput, and nothing here writes to two repositories at once: the token bucket paces writes at roughly one a second anyway, so parallelism would buy no wall-clock time and would cost the guarantee.

A failed repository is not a failed run

A repository that becomes unreachable partway through is abandoned and the run continues with the next one. The failure is already recorded in github.Failures by the time it is returned, so the end-of-run summary names it and the exit code carries [exit.Skipped]. Every other error — a cancelled context, a rate-limit wait past --max-wait — ends the run, because it is not about one repository.

Index

Constants

View Source
const ReportKind = "applied"

ReportKind is the "kind" of the record Report marshals to.

Like the planner's summary and repository kinds it is not an action kind and is never sent to the API. It shares the "kind" key so a consumer reading the NDJSON stream has one discriminator: `jq 'select(.kind == "applied")'` picks out what the run actually did, which on a partially failed run is not the same thing as what it planned. The string is a wire contract — added to, never renamed.

Variables

This section is empty.

Functions

func Writes

func Writes(p plan.Plan) int

Writes is how many requests executing p will send: every action except the no-ops, which never reach the API.

It is the number the startup budget check is made against, and the number the rate-limit countdown counts down.

Types

type Report

type Report struct {
	Kind string `json:"kind"` // always ReportKind

	// Repositories is how many repositories were written to or confirmed clean —
	// every one the run reached, including the ones that needed nothing.
	Repositories int `json:"repositories"`

	Created   int `json:"created"`
	Updated   int `json:"updated"`
	Deleted   int `json:"deleted"`
	Unchanged int `json:"unchanged"`

	// Abandoned names the repositories a failure cut short, in the order they
	// were reached. Being here does not say whether anything was written before
	// the failure, which is the honest answer: the write that failed is the only
	// one whose outcome is known.
	Abandoned []string `json:"abandoned,omitempty"`
}

Report is what a run of Apply did, as opposed to what its plan proposed. The two differ exactly when a repository failed partway.

func Apply

func Apply(ctx context.Context, client Writer, p plan.Plan, mode plan.Mode) (Report, error)

Apply executes p under mode and reports what it did.

Deletes are refused unless the mode asked for them

Append mode never deletes, and the guard is here rather than only in the planner because this is the package holding the destructive call. A plan carrying a delete under plan.ModeAppend — read back from a file, or computed under prune and applied by a caller that forgot which mode it was in — is refused before the **first** write of the run rather than partway through, so a refused apply has changed nothing.

Under plan.ModePrune the deletes in p execute. p is expected to hold only the candidates the user accepted: narrowing it is plan.RetainDeletes' job and the command's decision, and nothing here can tell a candidate that was chosen from one that was never offered.

type Writer

type Writer interface {
	CreateLabel(ctx context.Context, owner, repo string, label github.Label) error
	PatchLabel(ctx context.Context, owner, repo, current string, patch github.LabelPatch) error

	// DeleteLabel is reached only under [plan.ModePrune]. It is on the interface
	// unconditionally so that an append-mode fake still has to declare it, which
	// is what makes "append mode never called this" an assertion a test can make
	// rather than a method it cannot see.
	DeleteLabel(ctx context.Context, owner, repo, name string) error
}

Writer is the half of *github.Client that applying a plan uses.

It is an interface so the semantics here — the order, the abandonment of a failed repository, the refusal to delete — are testable against a fake that records calls, without an HTTP mock deciding whether the test passes. The end-to-end suite drives the real client against httptest; this narrows what has to be simulated to answer "did it do the right things, in the right order".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL