Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions internal/deps/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,14 @@ func guardTarget(target string, src Source) (replaced bool, err error) {
"%s already exists and carries no provenance header, so it is a model this repository owns rather than a copy of one — importing would overwrite it. Import into a different directory, or move that file aside first",
target)
}
// A header too malformed to name where it came from is still a vendored
// copy, and replacing it is how it gets repaired; only a header that names
// a *different* model blocks the write.
// A malformed header can still be repaired, but only if its surviving
// identity names this exact source. A reserved-prefix comment alone is not
// enough to establish that this repository does not own the file.
h, _ := provenance.Parse(existing)
if h.Origin == "" || h.Path == "" {
return true, nil
return false, fmt.Errorf(
"%s cannot be identified as a copy of the source (%s/%s) because its provenance header does not name both an origin and path. Import into a different directory, or deliberately move or delete the existing file first",
target, src.Origin, src.Path)
}
switch {
// GitHub treats an owner and a repository name case-insensitively, and
Expand Down
33 changes: 33 additions & 0 deletions internal/deps/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,39 @@ func TestImport_RefusesToOverwriteWhatItDidNotWrite(t *testing.T) {
}
}

func TestImport_RefusesUnknownReservedPrefixAtTarget(t *testing.T) {
t.Parallel()

dir := t.TempDir()
target := filepath.Join(dir, "payments.modelith.yaml")
existing := "# modelith-note: locally owned\n" + strings.Replace(upstream, "title: Payments", "title: Ours", 1)
if err := os.WriteFile(target, []byte(existing), 0o644); err != nil {
t.Fatal(err)
}

_, err := importInto(t, dir, &fakeRunner{content: upstream, sha: sha}, blobURL)
if err == nil {
t.Fatal("an unknown reserved-prefix comment allowed import to overwrite a local model")
}
for _, want := range []string{
"cannot be identified as a copy of the source",
"Import into a different directory",
"deliberately move or delete",
} {
if !strings.Contains(err.Error(), want) {
t.Errorf("the error does not offer %q: %v", want, err)
}
}

got, readErr := os.ReadFile(target)
if readErr != nil {
t.Fatal(readErr)
}
if string(got) != existing {
t.Errorf("the refused import wrote over the file anyway:\n%s", got)
}
}

// TestSplitHint pins that the ref/path hint is offered for the failure it
// explains and no other. It was appended to every fetch error, so "gh is not
// installed" arrived with a paragraph about ref splitting attached — advice
Expand Down
Loading