affected

package
v0.23.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MPL-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package affected answers "which estate roots does this git range touch" (GitHub issue #1751, part of epic #1749; it takes over section 4 of #1106). It reads each root's module graph at both ends of the range, walks every changed file to the roots that reach it, follows cross-estate reads (#561) to dependents, and answers indeterminate, with the reason, for a change it cannot place.

Rules

A changed file is read at its base path (deleted, modified, or the old side of a rename) against the base revision's graphs, and at its head path against the head revision's. Its module is the nearest directory at or above it that holds configuration (.tf, .tofu, .tf.json, .tofu.json) at that revision, so a template beside a module's .tf files belongs to that module.

  • A file in a root's own directory names that root: "changed".
  • A file in a module a root reaches by local paths, directly or through nested modules, names that root: "uses <module dir>".
  • A module call outside the working tree (oci://, registry, git) whose source or version changes names the roots whose graph holds the call: "pin <call> <from> -> <to>". A change to the module's own directory names no root, since no root reads the working tree for it.
  • A root that reads a named root's estate through a cross-estate reference is named too, transitively: "reads <estate>".

Indeterminate, with the reason; the consumer plans every root:

  • a changed .terraform.lock.hcl anywhere;
  • a required_providers constraint that differs between the revisions in any directory a root reaches;
  • a change in a module directory other than a root's own while some root calls a module by a floating source (a registry source with no exact version, an OCI source with no tag or digest, a git ref that is neither a commit nor a version tag, any other remote kind): the change may reach that root through a published version. This command does not read the installed version (.terraform/modules/modules.json), which is a property of a working directory's last init, not of the range;
  • a file in no module directory that is not documentation: a plan can read it (a -var-file, a file() call, a wrapper's configuration) and nothing in the configuration says which;
  • a root whose module graph does not load at either revision.

Documentation outside every module directory (.md, .markdown, .rst, .adoc, LICENSE, COPYING, NOTICE) and paths matching an -ignore pattern are listed as unplaced and name nothing. Module code that no root reaches by a local path is listed as unplaced too: that is the pinned case.

Bounds

A cross-estate read inside a module called from outside the working tree is not seen, since that module is not descended into. Reads are found by waves.EstatesRead, the reader live-waves (#1754) orders a set by: a marker-filtered data source (filter or tags) or a terraform_estate_outputs read. A read whose estate is not a literal makes the answer indeterminate (unreadable-read), never a read missed.

Index

Constants

View Source
const (
	KindChanged = "changed"
	KindUses    = "uses"
	KindPin     = "pin"
	KindReads   = "reads"
)

Reason kinds.

View Source
const (
	IndetLock     = "lock-file"
	IndetProvider = "provider-version"
	IndetFloating = "floating-module"
	IndetUnplaced = "unplaced-file"
	IndetLoad     = "load-error"
	// IndetReads is a root at the head revision with a cross-estate read
	// whose estate is not a literal: whether it reads a named root, and so
	// is named itself, cannot be told.
	IndetReads = "unreadable-read"
)

Indeterminate kinds.

View Source
const (
	UnplacedDocs    = "documentation"
	UnplacedIgnored = "ignored"
	UnplacedUnread  = "unread-module"
)

Unplaced kinds.

View Source
const Schema = 1

Schema is the -json document's version. A field's meaning never changes under one value; a new field may be added.

Variables

This section is empty.

Functions

This section is empty.

Types

type Indeterminacy

type Indeterminacy struct {
	// Kind is "lock-file", "provider-version", "floating-module",
	// "unplaced-file", "load-error" or "unreadable-read".
	Kind string `json:"kind"`
	// Path is the changed file or root directory it concerns.
	Path string `json:"path"`
	Text string `json:"text"`
}

Indeterminacy is one reason the answer is indeterminate.

type Options

type Options struct {
	// RepoDir is any directory inside the repository.
	RepoDir string
	// Spec is the range: "A..B", "A...B" or "A".
	Spec string
	// Roots, when non-empty, are the root directories to consider,
	// relative to RepoDir; otherwise every directory declaring an estate.
	Roots []string
	// Ignore are path patterns (doublestar, against the path from the
	// repository top) whose changes name nothing.
	Ignore []string
	// Reads finds a root's cross-estate reads; nil is [waves.EstatesRead].
	Reads ReadsFunc
	// TempDir is where the two revisions are extracted; "" is os.TempDir.
	TempDir string
}

Options are Compute's inputs.

type Outcome

type Outcome string

Outcome is the answer's kind.

const (
	// Determinate means Roots is the whole affected set.
	Determinate Outcome = "determinate"
	// Indeterminate means some change could not be attributed; plan every
	// root. Roots still lists what was attributed.
	Indeterminate Outcome = "indeterminate"
)

type Range

type Range struct {
	Spec string `json:"spec"`
	Base string `json:"base"`
	Head string `json:"head"`
}

Range is the range a result answers.

type ReadsFunc

type ReadsFunc func(cfg *configs.Config) ([]string, error)

ReadsFunc names the estates a root's configuration reads through a cross-estate reference (#561). waves.EstatesRead is the default.

type Reason

type Reason struct {
	// Kind is "changed", "uses", "pin" or "reads".
	Kind string `json:"kind"`
	// Module is the module directory for "uses" and the module call path
	// for "pin".
	Module string `json:"module,omitempty"`
	// From and To are the pin's two sides for "pin".
	From string `json:"from,omitempty"`
	To   string `json:"to,omitempty"`
	// Estate is the estate read, for "reads".
	Estate string `json:"estate,omitempty"`
	// Paths are the changed files behind "changed" and "uses", sorted.
	Paths []string `json:"paths,omitempty"`
	// Text is the reason as the text output prints it.
	Text string `json:"text"`
}

Reason is one cause for naming a root.

type Result

type Result struct {
	Schema int `json:"schema"`
	// Range is the range as given and the two commits it resolved to.
	Range Range `json:"range"`
	// Outcome is "determinate" or "indeterminate".
	Outcome Outcome `json:"outcome"`
	// RootsTotal is the number of estate roots at either end of the range.
	RootsTotal int `json:"roots_total"`
	// Roots are the affected roots, sorted by directory.
	Roots []Root `json:"roots"`
	// Indeterminate are the reasons the answer is not the whole set.
	Indeterminate []Indeterminacy `json:"indeterminate"`
	// Unplaced are changed paths that name no root and do not make the
	// answer indeterminate.
	Unplaced []Unplaced `json:"unplaced"`
}

Result is the -json document. Every slice is non-nil, so a consumer reads an empty array rather than null.

func Compute

func Compute(ctx context.Context, o Options) (*Result, error)

Compute answers the range.

func (*Result) JSON

func (r *Result) JSON() (string, error)

JSON is the result as the -json document.

func (*Result) Text

func (r *Result) Text() string

Text is the result as live-affected prints it without -json: one line per affected root with every reason, then what made the answer indeterminate, then the changed paths that name nothing.

type Root

type Root struct {
	// Dir is the root's directory relative to the repository top.
	Dir string `json:"dir"`
	// Estate is the estate the root declares.
	Estate string `json:"estate"`
	// Removed is true for a root that exists at the base revision only.
	Removed bool `json:"removed,omitempty"`
	// Reasons say why the root is named; at least one.
	Reasons []Reason `json:"reasons"`
}

Root is one affected estate root.

type Unplaced

type Unplaced struct {
	Path string `json:"path"`
	// Why is "documentation", "ignored" or "unread-module".
	Why  string `json:"why"`
	Text string `json:"text"`
}

Unplaced is a changed path that names no root.

Jump to

Keyboard shortcuts

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