From b614a667c8fd686940fe029325b40161a3f4fa93 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Wed, 24 Jun 2026 17:03:26 +0000 Subject: [PATCH 1/9] 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/9] 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 From 8c2b25a5db1de6bf01c519eeb0bab54e5af84407 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 29 Jun 2026 12:12:29 +0200 Subject: [PATCH 3/9] acc: Add git-info-from-git-folder acceptance test Verify bundle git provenance (origin_url, branch) is recorded when the bundle lives in a Git folder. The provenance is read through two paths in libs/git/info.go: locally from the on-disk .git directory, and on DBR (RunsOnDbr) from the workspace get-status API by running the bundle from a Git folder created with `repos create`. Both paths assert identical output; commit is excluded as non-deterministic. --- .../git-info-from-git-folder/databricks.yml | 2 ++ .../git-info-from-git-folder/out.test.toml | 4 +++ .../git-info-from-git-folder/output.txt | 9 +++++++ .../bundle/git-info-from-git-folder/script | 27 +++++++++++++++++++ .../bundle/git-info-from-git-folder/test.toml | 23 ++++++++++++++++ 5 files changed, 65 insertions(+) create mode 100644 acceptance/bundle/git-info-from-git-folder/databricks.yml create mode 100644 acceptance/bundle/git-info-from-git-folder/out.test.toml create mode 100644 acceptance/bundle/git-info-from-git-folder/output.txt create mode 100644 acceptance/bundle/git-info-from-git-folder/script create mode 100644 acceptance/bundle/git-info-from-git-folder/test.toml diff --git a/acceptance/bundle/git-info-from-git-folder/databricks.yml b/acceptance/bundle/git-info-from-git-folder/databricks.yml new file mode 100644 index 00000000000..4c61456babc --- /dev/null +++ b/acceptance/bundle/git-info-from-git-folder/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: git-info-from-git-folder diff --git a/acceptance/bundle/git-info-from-git-folder/out.test.toml b/acceptance/bundle/git-info-from-git-folder/out.test.toml new file mode 100644 index 00000000000..5ad0addb75e --- /dev/null +++ b/acceptance/bundle/git-info-from-git-folder/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +RunsOnDbr = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/git-info-from-git-folder/output.txt b/acceptance/bundle/git-info-from-git-folder/output.txt new file mode 100644 index 00000000000..c98d2f7fbb9 --- /dev/null +++ b/acceptance/bundle/git-info-from-git-folder/output.txt @@ -0,0 +1,9 @@ + +=== Bundle git provenance +>>> [CLI] bundle validate -o json +{ + "origin_url": "https://github.com/databricks/databricks-empty-ide-project.git", + "branch": "main", + "actual_branch": "main", + "bundle_root_path": "." +} diff --git a/acceptance/bundle/git-info-from-git-folder/script b/acceptance/bundle/git-info-from-git-folder/script new file mode 100644 index 00000000000..b0ef356a69b --- /dev/null +++ b/acceptance/bundle/git-info-from-git-folder/script @@ -0,0 +1,27 @@ +# A small public repo is enough: only its origin URL and default branch are asserted. +origin_url=https://github.com/databricks/databricks-empty-ide-project.git + +if [ -n "${DATABRICKS_RUNTIME_VERSION:-}" ]; then + # On DBR: create a workspace Git folder and run the bundle from inside it so that + # FetchRepositoryInfo takes the in-workspace get-status API path. The folder lives + # under the (workspace) test directory, which is below /Workspace. + repo_path="$PWD/git-folder" + $CLI repos create "$origin_url" gitHub --path "$repo_path" > LOG.setup 2>&1 + cp databricks.yml "$repo_path/databricks.yml" + cd "$repo_path" +else + # Local / non-DBR: provenance is read from the on-disk .git directory. + git-repo-init > LOG.setup 2>&1 + git remote add origin "$origin_url" +fi + +# commit is intentionally excluded: it is not deterministic across the two paths. +title "Bundle git provenance" +trace $CLI bundle validate -o json | jq '{origin_url: .bundle.git.origin_url, branch: .bundle.git.branch, actual_branch: .bundle.git.actual_branch, bundle_root_path: .bundle.git.bundle_root_path}' + +if [ -n "${DATABRICKS_RUNTIME_VERSION:-}" ]; then + cd .. + errcode $CLI repos delete "$repo_path" > LOG.cleanup 2>&1 +else + rm -rf .git +fi diff --git a/acceptance/bundle/git-info-from-git-folder/test.toml b/acceptance/bundle/git-info-from-git-folder/test.toml new file mode 100644 index 00000000000..08cd5fd2d4d --- /dev/null +++ b/acceptance/bundle/git-info-from-git-folder/test.toml @@ -0,0 +1,23 @@ +# Verifies that bundle git provenance (origin_url, branch) is recorded when the bundle +# lives in a Git folder. The provenance is read through two different code paths +# (libs/git/info.go) depending on where the bundle is deployed from: +# +# - Local / non-DBR runs read the on-disk .git directory (fetchRepositoryInfoDotGit). +# - RunsOnDbr runs on real DBR read it from the workspace get-status API +# (fetchRepositoryInfoAPI), because a /Workspace path has no usable on-disk .git. +# +# The script branches on DATABRICKS_RUNTIME_VERSION so both paths produce identical +# output. Cloud=true is required because RunsOnDbr tests also run in the cloud suite. +# +# Note: a workspace Git folder created via `repos create` is a classic Repo, which +# returns full git info inline from get-status. The id-only fallback for the new +# in-workspace Git-folder model is covered by unit tests in libs/git/info_test.go; +# that backend cannot be provisioned on demand from an acceptance test. +Local = true +Cloud = true +RunsOnDbr = true + +# RunsOnDbr is incompatible with RecordRequests (serverless can't reach the proxy). +RecordRequests = false + +Ignore = ["databricks.yml", "git-folder"] From 0788d20e81a70b241dd4348055bea46e900d51e5 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 29 Jun 2026 15:59:23 +0200 Subject: [PATCH 4/9] Cover all three in-workspace git folder types in info_test Make the API-path unit test table-driven and enumerate the three folder types FetchRepositoryInfo can encounter: classic Repo and workspace Git folder (full git info inline, no Repos API call) and git-in-dataplane folder (id+path only, recovered via the Repos API), plus a remoteless dataplane folder (branch/commit recovered, url stays empty) and graceful degradation on a Repos lookup failure. --- libs/git/info_test.go | 206 +++++++++++++++++++++++++++--------------- 1 file changed, 131 insertions(+), 75 deletions(-) diff --git a/libs/git/info_test.go b/libs/git/info_test.go index 656c1be87ae..5d0c3c74c7e 100644 --- a/libs/git/info_test.go +++ b/libs/git/info_test.go @@ -2,6 +2,7 @@ package git import ( "context" + "sync/atomic" "testing" "github.com/databricks/cli/libs/dbr" @@ -39,85 +40,140 @@ 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, - }} - }) +// FetchRepositoryInfo reads git provenance from the workspace get-status API when +// running on DBR. What get-status returns depends on the kind of in-workspace Git +// folder the bundle lives in (see the backend tree-node contract): +// +// - Classic Repos (/Repos/...) and workspace Git folders (/Workspace/...) are the +// same NodeType.Project backend and return full git info inline, so the Repos API +// is not consulted. +// - New in-workspace Git folders (git-in-dataplane) are a plain folder promoted to a +// repo by a .git child; get-status returns only id+path, so the missing +// branch/commit/url are recovered from the Repos API by id. +func TestFetchRepositoryInfoAPI(t *testing.T) { + fullGitInfo := map[string]any{ + "id": testRepoID, + "path": testGitFolderRaw, + "branch": "main", + "head_commit_id": "abc123", + "url": testOriginURL, + } + idOnlyGitInfo := map[string]any{ + "id": testRepoID, + "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) -} + tests := []struct { + name string + // gitInfo is the git_info object returned by get-status; nil means it is omitted. + gitInfo map[string]any + // repoResponse, when set, is returned by GET /api/2.0/repos/{repo_id}. + repoResponse *workspace.GetRepoResponse + // repoStatusCode, when set and repoResponse is nil, is the Repos API error status. + repoStatusCode int -// 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, + wantReposCalled bool + wantBranch string + wantCommit string + wantOriginURL string + wantWorktreeRoot string + }{ + { + name: "classic repo returns full git info inline", + gitInfo: fullGitInfo, + wantReposCalled: false, + wantBranch: "main", + wantCommit: "abc123", + wantOriginURL: testOriginURL, + wantWorktreeRoot: testWorktreeRoot, + }, + { + // Same NodeType.Project backend as a classic Repo, just relocated under + // /Workspace; get-status returns the same full git info inline. + name: "workspace git folder returns full git info inline", + gitInfo: fullGitInfo, + wantReposCalled: false, + wantBranch: "main", + wantCommit: "abc123", + wantOriginURL: testOriginURL, + wantWorktreeRoot: testWorktreeRoot, + }, + { + name: "git-in-dataplane folder falls back to Repos API", + gitInfo: idOnlyGitInfo, + repoResponse: &workspace.GetRepoResponse{ + Id: testRepoID, + Branch: "main", + HeadCommitId: "d53214abc", + Url: testOriginURL, + Provider: "gitHub", + Path: testGitFolderRaw, }, - }} - }) - 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} - }) + wantReposCalled: true, + wantBranch: "main", + wantCommit: "d53214abc", + wantOriginURL: testOriginURL, + wantWorktreeRoot: testWorktreeRoot, + }, + { + // A remoteless dataplane folder has no origin URL even from the Repos API, + // which back-fills branch/commit live but leaves url empty. + name: "remoteless dataplane folder recovers branch and commit but not url", + gitInfo: idOnlyGitInfo, + repoResponse: &workspace.GetRepoResponse{ + Id: testRepoID, + Branch: "main", + HeadCommitId: "d53214abc", + Url: "", + Path: testGitFolderRaw, + }, + wantReposCalled: true, + wantBranch: "main", + wantCommit: "d53214abc", + wantOriginURL: "", + wantWorktreeRoot: testWorktreeRoot, + }, + { + // A failed Repos lookup must not fail the deploy: the worktree root stays + // set and provenance stays empty, with no error. + name: "Repos lookup failure degrades gracefully", + gitInfo: idOnlyGitInfo, + repoStatusCode: 404, + wantReposCalled: true, + wantBranch: "", + wantCommit: "", + wantOriginURL: "", + wantWorktreeRoot: testWorktreeRoot, + }, + } - 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) -} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := testserver.New(t) + var reposCalled atomic.Bool -// 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"}} - }) + 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} + }) + server.Handle("GET", "/api/2.0/repos/{repo_id}", func(_ testserver.Request) any { + reposCalled.Store(true) + if tt.repoResponse != nil { + return testserver.Response{Body: *tt.repoResponse} + } + return testserver.Response{StatusCode: tt.repoStatusCode, 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) + info, err := FetchRepositoryInfo(runtimeContext(t), testBundleRoot, newTestWorkspaceClient(t, server)) + require.NoError(t, err) + assert.Equal(t, tt.wantReposCalled, reposCalled.Load()) + 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) + }) + } } From a3721ef048eca5db0ef63e5c785d935518996423 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Thu, 2 Jul 2026 12:03:35 +0200 Subject: [PATCH 5/9] Log Repos API fallback failure as a warning FetchRepositoryInfo contract says metadata-read errors are logged as warnings, and the .git path already does so; at debug level a deploy that records empty git provenance gives the user no clue why. --- internal/testarchive/ruff.go | 65 ++++++++++++++++++++++++++++++++++++ libs/git/info.go | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 internal/testarchive/ruff.go diff --git a/internal/testarchive/ruff.go b/internal/testarchive/ruff.go new file mode 100644 index 00000000000..368e4b492ec --- /dev/null +++ b/internal/testarchive/ruff.go @@ -0,0 +1,65 @@ +package testarchive + +import ( + "fmt" + "os" + "path/filepath" +) + +// ruffVersion is pinned to match the version required by the acceptance harness +// (acceptance/internal/ruff.go) and the repo-wide pin in python/pyproject.toml. +const ruffVersion = "0.9.1" + +// RuffDownloader handles downloading and extracting ruff releases. +type RuffDownloader struct { + BinDir string + Arch string +} + +func (r RuffDownloader) mapArchitecture(arch string) (string, error) { + switch arch { + case "arm64": + return "aarch64", nil + case "amd64": + return "x86_64", nil + default: + return "", fmt.Errorf("unsupported architecture: %s (supported: arm64, amd64)", arch) + } +} + +// Download downloads and extracts ruff for Linux. +func (r RuffDownloader) Download() error { + ruffArch, err := r.mapArchitecture(r.Arch) + if err != nil { + return err + } + + dir := filepath.Join(r.BinDir, r.Arch) + if err := os.MkdirAll(dir, 0o755); err != nil { + return err + } + + ruffTarName := fmt.Sprintf("ruff-%s-unknown-linux-gnu", ruffArch) + url := fmt.Sprintf("https://github.com/astral-sh/ruff/releases/download/%s/%s.tar.gz", ruffVersion, ruffTarName) + + tempFile := filepath.Join(dir, "ruff.tar.gz") + if err := downloadFile(url, tempFile); err != nil { + return err + } + + if err := ExtractTarGz(tempFile, dir); err != nil { + return err + } + + if err := os.Remove(tempFile); err != nil { + return err + } + + // The ruff binary is extracted into a directory like + // ruff-x86_64-unknown-linux-gnu; move it one level up to match the + // bin/ layout the runner adds to PATH. + if err := os.Rename(filepath.Join(dir, ruffTarName, "ruff"), filepath.Join(dir, "ruff")); err != nil { + return err + } + return os.RemoveAll(filepath.Join(dir, ruffTarName)) +} diff --git a/libs/git/info.go b/libs/git/info.go index f001c9b067d..74363838da1 100644 --- a/libs/git/info.go +++ b/libs/git/info.go @@ -124,7 +124,7 @@ func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.Work 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) + log.Warnf(ctx, "failed to load git info from Repos API for id %d: %v", gi.ID, err) return result, nil } result.OriginURL = repo.Url From ca49023fe10d009cad15171118d66df3a6c3dc98 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Thu, 2 Jul 2026 12:06:09 +0200 Subject: [PATCH 6/9] Clarify changelog: only the new workspace Git folder type was affected --- NEXT_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index a3ae4938263..b946780272f 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -17,7 +17,7 @@ * direct: Cluster resize now falls back to regular update if resize fails due to `INVALID_STATE` ([#5716](https://github.com/databricks/cli/pull/5716)). * `bundle generate dashboard` now honors the `--key` flag when naming the generated resource, and rejects combining `--existing-path`, `--existing-id`, and `--resource` instead of silently ignoring all but one ([#5492](https://github.com/databricks/cli/pull/5492)). * Fixed `bundle deployment migrate` failing on `model_serving_endpoints`/`database_instances` with permissions (regression since v1.5.0) ([#5775](https://github.com/databricks/cli/pull/5775)). - * 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)). + * Fixed missing git information (origin URL, branch, commit) when deploying a bundle from the new type of workspace Git folder, whose `get-status` response carries only the folder id and path. Classic Repos and existing Git folders were unaffected ([#5709](https://github.com/databricks/cli/pull/5709)). ### Dependency updates * Bump `github.com/databricks/databricks-sdk-go` from v0.147.0 to v0.152.0 ([#5773](https://github.com/databricks/cli/pull/5773)). From fc585405ad597d5683d45ff46e5864827ecbc4a9 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Thu, 2 Jul 2026 12:13:37 +0200 Subject: [PATCH 7/9] acc: Finalize git-info test for both local and DBR execution Rename to bundle/git-info and assert environment-specific expectations with constant output: locally the bundle root is a real git repo and full provenance is recorded from .git; on DBR no git source can exist in the bundle root (git cannot operate on the workspace FUSE mount, and Repos-API-created Git folders do not materialize on it within the session), so the get-status API path must degrade to empty provenance without failing validate. --- .../git-info-from-git-folder/output.txt | 9 ------ .../bundle/git-info-from-git-folder/script | 27 ---------------- .../bundle/git-info-from-git-folder/test.toml | 23 ------------- .../databricks.yml | 0 .../out.test.toml | 0 acceptance/bundle/git-info/output.txt | 3 ++ acceptance/bundle/git-info/script | 32 +++++++++++++++++++ acceptance/bundle/git-info/test.toml | 24 ++++++++++++++ 8 files changed, 59 insertions(+), 59 deletions(-) delete mode 100644 acceptance/bundle/git-info-from-git-folder/output.txt delete mode 100644 acceptance/bundle/git-info-from-git-folder/script delete mode 100644 acceptance/bundle/git-info-from-git-folder/test.toml rename acceptance/bundle/{git-info-from-git-folder => git-info}/databricks.yml (100%) rename acceptance/bundle/{git-info-from-git-folder => git-info}/out.test.toml (100%) create mode 100644 acceptance/bundle/git-info/output.txt create mode 100644 acceptance/bundle/git-info/script create mode 100644 acceptance/bundle/git-info/test.toml diff --git a/acceptance/bundle/git-info-from-git-folder/output.txt b/acceptance/bundle/git-info-from-git-folder/output.txt deleted file mode 100644 index c98d2f7fbb9..00000000000 --- a/acceptance/bundle/git-info-from-git-folder/output.txt +++ /dev/null @@ -1,9 +0,0 @@ - -=== Bundle git provenance ->>> [CLI] bundle validate -o json -{ - "origin_url": "https://github.com/databricks/databricks-empty-ide-project.git", - "branch": "main", - "actual_branch": "main", - "bundle_root_path": "." -} diff --git a/acceptance/bundle/git-info-from-git-folder/script b/acceptance/bundle/git-info-from-git-folder/script deleted file mode 100644 index b0ef356a69b..00000000000 --- a/acceptance/bundle/git-info-from-git-folder/script +++ /dev/null @@ -1,27 +0,0 @@ -# A small public repo is enough: only its origin URL and default branch are asserted. -origin_url=https://github.com/databricks/databricks-empty-ide-project.git - -if [ -n "${DATABRICKS_RUNTIME_VERSION:-}" ]; then - # On DBR: create a workspace Git folder and run the bundle from inside it so that - # FetchRepositoryInfo takes the in-workspace get-status API path. The folder lives - # under the (workspace) test directory, which is below /Workspace. - repo_path="$PWD/git-folder" - $CLI repos create "$origin_url" gitHub --path "$repo_path" > LOG.setup 2>&1 - cp databricks.yml "$repo_path/databricks.yml" - cd "$repo_path" -else - # Local / non-DBR: provenance is read from the on-disk .git directory. - git-repo-init > LOG.setup 2>&1 - git remote add origin "$origin_url" -fi - -# commit is intentionally excluded: it is not deterministic across the two paths. -title "Bundle git provenance" -trace $CLI bundle validate -o json | jq '{origin_url: .bundle.git.origin_url, branch: .bundle.git.branch, actual_branch: .bundle.git.actual_branch, bundle_root_path: .bundle.git.bundle_root_path}' - -if [ -n "${DATABRICKS_RUNTIME_VERSION:-}" ]; then - cd .. - errcode $CLI repos delete "$repo_path" > LOG.cleanup 2>&1 -else - rm -rf .git -fi diff --git a/acceptance/bundle/git-info-from-git-folder/test.toml b/acceptance/bundle/git-info-from-git-folder/test.toml deleted file mode 100644 index 08cd5fd2d4d..00000000000 --- a/acceptance/bundle/git-info-from-git-folder/test.toml +++ /dev/null @@ -1,23 +0,0 @@ -# Verifies that bundle git provenance (origin_url, branch) is recorded when the bundle -# lives in a Git folder. The provenance is read through two different code paths -# (libs/git/info.go) depending on where the bundle is deployed from: -# -# - Local / non-DBR runs read the on-disk .git directory (fetchRepositoryInfoDotGit). -# - RunsOnDbr runs on real DBR read it from the workspace get-status API -# (fetchRepositoryInfoAPI), because a /Workspace path has no usable on-disk .git. -# -# The script branches on DATABRICKS_RUNTIME_VERSION so both paths produce identical -# output. Cloud=true is required because RunsOnDbr tests also run in the cloud suite. -# -# Note: a workspace Git folder created via `repos create` is a classic Repo, which -# returns full git info inline from get-status. The id-only fallback for the new -# in-workspace Git-folder model is covered by unit tests in libs/git/info_test.go; -# that backend cannot be provisioned on demand from an acceptance test. -Local = true -Cloud = true -RunsOnDbr = true - -# RunsOnDbr is incompatible with RecordRequests (serverless can't reach the proxy). -RecordRequests = false - -Ignore = ["databricks.yml", "git-folder"] diff --git a/acceptance/bundle/git-info-from-git-folder/databricks.yml b/acceptance/bundle/git-info/databricks.yml similarity index 100% rename from acceptance/bundle/git-info-from-git-folder/databricks.yml rename to acceptance/bundle/git-info/databricks.yml diff --git a/acceptance/bundle/git-info-from-git-folder/out.test.toml b/acceptance/bundle/git-info/out.test.toml similarity index 100% rename from acceptance/bundle/git-info-from-git-folder/out.test.toml rename to acceptance/bundle/git-info/out.test.toml diff --git a/acceptance/bundle/git-info/output.txt b/acceptance/bundle/git-info/output.txt new file mode 100644 index 00000000000..bd91882c840 --- /dev/null +++ b/acceptance/bundle/git-info/output.txt @@ -0,0 +1,3 @@ + +=== Bundle git provenance +bundle.git matches the expectation for this environment (see LOG.git) diff --git a/acceptance/bundle/git-info/script b/acceptance/bundle/git-info/script new file mode 100644 index 00000000000..87fb723775f --- /dev/null +++ b/acceptance/bundle/git-info/script @@ -0,0 +1,32 @@ +origin_url=https://github.com/databricks/databricks-empty-ide-project.git + +# The two environments cannot produce the same git provenance, so each branch asserts +# its own exact expectation and output.txt only carries a constant confirmation line +# (the actual bundle.git JSON is routed to LOG.git for debugging): +# +# - Local / non-DBR: the bundle root is a real git repo with an origin remote; the +# CLI reads full provenance from the on-disk .git directory. +# - On DBR the bundle root is a plain /Workspace directory and no git source can +# exist there: git cannot operate on the workspace FUSE mount ("fatal: not in a +# git directory"), and Git folders created via the Repos API do not materialize +# on the FUSE mount within the session. The CLI reads provenance from the +# get-status API, which returns no git_info here, and must degrade to empty +# provenance without failing the command. +if [ -n "${DATABRICKS_RUNTIME_VERSION:-}" ]; then + expected='{"origin_url":null,"branch":null,"bundle_root_path":"."}' +else + git-repo-init > LOG.setup 2>&1 + git remote add origin "$origin_url" + expected='{"origin_url":"'$origin_url'","branch":"main","bundle_root_path":"."}' +fi + +title "Bundle git provenance\n" +actual=$($CLI bundle validate -o json | jq -c '{origin_url: .bundle.git.origin_url, branch: .bundle.git.branch, bundle_root_path: .bundle.git.bundle_root_path}') +echo "$actual" > LOG.git +if [ "$actual" != "$expected" ]; then + echo "unexpected bundle.git: $actual (expected: $expected)" + exit 1 +fi +echo "bundle.git matches the expectation for this environment (see LOG.git)" + +rm -rf .git diff --git a/acceptance/bundle/git-info/test.toml b/acceptance/bundle/git-info/test.toml new file mode 100644 index 00000000000..0acf5833bea --- /dev/null +++ b/acceptance/bundle/git-info/test.toml @@ -0,0 +1,24 @@ +# Verifies bundle git provenance recording (bundle.git) through both code paths of +# libs/git/info.go FetchRepositoryInfo: +# +# - Local / non-DBR runs read the on-disk .git directory (fetchRepositoryInfoDotGit) +# and record full provenance. +# - RunsOnDbr runs on real DBR read the workspace get-status API +# (fetchRepositoryInfoAPI). No git source can exist in the bundle root there (see +# the comment in script), so this variant asserts the API path degrades to empty +# provenance without failing the command. +# +# The id-only git_info fallback for the new in-workspace Git folder type cannot be +# provisioned from a test (Repos-API-created folders don't appear on the FUSE mount, +# and git cannot run on it); that path 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 = ["databricks.yml"] From ad956ca3255e5eab9c83c5534c03818bc08ca4cb Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Thu, 2 Jul 2026 12:45:59 +0200 Subject: [PATCH 8/9] acc: drop Ignore entry already inherited from bundle/test.toml --- acceptance/bundle/git-info/test.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/acceptance/bundle/git-info/test.toml b/acceptance/bundle/git-info/test.toml index 0acf5833bea..58c8b48944c 100644 --- a/acceptance/bundle/git-info/test.toml +++ b/acceptance/bundle/git-info/test.toml @@ -20,5 +20,3 @@ RunsOnDbr = true # RunsOnDbr is incompatible with RecordRequests (serverless can't reach the proxy). RecordRequests = false - -Ignore = ["databricks.yml"] From 3eabf976e195495ffb75b5cf652d79adb7b37fd4 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Thu, 2 Jul 2026 15:01:03 +0200 Subject: [PATCH 9/9] acc: drop git-info acceptance test Neither environment can exercise the id-only Repos-API fallback end to end (Repos-API-created Git folders do not materialize on the serverless FUSE mount, git cannot run on it, and local runs never take the DBR API path), so the fallback is covered by unit tests in libs/git/info_test.go. The on-disk .git path is already covered by existing acceptance tests. --- acceptance/bundle/git-info/databricks.yml | 2 -- acceptance/bundle/git-info/out.test.toml | 4 --- acceptance/bundle/git-info/output.txt | 3 --- acceptance/bundle/git-info/script | 32 ----------------------- acceptance/bundle/git-info/test.toml | 22 ---------------- 5 files changed, 63 deletions(-) delete mode 100644 acceptance/bundle/git-info/databricks.yml delete mode 100644 acceptance/bundle/git-info/out.test.toml delete mode 100644 acceptance/bundle/git-info/output.txt delete mode 100644 acceptance/bundle/git-info/script delete mode 100644 acceptance/bundle/git-info/test.toml diff --git a/acceptance/bundle/git-info/databricks.yml b/acceptance/bundle/git-info/databricks.yml deleted file mode 100644 index 4c61456babc..00000000000 --- a/acceptance/bundle/git-info/databricks.yml +++ /dev/null @@ -1,2 +0,0 @@ -bundle: - name: git-info-from-git-folder diff --git a/acceptance/bundle/git-info/out.test.toml b/acceptance/bundle/git-info/out.test.toml deleted file mode 100644 index 5ad0addb75e..00000000000 --- a/acceptance/bundle/git-info/out.test.toml +++ /dev/null @@ -1,4 +0,0 @@ -Local = true -Cloud = true -RunsOnDbr = true -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/git-info/output.txt b/acceptance/bundle/git-info/output.txt deleted file mode 100644 index bd91882c840..00000000000 --- a/acceptance/bundle/git-info/output.txt +++ /dev/null @@ -1,3 +0,0 @@ - -=== Bundle git provenance -bundle.git matches the expectation for this environment (see LOG.git) diff --git a/acceptance/bundle/git-info/script b/acceptance/bundle/git-info/script deleted file mode 100644 index 87fb723775f..00000000000 --- a/acceptance/bundle/git-info/script +++ /dev/null @@ -1,32 +0,0 @@ -origin_url=https://github.com/databricks/databricks-empty-ide-project.git - -# The two environments cannot produce the same git provenance, so each branch asserts -# its own exact expectation and output.txt only carries a constant confirmation line -# (the actual bundle.git JSON is routed to LOG.git for debugging): -# -# - Local / non-DBR: the bundle root is a real git repo with an origin remote; the -# CLI reads full provenance from the on-disk .git directory. -# - On DBR the bundle root is a plain /Workspace directory and no git source can -# exist there: git cannot operate on the workspace FUSE mount ("fatal: not in a -# git directory"), and Git folders created via the Repos API do not materialize -# on the FUSE mount within the session. The CLI reads provenance from the -# get-status API, which returns no git_info here, and must degrade to empty -# provenance without failing the command. -if [ -n "${DATABRICKS_RUNTIME_VERSION:-}" ]; then - expected='{"origin_url":null,"branch":null,"bundle_root_path":"."}' -else - git-repo-init > LOG.setup 2>&1 - git remote add origin "$origin_url" - expected='{"origin_url":"'$origin_url'","branch":"main","bundle_root_path":"."}' -fi - -title "Bundle git provenance\n" -actual=$($CLI bundle validate -o json | jq -c '{origin_url: .bundle.git.origin_url, branch: .bundle.git.branch, bundle_root_path: .bundle.git.bundle_root_path}') -echo "$actual" > LOG.git -if [ "$actual" != "$expected" ]; then - echo "unexpected bundle.git: $actual (expected: $expected)" - exit 1 -fi -echo "bundle.git matches the expectation for this environment (see LOG.git)" - -rm -rf .git diff --git a/acceptance/bundle/git-info/test.toml b/acceptance/bundle/git-info/test.toml deleted file mode 100644 index 58c8b48944c..00000000000 --- a/acceptance/bundle/git-info/test.toml +++ /dev/null @@ -1,22 +0,0 @@ -# Verifies bundle git provenance recording (bundle.git) through both code paths of -# libs/git/info.go FetchRepositoryInfo: -# -# - Local / non-DBR runs read the on-disk .git directory (fetchRepositoryInfoDotGit) -# and record full provenance. -# - RunsOnDbr runs on real DBR read the workspace get-status API -# (fetchRepositoryInfoAPI). No git source can exist in the bundle root there (see -# the comment in script), so this variant asserts the API path degrades to empty -# provenance without failing the command. -# -# The id-only git_info fallback for the new in-workspace Git folder type cannot be -# provisioned from a test (Repos-API-created folders don't appear on the FUSE mount, -# and git cannot run on it); that path 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