Compare commits

...
5 changed files with 83 additions and 14 deletions
+43 -10
View File
@@ -82,18 +82,20 @@ func narrow(ctx context.Context, run forgekey.Runner, env []string, dir, commit,
for i, p := range paths { for i, p := range paths {
full[i] = path.Join(mdir, p) full[i] = path.Join(mdir, p)
} }
r := run(ctx, env, "git", append([]string{"-C", dir, "ls-tree", "-z", commit, "--"}, full...)...)
if r.Status != 0 || r.Err != "" {
return fmt.Errorf("git ls-tree %s of the module %s: %s", commit, mdir, forgekey.Said(r.Err+"\n"+r.Stderr))
}
objects := map[string]string{} objects := map[string]string{}
for _, l := range strings.Split(r.Stdout, "\x00") { for _, round := range unnested(full) {
meta, name, ok := strings.Cut(l, "\t") r := run(ctx, env, "git", append([]string{"-C", dir, "ls-tree", "-z", commit, "--"}, round...)...)
fields := strings.Fields(meta) if r.Status != 0 || r.Err != "" {
if !ok || len(fields) != 3 { return fmt.Errorf("git ls-tree %s of the module %s: %s", commit, mdir, forgekey.Said(r.Err+"\n"+r.Stderr))
continue }
for _, l := range strings.Split(r.Stdout, "\x00") {
meta, name, ok := strings.Cut(l, "\t")
fields := strings.Fields(meta)
if !ok || len(fields) != 3 {
continue
}
objects[name] = fields[2]
} }
objects[name] = fields[2]
} }
mt.Paths = map[string]string{} mt.Paths = map[string]string{}
for i, p := range paths { for i, p := range paths {
@@ -106,4 +108,35 @@ func narrow(ctx context.Context, run forgekey.Runner, env []string, dir, commit,
return nil return nil
} }
// unnested splits paths into rounds of one git ls-tree each, none of whose paths lies inside another of its round: given
// "go" and "go/seats/x" together, git ls-tree descends into go and lists its children instead of go itself, so the
// object of go is missing from its answer (issue 1966: mesh-contracts' manifest names both). Usually one round; the
// order of paths is kept within a round.
func unnested(paths []string) [][]string {
var rounds [][]string
for _, p := range paths {
placed := false
for i, round := range rounds {
if !nestsWithAny(p, round) {
rounds[i] = append(round, p)
placed = true
break
}
}
if !placed {
rounds = append(rounds, []string{p})
}
}
return rounds
}
func nestsWithAny(p string, round []string) bool {
for _, q := range round {
if p == q || strings.HasPrefix(p, q+"/") || strings.HasPrefix(q, p+"/") || p == "." || q == "." {
return true
}
}
return false
}
func sha256Hex(b []byte) string { h := sha256.Sum256(b); return hex.EncodeToString(h[:]) } func sha256Hex(b []byte) string { h := sha256.Sum256(b); return hex.EncodeToString(h[:]) }
@@ -62,4 +62,16 @@ func TestAGoOnlyCommitLeavesTheNamedObjectsEqualOnRealGit(t *testing.T) {
if src["."].Paths["src"] != realGit(t, repo, "rev-parse", "HEAD:src") { if src["."].Paths["src"] != realGit(t, repo, "rev-parse", "HEAD:src") {
t.Fatalf("the object of src is %s, git says %s", src["."].Paths["src"], realGit(t, repo, "rev-parse", "HEAD:src")) t.Fatalf("the object of src is %s, git says %s", src["."].Paths["src"], realGit(t, repo, "rev-parse", "HEAD:src"))
} }
// A manifest naming a path and a path inside it (mesh-contracts' "go" and "go/seats/meshvault"; issue 1966): each
// gets its own object, not git ls-tree's listing of the outer one's children.
writeFile(t, repo, "nested/module.json", `{"module":"nested","version":"1","build":{"artifacts":[`+
`{"name":"go","kind":"package","language":"go","paths":["go"]},`+
`{"name":"go-seat","kind":"package","language":"go","paths":["go/seats/a"]}]}}`)
writeFile(t, repo, "nested/go/go.mod", "module x\n")
writeFile(t, repo, "nested/go/seats/a/a.go", "package a\n")
nested := trees(commit("nested"))["nested"]
if nested.Paths["go"] != realGit(t, repo, "rev-parse", "HEAD:nested/go") || nested.Paths["go/seats/a"] != realGit(t, repo, "rev-parse", "HEAD:nested/go/seats/a") {
t.Fatalf("a path and a path inside it: %v", nested.Paths)
}
} }
@@ -97,9 +97,10 @@ func (m *Module) candidate(ctx context.Context, workspace string) (string, error
// compileError is the first line of the compiler's output that says an error: not the `# package` heading go build // compileError is the first line of the compiler's output that says an error: not the `# package` heading go build
// prints above a package's errors, nor Go's progress. // prints above a package's errors, nor Go's progress.
func compileError(out string) string { func compileError(out string) string {
for _, l := range strings.Split(out, "\n") { lines := strings.Split(out, "\n")
for i, l := range lines {
if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "#") && !strings.HasPrefix(l, "go: downloading ") { if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "#") && !strings.HasPrefix(l, "go: downloading ") {
return l return withContinuation(l, lines[i+1:])
} }
} }
return firstLine(out) return firstLine(out)
@@ -351,15 +351,33 @@ func (m *Module) now() time.Time { return m.clock() }
// causeLine is the first line of a go command's output that says something other than Go's own progress: `go: // causeLine is the first line of a go command's output that says something other than Go's own progress: `go:
// downloading ...` comes first when Go fetches a module, and `# <package>` heads a compile error of go build or go vet // downloading ...` comes first when Go fetches a module, and `# <package>` heads a compile error of go build or go vet
// (issue 1703: the reason said only the package), and the cause of a failure on a later line. // (issue 1703: the reason said only the package), and the cause of a failure on a later line.
//
// A cause Go writes over several lines keeps its indented continuation lines, joined by a space: a module graph error
// is "X requires" with the module that fails, and why, on the next line (issue 1966: the composer's reason ended at
// "requires" and hid "golang.org/x/tools@v0.42.0: module lookup disabled").
func causeLine(s string) string { func causeLine(s string) string {
for _, l := range strings.Split(s, "\n") { lines := strings.Split(s, "\n")
for i, l := range lines {
if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "go: downloading ") && !strings.HasPrefix(l, "# ") { if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "go: downloading ") && !strings.HasPrefix(l, "# ") {
return l return withContinuation(l, lines[i+1:])
} }
} }
return firstLine(s) return firstLine(s)
} }
// withContinuation is first with each following line Go indented under it, trimmed and joined by a space.
func withContinuation(first string, rest []string) string {
for _, l := range rest {
if !strings.HasPrefix(l, "\t") && !strings.HasPrefix(l, " ") {
break
}
if l = strings.TrimSpace(l); l != "" {
first += " " + l
}
}
return first
}
func firstLine(s string) string { func firstLine(s string) string {
for _, l := range strings.Split(s, "\n") { for _, l := range strings.Split(s, "\n") {
if l = strings.TrimSpace(l); l != "" { if l = strings.TrimSpace(l); l != "" {
@@ -309,9 +309,14 @@ func TestCauseLineSkipsProgressAndThePackageHeading(t *testing.T) {
"go: downloading x v1\n# git.example/m/cmd\n./main.go:3:2: undefined: y\n": "./main.go:3:2: undefined: y", "go: downloading x v1\n# git.example/m/cmd\n./main.go:3:2: undefined: y\n": "./main.go:3:2: undefined: y",
"\ngo: example.com/x@v1: reading: 404\n": "go: example.com/x@v1: reading: 404", "\ngo: example.com/x@v1: reading: 404\n": "go: example.com/x@v1: reading: 404",
"go: downloading x v1\n": "go: downloading x v1", "go: downloading x v1\n": "go: downloading x v1",
// issue 1966: a module graph error goes on, indented, on the next line; a later error is not taken in.
"a.go:3:8: x@v1 requires\n\ty@v2: module lookup disabled by GOPROXY=off\nb.go:1:1: other\n": "a.go:3:8: x@v1 requires y@v2: module lookup disabled by GOPROXY=off",
} { } {
if got := causeLine(in); got != want { if got := causeLine(in); got != want {
t.Errorf("causeLine(%q) = %q, want %q", in, got, want) t.Errorf("causeLine(%q) = %q, want %q", in, got, want)
} }
if got := compileError(in); got != want {
t.Errorf("compileError(%q) = %q, want %q", in, got, want)
}
} }
} }