Skip to content
Merged
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
1 change: 1 addition & 0 deletions .nextchanges/bundles/git-folder-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `bundle.git.branch`, `bundle.git.commit`, and `bundle.git.origin_url` being empty for bundles deployed from a workspace Git folder that has Git CLI access. The workspace API does not report git metadata for those folders, so it is now read from the Repos API instead.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bundle:
name: test-bundle

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

>>> [CLI] bundle debug fetch-repository-info --path /Workspace/Users/[USERNAME]/test-folder-[UNIQUE_NAME]
Warn: failed to load git info for repo [NUMID]: Fault injected by test.
{
"worktree_root": "/Workspace/Users/[USERNAME]/test-folder-[UNIQUE_NAME]",
"current_branch": "",
"latest_commit": "",
"origin_url": ""
}
16 changes: 16 additions & 0 deletions acceptance/bundle/debug/fetch-repository-info-repos-error/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# A Git folder outside /Repos has Git CLI access, so the workspace API reports no
# git metadata for it and it is read from the Repos API instead. Make that read
# fail: the worktree root must still be reported, with the metadata left empty
# rather than failing the command.
#
# Registered before MSYS_NO_PATHCONV is exported, which on Windows would rewrite
# the path Git Bash passes for this helper's own script.
fault.py "GET /api/2.0/repos/*" 403 0 1

# Keep Git Bash from rewriting the /Workspace path passed to --path.
export MSYS_NO_PATHCONV=1

folder=/Workspace/Users/$CURRENT_USER_NAME/test-folder-$UNIQUE_NAME
$CLI repos create https://github.com/databricks/bundle-examples gitHub --path "$folder" > /dev/null

trace $CLI bundle debug fetch-repository-info --path "$folder"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Local only: the failure is injected into the testserver.
Cloud = false

# This exercises git.FetchRepositoryInfo, which is independent of the deploy engine.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
2 changes: 2 additions & 0 deletions acceptance/bundle/debug/fetch-repository-info/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bundle:
name: test-bundle
2 changes: 2 additions & 0 deletions acceptance/bundle/debug/fetch-repository-info/out.test.toml

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

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

=== the root of a Git folder under /Repos

>>> [CLI] bundle debug fetch-repository-info --path /Workspace/Repos/[USERNAME]/test-repo-[UNIQUE_NAME]
{
"worktree_root": "/Workspace/Repos/[USERNAME]/test-repo-[UNIQUE_NAME]",
"current_branch": "main",
"latest_commit": "[COMMIT_SHA]",
"origin_url": "https://github.com/databricks/bundle-examples"
}

=== a subdirectory of it, which resolves to the same root

>>> [CLI] bundle debug fetch-repository-info --path /Workspace/Repos/[USERNAME]/test-repo-[UNIQUE_NAME]/subdir
{
"worktree_root": "/Workspace/Repos/[USERNAME]/test-repo-[UNIQUE_NAME]",
"current_branch": "main",
"latest_commit": "[COMMIT_SHA]",
"origin_url": "https://github.com/databricks/bundle-examples"
}

=== the root of a Git folder outside /Repos

>>> [CLI] bundle debug fetch-repository-info --path /Workspace/Users/[USERNAME]/test-folder-[UNIQUE_NAME]
{
"worktree_root": "/Workspace/Users/[USERNAME]/test-folder-[UNIQUE_NAME]",
"current_branch": "main",
"latest_commit": "[COMMIT_SHA]",
"origin_url": "https://github.com/databricks/bundle-examples"
}

=== a path in no Git folder at all

>>> [CLI] bundle debug fetch-repository-info --path /Workspace/Users/[USERNAME]
{
"worktree_root": "",
"current_branch": "",
"latest_commit": "",
"origin_url": ""
}
33 changes: 33 additions & 0 deletions acceptance/bundle/debug/fetch-repository-info/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# The fake workspace reports "main", which is this repo's default branch too.
url=https://github.com/databricks/bundle-examples

# A Git folder under /Repos and one outside it. Once a Git folder has Git CLI
# access the workspace API reports no git metadata for it, so both resolve
# through the Repos API; the resolved metadata must be identical either way.
repo=/Workspace/Repos/$CURRENT_USER_NAME/test-repo-$UNIQUE_NAME
folder=/Workspace/Users/$CURRENT_USER_NAME/test-folder-$UNIQUE_NAME

$CLI repos create $url gitHub --path "$repo" > /dev/null
$CLI repos create $url gitHub --path "$folder" > /dev/null

# A directory inside the Git folder, so the subdirectory case below resolves a
# path that exists rather than a missing one.
$CLI workspace mkdirs "$repo/subdir"

cleanup() {
$CLI repos delete "$repo" 2>>LOG.cleanup
$CLI repos delete "$folder" 2>>LOG.cleanup
}
trap cleanup EXIT

title "the root of a Git folder under /Repos\n"
trace $CLI bundle debug fetch-repository-info --path "$repo"

title "a subdirectory of it, which resolves to the same root\n"
trace $CLI bundle debug fetch-repository-info --path "$repo/subdir"

title "the root of a Git folder outside /Repos\n"
trace $CLI bundle debug fetch-repository-info --path "$folder"

title "a path in no Git folder at all\n"
trace $CLI bundle debug fetch-repository-info --path "/Workspace/Users/$CURRENT_USER_NAME"
13 changes: 13 additions & 0 deletions acceptance/bundle/debug/fetch-repository-info/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Cloud = true

# This exercises git.FetchRepositoryInfo, which is independent of the deploy engine.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# Keep Git Bash from rewriting the /Workspace paths passed to --path.
Env.MSYS_NO_PATHCONV = "1"

# The commit differs between the fake workspace and a real clone of the repo, so
# the golden matches both.
[[Repls]]
Old = '\b[0-9a-f]{40}\b'
New = '[COMMIT_SHA]'
2 changes: 2 additions & 0 deletions acceptance/workspace/repos/create_with_provider/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
>>> [CLI] repos get [NUMID] -o json
{
"branch": "main",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Repos/me@databricks.com/test-repo",
"provider": "gitHub",
Expand All @@ -15,6 +16,7 @@
>>> [CLI] repos get /Repos/me@databricks.com/test-repo -o json
{
"branch": "main",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Repos/me@databricks.com/test-repo",
"provider": "gitHub",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
>>> [CLI] repos create https://github.com/databricks/databricks-empty-ide-project.git --path /Repos/me@databricks.com/test-repo
{
"branch": "main",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Repos/me@databricks.com/test-repo",
"provider": "gitHub",
Expand Down
1 change: 1 addition & 0 deletions acceptance/workspace/repos/delete_by_path/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
>>> [CLI] repos get /Repos/me@databricks.com/test-repo -o json
{
"branch": "main",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Repos/me@databricks.com/test-repo",
"provider": "gitHub",
Expand Down
1 change: 1 addition & 0 deletions acceptance/workspace/repos/git_cli_folder/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
>>> [CLI] repos get /Workspace/Users/me@databricks.com/test-repo -o json
{
"branch": "main",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Workspace/Users/me@databricks.com/test-repo",
"provider": "gitHub",
Expand Down
2 changes: 2 additions & 0 deletions acceptance/workspace/repos/update/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
>>> [CLI] repos get [NUMID] -o json
{
"branch": "update-by-id",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Repos/me@databricks.com/test-repo",
"provider": "gitHub",
Expand All @@ -17,6 +18,7 @@
>>> [CLI] repos get [NUMID] -o json
{
"branch": "update-by-path",
"head_commit_id": "0a1b2c3d4e5f6a7b8e7dadd73e50a69d8ba47d8f",
"id": [NUMID],
"path": "/Repos/me@databricks.com/test-repo",
"provider": "gitHub",
Expand Down
1 change: 1 addition & 0 deletions cmd/bundle/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ func newDebugCommand() *cobra.Command {
cmd.AddCommand(debug.NewStatesCommand())
cmd.AddCommand(debug.NewRenderTemplateSchemaCommand())
cmd.AddCommand(debug.NewListTargetsCommand())
cmd.AddCommand(debug.NewFetchRepositoryInfoCommand())
return cmd
}
63 changes: 63 additions & 0 deletions cmd/bundle/debug/fetch_repository_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package debug

import (
"encoding/json"

"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/git"
"github.com/spf13/cobra"
)

// repositoryInfoOutput mirrors git.RepositoryInfo, which has no JSON tags.
type repositoryInfoOutput struct {
WorktreeRoot string `json:"worktree_root"`
CurrentBranch string `json:"current_branch"`
LatestCommit string `json:"latest_commit"`
OriginURL string `json:"origin_url"`
}

// NewFetchRepositoryInfoCommand returns a command that reports what
// [git.FetchRepositoryInfoAPI] resolves for a path. It exists so that function
// can be exercised through the CLI on both a fake and a real workspace; nothing
// in the product calls it.
//
// It always reads the workspace API, which [git.FetchRepositoryInfo] only does on
// a Databricks Runtime. That cannot be detected off-cluster, and reading .git is
// covered by the TestFetchRepositoryInfoDotGit integration tests.
func NewFetchRepositoryInfoCommand() *cobra.Command {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have it if nothing in product calls it? For acceptance tests? We seem to have TestFetchRepositoryInfoAPI* unit tests which covers (somewhat?) the same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for acc tests so we can have local + cloud test and deprecate corresponding integration/ tests

cmd := &cobra.Command{
Use: "fetch-repository-info",
Short: "Report the git metadata the workspace API resolves for a path",
Args: root.NoArgs,
Hidden: true,
}

var path string
cmd.Flags().StringVar(&path, "path", ".", "Path to resolve git metadata for")

cmd.PreRunE = root.MustWorkspaceClient
cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

info, err := git.FetchRepositoryInfoAPI(ctx, path, cmdctx.WorkspaceClient(ctx))
if err != nil {
return err
}

buf, err := json.MarshalIndent(repositoryInfoOutput{
WorktreeRoot: info.WorktreeRoot,
CurrentBranch: info.CurrentBranch,
LatestCommit: info.LatestCommit,
OriginURL: info.OriginURL,
}, "", " ")
if err != nil {
return err
}

_, err = cmd.OutOrStdout().Write(append(buf, '\n'))
return err
}

return cmd
}
8 changes: 0 additions & 8 deletions integration/libs/git/git_fetch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"testing"

"github.com/databricks/cli/integration/internal/acc"
"github.com/databricks/cli/internal/testutil"
"github.com/databricks/cli/libs/dbr"
"github.com/databricks/cli/libs/git"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -48,13 +47,6 @@ func ensureWorkspacePrefix(root string) string {
}

func TestFetchRepositoryInfoAPI_FromRepo(t *testing.T) {
// On GCP the Repos API returns no branch, commit, or origin URL for a
// freshly-cloned repo, so FetchRepositoryInfo comes back empty and the
// assertions in assertFullGitInfo fail.
if testutil.GetCloud(t) == testutil.GCP {
t.Skip("Skipping on GCP: Repos API does not return git metadata")
}

ctx, wt := acc.WorkspaceTest(t)
targetPath := ensureWorkspacePrefix(acc.TemporaryRepo(wt, examplesRepoUrl))

Expand Down
28 changes: 26 additions & 2 deletions libs/git/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/client"
"github.com/databricks/databricks-sdk-go/service/workspace"
)

type RepositoryInfo struct {
Expand All @@ -31,6 +32,7 @@ type RepositoryInfo struct {
type gitInfo struct {
Branch string `json:"branch"`
HeadCommitID string `json:"head_commit_id"`
ID int64 `json:"id"`
Path string `json:"path"`
URL string `json:"url"`
}
Expand All @@ -50,7 +52,7 @@ func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.Workspa
var info RepositoryInfo
var err error
if strings.HasPrefix(path, "/Workspace/") && dbr.RunsOnRuntime(ctx) {
info, err = fetchRepositoryInfoAPI(ctx, path, w)
info, err = FetchRepositoryInfoAPI(ctx, path, w)
} else {
info, err = fetchRepositoryInfoDotGit(ctx, path)
}
Expand All @@ -65,7 +67,14 @@ func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.Workspa
return info, err
}

func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
// FetchRepositoryInfoAPI reads the metadata from the workspace API, which is what
// FetchRepositoryInfo does on a Databricks Runtime. Exported so that
// `bundle debug fetch-repository-info --workspace-api` can reach this path
// off-runtime; the product calls it through FetchRepositoryInfo.
//
// A path that does not exist is reported as fs.ErrNotExist; callers that treat
// that as "no repository here" must normalize it the way FetchRepositoryInfo does.
func FetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
result := RepositoryInfo{}

apiClient, err := client.New(w.Config)
Expand Down Expand Up @@ -108,6 +117,21 @@ func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.Work
result.LatestCommit = gi.HeadCommitID
result.CurrentBranch = gi.Branch
result.WorktreeRoot = fixedPath

// A Git folder with Git CLI access does not store the git metadata on the
// workspace object, so get-status returns only the id and path for it. The
// Repos API still has the metadata, and git_info.id identifies the Git
// folder root even when the queried path is a subdirectory of it.
if gi.ID != 0 && gi.Branch == "" && gi.HeadCommitID == "" && gi.URL == "" {
repo, err := w.Repos.Get(ctx, workspace.GetRepoRequest{RepoId: gi.ID})
if err != nil {
log.Warnf(ctx, "failed to load git info for repo %d: %s", gi.ID, err)
} else {
result.OriginURL = repo.Url
result.LatestCommit = repo.HeadCommitId
result.CurrentBranch = repo.Branch
}
}
} else {
log.Infof(ctx, "Failed to load git info from %s", apiEndpoint)
}
Expand Down
Loading
Loading