mirror of https://github.com/databricks/cli.git
Properly read Git metadata when running inside workspace (#1945)
## Changes Since there is no .git directory in Workspace file system, we need to make an API call to api/2.0/workspace/get-status?return_git_info=true to fetch git the root of the repo, current branch, commit and origin. Added new function FetchRepositoryInfo that either looks up and parses .git or calls remote API depending on env. Refactor Repository/View/FileSet to accept repository root rather than calculate it. This helps because: - Repository is currently created in multiple places and finding the repository root is becoming relatively expensive (API call needed). - Repository/FileSet/View do not have access to current Bundle which is where WorkplaceClient is stored. ## Tests - Tested manually by running "bundle validate --json" inside web terminal within Databricks env. - Added integration tests for the new API. --------- Co-authored-by: Andrew Nester <andrew.nester@databricks.com> Co-authored-by: Pieter Noordhuis <pieter.noordhuis@databricks.com>
This commit is contained in:
parent
0a36681bef
commit
0ad790e468
|
@ -48,6 +48,10 @@ type Bundle struct {
|
||||||
// Exclusively use this field for filesystem operations.
|
// Exclusively use this field for filesystem operations.
|
||||||
SyncRoot vfs.Path
|
SyncRoot vfs.Path
|
||||||
|
|
||||||
|
// Path to the root of git worktree containing the bundle.
|
||||||
|
// https://git-scm.com/docs/git-worktree
|
||||||
|
WorktreeRoot vfs.Path
|
||||||
|
|
||||||
// Config contains the bundle configuration.
|
// Config contains the bundle configuration.
|
||||||
// It is loaded from the bundle configuration files and mutators may update it.
|
// It is loaded from the bundle configuration files and mutators may update it.
|
||||||
Config config.Root
|
Config config.Root
|
||||||
|
|
|
@ -32,6 +32,10 @@ func (r ReadOnlyBundle) SyncRoot() vfs.Path {
|
||||||
return r.b.SyncRoot
|
return r.b.SyncRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r ReadOnlyBundle) WorktreeRoot() vfs.Path {
|
||||||
|
return r.b.WorktreeRoot
|
||||||
|
}
|
||||||
|
|
||||||
func (r ReadOnlyBundle) WorkspaceClient() *databricks.WorkspaceClient {
|
func (r ReadOnlyBundle) WorkspaceClient() *databricks.WorkspaceClient {
|
||||||
return r.b.WorkspaceClient()
|
return r.b.WorkspaceClient()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/libs/diag"
|
"github.com/databricks/cli/libs/diag"
|
||||||
"github.com/databricks/cli/libs/git"
|
"github.com/databricks/cli/libs/git"
|
||||||
"github.com/databricks/cli/libs/log"
|
"github.com/databricks/cli/libs/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loadGitDetails struct{}
|
type loadGitDetails struct{}
|
||||||
|
@ -21,45 +21,40 @@ func (m *loadGitDetails) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *loadGitDetails) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
func (m *loadGitDetails) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
// Load relevant git repository
|
var diags diag.Diagnostics
|
||||||
repo, err := git.NewRepository(b.BundleRoot)
|
info, err := git.FetchRepositoryInfo(ctx, b.BundleRoot.Native(), b.WorkspaceClient())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.FromErr(err)
|
diags = append(diags, diag.WarningFromErr(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read branch name of current checkout
|
if info.WorktreeRoot == "" {
|
||||||
branch, err := repo.CurrentBranch()
|
b.WorktreeRoot = b.BundleRoot
|
||||||
if err == nil {
|
|
||||||
b.Config.Bundle.Git.ActualBranch = branch
|
|
||||||
if b.Config.Bundle.Git.Branch == "" {
|
|
||||||
// Only load branch if there's no user defined value
|
|
||||||
b.Config.Bundle.Git.Inferred = true
|
|
||||||
b.Config.Bundle.Git.Branch = branch
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.Warnf(ctx, "failed to load current branch: %s", err)
|
b.WorktreeRoot = vfs.MustNew(info.WorktreeRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Config.Bundle.Git.ActualBranch = info.CurrentBranch
|
||||||
|
if b.Config.Bundle.Git.Branch == "" {
|
||||||
|
// Only load branch if there's no user defined value
|
||||||
|
b.Config.Bundle.Git.Inferred = true
|
||||||
|
b.Config.Bundle.Git.Branch = info.CurrentBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
// load commit hash if undefined
|
// load commit hash if undefined
|
||||||
if b.Config.Bundle.Git.Commit == "" {
|
if b.Config.Bundle.Git.Commit == "" {
|
||||||
commit, err := repo.LatestCommit()
|
b.Config.Bundle.Git.Commit = info.LatestCommit
|
||||||
if err != nil {
|
|
||||||
log.Warnf(ctx, "failed to load latest commit: %s", err)
|
|
||||||
} else {
|
|
||||||
b.Config.Bundle.Git.Commit = commit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// load origin url if undefined
|
|
||||||
if b.Config.Bundle.Git.OriginURL == "" {
|
|
||||||
remoteUrl := repo.OriginUrl()
|
|
||||||
b.Config.Bundle.Git.OriginURL = remoteUrl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// repo.Root() returns the absolute path of the repo
|
// load origin url if undefined
|
||||||
relBundlePath, err := filepath.Rel(repo.Root(), b.BundleRoot.Native())
|
if b.Config.Bundle.Git.OriginURL == "" {
|
||||||
if err != nil {
|
b.Config.Bundle.Git.OriginURL = info.OriginURL
|
||||||
return diag.FromErr(err)
|
|
||||||
}
|
}
|
||||||
b.Config.Bundle.Git.BundleRootPath = filepath.ToSlash(relBundlePath)
|
|
||||||
return nil
|
relBundlePath, err := filepath.Rel(b.WorktreeRoot.Native(), b.BundleRoot.Native())
|
||||||
|
if err != nil {
|
||||||
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
} else {
|
||||||
|
b.Config.Bundle.Git.BundleRootPath = filepath.ToSlash(relBundlePath)
|
||||||
|
}
|
||||||
|
return diags
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ func setupBundleForFilesToSyncTest(t *testing.T) *bundle.Bundle {
|
||||||
BundleRoot: vfs.MustNew(dir),
|
BundleRoot: vfs.MustNew(dir),
|
||||||
SyncRootPath: dir,
|
SyncRootPath: dir,
|
||||||
SyncRoot: vfs.MustNew(dir),
|
SyncRoot: vfs.MustNew(dir),
|
||||||
|
WorktreeRoot: vfs.MustNew(dir),
|
||||||
Config: config.Root{
|
Config: config.Root{
|
||||||
Bundle: config.Bundle{
|
Bundle: config.Bundle{
|
||||||
Target: "default",
|
Target: "default",
|
||||||
|
|
|
@ -28,10 +28,11 @@ func GetSyncOptions(ctx context.Context, rb bundle.ReadOnlyBundle) (*sync.SyncOp
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &sync.SyncOptions{
|
opts := &sync.SyncOptions{
|
||||||
LocalRoot: rb.SyncRoot(),
|
WorktreeRoot: rb.WorktreeRoot(),
|
||||||
Paths: rb.Config().Sync.Paths,
|
LocalRoot: rb.SyncRoot(),
|
||||||
Include: includes,
|
Paths: rb.Config().Sync.Paths,
|
||||||
Exclude: rb.Config().Sync.Exclude,
|
Include: includes,
|
||||||
|
Exclude: rb.Config().Sync.Exclude,
|
||||||
|
|
||||||
RemotePath: rb.Config().Workspace.FilePath,
|
RemotePath: rb.Config().Workspace.FilePath,
|
||||||
Host: rb.WorkspaceClient().Config.Host,
|
Host: rb.WorkspaceClient().Config.Host,
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"github.com/databricks/cli/bundle/deploy/files"
|
"github.com/databricks/cli/bundle/deploy/files"
|
||||||
"github.com/databricks/cli/cmd/root"
|
"github.com/databricks/cli/cmd/root"
|
||||||
"github.com/databricks/cli/libs/flags"
|
"github.com/databricks/cli/libs/flags"
|
||||||
|
"github.com/databricks/cli/libs/git"
|
||||||
|
"github.com/databricks/cli/libs/log"
|
||||||
"github.com/databricks/cli/libs/sync"
|
"github.com/databricks/cli/libs/sync"
|
||||||
"github.com/databricks/cli/libs/vfs"
|
"github.com/databricks/cli/libs/vfs"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -37,6 +39,7 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b *
|
||||||
|
|
||||||
opts.Full = f.full
|
opts.Full = f.full
|
||||||
opts.PollInterval = f.interval
|
opts.PollInterval = f.interval
|
||||||
|
opts.WorktreeRoot = b.WorktreeRoot
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,11 +63,30 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := cmd.Context()
|
||||||
|
client := root.WorkspaceClient(ctx)
|
||||||
|
|
||||||
|
localRoot := vfs.MustNew(args[0])
|
||||||
|
info, err := git.FetchRepositoryInfo(ctx, localRoot.Native(), client)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(ctx, "Failed to read git info: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var worktreeRoot vfs.Path
|
||||||
|
|
||||||
|
if info.WorktreeRoot == "" {
|
||||||
|
worktreeRoot = localRoot
|
||||||
|
} else {
|
||||||
|
worktreeRoot = vfs.MustNew(info.WorktreeRoot)
|
||||||
|
}
|
||||||
|
|
||||||
opts := sync.SyncOptions{
|
opts := sync.SyncOptions{
|
||||||
LocalRoot: vfs.MustNew(args[0]),
|
WorktreeRoot: worktreeRoot,
|
||||||
Paths: []string{"."},
|
LocalRoot: localRoot,
|
||||||
Include: nil,
|
Paths: []string{"."},
|
||||||
Exclude: nil,
|
Include: nil,
|
||||||
|
Exclude: nil,
|
||||||
|
|
||||||
RemotePath: args[1],
|
RemotePath: args[1],
|
||||||
Full: f.full,
|
Full: f.full,
|
||||||
|
@ -75,7 +97,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn
|
||||||
// The sync code will automatically create this directory if it doesn't
|
// The sync code will automatically create this directory if it doesn't
|
||||||
// exist and add it to the `.gitignore` file in the root.
|
// exist and add it to the `.gitignore` file in the root.
|
||||||
SnapshotBasePath: filepath.Join(args[0], ".databricks"),
|
SnapshotBasePath: filepath.Join(args[0], ".databricks"),
|
||||||
WorkspaceClient: root.WorkspaceClient(cmd.Context()),
|
WorkspaceClient: client,
|
||||||
|
|
||||||
OutputHandler: outputHandler,
|
OutputHandler: outputHandler,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/internal/acc"
|
||||||
|
"github.com/databricks/cli/libs/dbr"
|
||||||
|
"github.com/databricks/cli/libs/git"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const examplesRepoUrl = "https://github.com/databricks/bundle-examples"
|
||||||
|
const examplesRepoProvider = "gitHub"
|
||||||
|
|
||||||
|
func assertFullGitInfo(t *testing.T, expectedRoot string, info git.RepositoryInfo) {
|
||||||
|
assert.Equal(t, "main", info.CurrentBranch)
|
||||||
|
assert.NotEmpty(t, info.LatestCommit)
|
||||||
|
assert.Equal(t, examplesRepoUrl, info.OriginURL)
|
||||||
|
assert.Equal(t, expectedRoot, info.WorktreeRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEmptyGitInfo(t *testing.T, info git.RepositoryInfo) {
|
||||||
|
assertSparseGitInfo(t, "", info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSparseGitInfo(t *testing.T, expectedRoot string, info git.RepositoryInfo) {
|
||||||
|
assert.Equal(t, "", info.CurrentBranch)
|
||||||
|
assert.Equal(t, "", info.LatestCommit)
|
||||||
|
assert.Equal(t, "", info.OriginURL)
|
||||||
|
assert.Equal(t, expectedRoot, info.WorktreeRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFetchRepositoryInfoAPI_FromRepo(t *testing.T) {
|
||||||
|
ctx, wt := acc.WorkspaceTest(t)
|
||||||
|
me, err := wt.W.CurrentUser.Me(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
targetPath := acc.RandomName(path.Join("/Workspace/Users", me.UserName, "/testing-clone-bundle-examples-"))
|
||||||
|
stdout, stderr := RequireSuccessfulRun(t, "repos", "create", examplesRepoUrl, examplesRepoProvider, "--path", targetPath)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
RequireSuccessfulRun(t, "repos", "delete", targetPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Empty(t, stderr.String())
|
||||||
|
assert.NotEmpty(t, stdout.String())
|
||||||
|
ctx = dbr.MockRuntime(ctx, true)
|
||||||
|
|
||||||
|
for _, inputPath := range []string{
|
||||||
|
path.Join(targetPath, "knowledge_base/dashboard_nyc_taxi"),
|
||||||
|
targetPath,
|
||||||
|
} {
|
||||||
|
t.Run(inputPath, func(t *testing.T) {
|
||||||
|
info, err := git.FetchRepositoryInfo(ctx, inputPath, wt.W)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertFullGitInfo(t, targetPath, info)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFetchRepositoryInfoAPI_FromNonRepo(t *testing.T) {
|
||||||
|
ctx, wt := acc.WorkspaceTest(t)
|
||||||
|
me, err := wt.W.CurrentUser.Me(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rootPath := acc.RandomName(path.Join("/Workspace/Users", me.UserName, "testing-nonrepo-"))
|
||||||
|
_, stderr := RequireSuccessfulRun(t, "workspace", "mkdirs", path.Join(rootPath, "a/b/c"))
|
||||||
|
t.Cleanup(func() {
|
||||||
|
RequireSuccessfulRun(t, "workspace", "delete", "--recursive", rootPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Empty(t, stderr.String())
|
||||||
|
ctx = dbr.MockRuntime(ctx, true)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: path.Join(rootPath, "a/b/c"),
|
||||||
|
msg: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: rootPath,
|
||||||
|
msg: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: path.Join(rootPath, "/non-existent"),
|
||||||
|
msg: "doesn't exist",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.input+" <==> "+test.msg, func(t *testing.T) {
|
||||||
|
info, err := git.FetchRepositoryInfo(ctx, test.input, wt.W)
|
||||||
|
if test.msg == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), test.msg)
|
||||||
|
}
|
||||||
|
assertEmptyGitInfo(t, info)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFetchRepositoryInfoDotGit_FromGitRepo(t *testing.T) {
|
||||||
|
ctx, wt := acc.WorkspaceTest(t)
|
||||||
|
|
||||||
|
repo := cloneRepoLocally(t, examplesRepoUrl)
|
||||||
|
|
||||||
|
for _, inputPath := range []string{
|
||||||
|
filepath.Join(repo, "knowledge_base/dashboard_nyc_taxi"),
|
||||||
|
repo,
|
||||||
|
} {
|
||||||
|
t.Run(inputPath, func(t *testing.T) {
|
||||||
|
info, err := git.FetchRepositoryInfo(ctx, inputPath, 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 TestAccFetchRepositoryInfoDotGit_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"), 0700))
|
||||||
|
|
||||||
|
tests := []string{
|
||||||
|
filepath.Join(root, "a/b/c"),
|
||||||
|
root,
|
||||||
|
filepath.Join(root, "/non-existent"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, input := range tests {
|
||||||
|
t.Run(input, func(t *testing.T) {
|
||||||
|
info, err := git.FetchRepositoryInfo(ctx, input, wt.W)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertEmptyGitInfo(t, info)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccFetchRepositoryInfoDotGit_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, 0700))
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(root, ".git"), []byte(""), 0000))
|
||||||
|
|
||||||
|
info, err := git.FetchRepositoryInfo(ctx, path, wt.W)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertSparseGitInfo(t, root, info)
|
||||||
|
}
|
|
@ -53,6 +53,19 @@ func FromErr(err error) Diagnostics {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromErr returns a new warning diagnostic from the specified error, if any.
|
||||||
|
func WarningFromErr(err error) Diagnostics {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []Diagnostic{
|
||||||
|
{
|
||||||
|
Severity: Warning,
|
||||||
|
Summary: err.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Warningf creates a new warning diagnostic.
|
// Warningf creates a new warning diagnostic.
|
||||||
func Warningf(format string, args ...any) Diagnostics {
|
func Warningf(format string, args ...any) Diagnostics {
|
||||||
return []Diagnostic{
|
return []Diagnostic{
|
||||||
|
|
|
@ -13,10 +13,10 @@ type FileSet struct {
|
||||||
view *View
|
view *View
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSet returns [FileSet] for the Git repository located at `root`.
|
// NewFileSet returns [FileSet] for the directory `root` which is contained within Git worktree located at `worktreeRoot`.
|
||||||
func NewFileSet(root vfs.Path, paths ...[]string) (*FileSet, error) {
|
func NewFileSet(worktreeRoot, root vfs.Path, paths ...[]string) (*FileSet, error) {
|
||||||
fs := fileset.New(root, paths...)
|
fs := fileset.New(root, paths...)
|
||||||
v, err := NewView(root)
|
v, err := NewView(worktreeRoot, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,10 @@ func NewFileSet(root vfs.Path, paths ...[]string) (*FileSet, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFileSetAtRoot(root vfs.Path, paths ...[]string) (*FileSet, error) {
|
||||||
|
return NewFileSet(root, root, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FileSet) IgnoreFile(file string) (bool, error) {
|
func (f *FileSet) IgnoreFile(file string) (bool, error) {
|
||||||
return f.view.IgnoreFile(file)
|
return f.view.IgnoreFile(file)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testFileSetAll(t *testing.T, root string) {
|
func testFileSetAll(t *testing.T, worktreeRoot, root string) {
|
||||||
fileSet, err := NewFileSet(vfs.MustNew(root))
|
fileSet, err := NewFileSet(vfs.MustNew(worktreeRoot), vfs.MustNew(root))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
files, err := fileSet.Files()
|
files, err := fileSet.Files()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -24,18 +24,28 @@ func testFileSetAll(t *testing.T, root string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSetListAllInRepo(t *testing.T) {
|
func TestFileSetListAllInRepo(t *testing.T) {
|
||||||
testFileSetAll(t, "./testdata")
|
testFileSetAll(t, "./testdata", "./testdata")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileSetListAllInRepoDifferentRoot(t *testing.T) {
|
||||||
|
testFileSetAll(t, ".", "./testdata")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSetListAllInTempDir(t *testing.T) {
|
func TestFileSetListAllInTempDir(t *testing.T) {
|
||||||
testFileSetAll(t, copyTestdata(t, "./testdata"))
|
dir := copyTestdata(t, "./testdata")
|
||||||
|
testFileSetAll(t, dir, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileSetListAllInTempDirDifferentRoot(t *testing.T) {
|
||||||
|
dir := copyTestdata(t, "./testdata")
|
||||||
|
testFileSetAll(t, filepath.Dir(dir), dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSetNonCleanRoot(t *testing.T) {
|
func TestFileSetNonCleanRoot(t *testing.T) {
|
||||||
// Test what happens if the root directory can be simplified.
|
// Test what happens if the root directory can be simplified.
|
||||||
// Path simplification is done by most filepath functions.
|
// Path simplification is done by most filepath functions.
|
||||||
// This should yield the same result as above test.
|
// This should yield the same result as above test.
|
||||||
fileSet, err := NewFileSet(vfs.MustNew("./testdata/../testdata"))
|
fileSet, err := NewFileSetAtRoot(vfs.MustNew("./testdata/../testdata"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
files, err := fileSet.Files()
|
files, err := fileSet.Files()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -44,7 +54,7 @@ func TestFileSetNonCleanRoot(t *testing.T) {
|
||||||
|
|
||||||
func TestFileSetAddsCacheDirToGitIgnore(t *testing.T) {
|
func TestFileSetAddsCacheDirToGitIgnore(t *testing.T) {
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fileSet.EnsureValidGitIgnoreExists()
|
fileSet.EnsureValidGitIgnoreExists()
|
||||||
|
|
||||||
|
@ -59,7 +69,7 @@ func TestFileSetDoesNotCacheDirToGitIgnoreIfAlreadyPresent(t *testing.T) {
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
gitIgnorePath := filepath.Join(projectDir, ".gitignore")
|
gitIgnorePath := filepath.Join(projectDir, ".gitignore")
|
||||||
|
|
||||||
fileSet, err := NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = os.WriteFile(gitIgnorePath, []byte(".databricks"), 0o644)
|
err = os.WriteFile(gitIgnorePath, []byte(".databricks"), 0o644)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/libs/dbr"
|
||||||
|
"github.com/databricks/cli/libs/log"
|
||||||
|
"github.com/databricks/cli/libs/vfs"
|
||||||
|
"github.com/databricks/databricks-sdk-go"
|
||||||
|
"github.com/databricks/databricks-sdk-go/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepositoryInfo struct {
|
||||||
|
// Various metadata about the repo. Each could be "" if it could not be read. No error is returned for such case.
|
||||||
|
OriginURL string
|
||||||
|
LatestCommit string
|
||||||
|
CurrentBranch string
|
||||||
|
|
||||||
|
// Absolute path to determined worktree root or "" if worktree root could not be determined.
|
||||||
|
WorktreeRoot string
|
||||||
|
}
|
||||||
|
|
||||||
|
type gitInfo struct {
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
HeadCommitID string `json:"head_commit_id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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).
|
||||||
|
// - In case we could not find git repository, 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.
|
||||||
|
// - If we could determine git worktree root but there were errors when reading metadata (origin, branch, commit), those errors
|
||||||
|
// will be logged as warnings, RepositoryInfo is guaranteed to have non-empty WorktreeRoot and other fields on best effort basis.
|
||||||
|
// - In successful case, all fields are set to proper git repository metadata.
|
||||||
|
func FetchRepositoryInfo(ctx context.Context, path string, w *databricks.WorkspaceClient) (RepositoryInfo, 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 string, w *databricks.WorkspaceClient) (RepositoryInfo, error) {
|
||||||
|
result := RepositoryInfo{}
|
||||||
|
|
||||||
|
apiClient, err := client.New(w.Config)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response response
|
||||||
|
const apiEndpoint = "/api/2.0/workspace/get-status"
|
||||||
|
|
||||||
|
err = apiClient.Do(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
apiEndpoint,
|
||||||
|
nil,
|
||||||
|
map[string]string{
|
||||||
|
"path": path,
|
||||||
|
"return_git_info": "true",
|
||||||
|
},
|
||||||
|
&response,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if GitInfo is present and extract relevant fields
|
||||||
|
gi := response.GitInfo
|
||||||
|
if gi != nil {
|
||||||
|
fixedPath := ensureWorkspacePrefix(gi.Path)
|
||||||
|
result.OriginURL = gi.URL
|
||||||
|
result.LatestCommit = gi.HeadCommitID
|
||||||
|
result.CurrentBranch = gi.Branch
|
||||||
|
result.WorktreeRoot = fixedPath
|
||||||
|
} else {
|
||||||
|
log.Warnf(ctx, "Failed to load git info from %s", apiEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureWorkspacePrefix(p string) string {
|
||||||
|
if !strings.HasPrefix(p, "/Workspace/") {
|
||||||
|
return path.Join("/Workspace", p)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchRepositoryInfoDotGit(ctx context.Context, path string) (RepositoryInfo, error) {
|
||||||
|
result := RepositoryInfo{}
|
||||||
|
|
||||||
|
rootDir, err := findLeafInTree(path, GitDirectoryName)
|
||||||
|
if rootDir == "" {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.WorktreeRoot = rootDir
|
||||||
|
|
||||||
|
repo, err := NewRepository(vfs.MustNew(rootDir))
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(ctx, "failed to read .git: %s", err)
|
||||||
|
|
||||||
|
// return early since operations below won't work
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result.OriginURL = repo.OriginUrl()
|
||||||
|
|
||||||
|
result.CurrentBranch, err = repo.CurrentBranch()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(ctx, "failed to load current branch: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.LatestCommit, err = repo.LatestCommit()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(ctx, "failed to load latest commit: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [leafName] 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
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -204,17 +202,7 @@ func (r *Repository) Ignore(relPath string) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepository(path vfs.Path) (*Repository, error) {
|
func NewRepository(rootDir vfs.Path) (*Repository, error) {
|
||||||
rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Cannot find `.git` directory.
|
|
||||||
// Treat the specified path as a potential repository root checkout.
|
|
||||||
rootDir = path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository.
|
// Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository.
|
||||||
// If it isn't a real repository, they'll point to the (non-existent) `.git` directory.
|
// If it isn't a real repository, they'll point to the (non-existent) `.git` directory.
|
||||||
gitDir, gitCommonDir, err := resolveGitDirs(rootDir)
|
gitDir, gitCommonDir, err := resolveGitDirs(rootDir)
|
||||||
|
|
|
@ -72,8 +72,8 @@ func (v *View) IgnoreDirectory(dir string) (bool, error) {
|
||||||
return v.Ignore(dir + "/")
|
return v.Ignore(dir + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewView(root vfs.Path) (*View, error) {
|
func NewView(worktreeRoot, root vfs.Path) (*View, error) {
|
||||||
repo, err := NewRepository(root)
|
repo, err := NewRepository(worktreeRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,10 @@ func NewView(root vfs.Path) (*View, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewViewAtRoot(root vfs.Path) (*View, error) {
|
||||||
|
return NewView(root, root)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *View) EnsureValidGitIgnoreExists() error {
|
func (v *View) EnsureValidGitIgnoreExists() error {
|
||||||
ign, err := v.IgnoreDirectory(".databricks")
|
ign, err := v.IgnoreDirectory(".databricks")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -90,19 +90,19 @@ func testViewAtRoot(t *testing.T, tv testView) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRootInBricksRepo(t *testing.T) {
|
func TestViewRootInBricksRepo(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew("./testdata"))
|
v, err := NewViewAtRoot(vfs.MustNew("./testdata"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtRoot(t, testView{t, v})
|
testViewAtRoot(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRootInTempRepo(t *testing.T) {
|
func TestViewRootInTempRepo(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew(createFakeRepo(t, "testdata")))
|
v, err := NewViewAtRoot(vfs.MustNew(createFakeRepo(t, "testdata")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtRoot(t, testView{t, v})
|
testViewAtRoot(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRootInTempDir(t *testing.T) {
|
func TestViewRootInTempDir(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew(copyTestdata(t, "testdata")))
|
v, err := NewViewAtRoot(vfs.MustNew(copyTestdata(t, "testdata")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtRoot(t, testView{t, v})
|
testViewAtRoot(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
@ -125,20 +125,21 @@ func testViewAtA(t *testing.T, tv testView) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewAInBricksRepo(t *testing.T) {
|
func TestViewAInBricksRepo(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew("./testdata/a"))
|
v, err := NewView(vfs.MustNew("."), vfs.MustNew("./testdata/a"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtA(t, testView{t, v})
|
testViewAtA(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewAInTempRepo(t *testing.T) {
|
func TestViewAInTempRepo(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew(filepath.Join(createFakeRepo(t, "testdata"), "a")))
|
repo := createFakeRepo(t, "testdata")
|
||||||
|
v, err := NewView(vfs.MustNew(repo), vfs.MustNew(filepath.Join(repo, "a")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtA(t, testView{t, v})
|
testViewAtA(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewAInTempDir(t *testing.T) {
|
func TestViewAInTempDir(t *testing.T) {
|
||||||
// Since this is not a fake repo it should not traverse up the tree.
|
// Since this is not a fake repo it should not traverse up the tree.
|
||||||
v, err := NewView(vfs.MustNew(filepath.Join(copyTestdata(t, "testdata"), "a")))
|
v, err := NewViewAtRoot(vfs.MustNew(filepath.Join(copyTestdata(t, "testdata"), "a")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tv := testView{t, v}
|
tv := testView{t, v}
|
||||||
|
|
||||||
|
@ -175,20 +176,21 @@ func testViewAtAB(t *testing.T, tv testView) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewABInBricksRepo(t *testing.T) {
|
func TestViewABInBricksRepo(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew("./testdata/a/b"))
|
v, err := NewView(vfs.MustNew("."), vfs.MustNew("./testdata/a/b"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtAB(t, testView{t, v})
|
testViewAtAB(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewABInTempRepo(t *testing.T) {
|
func TestViewABInTempRepo(t *testing.T) {
|
||||||
v, err := NewView(vfs.MustNew(filepath.Join(createFakeRepo(t, "testdata"), "a", "b")))
|
repo := createFakeRepo(t, "testdata")
|
||||||
|
v, err := NewView(vfs.MustNew(repo), vfs.MustNew(filepath.Join(repo, "a", "b")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testViewAtAB(t, testView{t, v})
|
testViewAtAB(t, testView{t, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewABInTempDir(t *testing.T) {
|
func TestViewABInTempDir(t *testing.T) {
|
||||||
// Since this is not a fake repo it should not traverse up the tree.
|
// Since this is not a fake repo it should not traverse up the tree.
|
||||||
v, err := NewView(vfs.MustNew(filepath.Join(copyTestdata(t, "testdata"), "a", "b")))
|
v, err := NewViewAtRoot(vfs.MustNew(filepath.Join(copyTestdata(t, "testdata"), "a", "b")))
|
||||||
tv := testView{t, v}
|
tv := testView{t, v}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -215,7 +217,7 @@ func TestViewDoesNotChangeGitignoreIfCacheDirAlreadyIgnoredAtRoot(t *testing.T)
|
||||||
|
|
||||||
// Since root .gitignore already has .databricks, there should be no edits
|
// Since root .gitignore already has .databricks, there should be no edits
|
||||||
// to root .gitignore
|
// to root .gitignore
|
||||||
v, err := NewView(vfs.MustNew(repoPath))
|
v, err := NewViewAtRoot(vfs.MustNew(repoPath))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = v.EnsureValidGitIgnoreExists()
|
err = v.EnsureValidGitIgnoreExists()
|
||||||
|
@ -235,7 +237,7 @@ func TestViewDoesNotChangeGitignoreIfCacheDirAlreadyIgnoredInSubdir(t *testing.T
|
||||||
|
|
||||||
// Since root .gitignore already has .databricks, there should be no edits
|
// Since root .gitignore already has .databricks, there should be no edits
|
||||||
// to a/.gitignore
|
// to a/.gitignore
|
||||||
v, err := NewView(vfs.MustNew(filepath.Join(repoPath, "a")))
|
v, err := NewView(vfs.MustNew(repoPath), vfs.MustNew(filepath.Join(repoPath, "a")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = v.EnsureValidGitIgnoreExists()
|
err = v.EnsureValidGitIgnoreExists()
|
||||||
|
@ -253,7 +255,7 @@ func TestViewAddsGitignoreWithCacheDir(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Since root .gitignore was deleted, new view adds .databricks to root .gitignore
|
// Since root .gitignore was deleted, new view adds .databricks to root .gitignore
|
||||||
v, err := NewView(vfs.MustNew(repoPath))
|
v, err := NewViewAtRoot(vfs.MustNew(repoPath))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = v.EnsureValidGitIgnoreExists()
|
err = v.EnsureValidGitIgnoreExists()
|
||||||
|
@ -271,7 +273,7 @@ func TestViewAddsGitignoreWithCacheDirAtSubdir(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Since root .gitignore was deleted, new view adds .databricks to a/.gitignore
|
// Since root .gitignore was deleted, new view adds .databricks to a/.gitignore
|
||||||
v, err := NewView(vfs.MustNew(filepath.Join(repoPath, "a")))
|
v, err := NewView(vfs.MustNew(repoPath), vfs.MustNew(filepath.Join(repoPath, "a")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = v.EnsureValidGitIgnoreExists()
|
err = v.EnsureValidGitIgnoreExists()
|
||||||
|
@ -288,7 +290,7 @@ func TestViewAddsGitignoreWithCacheDirAtSubdir(t *testing.T) {
|
||||||
func TestViewAlwaysIgnoresCacheDir(t *testing.T) {
|
func TestViewAlwaysIgnoresCacheDir(t *testing.T) {
|
||||||
repoPath := createFakeRepo(t, "testdata")
|
repoPath := createFakeRepo(t, "testdata")
|
||||||
|
|
||||||
v, err := NewView(vfs.MustNew(repoPath))
|
v, err := NewViewAtRoot(vfs.MustNew(repoPath))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = v.EnsureValidGitIgnoreExists()
|
err = v.EnsureValidGitIgnoreExists()
|
||||||
|
|
|
@ -30,7 +30,7 @@ func TestDiff(t *testing.T) {
|
||||||
|
|
||||||
// Create temp project dir
|
// Create temp project dir
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := git.NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := git.NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
state := Snapshot{
|
state := Snapshot{
|
||||||
SnapshotState: &SnapshotState{
|
SnapshotState: &SnapshotState{
|
||||||
|
@ -94,7 +94,7 @@ func TestSymlinkDiff(t *testing.T) {
|
||||||
|
|
||||||
// Create temp project dir
|
// Create temp project dir
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := git.NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := git.NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
state := Snapshot{
|
state := Snapshot{
|
||||||
SnapshotState: &SnapshotState{
|
SnapshotState: &SnapshotState{
|
||||||
|
@ -125,7 +125,7 @@ func TestFolderDiff(t *testing.T) {
|
||||||
|
|
||||||
// Create temp project dir
|
// Create temp project dir
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := git.NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := git.NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
state := Snapshot{
|
state := Snapshot{
|
||||||
SnapshotState: &SnapshotState{
|
SnapshotState: &SnapshotState{
|
||||||
|
@ -170,7 +170,7 @@ func TestPythonNotebookDiff(t *testing.T) {
|
||||||
|
|
||||||
// Create temp project dir
|
// Create temp project dir
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := git.NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := git.NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
state := Snapshot{
|
state := Snapshot{
|
||||||
SnapshotState: &SnapshotState{
|
SnapshotState: &SnapshotState{
|
||||||
|
@ -245,7 +245,7 @@ func TestErrorWhenIdenticalRemoteName(t *testing.T) {
|
||||||
|
|
||||||
// Create temp project dir
|
// Create temp project dir
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := git.NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := git.NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
state := Snapshot{
|
state := Snapshot{
|
||||||
SnapshotState: &SnapshotState{
|
SnapshotState: &SnapshotState{
|
||||||
|
@ -282,7 +282,7 @@ func TestNoErrorRenameWithIdenticalRemoteName(t *testing.T) {
|
||||||
|
|
||||||
// Create temp project dir
|
// Create temp project dir
|
||||||
projectDir := t.TempDir()
|
projectDir := t.TempDir()
|
||||||
fileSet, err := git.NewFileSet(vfs.MustNew(projectDir))
|
fileSet, err := git.NewFileSetAtRoot(vfs.MustNew(projectDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
state := Snapshot{
|
state := Snapshot{
|
||||||
SnapshotState: &SnapshotState{
|
SnapshotState: &SnapshotState{
|
||||||
|
|
|
@ -19,10 +19,11 @@ import (
|
||||||
type OutputHandler func(context.Context, <-chan Event)
|
type OutputHandler func(context.Context, <-chan Event)
|
||||||
|
|
||||||
type SyncOptions struct {
|
type SyncOptions struct {
|
||||||
LocalRoot vfs.Path
|
WorktreeRoot vfs.Path
|
||||||
Paths []string
|
LocalRoot vfs.Path
|
||||||
Include []string
|
Paths []string
|
||||||
Exclude []string
|
Include []string
|
||||||
|
Exclude []string
|
||||||
|
|
||||||
RemotePath string
|
RemotePath string
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ type Sync struct {
|
||||||
|
|
||||||
// New initializes and returns a new [Sync] instance.
|
// New initializes and returns a new [Sync] instance.
|
||||||
func New(ctx context.Context, opts SyncOptions) (*Sync, error) {
|
func New(ctx context.Context, opts SyncOptions) (*Sync, error) {
|
||||||
fileSet, err := git.NewFileSet(opts.LocalRoot, opts.Paths)
|
fileSet, err := git.NewFileSet(opts.WorktreeRoot, opts.LocalRoot, opts.Paths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ func TestGetFileSet(t *testing.T) {
|
||||||
|
|
||||||
dir := setupFiles(t)
|
dir := setupFiles(t)
|
||||||
root := vfs.MustNew(dir)
|
root := vfs.MustNew(dir)
|
||||||
fileSet, err := git.NewFileSet(root)
|
fileSet, err := git.NewFileSetAtRoot(root)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = fileSet.EnsureValidGitIgnoreExists()
|
err = fileSet.EnsureValidGitIgnoreExists()
|
||||||
|
@ -103,7 +103,7 @@ func TestRecursiveExclude(t *testing.T) {
|
||||||
|
|
||||||
dir := setupFiles(t)
|
dir := setupFiles(t)
|
||||||
root := vfs.MustNew(dir)
|
root := vfs.MustNew(dir)
|
||||||
fileSet, err := git.NewFileSet(root)
|
fileSet, err := git.NewFileSetAtRoot(root)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = fileSet.EnsureValidGitIgnoreExists()
|
err = fileSet.EnsureValidGitIgnoreExists()
|
||||||
|
@ -133,7 +133,7 @@ func TestNegateExclude(t *testing.T) {
|
||||||
|
|
||||||
dir := setupFiles(t)
|
dir := setupFiles(t)
|
||||||
root := vfs.MustNew(dir)
|
root := vfs.MustNew(dir)
|
||||||
fileSet, err := git.NewFileSet(root)
|
fileSet, err := git.NewFileSetAtRoot(root)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = fileSet.EnsureValidGitIgnoreExists()
|
err = fileSet.EnsureValidGitIgnoreExists()
|
||||||
|
|
Loading…
Reference in New Issue