diff --git a/acceptance/bundle/debug/fetch-repository-info/databricks.yml b/acceptance/bundle/debug/fetch-repository-info/databricks.yml new file mode 100644 index 00000000000..576d7a9ef25 --- /dev/null +++ b/acceptance/bundle/debug/fetch-repository-info/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: test-bundle diff --git a/acceptance/bundle/debug/fetch-repository-info/out.test.toml b/acceptance/bundle/debug/fetch-repository-info/out.test.toml new file mode 100644 index 00000000000..2a13818c13f --- /dev/null +++ b/acceptance/bundle/debug/fetch-repository-info/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/debug/fetch-repository-info/output.txt b/acceptance/bundle/debug/fetch-repository-info/output.txt new file mode 100644 index 00000000000..2f055139a74 --- /dev/null +++ b/acceptance/bundle/debug/fetch-repository-info/output.txt @@ -0,0 +1,47 @@ + +=== from the worktree root + +>>> [CLI] bundle debug fetch-repository-info --output json +{ + "worktree_root": "[TEST_TMP_DIR]", + "current_branch": "main", + "latest_commit": "[COMMIT_SHA]", + "origin_url": "https://github.com/databricks/cli.test" +} +=== from a subdirectory (resolves to the same worktree root) + +>>> [CLI] bundle debug fetch-repository-info --path a/b/c --output json +{ + "worktree_root": "[TEST_TMP_DIR]", + "current_branch": "main", + "latest_commit": "[COMMIT_SHA]", + "origin_url": "https://github.com/databricks/cli.test" +} +=== outside a git repository + +>>> [CLI] bundle debug fetch-repository-info --path a/b/c --output json +{ + "worktree_root": "", + "current_branch": "", + "latest_commit": "", + "origin_url": "" +} +=== a path that does not exist + +>>> [CLI] bundle debug fetch-repository-info --path does-not-exist --output json +{ + "worktree_root": "", + "current_branch": "", + "latest_commit": "", + "origin_url": "" +} +=== a broken .git + +>>> [CLI] bundle debug fetch-repository-info --path a/b/c --output json +Warn: failed to read .git: expected "[TEST_TMP_DIR]/.git" to contain a line with "gitdir: [...]" +{ + "worktree_root": "[TEST_TMP_DIR]", + "current_branch": "", + "latest_commit": "", + "origin_url": "" +} \ No newline at end of file diff --git a/acceptance/bundle/debug/fetch-repository-info/script b/acceptance/bundle/debug/fetch-repository-info/script new file mode 100644 index 00000000000..612aff1962c --- /dev/null +++ b/acceptance/bundle/debug/fetch-repository-info/script @@ -0,0 +1,26 @@ +# A git repository: all metadata resolves, from the root and from a subdirectory. +git-repo-init +git remote add origin https://github.com/databricks/cli.test +mkdir -p a/b/c + +title "from the worktree root\n" +trace $CLI bundle debug fetch-repository-info --output json + +title "from a subdirectory (resolves to the same worktree root)\n" +trace $CLI bundle debug fetch-repository-info --path a/b/c --output json + +rm -fr .git + +# Not a git repository: every field is empty and it is not an error. +title "outside a git repository\n" +trace $CLI bundle debug fetch-repository-info --path a/b/c --output json + +title "a path that does not exist\n" +trace $CLI bundle debug fetch-repository-info --path does-not-exist --output json + +# A .git that is a file rather than a directory: the worktree root is still +# found, but no metadata can be read from it. +title "a broken .git\n" +touch .git +trace $CLI bundle debug fetch-repository-info --path a/b/c --output json +rm -f .git diff --git a/acceptance/bundle/debug/fetch-repository-info/test.toml b/acceptance/bundle/debug/fetch-repository-info/test.toml new file mode 100644 index 00000000000..ebf58dfdcaf --- /dev/null +++ b/acceptance/bundle/debug/fetch-repository-info/test.toml @@ -0,0 +1,6 @@ +Cloud = true + +# The commit is created by the test, so its sha differs on every run. +[[Repls]] +Old = '\b[0-9a-f]{40}\b' +New = '[COMMIT_SHA]' diff --git a/cmd/bundle/debug.go b/cmd/bundle/debug.go index c62c75080cc..a07da4c4fda 100644 --- a/cmd/bundle/debug.go +++ b/cmd/bundle/debug.go @@ -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 } diff --git a/cmd/bundle/debug/fetch_repository_info.go b/cmd/bundle/debug/fetch_repository_info.go new file mode 100644 index 00000000000..85b1c0bc49b --- /dev/null +++ b/cmd/bundle/debug/fetch_repository_info.go @@ -0,0 +1,75 @@ +package debug + +import ( + "encoding/json" + "fmt" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "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.FetchRepositoryInfo] 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. +func NewFetchRepositoryInfoCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "fetch-repository-info", + Short: "Report the git metadata resolved 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() + w := cmdctx.WorkspaceClient(ctx) + + info, err := git.FetchRepositoryInfo(ctx, path, w) + if err != nil { + return err + } + + out := repositoryInfoOutput{ + WorktreeRoot: info.WorktreeRoot, + CurrentBranch: info.CurrentBranch, + LatestCommit: info.LatestCommit, + OriginURL: info.OriginURL, + } + + switch root.OutputType(cmd) { + case flags.OutputText: + cmdio.LogString(ctx, "worktree_root: "+out.WorktreeRoot) + cmdio.LogString(ctx, "current_branch: "+out.CurrentBranch) + cmdio.LogString(ctx, "latest_commit: "+out.LatestCommit) + cmdio.LogString(ctx, "origin_url: "+out.OriginURL) + case flags.OutputJSON: + buf, err := json.MarshalIndent(out, "", " ") + if err != nil { + return err + } + _, _ = cmd.OutOrStdout().Write(buf) + default: + return fmt.Errorf("unknown output type %s", root.OutputType(cmd)) + } + + return nil + } + + return cmd +} diff --git a/integration/libs/git/git_fetch_test.go b/integration/libs/git/git_fetch_test.go index 094389284cb..d3507bff5e5 100644 --- a/integration/libs/git/git_fetch_test.go +++ b/integration/libs/git/git_fetch_test.go @@ -1,10 +1,7 @@ package git_test import ( - "os" - "os/exec" "path" - "path/filepath" "strings" "testing" @@ -104,72 +101,3 @@ func TestFetchRepositoryInfoAPI_FromNonRepo(t *testing.T) { }) } } - -func TestFetchRepositoryInfoDotGit_FromGitRepo(t *testing.T) { - ctx, wt := acc.WorkspaceTest(t) - - repo := cloneRepoLocally(t, examplesRepoUrl) - - for _, tc := range []struct { - name string - input string - }{ - {"subdir", filepath.Join(repo, "knowledge_base/dashboard_nyc_taxi")}, - {"root", repo}, - } { - t.Run(tc.name, func(t *testing.T) { - info, err := git.FetchRepositoryInfo(ctx, tc.input, wt.W) - assert.NoError(t, err) - assertFullGitInfo(t, repo, info) - }) - } -} - -func cloneRepoLocally(t *testing.T, repoUrl string) string { - tempDir := t.TempDir() - localRoot := filepath.Join(tempDir, "repo") - - cmd := exec.Command("git", "clone", "--depth=1", examplesRepoUrl, localRoot) - err := cmd.Run() - require.NoError(t, err) - return localRoot -} - -func TestFetchRepositoryInfoDotGit_FromNonGitRepo(t *testing.T) { - ctx, wt := acc.WorkspaceTest(t) - - tempDir := t.TempDir() - root := filepath.Join(tempDir, "repo") - require.NoError(t, os.MkdirAll(filepath.Join(root, "a/b/c"), 0o700)) - - tests := []struct { - name string - input string - }{ - {"subdir", filepath.Join(root, "a/b/c")}, - {"root", root}, - {"non-existent", filepath.Join(root, "/non-existent")}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - info, err := git.FetchRepositoryInfo(ctx, test.input, wt.W) - assert.NoError(t, err) - assertEmptyGitInfo(t, info) - }) - } -} - -func TestFetchRepositoryInfoDotGit_FromBrokenGitRepo(t *testing.T) { - ctx, wt := acc.WorkspaceTest(t) - - tempDir := t.TempDir() - root := filepath.Join(tempDir, "repo") - path := filepath.Join(root, "a/b/c") - require.NoError(t, os.MkdirAll(path, 0o700)) - require.NoError(t, os.WriteFile(filepath.Join(root, ".git"), []byte(""), 0o000)) - - info, err := git.FetchRepositoryInfo(ctx, path, wt.W) - assert.NoError(t, err) - assertSparseGitInfo(t, root, info) -}