Skip to content

Commit b614a66

Browse files
committed
Fix missing git info for bundles deployed from in-workspace Git folders
Co-authored-by: Isaac
1 parent 39e4848 commit b614a66

3 files changed

Lines changed: 152 additions & 7 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bundles
1010

11+
* Fixed missing git information (origin URL, branch, commit) when deploying a bundle from a Git folder inside the workspace.
12+
1113
### Dependency updates
1214

1315
### API Changes

libs/git/info.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type gitInfo struct {
3333
HeadCommitID string `json:"head_commit_id"`
3434
Path string `json:"path"`
3535
URL string `json:"url"`
36+
// ID of the git folder object. Some workspace git folders return only id+path
37+
// from get-status (omitting branch/commit/url), so the id lets us recover the
38+
// rest via the Repos API. See the fallback in fetchRepositoryInfoAPI.
39+
ID int64 `json:"id"`
3640
}
3741

3842
type response struct {
@@ -102,14 +106,30 @@ func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.Work
102106

103107
// Check if GitInfo is present and extract relevant fields
104108
gi := response.GitInfo
105-
if gi != nil {
106-
fixedPath := ensureWorkspacePrefix(gi.Path)
107-
result.OriginURL = gi.URL
108-
result.LatestCommit = gi.HeadCommitID
109-
result.CurrentBranch = gi.Branch
110-
result.WorktreeRoot = fixedPath
111-
} else {
109+
if gi == nil {
112110
log.Infof(ctx, "Failed to load git info from %s", apiEndpoint)
111+
return result, nil
112+
}
113+
114+
result.OriginURL = gi.URL
115+
result.LatestCommit = gi.HeadCommitID
116+
result.CurrentBranch = gi.Branch
117+
result.WorktreeRoot = ensureWorkspacePrefix(gi.Path)
118+
119+
// Some workspace git folders return only id+path from get-status and omit the
120+
// origin URL. When that happens, fetch the full provenance from the Repos API
121+
// by id. Classic repos return the URL inline and skip this extra call.
122+
if gi.ID != 0 && result.OriginURL == "" {
123+
repo, err := w.Repos.GetByRepoId(ctx, gi.ID)
124+
if err != nil {
125+
// Best effort: WorktreeRoot is already set, so degrade to partial info
126+
// rather than failing the deploy (see FetchRepositoryInfo's contract).
127+
log.Debugf(ctx, "Failed to load git info from Repos API for id %d: %v", gi.ID, err)
128+
return result, nil
129+
}
130+
result.OriginURL = repo.Url
131+
result.LatestCommit = repo.HeadCommitId
132+
result.CurrentBranch = repo.Branch
113133
}
114134

115135
return result, nil

libs/git/info_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package git
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/databricks/cli/libs/dbr"
8+
"github.com/databricks/cli/libs/testserver"
9+
"github.com/databricks/databricks-sdk-go"
10+
"github.com/databricks/databricks-sdk-go/service/workspace"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
const (
16+
// Bundle root passed to FetchRepositoryInfo: a subdirectory of the git folder.
17+
testBundleRoot = "/Workspace/Users/test/bundle-examples/dabs_in_ws_bundle"
18+
// Git folder path as get-status returns it (without the /Workspace prefix).
19+
testGitFolderRaw = "/Users/test/bundle-examples"
20+
// Expected worktree root after ensureWorkspacePrefix is applied.
21+
testWorktreeRoot = "/Workspace/Users/test/bundle-examples"
22+
testRepoID = int64(2884540697170475)
23+
testOriginURL = "https://github.com/databricks/bundle-examples.git"
24+
)
25+
26+
func newTestWorkspaceClient(t *testing.T, server *testserver.Server) *databricks.WorkspaceClient {
27+
t.Helper()
28+
w, err := databricks.NewWorkspaceClient(&databricks.Config{
29+
Host: server.URL,
30+
Token: "testtoken",
31+
})
32+
require.NoError(t, err)
33+
return w
34+
}
35+
36+
// runtimeContext forces the in-workspace API branch of FetchRepositoryInfo
37+
// without needing a real /databricks directory on the test host.
38+
func runtimeContext(t *testing.T) context.Context {
39+
return dbr.MockRuntime(t.Context(), dbr.Environment{IsDbr: true, Version: "15.4"})
40+
}
41+
42+
// New workspace git folders return only id+path from get-status; the missing
43+
// branch/commit/url are recovered from the Repos API by id.
44+
func TestFetchRepositoryInfoNewGitFolderFallsBackToReposAPI(t *testing.T) {
45+
server := testserver.New(t)
46+
server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any {
47+
return testserver.Response{Body: map[string]any{
48+
"git_info": map[string]any{
49+
"id": testRepoID,
50+
"path": testGitFolderRaw,
51+
},
52+
}}
53+
})
54+
server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any {
55+
return testserver.Response{Body: workspace.GetRepoResponse{
56+
Id: testRepoID,
57+
Branch: "main",
58+
HeadCommitId: "d53214abc",
59+
Url: testOriginURL,
60+
Provider: "gitHub",
61+
Path: testGitFolderRaw,
62+
}}
63+
})
64+
65+
info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server))
66+
require.NoError(t, err)
67+
assert.Equal(t, "main", info.CurrentBranch)
68+
assert.Equal(t, "d53214abc", info.LatestCommit)
69+
assert.Equal(t, testOriginURL, info.OriginURL)
70+
assert.Equal(t, testWorktreeRoot, info.WorktreeRoot)
71+
}
72+
73+
// Classic Repos return full git info inline from get-status, so the Repos API is
74+
// not called.
75+
func TestFetchRepositoryInfoClassicRepoSkipsReposAPI(t *testing.T) {
76+
server := testserver.New(t)
77+
server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any {
78+
return testserver.Response{Body: map[string]any{
79+
"git_info": map[string]any{
80+
"id": testRepoID,
81+
"path": testGitFolderRaw,
82+
"branch": "main",
83+
"head_commit_id": "abc123",
84+
"url": testOriginURL,
85+
},
86+
}}
87+
})
88+
server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any {
89+
t.Error("Repos API must not be called when get-status returns the URL inline")
90+
return testserver.Response{StatusCode: 500}
91+
})
92+
93+
info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server))
94+
require.NoError(t, err)
95+
assert.Equal(t, "main", info.CurrentBranch)
96+
assert.Equal(t, "abc123", info.LatestCommit)
97+
assert.Equal(t, testOriginURL, info.OriginURL)
98+
assert.Equal(t, testWorktreeRoot, info.WorktreeRoot)
99+
}
100+
101+
// A failed Repos lookup must not fail the deploy: the worktree root stays set and
102+
// the provenance fields stay empty, with no error.
103+
func TestFetchRepositoryInfoReposLookupFailureDegradesGracefully(t *testing.T) {
104+
server := testserver.New(t)
105+
server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any {
106+
return testserver.Response{Body: map[string]any{
107+
"git_info": map[string]any{
108+
"id": testRepoID,
109+
"path": testGitFolderRaw,
110+
},
111+
}}
112+
})
113+
server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any {
114+
return testserver.Response{StatusCode: 404, Body: map[string]string{"message": "not found"}}
115+
})
116+
117+
info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server))
118+
require.NoError(t, err)
119+
assert.Empty(t, info.CurrentBranch)
120+
assert.Empty(t, info.LatestCommit)
121+
assert.Empty(t, info.OriginURL)
122+
assert.Equal(t, testWorktreeRoot, info.WorktreeRoot)
123+
}

0 commit comments

Comments
 (0)