Skip to content
Open
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
2 changes: 2 additions & 0 deletions acceptance/bundle/git-info/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bundle:
name: git-info
4 changes: 4 additions & 0 deletions acceptance/bundle/git-info/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions acceptance/bundle/git-info/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

=== Provenance read from the on-disk .git

>>> [CLI] bundle validate -o json
{
"actual_branch": "main",
"branch": "main",
"bundle_root_path": ".",
"commit": "4fa481d6502eb6104d62c33a693ef8a134e765e0",
"origin_url": "https://github.com/databricks/bundle-examples.git"
}

=== No .git: provenance degrades to empty without failing

>>> [CLI] bundle validate -o json
{
"bundle_root_path": "."
}
18 changes: 18 additions & 0 deletions acceptance/bundle/git-info/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# A .git written by hand rather than by `git init`: git cannot operate on the
# /Workspace FUSE mount ("detected dubious ownership"), but libs/git reads these
# files directly, so this is what the new in-workspace Git folders look like to it.
origin_url=https://github.com/databricks/bundle-examples.git
commit=4fa481d6502eb6104d62c33a693ef8a134e765e0

mkdir -p .git/refs/heads
printf 'ref: refs/heads/main\n' > .git/HEAD
printf '[core]\n\trepositoryformatversion = 1\n[remote "origin"]\n\turl = %s\n' "$origin_url" > .git/config
printf '%s\n' "$commit" > .git/refs/heads/main

title "Provenance read from the on-disk .git\n"
trace $CLI bundle validate -o json | jq '.bundle.git'

rm -rf .git

title "No .git: provenance degrades to empty without failing\n"
trace $CLI bundle validate -o json | jq '.bundle.git'
25 changes: 25 additions & 0 deletions acceptance/bundle/git-info/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Covers bundle git provenance (bundle.git) as recorded by libs/git.FetchRepositoryInfo.
#
# The bundle root here carries a hand-built .git, which is readable both locally and on
# the /Workspace FUSE mount of a DBR session. That makes the on-disk path the one under
# test in both environments, so the assertions and output are identical:
#
# - Local / non-DBR: fetchRepositoryInfoDotGit reads .git, as it always has.
# - On DBR: the new type of in-workspace Git folder exposes a real .git on the FUSE
# mount, so FetchRepositoryInfo prefers it over get-status, which returns only
# id+path for these folders. This is what the hasDotGit check selects.
#
# The API path itself (classic Repos and older Git folders, which have no .git on the
# mount) cannot be provisioned from a test: those folder types are created by the
# control plane, not by writing files. It is covered by unit tests in
# libs/git/info_test.go.
#
# Cloud=true is required because RunsOnDbr tests also run in the cloud suite.
Local = true
Cloud = true
RunsOnDbr = true

# RunsOnDbr is incompatible with RecordRequests (serverless can't reach the proxy).
RecordRequests = false

Ignore = [".git"]
16 changes: 15 additions & 1 deletion libs/git/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type response struct {
func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
var info RepositoryInfo
var err error
if strings.HasPrefix(path, "/Workspace/") && dbr.RunsOnRuntime(ctx) {
if strings.HasPrefix(path, "/Workspace/") && dbr.RunsOnRuntime(ctx) && !hasDotGit(path) {
info, err = fetchRepositoryInfoAPI(ctx, path, w)
} else {
info, err = fetchRepositoryInfoDotGit(ctx, path)
Expand Down Expand Up @@ -115,6 +115,20 @@ func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.Work
return result, nil
}

// hasDotGit reports whether a .git directory is reachable from path, walking up
// to the filesystem root the same way fetchRepositoryInfoDotGit does.
//
// The new type of in-workspace Git folder exposes a real .git on the /Workspace
// FUSE mount, and get-status returns only id+path for it, omitting the origin URL,
// branch and commit. Reading .git recovers all three, and is much cheaper than the
// API: these stat calls cost fractions of a millisecond against ~80ms for
// get-status. Classic Repos and older Git folders have no .git on the mount and
// still go through the API, which returns their git info inline.
func hasDotGit(path string) bool {
_, err := folders.FindDirWithLeaf(path, GitDirectoryName)
return err == nil
}

func ensureWorkspacePrefix(p string) string {
if !strings.HasPrefix(p, "/Workspace/") {
return path.Join("/Workspace", p)
Expand Down
170 changes: 170 additions & 0 deletions libs/git/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package git

import (
"context"
"os"
"path/filepath"
"sync/atomic"
"testing"

"github.com/databricks/cli/libs/dbr"
"github.com/databricks/cli/libs/testserver"
"github.com/databricks/databricks-sdk-go"
"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"
// Commit id written into the .git fixture; must be a full SHA-1 to parse.
testDotGitCommit = "4fa481d6502eb6104d62c33a693ef8a134e765e0"
)

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"})
}

// writeDotGit lays down the subset of .git that fetchRepositoryInfoDotGit reads,
// matching what the new in-workspace Git folders expose on the FUSE mount.
func writeDotGit(t *testing.T, root, originURL, branch, commit string) {
t.Helper()
gitDir := filepath.Join(root, GitDirectoryName)
require.NoError(t, os.MkdirAll(filepath.Join(gitDir, "refs", "heads"), 0o755))

write := func(name, content string) {
t.Helper()
require.NoError(t, os.WriteFile(filepath.Join(gitDir, name), []byte(content), 0o600))
}
write("HEAD", "ref: refs/heads/"+branch+"\n")
write("config", "[core]\n\trepositoryformatversion = 1\n[remote \"origin\"]\n\turl = "+originURL+"\n")
write(filepath.Join("refs", "heads", branch), commit+"\n")
}

// On DBR, a bundle root with a readable .git is served from disk: the new type of
// in-workspace Git folder exposes one on the /Workspace FUSE mount, and get-status
// returns only id+path for it, so the API cannot supply the provenance anyway.
func TestFetchRepositoryInfoDbrPrefersDotGit(t *testing.T) {
// The bundle root is a subdirectory, so the .git lookup has to walk up to find it.
gitFolder := filepath.Join(t.TempDir(), "bundle-examples")
bundleRoot := filepath.Join(gitFolder, "dabs_in_ws_bundle")
require.NoError(t, os.MkdirAll(bundleRoot, 0o755))
writeDotGit(t, gitFolder, testOriginURL, "main", testDotGitCommit)

server := testserver.New(t)
var apiCalled atomic.Bool
server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any {
apiCalled.Store(true)
return testserver.Response{Body: map[string]any{}}
})

info, err := FetchRepositoryInfo(runtimeContext(t), bundleRoot, newTestWorkspaceClient(t, server))
require.NoError(t, err)
assert.False(t, apiCalled.Load(), "get-status should not be called when .git is readable")
assert.Equal(t, testOriginURL, info.OriginURL)
assert.Equal(t, "main", info.CurrentBranch)
assert.Equal(t, testDotGitCommit, info.LatestCommit)
assert.Equal(t, gitFolder, info.WorktreeRoot)
}

// Without a .git on the mount, the API path still owns classic Repos and the older
// workspace Git folders, which return their git info inline.
func TestFetchRepositoryInfoAPI(t *testing.T) {
tests := []struct {
name string
// gitInfo is the git_info object returned by get-status; nil means it is omitted.
gitInfo map[string]any

wantBranch string
wantCommit string
wantOriginURL string
wantWorktreeRoot string
}{
{
name: "git folder returns full git info inline",
gitInfo: map[string]any{
"id": testRepoID,
"path": testGitFolderRaw,
"branch": "main",
"head_commit_id": "abc123",
"url": testOriginURL,
},
wantBranch: "main",
wantCommit: "abc123",
wantOriginURL: testOriginURL,
wantWorktreeRoot: testWorktreeRoot,
},
{
// A remoteless folder has no origin URL to report.
name: "remoteless git folder reports branch and commit but no url",
gitInfo: map[string]any{
"id": testRepoID,
"path": testGitFolderRaw,
"branch": "main",
"head_commit_id": "abc123",
},
wantBranch: "main",
wantCommit: "abc123",
wantOriginURL: "",
wantWorktreeRoot: testWorktreeRoot,
},
{
// A plain workspace directory: no git info, and no error either.
name: "no git info degrades to empty provenance",
gitInfo: nil,
wantBranch: "",
wantCommit: "",
wantOriginURL: "",
wantWorktreeRoot: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := testserver.New(t)
server.Handle("GET", "/api/2.0/workspace/get-status", func(_ testserver.Request) any {
body := map[string]any{}
if tt.gitInfo != nil {
body["git_info"] = tt.gitInfo
}
return testserver.Response{Body: body}
})

info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server))
require.NoError(t, err)
assert.Equal(t, tt.wantBranch, info.CurrentBranch)
assert.Equal(t, tt.wantCommit, info.LatestCommit)
assert.Equal(t, tt.wantOriginURL, info.OriginURL)
assert.Equal(t, tt.wantWorktreeRoot, info.WorktreeRoot)
})
}
}

func TestHasDotGit(t *testing.T) {
root := t.TempDir()
nested := filepath.Join(root, "repo", "nested", "bundle")
require.NoError(t, os.MkdirAll(nested, 0o755))
require.NoError(t, os.MkdirAll(filepath.Join(root, "repo", GitDirectoryName), 0o755))

assert.True(t, hasDotGit(filepath.Join(root, "repo")))
assert.True(t, hasDotGit(nested), "should walk up to find .git")
assert.False(t, hasDotGit(filepath.Join(t.TempDir(), "no-git-here")))
}
Loading