Documentation
¶
Overview ¶
Package templateupdate provides historical template rendering for template lifecycle operations (update, diff, check). It renders a template at a specific git commit without side effects (no hooks, no metadata files).
Index ¶
- func CleanOldBackups(projectDir string, maxAge time.Duration) error
- func ClearConflictStatus(projectRoot string) error
- func CollectNewHooks(changes []HookChange) *types.HooksConfig
- func CreateBackup(projectDir string, filePaths []string) (string, error)
- func CreateBackupFromResults(projectDir string, results []MergeResult, fromCommit, toCommit string) (string, error)
- func FindLatestBackup(projectDir string) (string, error)
- func FormatCleanSummary(w io.Writer, report *ConflictReport)
- func FormatConflictSummary(w io.Writer, report *ConflictReport)
- func FormatDiff(results []MergeResult, source, oldSHA, newSHA string, opts FormatOptions)
- func FormatHookChanges(changes []HookChange) []string
- func FormatVarChanges(changes []VarChange, userVars map[string]any) []string
- func HasExecutableChanges(changes []HookChange) bool
- func ReadProjectFiles(projectDir string) (map[string]*RenderedFile, error)
- func RemoveBackup(backupPath string) error
- func ResolveNewVariables(changes []VarChange, vars map[string]any, overrides map[string]string) []string
- func RestoreBackup(projectDir, backupPath string) error
- func RestoreFromManifest(projectDir, backupPath string) error
- func ToPointerMap(m map[string]RenderedFile) map[string]*RenderedFile
- func WriteConflictStatus(projectRoot string, status *ConflictStatus) error
- func WriteManifest(backupPath string, manifest *BackupManifest) error
- type BackupManifest
- type CheckOptions
- type CheckResult
- type Checker
- type CommitFetcher
- type ConflictReport
- type ConflictStatus
- type ConflictedFile
- type DiffOptions
- type DiffResult
- type Differ
- type FileAction
- type FormatOptions
- type GitMerger
- type HistoricalRenderer
- func (r *HistoricalRenderer) LoadConfigAtCommit(ctx context.Context, templateURL string, commitSHA string) (*tmplconfig.TemplateConfig, error)
- func (r *HistoricalRenderer) RenderAt(ctx context.Context, templateURL string, commitSHA string, vars map[string]any) (map[string]RenderedFile, error)
- func (r *HistoricalRenderer) RenderPair(ctx context.Context, templateURL string, oldSHA string, newSHA string, ...) (base, theirs map[string]*RenderedFile, err error)
- type HookChange
- type HookChangeType
- type IgnoreMatcher
- type IgnoreMatcherOptions
- type ManifestEntry
- type MergeEngine
- type MergeOp
- type MergeResult
- type RenderedFile
- type ResolveMode
- type TextMerger
- type UpdateMode
- type UpdateOptions
- type UpdateResult
- type Updater
- type VarChange
- type VarChangeType
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CleanOldBackups ¶
CleanOldBackups removes backup directories older than maxAge.
func ClearConflictStatus ¶
ClearConflictStatus removes the conflict status file.
func CollectNewHooks ¶
func CollectNewHooks(changes []HookChange) *types.HooksConfig
CollectNewHooks returns the new hook commands from added/modified hooks, organised into a HooksConfig for execution.
func CreateBackup ¶
CreateBackup copies the specified files from projectDir into .tag/backup/{timestamp}/ for restoration on --abort. Returns the backup directory path.
func CreateBackupFromResults ¶
func CreateBackupFromResults(projectDir string, results []MergeResult, fromCommit, toCommit string) (string, error)
CreateBackupFromResults creates a manifest-aware backup from merge results. Files being modified or deleted are copied to the backup directory; files being added are tracked in the manifest so they can be removed on rollback.
func FindLatestBackup ¶
FindLatestBackup returns the path of the most recent backup directory, or empty string if none exists.
func FormatCleanSummary ¶
func FormatCleanSummary(w io.Writer, report *ConflictReport)
FormatCleanSummary writes a success summary to the given writer.
func FormatConflictSummary ¶
func FormatConflictSummary(w io.Writer, report *ConflictReport)
FormatConflictSummary writes a human-readable conflict summary to the given writer. This is intended for stderr output.
func FormatDiff ¶
func FormatDiff(results []MergeResult, source, oldSHA, newSHA string, opts FormatOptions)
FormatDiff writes the diff output for the given merge results.
func FormatHookChanges ¶
func FormatHookChanges(changes []HookChange) []string
FormatHookChanges returns a human-readable summary of hook changes.
func FormatVarChanges ¶
FormatVarChanges returns a human-readable summary of variable changes.
func HasExecutableChanges ¶
func HasExecutableChanges(changes []HookChange) bool
HasExecutableChanges returns true if any hook changes would require execution.
func ReadProjectFiles ¶
func ReadProjectFiles(projectDir string) (map[string]*RenderedFile, error)
ReadProjectFiles walks the project directory and returns an in-memory snapshot of all files as a map of forward-slash paths to RenderedFile pointers.
It skips the .tag/ directory, .git/, and the .tagconfig.json file itself. Binary detection uses the same heuristic as template rendering.
func RemoveBackup ¶
RemoveBackup removes a backup directory.
func ResolveNewVariables ¶
func ResolveNewVariables(changes []VarChange, vars map[string]any, overrides map[string]string) []string
ResolveNewVariables applies defaults for new optional variables and returns the names of new required variables that still need values.
func RestoreBackup ¶
RestoreBackup restores files from a backup directory to the project.
func RestoreFromManifest ¶
RestoreFromManifest restores a project using the manifest for precise rollback. If no manifest exists (legacy backup), falls back to RestoreBackup.
func ToPointerMap ¶
func ToPointerMap(m map[string]RenderedFile) map[string]*RenderedFile
ToPointerMap converts a value map to a pointer map for MergeTrees compatibility.
func WriteConflictStatus ¶
func WriteConflictStatus(projectRoot string, status *ConflictStatus) error
WriteConflictStatus atomically writes the conflict status file to <projectRoot>/.tag/conflicts.json.
func WriteManifest ¶
func WriteManifest(backupPath string, manifest *BackupManifest) error
WriteManifest writes the manifest to the backup directory.
Types ¶
type BackupManifest ¶
type BackupManifest struct {
CreatedAt time.Time `json:"created_at"`
FromCommit string `json:"from_commit"`
ToCommit string `json:"to_commit"`
Files []ManifestEntry `json:"files"`
}
BackupManifest records metadata about a backup for reliable rollback.
func ReadManifest ¶
func ReadManifest(backupPath string) (*BackupManifest, error)
ReadManifest reads the manifest from a backup directory. Returns nil, nil if no manifest exists (legacy backup).
type CheckOptions ¶
type CheckOptions struct {
ProjectDir string // Project directory (default: ".")
Ref string // Override the template ref to check against
}
CheckOptions configures the check operation.
type CheckResult ¶
type CheckResult struct {
UpToDate bool // true if project matches latest template commit
CurrentSHA string // commit SHA stored in .tagconfig.json
LatestSHA string // latest commit SHA from remote
Source string // template source string
}
CheckResult contains the outcome of a template freshness check.
type Checker ¶
type Checker struct {
// contains filtered or unexported fields
}
Checker checks whether a project's template is up to date with upstream.
func NewChecker ¶
func NewChecker(resolver remote.LatestCommitResolver) *Checker
NewChecker creates a Checker with the given commit resolver.
func (*Checker) Check ¶
func (c *Checker) Check(ctx context.Context, opts CheckOptions) (*CheckResult, error)
Check reads the project's .tagconfig.json, resolves the latest upstream commit, and compares them.
type CommitFetcher ¶
type CommitFetcher interface {
FetchAtCommit(ctx context.Context, url, commitSHA, destDir string) (string, error)
}
CommitFetcher checks out a git repository at a specific commit SHA.
type ConflictReport ¶
type ConflictReport struct {
// Conflicts lists files that could not be cleanly merged.
Conflicts []ConflictedFile
// Clean lists files that were merged or updated without conflicts.
Clean []MergeResult
// Prompts lists files that need an explicit user decision.
Prompts []MergeResult
// Skipped lists paths excluded by ignore patterns.
Skipped []string
}
ConflictReport summarises the outcome of a tree merge for display and downstream processing.
func NewConflictReport ¶
func NewConflictReport(results []MergeResult, skipped []string) *ConflictReport
NewConflictReport builds a ConflictReport from merge results and skipped paths.
func (*ConflictReport) HasConflicts ¶
func (r *ConflictReport) HasConflicts() bool
HasConflicts reports whether any files have unresolved conflicts or prompts.
type ConflictStatus ¶
type ConflictStatus struct {
SchemaVersion int `json:"schema_version"`
UpdateCommit string `json:"update_commit"`
ConflictedFiles []string `json:"conflicted_files"`
PromptFiles []string `json:"prompt_files,omitempty"`
ResolvedFiles []string `json:"resolved_files"`
StartedAt time.Time `json:"started_at"`
}
ConflictStatus is persisted to .tag/conflicts.json when an update leaves unresolved conflicts. It allows resumption with --continue or --abort.
func NewConflictStatus ¶
func NewConflictStatus(report *ConflictReport, updateCommit string) *ConflictStatus
NewConflictStatus creates a ConflictStatus from a ConflictReport.
func ReadConflictStatus ¶
func ReadConflictStatus(projectRoot string) (*ConflictStatus, error)
ReadConflictStatus reads the conflict status from <projectRoot>/.tag/conflicts.json. Returns nil if the file does not exist.
type ConflictedFile ¶
type ConflictedFile struct {
Path string
MarkerCount int
BaseContent []byte
OursContent []byte
TheirsContent []byte
MergedContent []byte
Mode os.FileMode
}
ConflictedFile provides details about a single conflicted file.
type DiffOptions ¶
type DiffOptions struct {
ProjectDir string // Project directory (default: ".")
Ref string // Override template ref
}
DiffOptions configures the diff operation.
type DiffResult ¶
type DiffResult struct {
OldSHA string // commit SHA from .tagconfig.json
NewSHA string // latest commit SHA from remote
Source string // template source string
Results []MergeResult // merge results from dry-run
Skipped []string // paths skipped by ignore rules
}
DiffResult contains the outcome of a template diff operation.
type Differ ¶
type Differ struct {
// contains filtered or unexported fields
}
Differ performs a dry-run 3-way merge to show what would change on update.
func NewDiffer ¶
func NewDiffer(renderer *HistoricalRenderer, resolver remote.LatestCommitResolver) *Differ
NewDiffer creates a Differ with the given renderer and resolver.
func (*Differ) Diff ¶
func (d *Differ) Diff(ctx context.Context, opts DiffOptions) (*DiffResult, error)
Diff computes the differences between the current project and the latest template version. It performs a full 3-way merge in dry-run mode.
type FileAction ¶
type FileAction string
FileAction describes what happened to a file during an update.
const ( // FileModified means the file existed and was changed (back up, restore on rollback). FileModified FileAction = "modified" // FileDeleted means the file was removed by the merge (back up, restore on rollback). FileDeleted FileAction = "deleted" // FileAdded means the file was newly created by the merge (remove on rollback). FileAdded FileAction = "added" )
type FormatOptions ¶
type FormatOptions struct {
Color bool // Enable ANSI color output
Stat bool // Show diffstat instead of full diff
Writer io.Writer
}
FormatOptions controls diff output formatting.
type GitMerger ¶
type GitMerger struct{}
GitMerger implements TextMerger by shelling out to git merge-file.
func (*GitMerger) Merge3 ¶
func (g *GitMerger) Merge3(ctx context.Context, path string, base, ours, theirs []byte) ([]byte, bool, error)
Merge3 writes base, ours, and theirs to temporary files and runs git merge-file to produce a 3-way merge. Exit code 0 means clean merge, exit code >0 with output means conflicts, and any other error is fatal.
type HistoricalRenderer ¶
type HistoricalRenderer struct {
// contains filtered or unexported fields
}
HistoricalRenderer renders a template at a specific git commit using provided variables. It produces an in-memory snapshot of the rendered output without any side effects.
func NewHistoricalRenderer ¶
func NewHistoricalRenderer(fetcher CommitFetcher) *HistoricalRenderer
NewHistoricalRenderer creates a renderer that uses the given fetcher to check out templates at historical commits.
func (*HistoricalRenderer) LoadConfigAtCommit ¶
func (r *HistoricalRenderer) LoadConfigAtCommit( ctx context.Context, templateURL string, commitSHA string, ) (*tmplconfig.TemplateConfig, error)
LoadConfigAtCommit fetches the template at the given commit and returns the parsed TemplateConfig (tag.template.json). The checkout is created in a temporary directory and cleaned up before returning.
func (*HistoricalRenderer) RenderAt ¶
func (r *HistoricalRenderer) RenderAt( ctx context.Context, templateURL string, commitSHA string, vars map[string]any, ) (map[string]RenderedFile, error)
RenderAt checks out the template at commitSHA and renders it with vars. It returns a map of relative paths (using forward slashes) to rendered files. The caller-provided vars are used as-is; no variable collection or prompting occurs.
The method creates a temporary directory for the checkout and cleans it up before returning, regardless of success or failure.
func (*HistoricalRenderer) RenderPair ¶
func (r *HistoricalRenderer) RenderPair( ctx context.Context, templateURL string, oldSHA string, newSHA string, vars map[string]any, ) (base, theirs map[string]*RenderedFile, err error)
RenderPair renders the template at two commits (old and new) and returns pointer maps suitable for MergeTrees. This provides a clean API for future single-clone optimization while currently calling RenderAt twice.
type HookChange ¶
type HookChange struct {
Phase string // "pre_scaffold" or "post_scaffold"
Type HookChangeType
OldHooks []string // nil for HookAdded
NewHooks []string // nil for HookRemoved
}
HookChange describes a single hook difference between template versions.
func DetectHookChanges ¶
func DetectHookChanges(oldConfig, newConfig *tmplconfig.TemplateConfig) []HookChange
DetectHookChanges compares hook definitions between two template configs and returns a sorted list of changes.
type HookChangeType ¶
type HookChangeType int
HookChangeType classifies how a hook changed between template versions.
const ( // HookAdded means the hook exists in the new config but not the old. HookAdded HookChangeType = iota // HookRemoved means the hook existed in the old config but not the new. HookRemoved // HookModified means the hook commands changed between versions. HookModified )
func (HookChangeType) String ¶
func (t HookChangeType) String() string
String returns a human-readable label for a HookChangeType.
type IgnoreMatcher ¶
type IgnoreMatcher struct {
// contains filtered or unexported fields
}
IgnoreMatcher determines whether a file should be skipped during template updates. It merges patterns from four sources in priority order:
- Built-in defaults (lowest priority)
- .tagignore file (gitignore-style)
- .tagconfig.json skip_patterns
- CLI --skip flags (highest priority)
All sources are combined into a single gitignore-style matcher. Because the go-git matcher evaluates patterns in order and the last matching pattern wins, we append sources from lowest to highest priority so that higher-priority sources can override lower ones (including negation patterns).
func NewIgnoreMatcher ¶
func NewIgnoreMatcher(opts IgnoreMatcherOptions) (*IgnoreMatcher, error)
NewIgnoreMatcher creates an IgnoreMatcher by merging patterns from all configured sources.
func (*IgnoreMatcher) ShouldSkip ¶
func (m *IgnoreMatcher) ShouldSkip(relPath string, isDir bool) bool
ShouldSkip reports whether the given relative path should be excluded from template update operations.
type IgnoreMatcherOptions ¶
type IgnoreMatcherOptions struct {
// ProjectRoot is the path to the project directory. Used to read
// .tagignore and .tagconfig.json. May be empty if patterns are
// supplied directly.
ProjectRoot string
// TagignorePatterns are patterns parsed from a .tagignore file.
// If nil and ProjectRoot is set, patterns are loaded from the file.
TagignorePatterns []string
// TagconfigPatterns are patterns from .tagconfig.json skip_patterns.
// If nil and ProjectRoot is set, patterns are loaded from the file.
TagconfigPatterns []string
// CLIPatterns are patterns from --skip CLI flags.
CLIPatterns []string
}
IgnoreMatcherOptions configures the pattern sources for an IgnoreMatcher.
type ManifestEntry ¶
type ManifestEntry struct {
Path string `json:"path"`
Action FileAction `json:"action"`
}
ManifestEntry records a single file's role in a backup.
func BuildManifestEntries ¶
func BuildManifestEntries(results []MergeResult) []ManifestEntry
BuildManifestEntries categorises merge results into manifest entries.
type MergeEngine ¶
type MergeEngine struct {
// contains filtered or unexported fields
}
MergeEngine reconciles three file trees: base (old template render), ours (user's current project), and theirs (new template render).
func NewMergeEngine ¶
func NewMergeEngine(tm TextMerger, ignoreFn func(path string) bool) *MergeEngine
NewMergeEngine creates a MergeEngine with the given text merger and optional ignore function. If ignoreFn is nil, no files are skipped.
func (*MergeEngine) MergeFile ¶
func (m *MergeEngine) MergeFile( ctx context.Context, path string, base, ours, theirs *RenderedFile, ) (MergeResult, error)
MergeFile merges a single file path across the three trees using the decision matrix defined in the design document.
func (*MergeEngine) MergeTrees ¶
func (m *MergeEngine) MergeTrees( ctx context.Context, base, ours, theirs map[string]*RenderedFile, ) ([]MergeResult, []string, error)
MergeTrees merges three file trees and returns a sorted list of merge results plus a list of skipped paths.
type MergeOp ¶
type MergeOp int
MergeOp classifies the action to take for a file during a 3-way merge.
const ( // MergeKeep means the file needs no changes (user's version is current). MergeKeep MergeOp = iota // MergeAdd means a new file from the template should be added. MergeAdd // MergeDelete means the file was removed by both sides. MergeDelete // MergeUpdate means a clean merge was applied automatically. MergeUpdate // MergeConflict means overlapping changes require user resolution. MergeConflict // MergeUserAdded means the file exists only in the user's project. MergeUserAdded // MergePrompt means the situation needs an explicit user decision // (e.g. user deleted but template changed, or binary conflict). MergePrompt )
type MergeResult ¶
type MergeResult struct {
// Path is the relative file path within the project.
Path string
// Op is the merge operation classification.
Op MergeOp
// Content holds the merged content. For conflicts it contains conflict markers.
Content []byte
// Mode is the file permission mode for the merged file.
Mode os.FileMode
// Conflicted is true when Content contains unresolved conflict markers.
Conflicted bool
// IsBinary is true when the file was detected as binary.
IsBinary bool
// PromptReason describes why a user prompt is needed (non-empty only for MergePrompt).
PromptReason string
// BaseContent is the original base version (populated for conflicts/prompts to enable resolution).
BaseContent []byte
// OursContent is the user's version (populated for conflicts/prompts).
OursContent []byte
// TheirsContent is the template's version (populated for conflicts/prompts).
TheirsContent []byte
// OursSHA256 is the SHA256 hex digest of the user's binary content (for display).
OursSHA256 string
// TheirsSHA256 is the SHA256 hex digest of the template's binary content (for display).
TheirsSHA256 string
}
MergeResult describes the outcome of merging a single file path.
func ResolveConflicts ¶
func ResolveConflicts(report *ConflictReport, mode ResolveMode) []MergeResult
ResolveConflicts applies the given resolution mode to all conflicts and prompts (including binary file conflicts) in the report. Returns a new set of clean MergeResults that replace the conflicted/prompt entries.
type RenderedFile ¶
RenderedFile represents a single file produced by rendering a template.
type ResolveMode ¶
type ResolveMode int
ResolveMode specifies how to auto-resolve conflicts.
const ( // ResolveNone does not auto-resolve; conflict markers remain. ResolveNone ResolveMode = iota // ResolveOurs replaces conflicted content with the user's version. ResolveOurs // ResolveTheirs replaces conflicted content with the template's version. ResolveTheirs )
type TextMerger ¶
type TextMerger interface {
// Merge3 performs a 3-way merge of base, ours, and theirs content.
// Returns the merged content, whether conflicts were found, and any error.
Merge3(ctx context.Context, path string, base, ours, theirs []byte) (merged []byte, conflicted bool, err error)
}
TextMerger performs 3-way text merging of file contents.
type UpdateMode ¶
type UpdateMode int
UpdateMode defines the operational mode for the update command.
const ( // UpdateModeApply is the default mode: compute and apply changes. UpdateModeApply UpdateMode = iota // UpdateModeContinue resumes after manual conflict resolution. UpdateModeContinue // UpdateModeAbort restores from backup and cancels in-progress update. UpdateModeAbort )
type UpdateOptions ¶
type UpdateOptions struct {
ProjectDir string
Ref string
VarOverrides map[string]string
ResolveMode ResolveMode
SkipPatterns []string
DryRun bool
Backup bool
SkipHooks bool
AcceptHooks bool
Mode UpdateMode
}
UpdateOptions configures the update operation.
type UpdateResult ¶
type UpdateResult struct {
OldSHA string
NewSHA string
Applied []MergeResult
Conflicts *ConflictReport
VarChanges []VarChange
HookChanges []HookChange
NewFiles int
UpdatedFiles int
DeletedFiles int
}
UpdateResult contains the outcome of an update operation.
type Updater ¶
type Updater struct {
// contains filtered or unexported fields
}
Updater applies upstream template changes to the user's project.
func NewUpdater ¶
func NewUpdater(renderer *HistoricalRenderer, resolver remote.LatestCommitResolver) *Updater
NewUpdater creates an Updater with the given renderer and resolver.
func (*Updater) Update ¶
func (u *Updater) Update(ctx context.Context, opts UpdateOptions) (*UpdateResult, error)
Update performs the template update operation.
type VarChange ¶
type VarChange struct {
Name string
Type VarChangeType
OldDef *tmplconfig.VariableDef // nil for VarAdded
NewDef *tmplconfig.VariableDef // nil for VarRemoved
}
VarChange describes a single variable difference between template versions.
func DetectVarChanges ¶
func DetectVarChanges(oldConfig, newConfig *tmplconfig.TemplateConfig) []VarChange
DetectVarChanges compares variable definitions between two template configs and returns a sorted list of changes. Private variables (starting with _) are included since they may affect rendering.
func (VarChange) NeedsPrompt ¶
NeedsPrompt returns true if the variable change requires user input.
type VarChangeType ¶
type VarChangeType int
VarChangeType classifies how a template variable changed between versions.
const ( // VarAdded means the variable exists in the new config but not the old. VarAdded VarChangeType = iota // VarRemoved means the variable existed in the old config but not the new. VarRemoved // VarDefaultChanged means the variable's default value changed. VarDefaultChanged // VarTypeChanged means the variable's type changed. VarTypeChanged )
func (VarChangeType) String ¶
func (t VarChangeType) String() string
String returns a human-readable label for a VarChangeType.