package validation import ( "context" "fmt" "path" "gitea.maximumdirect.net/eric/ReleaseKit/internal/git" "golang.org/x/mod/modfile" ) func checkLocalReplacements(ctx context.Context, tree *CandidateTree) error { var findings []Finding for _, entry := range tree.Entries() { if path.Base(entry.Path) != "go.mod" { continue } contents, err := readModuleDeclaration(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), } } 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), } } for _, replacement := range moduleFile.Replace { if replacement.New.Version != "" { continue } finding := Finding{ Path: entry.Path, Message: "filesystem module replacement is committed", Target: replacement.New.Path, Directive: "replace", } if replacement.Syntax != nil { finding.Line = replacement.Syntax.Start.Line } findings = append(findings, finding) } } if len(findings) != 0 { return &Failure{Check: LocalReplacements, Findings: findings} } return nil } func readModuleDeclaration(ctx context.Context, tree *CandidateTree, entry git.TreeEntry) ([]byte, error) { if entry.Type != "blob" { return nil, fmt.Errorf("entry has type %q, not a blob", entry.Type) } if entry.Mode != "120000" { if entry.Mode != "100644" && entry.Mode != "100755" { return nil, fmt.Errorf("entry has unsupported mode %q", entry.Mode) } contents, err := tree.ReadFile(ctx, entry.Path) if err != nil { return nil, fmt.Errorf("read module declaration: %w", err) } return contents, nil } target, err := tree.ReadFile(ctx, entry.Path) if err != nil { return nil, fmt.Errorf("read symlink declaration: %w", err) } destination, err := tree.ResolveDestination(ctx, entry.Path, string(target)) if err != nil { return nil, fmt.Errorf("resolve symlink declaration target %q: %w", target, err) } if destination.Kind != DestinationEntry { return nil, 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 nil, fmt.Errorf("symlink declaration target %q does not resolve to a regular committed file", target) } contents, err := tree.ReadFile(ctx, destination.Path) if err != nil { return nil, fmt.Errorf("read resolved module declaration %q: %w", destination.Path, err) } return contents, nil }