diff --git a/libs/git/info.go b/libs/git/info.go index 6e31d68219c..18fa7e145ca 100644 --- a/libs/git/info.go +++ b/libs/git/info.go @@ -28,11 +28,12 @@ type RepositoryInfo struct { WorktreeRoot string } +// gitInfo holds the fields we read from get-status's git_info. Only id and path +// are reliable: branch/commit/url are absent for git-in-data-plane folders (and +// deprecated on the workspace API), so those are read from the Repos API by id. type gitInfo struct { - Branch string `json:"branch"` - HeadCommitID string `json:"head_commit_id"` - Path string `json:"path"` - URL string `json:"url"` + ID int64 `json:"id"` + Path string `json:"path"` } type response struct { @@ -100,17 +101,25 @@ func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.Work return result, err } - // Check if GitInfo is present and extract relevant fields gi := response.GitInfo - if gi != nil { - fixedPath := ensureWorkspacePrefix(gi.Path) - result.OriginURL = gi.URL - result.LatestCommit = gi.HeadCommitID - result.CurrentBranch = gi.Branch - result.WorktreeRoot = fixedPath - } else { + if gi == nil { log.Infof(ctx, "Failed to load git info from %s", apiEndpoint) + return result, nil + } + result.WorktreeRoot = ensureWorkspacePrefix(gi.Path) + + // get-status omits branch/commit/url for git-in-data-plane folders; only + // classic Repos return them inline, and those fields are being deprecated on + // the workspace API. Read them from the Repos API by id, which is + // authoritative for both folder types. + repo, err := w.Repos.GetByRepoId(ctx, gi.ID) + if err != nil { + log.Warnf(ctx, "failed to load git metadata from Repos API for %s: %s", result.WorktreeRoot, err) + return result, nil } + result.OriginURL = repo.Url + result.LatestCommit = repo.HeadCommitId + result.CurrentBranch = repo.Branch return result, nil } diff --git a/libs/git/info_test.go b/libs/git/info_test.go new file mode 100644 index 00000000000..772c0aa3586 --- /dev/null +++ b/libs/git/info_test.go @@ -0,0 +1,70 @@ +package git + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/databricks/databricks-sdk-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFetchRepositoryInfoAPIReadsMetadataFromReposAPI(t *testing.T) { + // git-in-data-plane folders: get-status returns only id+path in git_info, so + // branch/commit/url must be recovered from the Repos API by id. + var statusPath string + reposCalled := false + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/2.0/workspace/get-status": + statusPath = r.URL.Query().Get("path") + _, _ = w.Write([]byte(`{"object_type":"DIRECTORY","git_info":{"id":123,"path":"/Repos/alice/proj"}}`)) + case "/api/2.0/repos/123": + reposCalled = true + _, _ = w.Write([]byte(`{"id":123,"branch":"main","head_commit_id":"abc123","url":"https://github.com/databricks/bundle-examples"}`)) + default: + http.Error(w, "unexpected path: "+r.URL.Path, http.StatusNotFound) + } + })) + defer srv.Close() + + w := databricks.Must(databricks.NewWorkspaceClient(&databricks.Config{Host: srv.URL, Token: "dummy"})) + + info, err := fetchRepositoryInfoAPI(t.Context(), "/Workspace/Repos/alice/proj/sub/dir", w) + require.NoError(t, err) + + assert.Equal(t, "/Workspace/Repos/alice/proj/sub/dir", statusPath) + assert.True(t, reposCalled, "Repos API should be called to load git metadata") + assert.Equal(t, "/Workspace/Repos/alice/proj", info.WorktreeRoot) + assert.Equal(t, "main", info.CurrentBranch) + assert.Equal(t, "abc123", info.LatestCommit) + assert.Equal(t, "https://github.com/databricks/bundle-examples", info.OriginURL) +} + +func TestFetchRepositoryInfoAPINotAGitFolder(t *testing.T) { + // Outside any git folder get-status returns no git_info, so the result is + // empty and the Repos API is never called. + reposCalled := false + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/api/2.0/repos/") { + reposCalled = true + } + if r.URL.Path == "/api/2.0/workspace/get-status" { + _, _ = w.Write([]byte(`{"object_type":"DIRECTORY"}`)) + return + } + // Ignore the SDK's host-metadata probe and anything else. + _, _ = w.Write([]byte(`{}`)) + })) + defer srv.Close() + + w := databricks.Must(databricks.NewWorkspaceClient(&databricks.Config{Host: srv.URL, Token: "dummy"})) + + info, err := fetchRepositoryInfoAPI(t.Context(), "/Workspace/Users/alice/notebook", w) + require.NoError(t, err) + + assert.False(t, reposCalled, "Repos API should not be called when the path is not a git folder") + assert.Equal(t, RepositoryInfo{}, info) +}