118 lines
3.7 KiB
Go
118 lines
3.7 KiB
Go
package validation
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
|
"golang.org/x/mod/modfile"
|
|
)
|
|
|
|
func checkLocalReplacements(ctx context.Context, tree *CandidateTree) error {
|
|
var findings []Finding
|
|
replacementsByObjectID := make(map[string][]moduleReplacementSummary)
|
|
for _, entry := range tree.inputEntries {
|
|
if err := ctx.Err(); err != nil {
|
|
return &Failure{Check: LocalReplacements, Findings: findings, Cause: err}
|
|
}
|
|
if path.Base(entry.Path) != "go.mod" {
|
|
continue
|
|
}
|
|
|
|
declaration, err := resolveModuleDeclaration(ctx, tree, entry)
|
|
if err != nil {
|
|
return &Failure{
|
|
Check: LocalReplacements,
|
|
Findings: []Finding{{Path: entry.Path, Message: "could not inspect committed module declaration"}},
|
|
Cause: fmt.Errorf("inspect committed %s: %w", entry.Path, err),
|
|
}
|
|
}
|
|
replacements, ok := replacementsByObjectID[declaration.ObjectID]
|
|
if !ok {
|
|
contents, err := tree.ReadFile(ctx, declaration.Path)
|
|
if err != nil {
|
|
return &Failure{
|
|
Check: LocalReplacements,
|
|
Findings: []Finding{{Path: entry.Path, Message: "could not inspect committed module declaration"}},
|
|
Cause: fmt.Errorf("inspect committed %s: read module declaration: %w", entry.Path, err),
|
|
}
|
|
}
|
|
moduleFile, err := modfile.Parse(entry.Path, contents, nil)
|
|
if err != nil {
|
|
return &Failure{
|
|
Check: LocalReplacements,
|
|
Findings: []Finding{{Path: entry.Path, Message: "could not parse committed module declaration"}},
|
|
Cause: fmt.Errorf("parse committed %s: %w", entry.Path, err),
|
|
}
|
|
}
|
|
replacements = make([]moduleReplacementSummary, 0, len(moduleFile.Replace))
|
|
for _, replacement := range moduleFile.Replace {
|
|
if replacement.New.Version != "" {
|
|
continue
|
|
}
|
|
line := 0
|
|
if replacement.Syntax != nil {
|
|
line = replacement.Syntax.Start.Line
|
|
}
|
|
replacements = append(replacements, moduleReplacementSummary{
|
|
target: strings.Clone(replacement.New.Path),
|
|
line: line,
|
|
})
|
|
}
|
|
contents = nil
|
|
moduleFile = nil
|
|
replacementsByObjectID[declaration.ObjectID] = replacements
|
|
}
|
|
for _, replacement := range replacements {
|
|
finding := Finding{
|
|
Path: entry.Path,
|
|
Message: "filesystem module replacement is committed",
|
|
Target: replacement.target,
|
|
Directive: "replace",
|
|
Line: replacement.line,
|
|
}
|
|
findings = append(findings, finding)
|
|
}
|
|
}
|
|
if len(findings) != 0 {
|
|
return &Failure{Check: LocalReplacements, Findings: findings}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type moduleReplacementSummary struct {
|
|
target string
|
|
line int
|
|
}
|
|
|
|
func resolveModuleDeclaration(ctx context.Context, tree *CandidateTree, entry git.TreeEntry) (git.TreeEntry, error) {
|
|
if entry.Type != "blob" {
|
|
return git.TreeEntry{}, fmt.Errorf("entry has type %q, not a blob", entry.Type)
|
|
}
|
|
if entry.Mode != "120000" {
|
|
if entry.Mode != "100644" && entry.Mode != "100755" {
|
|
return git.TreeEntry{}, fmt.Errorf("entry has unsupported mode %q", entry.Mode)
|
|
}
|
|
return entry, nil
|
|
}
|
|
|
|
target, err := tree.readSymlinkTarget(ctx, entry)
|
|
if err != nil {
|
|
return git.TreeEntry{}, fmt.Errorf("read symlink declaration: %w", err)
|
|
}
|
|
destination, err := tree.ResolveDestination(ctx, entry.Path, target)
|
|
if err != nil {
|
|
return git.TreeEntry{}, fmt.Errorf("resolve symlink declaration target %q: %w", target, err)
|
|
}
|
|
if destination.Kind != DestinationEntry {
|
|
return git.TreeEntry{}, fmt.Errorf("symlink declaration target %q resolves to %s", target, destination.Kind)
|
|
}
|
|
resolved, ok := tree.Entry(destination.Path)
|
|
if !ok || resolved.Type != "blob" || (resolved.Mode != "100644" && resolved.Mode != "100755") {
|
|
return git.TreeEntry{}, fmt.Errorf("symlink declaration target %q does not resolve to a regular committed file", target)
|
|
}
|
|
return resolved, nil
|
|
}
|