Skip to content

Commit 05ff508

Browse files
committed
Properly read git metadata when inside Workspace
Since there is no .git directory in Workspace file system, we need to make an API call to fetch git checkout status (root of the repo, current branch, etc). (api/2.0/workspace/get-status?return_git_info=true). Refactor Repository to accept repository root rather than calculate it. This helps, because Repository is currently created in multiple places and finding the repository root is expensive.
1 parent 84d535a commit 05ff508

File tree

15 files changed

+217
-83
lines changed

15 files changed

+217
-83
lines changed

bundle/bundle.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ type Bundle struct {
4848
// Exclusively use this field for filesystem operations.
4949
SyncRoot vfs.Path
5050

51+
// Path to root of git worktree
52+
WorktreeRoot vfs.Path
53+
5154
// Config contains the bundle configuration.
5255
// It is loaded from the bundle configuration files and mutators may update it.
5356
Config config.Root

bundle/bundle_read_only.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ func (r ReadOnlyBundle) SyncRoot() vfs.Path {
3232
return r.b.SyncRoot
3333
}
3434

35+
func (r ReadOnlyBundle) WorktreeRoot() vfs.Path {
36+
return r.b.WorktreeRoot
37+
}
38+
3539
func (r ReadOnlyBundle) WorkspaceClient() *databricks.WorkspaceClient {
3640
return r.b.WorkspaceClient()
3741
}

bundle/config/mutator/load_git_details.go

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/databricks/cli/bundle"
88
"github.com/databricks/cli/libs/diag"
99
"github.com/databricks/cli/libs/git"
10-
"github.com/databricks/cli/libs/log"
1110
)
1211

1312
type loadGitDetails struct{}
@@ -21,50 +20,41 @@ func (m *loadGitDetails) Name() string {
2120
}
2221

2322
func (m *loadGitDetails) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
24-
// Load relevant git repository
25-
repo, err := git.NewRepository(b.BundleRoot)
23+
info, err := git.FetchRepositoryInfo(ctx, b.BundleRoot, b.WorkspaceClient())
2624
if err != nil {
2725
return diag.FromErr(err)
2826
}
2927

30-
// Read branch name of current checkout
31-
branch, err := repo.CurrentBranch()
32-
if err == nil {
33-
b.Config.Bundle.Git.ActualBranch = branch
34-
if b.Config.Bundle.Git.Branch == "" {
35-
// Only load branch if there's no user defined value
36-
b.Config.Bundle.Git.Inferred = true
37-
b.Config.Bundle.Git.Branch = branch
38-
}
39-
} else {
40-
log.Warnf(ctx, "failed to load current branch: %s", err)
28+
b.WorktreeRoot = info.WorktreeRoot
29+
30+
b.Config.Bundle.Git.ActualBranch = info.CurrentBranch
31+
if b.Config.Bundle.Git.Branch == "" {
32+
// Only load branch if there's no user defined value
33+
b.Config.Bundle.Git.Inferred = true
34+
b.Config.Bundle.Git.Branch = info.CurrentBranch
4135
}
4236

4337
// load commit hash if undefined
4438
if b.Config.Bundle.Git.Commit == "" {
45-
commit, err := repo.LatestCommit()
46-
if err != nil {
47-
log.Warnf(ctx, "failed to load latest commit: %s", err)
48-
} else {
49-
b.Config.Bundle.Git.Commit = commit
50-
}
39+
b.Config.Bundle.Git.Commit = info.LatestCommit
5140
}
41+
5242
// load origin url if undefined
5343
if b.Config.Bundle.Git.OriginURL == "" {
54-
remoteUrl := repo.OriginUrl()
55-
b.Config.Bundle.Git.OriginURL = remoteUrl
44+
b.Config.Bundle.Git.OriginURL = info.OriginURL
5645
}
5746

5847
// Compute relative path of the bundle root from the Git repo root.
5948
absBundlePath, err := filepath.Abs(b.BundleRootPath)
6049
if err != nil {
6150
return diag.FromErr(err)
6251
}
63-
// repo.Root() returns the absolute path of the repo
64-
relBundlePath, err := filepath.Rel(repo.Root(), absBundlePath)
52+
53+
relBundlePath, err := filepath.Rel(info.WorktreeRoot.Native(), absBundlePath)
6554
if err != nil {
6655
return diag.FromErr(err)
6756
}
57+
6858
b.Config.Bundle.Git.BundleRootPath = filepath.ToSlash(relBundlePath)
6959
return nil
7060
}

bundle/config/validate/files_to_sync_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func setupBundleForFilesToSyncTest(t *testing.T) *bundle.Bundle {
4444
BundleRoot: vfs.MustNew(dir),
4545
SyncRootPath: dir,
4646
SyncRoot: vfs.MustNew(dir),
47+
WorktreeRoot: vfs.MustNew(dir),
4748
Config: config.Root{
4849
Bundle: config.Bundle{
4950
Target: "default",

bundle/deploy/files/sync.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ func GetSyncOptions(ctx context.Context, rb bundle.ReadOnlyBundle) (*sync.SyncOp
2828
}
2929

3030
opts := &sync.SyncOptions{
31-
LocalRoot: rb.SyncRoot(),
32-
Paths: rb.Config().Sync.Paths,
33-
Include: includes,
34-
Exclude: rb.Config().Sync.Exclude,
31+
WorktreeRoot: rb.WorktreeRoot(),
32+
LocalRoot: rb.SyncRoot(),
33+
Paths: rb.Config().Sync.Paths,
34+
Include: includes,
35+
Exclude: rb.Config().Sync.Exclude,
3536

3637
RemotePath: rb.Config().Workspace.FilePath,
3738
Host: rb.WorkspaceClient().Config.Host,

cmd/sync/sync.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/databricks/cli/bundle/deploy/files"
1313
"github.com/databricks/cli/cmd/root"
1414
"github.com/databricks/cli/libs/flags"
15+
"github.com/databricks/cli/libs/git"
1516
"github.com/databricks/cli/libs/sync"
1617
"github.com/databricks/cli/libs/vfs"
1718
"github.com/spf13/cobra"
@@ -37,6 +38,7 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b *
3738

3839
opts.Full = f.full
3940
opts.PollInterval = f.interval
41+
opts.WorktreeRoot = b.WorktreeRoot
4042
return opts, nil
4143
}
4244

@@ -60,11 +62,21 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn
6062
}
6163
}
6264

65+
ctx := cmd.Context()
66+
client := root.WorkspaceClient(ctx)
67+
68+
localRoot := vfs.MustNew(args[0])
69+
info, err := git.FetchRepositoryInfo(ctx, localRoot, client)
70+
if err != nil {
71+
return nil, err
72+
}
73+
6374
opts := sync.SyncOptions{
64-
LocalRoot: vfs.MustNew(args[0]),
65-
Paths: []string{"."},
66-
Include: nil,
67-
Exclude: nil,
75+
WorktreeRoot: info.WorktreeRoot,
76+
LocalRoot: localRoot,
77+
Paths: []string{"."},
78+
Include: nil,
79+
Exclude: nil,
6880

6981
RemotePath: args[1],
7082
Full: f.full,
@@ -75,7 +87,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn
7587
// The sync code will automatically create this directory if it doesn't
7688
// exist and add it to the `.gitignore` file in the root.
7789
SnapshotBasePath: filepath.Join(args[0], ".databricks"),
78-
WorkspaceClient: root.WorkspaceClient(cmd.Context()),
90+
WorkspaceClient: client,
7991

8092
OutputHandler: outputHandler,
8193
}

libs/git/fileset.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ type FileSet struct {
1313
view *View
1414
}
1515

16-
// NewFileSet returns [FileSet] for the Git repository located at `root`.
17-
func NewFileSet(root vfs.Path, paths ...[]string) (*FileSet, error) {
16+
// NewFileSet returns [FileSet] for the directory `root` which is contained within Git repository located at `worktreeRoot`.
17+
func NewFileSet(worktreeRoot, root vfs.Path, paths ...[]string) (*FileSet, error) {
1818
fs := fileset.New(root, paths...)
19-
v, err := NewView(root)
19+
v, err := NewView(worktreeRoot, root)
2020
if err != nil {
2121
return nil, err
2222
}
@@ -27,6 +27,10 @@ func NewFileSet(root vfs.Path, paths ...[]string) (*FileSet, error) {
2727
}, nil
2828
}
2929

30+
func NewFileSetAtRoot(root vfs.Path, paths ...[]string) (*FileSet, error) {
31+
return NewFileSet(root, root, paths...)
32+
}
33+
3034
func (f *FileSet) IgnoreFile(file string) (bool, error) {
3135
return f.view.IgnoreFile(file)
3236
}

libs/git/fileset_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
func testFileSetAll(t *testing.T, root string) {
16-
fileSet, err := NewFileSet(vfs.MustNew(root))
16+
fileSet, err := NewFileSetAtRoot(vfs.MustNew(root))
1717
require.NoError(t, err)
1818
files, err := fileSet.Files()
1919
require.NoError(t, err)
@@ -35,7 +35,7 @@ func TestFileSetNonCleanRoot(t *testing.T) {
3535
// Test what happens if the root directory can be simplified.
3636
// Path simplification is done by most filepath functions.
3737
// This should yield the same result as above test.
38-
fileSet, err := NewFileSet(vfs.MustNew("./testdata/../testdata"))
38+
fileSet, err := NewFileSetAtRoot(vfs.MustNew("./testdata/../testdata"))
3939
require.NoError(t, err)
4040
files, err := fileSet.Files()
4141
require.NoError(t, err)
@@ -44,7 +44,7 @@ func TestFileSetNonCleanRoot(t *testing.T) {
4444

4545
func TestFileSetAddsCacheDirToGitIgnore(t *testing.T) {
4646
projectDir := t.TempDir()
47-
fileSet, err := NewFileSet(vfs.MustNew(projectDir))
47+
fileSet, err := NewFileSetAtRoot(vfs.MustNew(projectDir))
4848
require.NoError(t, err)
4949
fileSet.EnsureValidGitIgnoreExists()
5050

@@ -59,7 +59,7 @@ func TestFileSetDoesNotCacheDirToGitIgnoreIfAlreadyPresent(t *testing.T) {
5959
projectDir := t.TempDir()
6060
gitIgnorePath := filepath.Join(projectDir, ".gitignore")
6161

62-
fileSet, err := NewFileSet(vfs.MustNew(projectDir))
62+
fileSet, err := NewFileSetAtRoot(vfs.MustNew(projectDir))
6363
require.NoError(t, err)
6464
err = os.WriteFile(gitIgnorePath, []byte(".databricks"), 0o644)
6565
require.NoError(t, err)

libs/git/info.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package git
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io/fs"
7+
"net/http"
8+
"strings"
9+
10+
"github.com/databricks/cli/libs/dbr"
11+
"github.com/databricks/cli/libs/log"
12+
"github.com/databricks/cli/libs/vfs"
13+
"github.com/databricks/databricks-sdk-go"
14+
"github.com/databricks/databricks-sdk-go/client"
15+
)
16+
17+
type GitRepositoryInfo struct {
18+
OriginURL string
19+
LatestCommit string
20+
CurrentBranch string
21+
WorktreeRoot vfs.Path
22+
}
23+
24+
type gitInfo struct {
25+
Branch string `json:"branch"`
26+
HeadCommitID string `json:"head_commit_id"`
27+
Path string `json:"path"`
28+
URL string `json:"url"`
29+
}
30+
31+
type response struct {
32+
GitInfo *gitInfo `json:"git_info,omitempty"`
33+
}
34+
35+
func FetchRepositoryInfo(ctx context.Context, path vfs.Path, w *databricks.WorkspaceClient) (GitRepositoryInfo, error) {
36+
if strings.HasPrefix(path.Native(), "/Workspace/") && dbr.RunsOnRuntime(ctx) {
37+
return FetchRepositoryInfoAPI(ctx, path, w)
38+
} else {
39+
return FetchRepositoryInfoDotGit(ctx, path)
40+
}
41+
}
42+
43+
func FetchRepositoryInfoAPI(ctx context.Context, path vfs.Path, w *databricks.WorkspaceClient) (GitRepositoryInfo, error) {
44+
apiClient, err := client.New(w.Config)
45+
if err != nil {
46+
return GitRepositoryInfo{}, err
47+
}
48+
49+
var response response
50+
const apiEndpoint = "/api/2.0/workspace/get-status"
51+
52+
err = apiClient.Do(
53+
ctx,
54+
http.MethodGet,
55+
apiEndpoint,
56+
nil,
57+
map[string]string{
58+
"path": path.Native(),
59+
"return_git_info": "true",
60+
},
61+
&response,
62+
)
63+
64+
if err != nil {
65+
return GitRepositoryInfo{}, err
66+
}
67+
68+
// Check if GitInfo is present and extract relevant fields
69+
gi := response.GitInfo
70+
if gi == nil {
71+
log.Warnf(ctx, "Failed to load git info from %s", apiEndpoint)
72+
} else {
73+
fixedPath := fixResponsePath(gi.Path)
74+
return GitRepositoryInfo{
75+
OriginURL: gi.URL,
76+
LatestCommit: gi.HeadCommitID,
77+
CurrentBranch: gi.Branch,
78+
WorktreeRoot: vfs.MustNew(fixedPath),
79+
}, nil
80+
}
81+
82+
return GitRepositoryInfo{
83+
WorktreeRoot: path,
84+
}, nil
85+
}
86+
87+
func fixResponsePath(path string) string {
88+
if !strings.HasPrefix(path, "/Workspace/") {
89+
return "/Workspace/" + path
90+
}
91+
return path
92+
}
93+
94+
func FetchRepositoryInfoDotGit(ctx context.Context, path vfs.Path) (GitRepositoryInfo, error) {
95+
rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName)
96+
if err != nil {
97+
if !errors.Is(err, fs.ErrNotExist) {
98+
return GitRepositoryInfo{}, err
99+
}
100+
rootDir = path
101+
}
102+
103+
repo, err := NewRepository(rootDir)
104+
if err != nil {
105+
return GitRepositoryInfo{}, err
106+
}
107+
108+
branch, err := repo.CurrentBranch()
109+
if err != nil {
110+
return GitRepositoryInfo{}, nil
111+
}
112+
113+
commit, err := repo.LatestCommit()
114+
if err != nil {
115+
return GitRepositoryInfo{}, nil
116+
}
117+
118+
return GitRepositoryInfo{
119+
OriginURL: repo.OriginUrl(),
120+
LatestCommit: commit,
121+
CurrentBranch: branch,
122+
WorktreeRoot: rootDir,
123+
}, nil
124+
}

libs/git/repository.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package git
22

33
import (
4-
"errors"
54
"fmt"
6-
"io/fs"
75
"net/url"
86
"path"
97
"path/filepath"
@@ -204,17 +202,7 @@ func (r *Repository) Ignore(relPath string) (bool, error) {
204202
return false, nil
205203
}
206204

207-
func NewRepository(path vfs.Path) (*Repository, error) {
208-
rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName)
209-
if err != nil {
210-
if !errors.Is(err, fs.ErrNotExist) {
211-
return nil, err
212-
}
213-
// Cannot find `.git` directory.
214-
// Treat the specified path as a potential repository root checkout.
215-
rootDir = path
216-
}
217-
205+
func NewRepository(rootDir vfs.Path) (*Repository, error) {
218206
// Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository.
219207
// If it isn't a real repository, they'll point to the (non-existent) `.git` directory.
220208
gitDir, gitCommonDir, err := resolveGitDirs(rootDir)

0 commit comments

Comments
 (0)