From b614a667c8fd686940fe029325b40161a3f4fa93 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Wed, 24 Jun 2026 17:03:26 +0000 Subject: [PATCH 1/2] Fix missing git info for bundles deployed from in-workspace Git folders Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 2 + libs/git/info.go | 34 +++++++++--- libs/git/info_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 libs/git/info_test.go diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 1459df29473..55425961a4b 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -8,6 +8,8 @@ ### Bundles +* Fixed missing git information (origin URL, branch, commit) when deploying a bundle from a Git folder inside the workspace. + ### Dependency updates ### API Changes diff --git a/libs/git/info.go b/libs/git/info.go index 6e31d68219c..f001c9b067d 100644 --- a/libs/git/info.go +++ b/libs/git/info.go @@ -33,6 +33,10 @@ type gitInfo struct { HeadCommitID string `json:"head_commit_id"` Path string `json:"path"` URL string `json:"url"` + // ID of the git folder object. Some workspace git folders return only id+path + // from get-status (omitting branch/commit/url), so the id lets us recover the + // rest via the Repos API. See the fallback in fetchRepositoryInfoAPI. + ID int64 `json:"id"` } type response struct { @@ -102,14 +106,30 @@ func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.Work // 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.OriginURL = gi.URL + result.LatestCommit = gi.HeadCommitID + result.CurrentBranch = gi.Branch + result.WorktreeRoot = ensureWorkspacePrefix(gi.Path) + + // Some workspace git folders return only id+path from get-status and omit the + // origin URL. When that happens, fetch the full provenance from the Repos API + // by id. Classic repos return the URL inline and skip this extra call. + if gi.ID != 0 && result.OriginURL == "" { + repo, err := w.Repos.GetByRepoId(ctx, gi.ID) + if err != nil { + // Best effort: WorktreeRoot is already set, so degrade to partial info + // rather than failing the deploy (see FetchRepositoryInfo's contract). + log.Debugf(ctx, "Failed to load git info from Repos API for id %d: %v", gi.ID, 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..656c1be87ae --- /dev/null +++ b/libs/git/info_test.go @@ -0,0 +1,123 @@ +package git + +import ( + "context" + "testing" + + "github.com/databricks/cli/libs/dbr" + "github.com/databricks/cli/libs/testserver" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/workspace" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + // Bundle root passed to FetchRepositoryInfo: a subdirectory of the git folder. + testBundleRoot = "/Workspace/Users/test/bundle-examples/dabs_in_ws_bundle" + // Git folder path as get-status returns it (without the /Workspace prefix). + testGitFolderRaw = "/Users/test/bundle-examples" + // Expected worktree root after ensureWorkspacePrefix is applied. + testWorktreeRoot = "/Workspace/Users/test/bundle-examples" + testRepoID = int64(2884540697170475) + testOriginURL = "https://github.com/databricks/bundle-examples.git" +) + +func newTestWorkspaceClient(t *testing.T, server *testserver.Server) *databricks.WorkspaceClient { + t.Helper() + w, err := databricks.NewWorkspaceClient(&databricks.Config{ + Host: server.URL, + Token: "testtoken", + }) + require.NoError(t, err) + return w +} + +// runtimeContext forces the in-workspace API branch of FetchRepositoryInfo +// without needing a real /databricks directory on the test host. +func runtimeContext(t *testing.T) context.Context { + return dbr.MockRuntime(t.Context(), dbr.Environment{IsDbr: true, Version: "15.4"}) +} + +// New workspace git folders return only id+path from get-status; the missing +// branch/commit/url are recovered from the Repos API by id. +func TestFetchRepositoryInfoNewGitFolderFallsBackToReposAPI(t *testing.T) { + server := testserver.New(t) + server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any { + return testserver.Response{Body: map[string]any{ + "git_info": map[string]any{ + "id": testRepoID, + "path": testGitFolderRaw, + }, + }} + }) + server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any { + return testserver.Response{Body: workspace.GetRepoResponse{ + Id: testRepoID, + Branch: "main", + HeadCommitId: "d53214abc", + Url: testOriginURL, + Provider: "gitHub", + Path: testGitFolderRaw, + }} + }) + + info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server)) + require.NoError(t, err) + assert.Equal(t, "main", info.CurrentBranch) + assert.Equal(t, "d53214abc", info.LatestCommit) + assert.Equal(t, testOriginURL, info.OriginURL) + assert.Equal(t, testWorktreeRoot, info.WorktreeRoot) +} + +// Classic Repos return full git info inline from get-status, so the Repos API is +// not called. +func TestFetchRepositoryInfoClassicRepoSkipsReposAPI(t *testing.T) { + server := testserver.New(t) + server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any { + return testserver.Response{Body: map[string]any{ + "git_info": map[string]any{ + "id": testRepoID, + "path": testGitFolderRaw, + "branch": "main", + "head_commit_id": "abc123", + "url": testOriginURL, + }, + }} + }) + server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any { + t.Error("Repos API must not be called when get-status returns the URL inline") + return testserver.Response{StatusCode: 500} + }) + + info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server)) + require.NoError(t, err) + assert.Equal(t, "main", info.CurrentBranch) + assert.Equal(t, "abc123", info.LatestCommit) + assert.Equal(t, testOriginURL, info.OriginURL) + assert.Equal(t, testWorktreeRoot, info.WorktreeRoot) +} + +// A failed Repos lookup must not fail the deploy: the worktree root stays set and +// the provenance fields stay empty, with no error. +func TestFetchRepositoryInfoReposLookupFailureDegradesGracefully(t *testing.T) { + server := testserver.New(t) + server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any { + return testserver.Response{Body: map[string]any{ + "git_info": map[string]any{ + "id": testRepoID, + "path": testGitFolderRaw, + }, + }} + }) + server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any { + return testserver.Response{StatusCode: 404, Body: map[string]string{"message": "not found"}} + }) + + info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server)) + require.NoError(t, err) + assert.Empty(t, info.CurrentBranch) + assert.Empty(t, info.LatestCommit) + assert.Empty(t, info.OriginURL) + assert.Equal(t, testWorktreeRoot, info.WorktreeRoot) +} From 804d517c979dc0bfe9e257afd24e6a58abed3d30 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Wed, 24 Jun 2026 17:05:28 +0000 Subject: [PATCH 2/2] Add PR link to changelog Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 55425961a4b..ec4f9a2db10 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -8,7 +8,7 @@ ### Bundles -* Fixed missing git information (origin URL, branch, commit) when deploying a bundle from a Git folder inside the workspace. +* Fixed missing git information (origin URL, branch, commit) when deploying a bundle from a Git folder inside the workspace ([#5709](https://github.com/databricks/cli/pull/5709)). ### Dependency updates