From 400ec7ba3b82bec4e0c401bd055f8dace9240688 Mon Sep 17 00:00:00 2001 From: Your username Date: Fri, 31 Jul 2026 19:36:20 +0000 Subject: [PATCH 1/3] Fix repos get/update/delete for Git-CLI-enabled folders repoArgumentToRepoID resolved a path via workspace get-status and then required ObjectType == REPO. Since the Git CLI rollout, Git-CLI-enabled folders are materialized as plain DIRECTORY nodes, so get-status returns DIRECTORY and the gate rejected them with `object at path "..." is not a repo` even though the repos API still resolves their object ID as a repo. Drop the client-side object-type gate and let the repos API be the authority, per the repos API owner's guidance. The testserver now reports repos as DIRECTORY nodes so the path-based repos tests guard the fix. Co-authored-by: Isaac --- .nextchanges/cli/repos-git-cli-folders.md | 1 + .../workspace/repos/get_errors/out.requests.txt | 4 ++++ acceptance/workspace/repos/get_errors/output.txt | 2 +- acceptance/workspace/repos/get_errors/script | 2 ++ cmd/workspace/repos/overrides.go | 11 +++++++---- libs/testserver/fake_workspace.go | 6 +++++- 6 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 .nextchanges/cli/repos-git-cli-folders.md diff --git a/.nextchanges/cli/repos-git-cli-folders.md b/.nextchanges/cli/repos-git-cli-folders.md new file mode 100644 index 00000000000..dc85e5ef4c3 --- /dev/null +++ b/.nextchanges/cli/repos-git-cli-folders.md @@ -0,0 +1 @@ +Fixed `databricks repos get/update/delete` failing with `object at path "..." is not a repo` for Git-CLI-enabled folders, which the workspace API now reports as directories rather than repos. diff --git a/acceptance/workspace/repos/get_errors/out.requests.txt b/acceptance/workspace/repos/get_errors/out.requests.txt index 2bfe07b1317..45a0b97eec6 100644 --- a/acceptance/workspace/repos/get_errors/out.requests.txt +++ b/acceptance/workspace/repos/get_errors/out.requests.txt @@ -23,3 +23,7 @@ "path": "/not-a-repo" } } +{ + "method": "GET", + "path": "/api/2.0/repos/[NUMID]" +} diff --git a/acceptance/workspace/repos/get_errors/output.txt b/acceptance/workspace/repos/get_errors/output.txt index 443f30491f3..0b5f7b35460 100644 --- a/acceptance/workspace/repos/get_errors/output.txt +++ b/acceptance/workspace/repos/get_errors/output.txt @@ -3,4 +3,4 @@ Error: failed to look up repo by path: Path (/Repos/me@databricks.com/doesnotexist) doesn't exist. >>> [CLI] repos get /not-a-repo -o json -Error: object at path "/not-a-repo" is not a repo +Error: Resource workspace.RepoInfo not found: [NUMID] diff --git a/acceptance/workspace/repos/get_errors/script b/acceptance/workspace/repos/get_errors/script index b813fe7b4d4..3f7cb746a3d 100644 --- a/acceptance/workspace/repos/get_errors/script +++ b/acceptance/workspace/repos/get_errors/script @@ -1,4 +1,6 @@ musterr trace $CLI repos get /Repos/me@databricks.com/doesnotexist -o json +# A path that exists but is not a repo is no longer rejected client-side by an +# object-type check; the repos API is the authority and returns not found. $CLI workspace mkdirs /not-a-repo musterr trace $CLI repos get /not-a-repo -o json diff --git a/cmd/workspace/repos/overrides.go b/cmd/workspace/repos/overrides.go index 72f58bb5010..8b3fafa7690 100644 --- a/cmd/workspace/repos/overrides.go +++ b/cmd/workspace/repos/overrides.go @@ -166,14 +166,17 @@ func repoArgumentToRepoID(ctx context.Context, w *databricks.WorkspaceClient, ar return id, nil } - // If the argument cannot be parsed as a repo ID, try to look it up by name. + // If the argument cannot be parsed as a repo ID, look it up by path. + // + // We deliberately don't check oi.ObjectType here. Git-CLI-enabled folders + // are materialized as plain DIRECTORY nodes rather than REPO nodes, but the + // repos API still accepts their object ID as a repo ID. The repos API is the + // authority on whether the target is a repo, so gating on the object type + // here would reject valid Git CLI folders. oi, err := w.Workspace.GetStatusByPath(ctx, arg) if err != nil { return 0, fmt.Errorf("failed to look up repo by path: %w", err) } - if oi.ObjectType != workspace.ObjectTypeRepo { - return 0, fmt.Errorf("object at path %q is not a repo", arg) - } return oi.ObjectId, nil } diff --git a/libs/testserver/fake_workspace.go b/libs/testserver/fake_workspace.go index 8d6e8ee0dd3..90a7ab4e65b 100644 --- a/libs/testserver/fake_workspace.go +++ b/libs/testserver/fake_workspace.go @@ -418,7 +418,11 @@ func (s *FakeWorkspace) WorkspaceGetStatus(requestPath string) Response { } else if entry, ok := s.files[cleaned]; ok { info = entry.Info } else if repoId, ok := s.repoIdByPath[cleaned]; ok { - info = workspace.ObjectInfo{ObjectType: "REPO", Path: cleaned, ObjectId: repoId} + // Git-CLI-enabled repos are materialized as plain DIRECTORY nodes, not + // REPO nodes, so get-status reports DIRECTORY for them while the repos + // API still resolves the object ID as a repo. This mirrors the backend + // behavior that repoArgumentToRepoID must tolerate. + info = workspace.ObjectInfo{ObjectType: "DIRECTORY", Path: cleaned, ObjectId: repoId} } else { // Match the real Workspace API wording, which echoes the requested path. return Response{ From b6988ccc8c12365d00b2ad936d2daa30b76ec628 Mon Sep 17 00:00:00 2001 From: Your username Date: Fri, 31 Jul 2026 21:19:37 +0000 Subject: [PATCH 2/3] Address review: model both repo object types and trim comment - testserver get-status now reports control-plane repos (under /Repos) as REPO and Git-CLI folders elsewhere as DIRECTORY, so both behaviors are covered rather than forcing every repo to DIRECTORY. - Add acceptance test git_cli_folder covering path-based get/update/delete against a DIRECTORY-typed Git CLI folder; the /Repos tests keep exercising the REPO type. - Trim the explanatory comment in repoArgumentToRepoID. - Note in the changelog that Git CLI is a toggleable preview and can be turned off as a mitigation. Co-authored-by: Isaac --- .nextchanges/cli/repos-git-cli-folders.md | 2 +- .../repos/git_cli_folder/out.requests.txt | 49 +++++++++++++++++++ .../repos/git_cli_folder/out.test.toml | 3 ++ .../workspace/repos/git_cli_folder/output.txt | 19 +++++++ .../workspace/repos/git_cli_folder/script | 16 ++++++ cmd/workspace/repos/overrides.go | 10 ++-- libs/testserver/fake_workspace.go | 13 +++-- 7 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 acceptance/workspace/repos/git_cli_folder/out.requests.txt create mode 100644 acceptance/workspace/repos/git_cli_folder/out.test.toml create mode 100644 acceptance/workspace/repos/git_cli_folder/output.txt create mode 100644 acceptance/workspace/repos/git_cli_folder/script diff --git a/.nextchanges/cli/repos-git-cli-folders.md b/.nextchanges/cli/repos-git-cli-folders.md index dc85e5ef4c3..815f70dcaaf 100644 --- a/.nextchanges/cli/repos-git-cli-folders.md +++ b/.nextchanges/cli/repos-git-cli-folders.md @@ -1 +1 @@ -Fixed `databricks repos get/update/delete` failing with `object at path "..." is not a repo` for Git-CLI-enabled folders, which the workspace API now reports as directories rather than repos. +Fixed `databricks repos get/update/delete` failing with `object at path "..." is not a repo` for Git-CLI-enabled folders, which the workspace API reports as directories rather than repos. Git CLI is a preview feature; as an alternative mitigation it can be turned off in the workspace admin previews settings. diff --git a/acceptance/workspace/repos/git_cli_folder/out.requests.txt b/acceptance/workspace/repos/git_cli_folder/out.requests.txt new file mode 100644 index 00000000000..50626ea4e4b --- /dev/null +++ b/acceptance/workspace/repos/git_cli_folder/out.requests.txt @@ -0,0 +1,49 @@ +{ + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "method": "POST", + "path": "/api/2.0/repos", + "body": { + "path": "/Workspace/Users/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/get-status", + "q": { + "path": "/Workspace/Users/me@databricks.com/test-repo" + } +} +{ + "method": "GET", + "path": "/api/2.0/repos/[NUMID]" +} +{ + "method": "GET", + "path": "/api/2.0/workspace/get-status", + "q": { + "path": "/Workspace/Users/me@databricks.com/test-repo" + } +} +{ + "method": "PATCH", + "path": "/api/2.0/repos/[NUMID]", + "body": { + "branch": "update-by-path" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/get-status", + "q": { + "path": "/Workspace/Users/me@databricks.com/test-repo" + } +} +{ + "method": "DELETE", + "path": "/api/2.0/repos/[NUMID]" +} diff --git a/acceptance/workspace/repos/git_cli_folder/out.test.toml b/acceptance/workspace/repos/git_cli_folder/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/workspace/repos/git_cli_folder/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/repos/git_cli_folder/output.txt b/acceptance/workspace/repos/git_cli_folder/output.txt new file mode 100644 index 00000000000..e22b603c39a --- /dev/null +++ b/acceptance/workspace/repos/git_cli_folder/output.txt @@ -0,0 +1,19 @@ + +>>> [CLI] repos create https://github.com/databricks/databricks-empty-ide-project.git gitHub --path /Workspace/Users/me@databricks.com/test-repo +[NUMID] + +=== Get by path resolves a Git CLI folder +>>> [CLI] repos get /Workspace/Users/me@databricks.com/test-repo -o json +{ + "branch": "main", + "id": [NUMID], + "path": "/Workspace/Users/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" +} + +=== Update by path resolves a Git CLI folder +>>> [CLI] repos update /Workspace/Users/me@databricks.com/test-repo --branch update-by-path + +=== Delete by path resolves a Git CLI folder +>>> [CLI] repos delete /Workspace/Users/me@databricks.com/test-repo diff --git a/acceptance/workspace/repos/git_cli_folder/script b/acceptance/workspace/repos/git_cli_folder/script new file mode 100644 index 00000000000..6e617d6f798 --- /dev/null +++ b/acceptance/workspace/repos/git_cli_folder/script @@ -0,0 +1,16 @@ +url=https://github.com/databricks/databricks-empty-ide-project.git +provider=gitHub +# A Git-CLI-enabled folder lives outside /Repos and get-status reports it as a +# DIRECTORY, not a REPO. Path-based commands must still resolve it to a repo ID. +path=/Workspace/Users/me@databricks.com/test-repo + +trace $CLI repos create $url $provider --path $path | jq .id -r + +title "Get by path resolves a Git CLI folder" +trace $CLI repos get $path -o json + +title "Update by path resolves a Git CLI folder" +trace $CLI repos update $path --branch update-by-path + +title "Delete by path resolves a Git CLI folder" +trace $CLI repos delete $path diff --git a/cmd/workspace/repos/overrides.go b/cmd/workspace/repos/overrides.go index 8b3fafa7690..ca10dbd0fb3 100644 --- a/cmd/workspace/repos/overrides.go +++ b/cmd/workspace/repos/overrides.go @@ -166,13 +166,9 @@ func repoArgumentToRepoID(ctx context.Context, w *databricks.WorkspaceClient, ar return id, nil } - // If the argument cannot be parsed as a repo ID, look it up by path. - // - // We deliberately don't check oi.ObjectType here. Git-CLI-enabled folders - // are materialized as plain DIRECTORY nodes rather than REPO nodes, but the - // repos API still accepts their object ID as a repo ID. The repos API is the - // authority on whether the target is a repo, so gating on the object type - // here would reject valid Git CLI folders. + // Look up the path via get-status. We don't gate on the object type: Git-CLI + // folders report DIRECTORY rather than REPO, and the repos API is the + // authority on whether the ID resolves to a repo. oi, err := w.Workspace.GetStatusByPath(ctx, arg) if err != nil { return 0, fmt.Errorf("failed to look up repo by path: %w", err) diff --git a/libs/testserver/fake_workspace.go b/libs/testserver/fake_workspace.go index 90a7ab4e65b..5c7f9cfee40 100644 --- a/libs/testserver/fake_workspace.go +++ b/libs/testserver/fake_workspace.go @@ -418,11 +418,14 @@ func (s *FakeWorkspace) WorkspaceGetStatus(requestPath string) Response { } else if entry, ok := s.files[cleaned]; ok { info = entry.Info } else if repoId, ok := s.repoIdByPath[cleaned]; ok { - // Git-CLI-enabled repos are materialized as plain DIRECTORY nodes, not - // REPO nodes, so get-status reports DIRECTORY for them while the repos - // API still resolves the object ID as a repo. This mirrors the backend - // behavior that repoArgumentToRepoID must tolerate. - info = workspace.ObjectInfo{ObjectType: "DIRECTORY", Path: cleaned, ObjectId: repoId} + // Control-plane repos (under /Repos) report the REPO object type, while + // Git-CLI-enabled folders elsewhere are materialized as plain DIRECTORY + // nodes. Both resolve to a valid repo ID via the repos API. + objectType := workspace.ObjectTypeRepo + if !strings.HasPrefix(cleaned, "/Repos/") { + objectType = workspace.ObjectTypeDirectory + } + info = workspace.ObjectInfo{ObjectType: objectType, Path: cleaned, ObjectId: repoId} } else { // Match the real Workspace API wording, which echoes the requested path. return Response{ From cfb1677f3be8cc70dc6ccae646233a5c3431f81b Mon Sep 17 00:00:00 2001 From: Your username Date: Fri, 31 Jul 2026 21:41:13 +0000 Subject: [PATCH 3/3] Drop get_errors /not-a-repo case tied to the removed type check The case existed to exercise the client-side `!= REPO` gate. With that gate gone, it only tested that an arbitrary directory id isn't a registered repo, which is not a behavior this change is about. The positive REPO and DIRECTORY path-resolution tests cover the fix. Co-authored-by: Isaac --- .../repos/get_errors/out.requests.txt | 18 ------------------ .../workspace/repos/get_errors/output.txt | 3 --- acceptance/workspace/repos/get_errors/script | 5 ----- 3 files changed, 26 deletions(-) diff --git a/acceptance/workspace/repos/get_errors/out.requests.txt b/acceptance/workspace/repos/get_errors/out.requests.txt index 45a0b97eec6..ed3cb2661e7 100644 --- a/acceptance/workspace/repos/get_errors/out.requests.txt +++ b/acceptance/workspace/repos/get_errors/out.requests.txt @@ -9,21 +9,3 @@ "path": "/Repos/me@databricks.com/doesnotexist" } } -{ - "method": "POST", - "path": "/api/2.0/workspace/mkdirs", - "body": { - "path": "/not-a-repo" - } -} -{ - "method": "GET", - "path": "/api/2.0/workspace/get-status", - "q": { - "path": "/not-a-repo" - } -} -{ - "method": "GET", - "path": "/api/2.0/repos/[NUMID]" -} diff --git a/acceptance/workspace/repos/get_errors/output.txt b/acceptance/workspace/repos/get_errors/output.txt index 0b5f7b35460..9055734262b 100644 --- a/acceptance/workspace/repos/get_errors/output.txt +++ b/acceptance/workspace/repos/get_errors/output.txt @@ -1,6 +1,3 @@ >>> [CLI] repos get /Repos/me@databricks.com/doesnotexist -o json Error: failed to look up repo by path: Path (/Repos/me@databricks.com/doesnotexist) doesn't exist. - ->>> [CLI] repos get /not-a-repo -o json -Error: Resource workspace.RepoInfo not found: [NUMID] diff --git a/acceptance/workspace/repos/get_errors/script b/acceptance/workspace/repos/get_errors/script index 3f7cb746a3d..6050aa1cfc8 100644 --- a/acceptance/workspace/repos/get_errors/script +++ b/acceptance/workspace/repos/get_errors/script @@ -1,6 +1 @@ musterr trace $CLI repos get /Repos/me@databricks.com/doesnotexist -o json - -# A path that exists but is not a repo is no longer rejected client-side by an -# object-type check; the repos API is the authority and returns not found. -$CLI workspace mkdirs /not-a-repo -musterr trace $CLI repos get /not-a-repo -o json