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
32 changes: 30 additions & 2 deletions pkg/control/vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,9 +563,9 @@ func prepareRepositoryRevisionAt(
return preparedRepositoryRevision{}, fmt.Errorf("clone repository: %w", err)
}
}
configuredURL, err := gitWithEnvironment(ctx, cachePath, environment, "remote", "get-url", "origin")
configuredURL, err := ensureRepositoryOrigin(ctx, cachePath, environment, repositoryURL)
if err != nil {
return preparedRepositoryRevision{}, fmt.Errorf("resolve repository origin: %w", err)
return preparedRepositoryRevision{}, err
}
if configuredURL != repositoryURL {
return preparedRepositoryRevision{}, fmt.Errorf("repository cache origin %q does not match requested source", configuredURL)
Expand Down Expand Up @@ -624,6 +624,34 @@ func prepareRepositoryRevisionAt(
}, nil
}

// ensureRepositoryOrigin repairs only the absent-origin case in a Codefly-owned
// cache projection. The request URL is authoritative when no origin exists;
// an existing origin is returned unchanged so the caller can reject a source
// mismatch instead of silently retargeting project state.
func ensureRepositoryOrigin(ctx context.Context, cachePath string, environment []string, repositoryURL string) (string, error) {
remotes, err := gitWithEnvironment(ctx, cachePath, environment, "remote")
if err != nil {
return "", fmt.Errorf("list repository remotes: %w", err)
}
hasOrigin := false
for _, remote := range strings.Split(remotes, "\n") {
if strings.TrimSpace(remote) == "origin" {
hasOrigin = true
break
}
}
if !hasOrigin {
if _, addErr := gitWithEnvironment(ctx, cachePath, environment, "remote", "add", "origin", repositoryURL); addErr != nil {
return "", fmt.Errorf("repair missing repository origin: %w", addErr)
}
}
configuredURL, err := gitWithEnvironment(ctx, cachePath, environment, "remote", "get-url", "origin")
if err != nil {
return "", fmt.Errorf("resolve repository origin: %w", err)
}
return configuredURL, nil
}

func removeIncompleteRepositoryCache(cachePath string) error {
info, err := os.Lstat(cachePath)
switch {
Expand Down
49 changes: 49 additions & 0 deletions pkg/control/vcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,55 @@ func TestPublicHTTPSRepositorySnapshotIgnoresAmbientGitRewrite(t *testing.T) {
}
}

func TestPrepareRepositoryCheckoutRepairsMissingOrigin(t *testing.T) {
source := initGitRepo(t)
serverRoot := t.TempDir()
bare := filepath.Join(serverRoot, "repository.git")
runGit(t, serverRoot, "clone", "--bare", "--", source, bare)
revision, err := git(t.Context(), source, "rev-parse", "HEAD")
if err != nil {
t.Fatal(err)
}

root := t.TempDir()
request := PrepareRepositoryCheckoutRequest{
Dir: root, RepositoryURL: "file://" + bare, CacheDirectory: "cache/repository",
Revision: revision, FetchIdentity: "missing-origin-repair", RemoteAccess: RepositoryRemoteAccessLocalFile,
}
if _, err := New().PrepareRepositoryCheckout(t.Context(), request); err != nil {
t.Fatal(err)
}
cachePath := filepath.Join(root, request.CacheDirectory)
runGit(t, cachePath, "remote", "remove", "origin")
junk := filepath.Join(cachePath, "generated.tmp")
if err := os.WriteFile(junk, []byte("remove me"), 0o600); err != nil {
t.Fatal(err)
}

repaired, err := New().PrepareRepositoryCheckout(t.Context(), request)
if err != nil || repaired.Revision != revision {
t.Fatalf("repaired checkout = %+v, want revision %s (error = %v)", repaired, revision, err)
}
configuredURL, err := git(t.Context(), cachePath, "config", "--get", "remote.origin.url")
if err != nil {
t.Fatalf("read repaired origin: %v", err)
}
if configuredURL != request.RepositoryURL {
t.Fatalf("repaired origin = %q, want %q", configuredURL, request.RepositoryURL)
}
if _, err := os.Stat(junk); !os.IsNotExist(err) {
t.Fatalf("repaired checkout retained generated file (stat error = %v)", err)
}
if _, err := New().PrepareRepositoryCheckout(t.Context(), request); err != nil {
t.Fatalf("retry repaired checkout: %v", err)
}

runGit(t, cachePath, "remote", "set-url", "origin", "file://"+filepath.Join(serverRoot, "different.git"))
if _, err := New().PrepareRepositoryCheckout(t.Context(), request); err == nil || !strings.Contains(err.Error(), "does not match requested source") {
t.Fatalf("mismatched origin error = %v, want source mismatch", err)
}
}

func writeCommit(t *testing.T, plane Plane, ctx context.Context, dir, name, content, message string) GitCommit {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o600); err != nil {
Expand Down
Loading