diff --git a/bundle/config/mutator/load_git_details.go b/bundle/config/mutator/load_git_details.go index a325da549..45e88f6ef 100644 --- a/bundle/config/mutator/load_git_details.go +++ b/bundle/config/mutator/load_git_details.go @@ -21,7 +21,7 @@ func (m *loadGitDetails) Name() string { func (m *loadGitDetails) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - info, err := git.FetchRepositoryInfo(ctx, b.BundleRoot, b.WorkspaceClient()) + info, err := git.FetchRepositoryInfo(ctx, b.BundleRoot.Native(), b.WorkspaceClient()) if err != nil { diags = append(diags, diag.WarningFromErr(err)...) } diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 220f0939f..e6d529101 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -67,7 +67,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn client := root.WorkspaceClient(ctx) localRoot := vfs.MustNew(args[0]) - info, err := git.FetchRepositoryInfo(ctx, localRoot, client) + info, err := git.FetchRepositoryInfo(ctx, localRoot.Native(), client) if err != nil { log.Warnf(ctx, "Failed to read git info: %s", err) diff --git a/internal/git_fetch_test.go b/internal/git_fetch_test.go index 0623f5cc5..2088d23e7 100644 --- a/internal/git_fetch_test.go +++ b/internal/git_fetch_test.go @@ -9,7 +9,6 @@ import ( "github.com/databricks/cli/internal/acc" "github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/git" - "github.com/databricks/cli/libs/vfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -55,7 +54,7 @@ func TestAccFetchRepositoryInfoAPI_FromRepo(t *testing.T) { targetPath, } { t.Run(inputPath, func(t *testing.T) { - info, err := git.FetchRepositoryInfo(ctx, vfs.MustNew(inputPath), wt.W) + info, err := git.FetchRepositoryInfo(ctx, inputPath, wt.W) assert.NoError(t, err) assertFullGitInfo(t, targetPath, info) }) @@ -97,7 +96,7 @@ func TestAccFetchRepositoryInfoAPI_FromNonRepo(t *testing.T) { for _, test := range tests { t.Run(test.input+" <==> "+test.msg, func(t *testing.T) { - info, err := git.FetchRepositoryInfo(ctx, vfs.MustNew(test.input), wt.W) + info, err := git.FetchRepositoryInfo(ctx, test.input, wt.W) if test.msg == "" { assert.NoError(t, err) } else { @@ -119,7 +118,7 @@ func TestAccFetchRepositoryInfoDotGit_FromGitRepo(t *testing.T) { repo, } { t.Run(inputPath, func(t *testing.T) { - info, err := git.FetchRepositoryInfo(ctx, vfs.MustNew(inputPath), wt.W) + info, err := git.FetchRepositoryInfo(ctx, inputPath, wt.W) assert.NoError(t, err) assertFullGitInfo(t, repo, info) }) @@ -151,7 +150,7 @@ func TestAccFetchRepositoryInfoDotGit_FromNonGitRepo(t *testing.T) { for _, input := range tests { t.Run(input, func(t *testing.T) { - info, err := git.FetchRepositoryInfo(ctx, vfs.MustNew(input), wt.W) + info, err := git.FetchRepositoryInfo(ctx, input, wt.W) assert.NoError(t, err) assertEmptyGitInfo(t, info) }) @@ -167,7 +166,7 @@ func TestAccFetchRepositoryInfoDotGit_FromBrokenGitRepo(t *testing.T) { require.NoError(t, os.MkdirAll(path, 0700)) require.NoError(t, os.WriteFile(root+"/.git", []byte(""), 0000)) - info, err := git.FetchRepositoryInfo(ctx, vfs.MustNew(path), wt.W) + info, err := git.FetchRepositoryInfo(ctx, path, wt.W) assert.NoError(t, err) assertSparseGitInfo(t, root, info) } diff --git a/libs/git/info.go b/libs/git/info.go index 5df69f7ea..3b03835f4 100644 --- a/libs/git/info.go +++ b/libs/git/info.go @@ -5,7 +5,9 @@ import ( "errors" "io/fs" "net/http" + "os" "path" + "path/filepath" "strings" "github.com/databricks/cli/libs/dbr" @@ -48,18 +50,18 @@ type response struct { // will be logged as warnings, GitRepositoryInfo is guaranteed to have non-empty WorktreeRoot and corresponding GuessedWorktreeRoot // and other fields on best effort basis. The err will be nil. // - In successful case, all fields are set to proper git repository metadata. -func FetchRepositoryInfo(ctx context.Context, path vfs.Path, w *databricks.WorkspaceClient) (GitRepositoryInfo, error) { - if strings.HasPrefix(path.Native(), "/Workspace/") && dbr.RunsOnRuntime(ctx) { +func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.WorkspaceClient) (GitRepositoryInfo, error) { + if strings.HasPrefix(path, "/Workspace/") && dbr.RunsOnRuntime(ctx) { return fetchRepositoryInfoAPI(ctx, path, w) } else { return fetchRepositoryInfoDotGit(ctx, path) } } -func fetchRepositoryInfoAPI(ctx context.Context, path vfs.Path, w *databricks.WorkspaceClient) (GitRepositoryInfo, error) { +func fetchRepositoryInfoAPI(ctx context.Context, path string, w *databricks.WorkspaceClient) (GitRepositoryInfo, error) { result := GitRepositoryInfo{ // For convenience, this field defaults to input path, even if err is also set. - GuessedWorktreeRoot: path, + GuessedWorktreeRoot: vfs.MustNew(path), } apiClient, err := client.New(w.Config) @@ -76,7 +78,7 @@ func fetchRepositoryInfoAPI(ctx context.Context, path vfs.Path, w *databricks.Wo apiEndpoint, nil, map[string]string{ - "path": path.Native(), + "path": path, "return_git_info": "true", }, &response, @@ -94,6 +96,7 @@ func fetchRepositoryInfoAPI(ctx context.Context, path vfs.Path, w *databricks.Wo result.LatestCommit = gi.HeadCommitID result.CurrentBranch = gi.Branch result.WorktreeRoot = fixedPath + // Note, this won't work on Windows since vfs.MustNew will call filepath.Abs result.GuessedWorktreeRoot = vfs.MustNew(fixedPath) } else { log.Warnf(ctx, "Failed to load git info from %s", apiEndpoint) @@ -109,23 +112,20 @@ func ensureWorkspacePrefix(p string) string { return p } -func fetchRepositoryInfoDotGit(ctx context.Context, path vfs.Path) (GitRepositoryInfo, error) { +func fetchRepositoryInfoDotGit(ctx context.Context, path string) (GitRepositoryInfo, error) { result := GitRepositoryInfo{ - GuessedWorktreeRoot: path, + GuessedWorktreeRoot: vfs.MustNew(path), } - rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName) - if err != nil || rootDir == nil { - if errors.Is(err, fs.ErrNotExist) { - return result, nil - } + rootDir, err := findLeafInTree(path, GitDirectoryName) + if rootDir == "" { return result, err } - result.WorktreeRoot = rootDir.Native() - result.GuessedWorktreeRoot = rootDir + result.WorktreeRoot = rootDir + result.GuessedWorktreeRoot = vfs.MustNew(rootDir) - repo, err := NewRepository(rootDir) + repo, err := NewRepository(result.GuessedWorktreeRoot) if err != nil { log.Warnf(ctx, "failed to read .git: %s", err) @@ -147,3 +147,28 @@ func fetchRepositoryInfoDotGit(ctx context.Context, path vfs.Path) (GitRepositor return result, nil } + +func findLeafInTree(p string, leafName string) (string, error) { + var err error + for i := 0; i < 10000; i++ { + _, err = os.Stat(filepath.Join(p, leafName)) + + if err == nil { + // found .git in p + return p, nil + } + + // ErrNotExist means we continue traversal up the tree. + if errors.Is(err, fs.ErrNotExist) { + parent := filepath.Dir(p) + if parent == p { + return "", nil + } + p = parent + continue + } + break + } + + return "", err +}