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
20 changes: 14 additions & 6 deletions libs/git/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type response struct {
GitInfo *gitInfo `json:"git_info,omitempty"`
}

// Fetch repository information either by quering .git or by fetching it from API (for dabs-in-workspace case).
// FetchRepositoryInfo reads Workspace Git metadata or a local .git checkout.
// - In case we could not find git repository (including when the path does not exist), all string fields of RepositoryInfo will be "" and err will be nil.
// - If there were any errors when trying to determine git root (e.g. API call returned an error or there were permission issues
// reading the file system), all strings fields of RepositoryInfo will be "" and err will be non-nil.
Expand All @@ -52,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 = fetchRepositoryInfoFromWorkspace(ctx, path, w)
} else {
info, err = fetchRepositoryInfoDotGit(ctx, path)
}
Expand All @@ -67,10 +67,18 @@ func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.Workspa
return info, err
}

// 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.
func fetchRepositoryInfoFromWorkspace(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
info, err := FetchRepositoryInfoAPI(ctx, path, w)
if err == nil && info.WorktreeRoot == "" {
// Fall back to Git information from .git for unsupported Git folders like worktree folders
return fetchRepositoryInfoDotGit(ctx, path)
}
return info, err
}

// FetchRepositoryInfoAPI reads Workspace Git metadata, the primary source on a Runtime.
// It is exported so `bundle debug fetch-repository-info --workspace-api` can use this backend
// off Runtime; normal commands call 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.
Expand Down
90 changes: 90 additions & 0 deletions libs/git/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package git

import (
"io/fs"
"net/http"
"net/url"
"path/filepath"
"strings"
"testing"

"github.com/databricks/cli/internal/testutil"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/qa"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestFetchRepositoryInfoFromWorkspace(t *testing.T) {
dir := setupWorktree(t)
worktree := filepath.Join(dir, "my_worktree")
writeGitDir(t, dir, "gitdir: ../.git/worktrees/my_worktree")
writeGitCommonDir(t, dir, "../..")
want := RepositoryInfo{
OriginURL: "https://example.test/repo.git",
LatestCommit: strings.Repeat("1", 40),
CurrentBranch: "projects/task",
WorktreeRoot: worktree,
}
gitDirectoryWant := want
gitDirectoryWant.WorktreeRoot = dir
noGitRoot := filepath.VolumeName(dir) + string(filepath.Separator)
testutil.WriteFile(t, filepath.Join(dir, ".git", "refs", "heads", want.CurrentBranch), want.LatestCommit)
testutil.WriteFile(t, filepath.Join(dir, ".git", "HEAD"), "ref: refs/heads/"+want.CurrentBranch)
testutil.WriteFile(t, filepath.Join(dir, ".git", "worktrees", "my_worktree", "HEAD"), "ref: refs/heads/"+want.CurrentBranch)
testutil.WriteFile(t, filepath.Join(dir, ".git", "config"), "[remote \"origin\"]\n\turl = "+want.OriginURL)
file := testutil.Touch(t, worktree, "subdir", "README.md")
malformed := t.TempDir()
testutil.WriteFile(t, filepath.Join(malformed, ".git"), "invalid gitfile")

for _, tc := range []struct {
name string
path string
body string
status int
want RepositoryInfo
wantErr error
}{
{name: "worktree", path: worktree, want: want},
Comment thread
lennartkats-db marked this conversation as resolved.
Resolved
{name: "subdirectory", path: filepath.Dir(file), want: want},
{
name: "workspace metadata takes precedence", path: worktree,
body: `{"git_info":{"branch":"api-main","head_commit_id":"2222222222222222222222222222222222222222","path":"/Users/me/api","url":"https://example.test/api.git"}}`,
want: RepositoryInfo{
OriginURL: "https://example.test/api.git", LatestCommit: strings.Repeat("2", 40),
CurrentBranch: "api-main", WorktreeRoot: "/Workspace/Users/me/api",
},
},
{name: "git directory", path: dir, want: gitDirectoryWant},
{name: "no git metadata", path: noGitRoot, wantErr: fs.ErrNotExist},
{name: "malformed gitfile", path: malformed, want: RepositoryInfo{WorktreeRoot: malformed}},
{
name: "permission denied", path: worktree, status: http.StatusForbidden,
body: `{"error_code":"PERMISSION_DENIED","message":"denied"}`, wantErr: apierr.ErrPermissionDenied,
},
{
name: "workspace path missing", path: worktree, status: http.StatusNotFound,
body: `{"error_code":"RESOURCE_DOES_NOT_EXIST","message":"missing"}`, wantErr: fs.ErrNotExist,
},
} {
t.Run(tc.name, func(t *testing.T) {
body := tc.body
if body == "" {
body = `{}`
}
cfg, server := qa.HTTPFixtures{{
Method: "GET",
Resource: "/api/2.0/workspace/get-status?path=" + url.QueryEscape(tc.path) + "&return_git_info=true",
Response: body,
Status: tc.status,
}}.Config(t)
t.Cleanup(server.Close)
w := databricks.Must(databricks.NewWorkspaceClient((*databricks.Config)(cfg)))

got, err := fetchRepositoryInfoFromWorkspace(t.Context(), tc.path, w)
require.ErrorIs(t, err, tc.wantErr)
assert.Equal(t, tc.want, got)
})
}
}
Loading