From 58bdc7fd3d3f9341cdd0c64776ff090a5d9252e4 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:05:05 +0200 Subject: [PATCH 01/16] add --exclude-from flag to sync commands --- cmd/bundle/sync.go | 48 +++++++++++++++++++++++++--- cmd/bundle/sync_test.go | 70 +++++++++++++++++++++++++++++++++++++++++ cmd/sync/sync.go | 60 +++++++++++++++++++++++++++++------ cmd/sync/sync_test.go | 43 +++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 14 deletions(-) create mode 100644 cmd/bundle/sync_test.go diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index 65cda6d6c9..cfe03b4045 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -1,9 +1,13 @@ package bundle import ( + "bufio" + "bytes" "context" "fmt" "io" + "os" + "strings" "time" "github.com/databricks/cli/bundle" @@ -18,11 +22,39 @@ import ( ) type syncFlags struct { - interval time.Duration - full bool - watch bool - output flags.Output + interval time.Duration + full bool + watch bool + output flags.Output dryRun bool + excludeFrom string +} + +func (f *syncFlags) readExcludeFrom(ctx context.Context) ([]string, error) { + if f.excludeFrom == "" { + return nil, nil + } + + data, err := os.ReadFile(f.excludeFrom) + if err != nil { + return nil, fmt.Errorf("failed to read exclude-from file: %w", err) + } + + var patterns []string + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + // Skip empty lines and comments + if line != "" && !strings.HasPrefix(line, "#") { + patterns = append(patterns, line) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading exclude-from file: %w", err) + } + + return patterns, nil } func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOptions, error) { @@ -31,6 +63,11 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) return nil, fmt.Errorf("cannot get sync options: %w", err) } + excludePatterns, err := f.readExcludeFrom(cmd.Context()) + if err != nil { + return nil, err + } + if f.output != "" { var outputFunc func(context.Context, <-chan sync.Event, io.Writer) switch f.output { @@ -48,7 +85,7 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) opts.Full = f.full opts.PollInterval = f.interval - opts.DryRun = f.dryRun + opts.Exclude = append(opts.Exclude, excludePatterns...) return opts, nil } @@ -65,6 +102,7 @@ func newSyncCommand() *cobra.Command { cmd.Flags().BoolVar(&f.watch, "watch", false, "watch local file system for changes") cmd.Flags().Var(&f.output, "output", "type of the output format") cmd.Flags().BoolVar(&f.dryRun, "dry-run", false, "simulate sync execution without making actual changes") + cmd.Flags().StringVar(&f.excludeFrom, "exclude-from", "", "file containing patterns to exclude from sync (one pattern per line)") cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() diff --git a/cmd/bundle/sync_test.go b/cmd/bundle/sync_test.go new file mode 100644 index 0000000000..b75607c8ee --- /dev/null +++ b/cmd/bundle/sync_test.go @@ -0,0 +1,70 @@ +package bundle + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/vfs" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBundleSyncExcludeFromFlag(t *testing.T) { + // Create a temporary directory + tempDir := t.TempDir() + + // Create a bundle + b := &bundle.Bundle{ + BundleRootPath: tempDir, + BundleRoot: vfs.MustNew(tempDir), + SyncRootPath: tempDir, + SyncRoot: vfs.MustNew(tempDir), + Config: config.Root{ + Bundle: config.Bundle{ + Target: "default", + }, + Workspace: config.Workspace{ + FilePath: "/Users/jane@doe.com/path", + }, + }, + } + + // Create a temporary exclude-from file + excludeFromPath := filepath.Join(tempDir, "exclude-patterns.txt") + excludePatterns := []string{ + "*.log", + "build/", + "# This is a comment", + "", + "temp/*.tmp", + } + require.NoError(t, os.WriteFile(excludeFromPath, []byte(strings.Join(excludePatterns, "\n")), 0o644)) + + // Set up the flags + f := syncFlags{ + excludeFrom: excludeFromPath, + } + + // Test syncOptionsFromBundle + cmd := &cobra.Command{} + opts, err := f.syncOptionsFromBundle(cmd, b) + require.NoError(t, err) + + // Expected patterns (should skip comments and empty lines) + expected := []string{"*.log", "build/", "temp/*.tmp"} + assert.ElementsMatch(t, expected, opts.Exclude) + + // Test with both exclude flag and exclude-from flag + f.exclude = []string{"node_modules/"} + opts, err = f.syncOptionsFromBundle(cmd, b) + require.NoError(t, err) + + // Should include both exclude flag and exclude-from patterns + expected = []string{"node_modules/", "*.log", "build/", "temp/*.tmp"} + assert.ElementsMatch(t, expected, opts.Exclude) +} diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 3ccae8cbfe..5f942490be 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -1,12 +1,16 @@ package sync import ( + "bufio" + "bytes" "context" "errors" "flag" "fmt" "io" + "os" "path/filepath" + "strings" "time" "github.com/databricks/cli/bundle" @@ -23,13 +27,41 @@ import ( type syncFlags struct { // project files polling interval - interval time.Duration - full bool - watch bool - output flags.Output - exclude []string - include []string - dryRun bool + interval time.Duration + full bool + watch bool + output flags.Output + exclude []string + include []string + dryRun bool + excludeFrom string +} + +func (f *syncFlags) readExcludeFrom(ctx context.Context) ([]string, error) { + if f.excludeFrom == "" { + return nil, nil + } + + data, err := os.ReadFile(f.excludeFrom) + if err != nil { + return nil, fmt.Errorf("failed to read exclude-from file: %w", err) + } + + var patterns []string + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + // Skip empty lines and comments + if line != "" && !strings.HasPrefix(line, "#") { + patterns = append(patterns, line) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading exclude-from file: %w", err) + } + + return patterns, nil } func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b *bundle.Bundle) (*sync.SyncOptions, error) { @@ -42,10 +74,16 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b * return nil, fmt.Errorf("cannot get sync options: %w", err) } + excludePatterns, err := f.readExcludeFrom(cmd.Context()) + if err != nil { + return nil, err + } + opts.Full = f.full opts.PollInterval = f.interval opts.WorktreeRoot = b.WorktreeRoot opts.Exclude = append(opts.Exclude, f.exclude...) + opts.Exclude = append(opts.Exclude, excludePatterns...) opts.Include = append(opts.Include, f.include...) opts.DryRun = f.dryRun return opts, nil @@ -78,6 +116,11 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn log.Warnf(ctx, "Running in dry-run mode. No actual changes will be made.") } + excludePatterns, err := f.readExcludeFrom(ctx) + if err != nil { + return nil, err + } + localRoot := vfs.MustNew(args[0]) info, err := git.FetchRepositoryInfo(ctx, localRoot.Native(), client) if err != nil { @@ -97,7 +140,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn LocalRoot: localRoot, Paths: []string{"."}, Include: f.include, - Exclude: f.exclude, + Exclude: append(f.exclude, excludePatterns...), RemotePath: args[1], Full: f.full, @@ -133,7 +176,6 @@ func New() *cobra.Command { cmd.Flags().Var(&f.output, "output", "type of output format") cmd.Flags().StringSliceVar(&f.exclude, "exclude", nil, "patterns to exclude from sync (can be specified multiple times)") cmd.Flags().StringSliceVar(&f.include, "include", nil, "patterns to include in sync (can be specified multiple times)") - cmd.Flags().BoolVar(&f.dryRun, "dry-run", false, "simulate sync execution without making actual changes") // Wrapper for [root.MustWorkspaceClient] that disables loading authentication configuration from a bundle. mustWorkspaceClient := func(cmd *cobra.Command, args []string) error { diff --git a/cmd/sync/sync_test.go b/cmd/sync/sync_test.go index 09aed7ca34..bb7360731f 100644 --- a/cmd/sync/sync_test.go +++ b/cmd/sync/sync_test.go @@ -3,7 +3,9 @@ package sync import ( "context" "flag" + "os" "path/filepath" + "strings" "testing" "github.com/databricks/cli/bundle" @@ -64,3 +66,44 @@ func TestSyncOptionsFromArgs(t *testing.T) { assert.Equal(t, local, opts.LocalRoot.Native()) assert.Equal(t, remote, opts.RemotePath) } + +func TestExcludeFromFlag(t *testing.T) { + // Create a temporary directory + tempDir := t.TempDir() + local := filepath.Join(tempDir, "local") + require.NoError(t, os.MkdirAll(local, 0o755)) + remote := "/remote" + + // Create a temporary exclude-from file + excludeFromPath := filepath.Join(tempDir, "exclude-patterns.txt") + excludePatterns := []string{ + "*.log", + "build/", + "# This is a comment", + "", + "temp/*.tmp", + } + require.NoError(t, os.WriteFile(excludeFromPath, []byte(strings.Join(excludePatterns, "\n")), 0o644)) + + // Set up the flags + f := syncFlags{excludeFrom: excludeFromPath} + cmd := New() + cmd.SetContext(cmdctx.SetWorkspaceClient(context.Background(), nil)) + + // Test syncOptionsFromArgs + opts, err := f.syncOptionsFromArgs(cmd, []string{local, remote}) + require.NoError(t, err) + + // Expected patterns (should skip comments and empty lines) + expected := []string{"*.log", "build/", "temp/*.tmp"} + assert.ElementsMatch(t, expected, opts.Exclude) + + // Test with both exclude flag and exclude-from flag + f.exclude = []string{"node_modules/"} + opts, err = f.syncOptionsFromArgs(cmd, []string{local, remote}) + require.NoError(t, err) + + // Should include both exclude flag and exclude-from patterns + expected = []string{"node_modules/", "*.log", "build/", "temp/*.tmp"} + assert.ElementsMatch(t, expected, opts.Exclude) +} From 20d61531ef5a4dd2b15ff7a1f1c516221e038c88 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:13:29 +0200 Subject: [PATCH 02/16] fix the acceptance tests --- acceptance/bundle/help/bundle-sync/output.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/acceptance/bundle/help/bundle-sync/output.txt b/acceptance/bundle/help/bundle-sync/output.txt index f9bcc40cfa..be9df13b7f 100644 --- a/acceptance/bundle/help/bundle-sync/output.txt +++ b/acceptance/bundle/help/bundle-sync/output.txt @@ -6,12 +6,14 @@ Usage: databricks bundle sync [flags] Flags: - --dry-run simulate sync execution without making actual changes - --full perform full synchronization (default is incremental) - -h, --help help for sync - --interval duration file system polling interval (for --watch) (default 1s) - --output type type of the output format - --watch watch local file system for changes + --dry-run simulate sync execution without making actual changes + --exclude-from string file containing patterns to exclude from sync (one pattern per line) + --full perform full synchronization (default is incremental) + -h, --help help for sync + --include strings patterns to include in sync (can be specified multiple times) + --interval duration file system polling interval (for --watch) (default 1s) + --output type type of the output format + --watch watch local file system for changes Global Flags: --debug enable debug logging From 2819c9b83ef56141deca04a8108c03bc61dae7c6 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:20:01 +0200 Subject: [PATCH 03/16] add acceptance test for cmd/sync/from-file --- acceptance/cmd/sync/from-file/output.txt | 8 ++++++++ acceptance/cmd/sync/from-file/script | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 acceptance/cmd/sync/from-file/output.txt create mode 100644 acceptance/cmd/sync/from-file/script diff --git a/acceptance/cmd/sync/from-file/output.txt b/acceptance/cmd/sync/from-file/output.txt new file mode 100644 index 0000000000..ab69a28358 --- /dev/null +++ b/acceptance/cmd/sync/from-file/output.txt @@ -0,0 +1,8 @@ + +>>> [CLI] sync . /Users/[USERNAME] --exclude-from .databricksignore +Initial Sync Complete +Uploaded .databricksignore +Uploaded .gitignore +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml diff --git a/acceptance/cmd/sync/from-file/script b/acceptance/cmd/sync/from-file/script new file mode 100644 index 0000000000..66d5510aff --- /dev/null +++ b/acceptance/cmd/sync/from-file/script @@ -0,0 +1,22 @@ +mkdir "project-folder" "project-folder/blob" "ignored-folder" "ignored-folder/folder1" ".git" +touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" "project-folder/blob/1" "project-folder/blob/2" +touch "ignored-folder/script.py" "ignored-folder/folder1/script.py" "ignored-folder/folder1/script.yaml" "ignored-folder/folder1/big-blob" +cat > .gitignore << EOF +ignored-folder/ +script +output.txt +repls.json +EOF +cat > .databricksignore << EOF +project-folder/blob/* +project-folder/*.sql +EOF + +cleanup() { + rm -rf project-folder ignored-folder .git .databricksignore +} +trap cleanup EXIT + +# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out +trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from '.databricksignore' | grep -v "^Action: " | sort + From e817036ad881575b2260ef5f4daf0b1225c6045b Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:22:38 +0200 Subject: [PATCH 04/16] add acceptance test for bundle/sync/from-file --- .../bundle/sync/from-file/databricks.yml | 7 +++++++ acceptance/bundle/sync/from-file/output.txt | 9 ++++++++ acceptance/bundle/sync/from-file/script | 21 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 acceptance/bundle/sync/from-file/databricks.yml create mode 100644 acceptance/bundle/sync/from-file/output.txt create mode 100644 acceptance/bundle/sync/from-file/script diff --git a/acceptance/bundle/sync/from-file/databricks.yml b/acceptance/bundle/sync/from-file/databricks.yml new file mode 100644 index 0000000000..c164f486a0 --- /dev/null +++ b/acceptance/bundle/sync/from-file/databricks.yml @@ -0,0 +1,7 @@ +bundle: + name: bundle-sync-test + +resources: + dashboards: + dashboard1: + display_name: My dashboard diff --git a/acceptance/bundle/sync/from-file/output.txt b/acceptance/bundle/sync/from-file/output.txt new file mode 100644 index 0000000000..f670eaf883 --- /dev/null +++ b/acceptance/bundle/sync/from-file/output.txt @@ -0,0 +1,9 @@ + +>>> [CLI] bundle sync --exclude-from .databricksignore --output text +Initial Sync Complete +Uploaded .databricksignore +Uploaded .gitignore +Uploaded databricks.yml +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml diff --git a/acceptance/bundle/sync/from-file/script b/acceptance/bundle/sync/from-file/script new file mode 100644 index 0000000000..4a81334b49 --- /dev/null +++ b/acceptance/bundle/sync/from-file/script @@ -0,0 +1,21 @@ +mkdir "project-folder" "project-folder/blob" "ignored-folder" "ignored-folder/folder1" ".git" +touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" "project-folder/blob/1" "project-folder/blob/2" +touch "ignored-folder/script.py" "ignored-folder/folder1/script.py" "ignored-folder/folder1/script.yaml" "ignored-folder/folder1/big-blob" +cat > .gitignore << EOF +ignored-folder/ +script +output.txt +repls.json +EOF +cat > .databricksignore << EOF +project-folder/blob/* +project-folder/*.sql +EOF + +cleanup() { + rm -rf project-folder ignored-folder .git .databricksignore +} +trap cleanup EXIT + +# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out +trace $CLI bundle sync --exclude-from '.databricksignore' --output text | grep -v "^Action: " | sort From 93d73713c905a8654806d6506407936344cff817 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:25:30 +0200 Subject: [PATCH 05/16] fix acceptance test --- acceptance/bundle/sync/output.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/acceptance/bundle/sync/output.txt b/acceptance/bundle/sync/output.txt index cd140e4fbe..6c4ca6b852 100644 --- a/acceptance/bundle/sync/output.txt +++ b/acceptance/bundle/sync/output.txt @@ -7,4 +7,14 @@ Uploaded dryrun Uploaded dryrun/databricks.yml Uploaded ignored-folder/folder1 Uploaded ignored-folder/folder1/script.py -Uploaded ignored-folder/script.py + +>>> [CLI] bundle sync --output text +Deleted ignored-folder +Deleted ignored-folder/folder1 +Deleted ignored-folder/folder1/script.py +Deleted ignored-folder/script.py +Initial Sync Complete +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml +Uploaded project-folder/query.sql From f096b9e0852c8e55518bab4c23c5838f16d7bf1d Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:29:17 +0200 Subject: [PATCH 06/16] update NEXT_CHANGELOG.md --- NEXT_CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 1a272aea45..8a5e5fbf93 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,6 +7,7 @@ ### Dependency updates ### CLI +* Added exclude-from flag support to bundle sync command ([#2660](https://github.com/databricks/cli/pull/2660)) ### Bundles From 3d55de8b5239ba5b151a6b240c69d464ba481a0c Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:33:36 +0200 Subject: [PATCH 07/16] fix whitespace --- acceptance/cmd/sync/from-file/script | 1 - 1 file changed, 1 deletion(-) diff --git a/acceptance/cmd/sync/from-file/script b/acceptance/cmd/sync/from-file/script index 66d5510aff..c00bb1a1eb 100644 --- a/acceptance/cmd/sync/from-file/script +++ b/acceptance/cmd/sync/from-file/script @@ -19,4 +19,3 @@ trap cleanup EXIT # Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from '.databricksignore' | grep -v "^Action: " | sort - From 76acd9b3a8d265622bd988cbc1974b3b2cfb4944 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:27:39 +0200 Subject: [PATCH 08/16] remove exclude-from flag from bundle sync --- .../bundle/sync/from-file/databricks.yml | 7 -- acceptance/bundle/sync/from-file/output.txt | 9 --- acceptance/bundle/sync/from-file/script | 21 ------ cmd/bundle/sync.go | 47 ++----------- cmd/bundle/sync_test.go | 70 ------------------- 5 files changed, 4 insertions(+), 150 deletions(-) delete mode 100644 acceptance/bundle/sync/from-file/databricks.yml delete mode 100644 acceptance/bundle/sync/from-file/output.txt delete mode 100644 acceptance/bundle/sync/from-file/script delete mode 100644 cmd/bundle/sync_test.go diff --git a/acceptance/bundle/sync/from-file/databricks.yml b/acceptance/bundle/sync/from-file/databricks.yml deleted file mode 100644 index c164f486a0..0000000000 --- a/acceptance/bundle/sync/from-file/databricks.yml +++ /dev/null @@ -1,7 +0,0 @@ -bundle: - name: bundle-sync-test - -resources: - dashboards: - dashboard1: - display_name: My dashboard diff --git a/acceptance/bundle/sync/from-file/output.txt b/acceptance/bundle/sync/from-file/output.txt deleted file mode 100644 index f670eaf883..0000000000 --- a/acceptance/bundle/sync/from-file/output.txt +++ /dev/null @@ -1,9 +0,0 @@ - ->>> [CLI] bundle sync --exclude-from .databricksignore --output text -Initial Sync Complete -Uploaded .databricksignore -Uploaded .gitignore -Uploaded databricks.yml -Uploaded project-folder -Uploaded project-folder/app.py -Uploaded project-folder/app.yaml diff --git a/acceptance/bundle/sync/from-file/script b/acceptance/bundle/sync/from-file/script deleted file mode 100644 index 4a81334b49..0000000000 --- a/acceptance/bundle/sync/from-file/script +++ /dev/null @@ -1,21 +0,0 @@ -mkdir "project-folder" "project-folder/blob" "ignored-folder" "ignored-folder/folder1" ".git" -touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" "project-folder/blob/1" "project-folder/blob/2" -touch "ignored-folder/script.py" "ignored-folder/folder1/script.py" "ignored-folder/folder1/script.yaml" "ignored-folder/folder1/big-blob" -cat > .gitignore << EOF -ignored-folder/ -script -output.txt -repls.json -EOF -cat > .databricksignore << EOF -project-folder/blob/* -project-folder/*.sql -EOF - -cleanup() { - rm -rf project-folder ignored-folder .git .databricksignore -} -trap cleanup EXIT - -# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out -trace $CLI bundle sync --exclude-from '.databricksignore' --output text | grep -v "^Action: " | sort diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index cfe03b4045..f5bba0add7 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -1,13 +1,9 @@ package bundle import ( - "bufio" - "bytes" "context" "fmt" "io" - "os" - "strings" "time" "github.com/databricks/cli/bundle" @@ -22,39 +18,11 @@ import ( ) type syncFlags struct { - interval time.Duration - full bool - watch bool - output flags.Output + interval time.Duration + full bool + watch bool + output flags.Output dryRun bool - excludeFrom string -} - -func (f *syncFlags) readExcludeFrom(ctx context.Context) ([]string, error) { - if f.excludeFrom == "" { - return nil, nil - } - - data, err := os.ReadFile(f.excludeFrom) - if err != nil { - return nil, fmt.Errorf("failed to read exclude-from file: %w", err) - } - - var patterns []string - scanner := bufio.NewScanner(bytes.NewReader(data)) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - // Skip empty lines and comments - if line != "" && !strings.HasPrefix(line, "#") { - patterns = append(patterns, line) - } - } - - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("error reading exclude-from file: %w", err) - } - - return patterns, nil } func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOptions, error) { @@ -63,11 +31,6 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) return nil, fmt.Errorf("cannot get sync options: %w", err) } - excludePatterns, err := f.readExcludeFrom(cmd.Context()) - if err != nil { - return nil, err - } - if f.output != "" { var outputFunc func(context.Context, <-chan sync.Event, io.Writer) switch f.output { @@ -85,7 +48,6 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) opts.Full = f.full opts.PollInterval = f.interval - opts.Exclude = append(opts.Exclude, excludePatterns...) return opts, nil } @@ -102,7 +64,6 @@ func newSyncCommand() *cobra.Command { cmd.Flags().BoolVar(&f.watch, "watch", false, "watch local file system for changes") cmd.Flags().Var(&f.output, "output", "type of the output format") cmd.Flags().BoolVar(&f.dryRun, "dry-run", false, "simulate sync execution without making actual changes") - cmd.Flags().StringVar(&f.excludeFrom, "exclude-from", "", "file containing patterns to exclude from sync (one pattern per line)") cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() diff --git a/cmd/bundle/sync_test.go b/cmd/bundle/sync_test.go deleted file mode 100644 index b75607c8ee..0000000000 --- a/cmd/bundle/sync_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package bundle - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/vfs" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestBundleSyncExcludeFromFlag(t *testing.T) { - // Create a temporary directory - tempDir := t.TempDir() - - // Create a bundle - b := &bundle.Bundle{ - BundleRootPath: tempDir, - BundleRoot: vfs.MustNew(tempDir), - SyncRootPath: tempDir, - SyncRoot: vfs.MustNew(tempDir), - Config: config.Root{ - Bundle: config.Bundle{ - Target: "default", - }, - Workspace: config.Workspace{ - FilePath: "/Users/jane@doe.com/path", - }, - }, - } - - // Create a temporary exclude-from file - excludeFromPath := filepath.Join(tempDir, "exclude-patterns.txt") - excludePatterns := []string{ - "*.log", - "build/", - "# This is a comment", - "", - "temp/*.tmp", - } - require.NoError(t, os.WriteFile(excludeFromPath, []byte(strings.Join(excludePatterns, "\n")), 0o644)) - - // Set up the flags - f := syncFlags{ - excludeFrom: excludeFromPath, - } - - // Test syncOptionsFromBundle - cmd := &cobra.Command{} - opts, err := f.syncOptionsFromBundle(cmd, b) - require.NoError(t, err) - - // Expected patterns (should skip comments and empty lines) - expected := []string{"*.log", "build/", "temp/*.tmp"} - assert.ElementsMatch(t, expected, opts.Exclude) - - // Test with both exclude flag and exclude-from flag - f.exclude = []string{"node_modules/"} - opts, err = f.syncOptionsFromBundle(cmd, b) - require.NoError(t, err) - - // Should include both exclude flag and exclude-from patterns - expected = []string{"node_modules/", "*.log", "build/", "temp/*.tmp"} - assert.ElementsMatch(t, expected, opts.Exclude) -} From fbaa1be2e6bfc023fc1b7a3fe38107eac2590992 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:37:37 +0200 Subject: [PATCH 09/16] remove exclude-from flag from bundle sync (fix acceptance test) --- acceptance/bundle/help/bundle-sync/output.txt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/acceptance/bundle/help/bundle-sync/output.txt b/acceptance/bundle/help/bundle-sync/output.txt index be9df13b7f..f9bcc40cfa 100644 --- a/acceptance/bundle/help/bundle-sync/output.txt +++ b/acceptance/bundle/help/bundle-sync/output.txt @@ -6,14 +6,12 @@ Usage: databricks bundle sync [flags] Flags: - --dry-run simulate sync execution without making actual changes - --exclude-from string file containing patterns to exclude from sync (one pattern per line) - --full perform full synchronization (default is incremental) - -h, --help help for sync - --include strings patterns to include in sync (can be specified multiple times) - --interval duration file system polling interval (for --watch) (default 1s) - --output type type of the output format - --watch watch local file system for changes + --dry-run simulate sync execution without making actual changes + --full perform full synchronization (default is incremental) + -h, --help help for sync + --interval duration file system polling interval (for --watch) (default 1s) + --output type type of the output format + --watch watch local file system for changes Global Flags: --debug enable debug logging From 2c66d9e8ae9ebb30ef03ad38cbb9ee8568cbf32a Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:42:12 +0200 Subject: [PATCH 10/16] fix acceptance test for dryrun sync --- cmd/sync/sync.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 5f942490be..948f21d0fd 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -176,6 +176,8 @@ func New() *cobra.Command { cmd.Flags().Var(&f.output, "output", "type of output format") cmd.Flags().StringSliceVar(&f.exclude, "exclude", nil, "patterns to exclude from sync (can be specified multiple times)") cmd.Flags().StringSliceVar(&f.include, "include", nil, "patterns to include in sync (can be specified multiple times)") + cmd.Flags().StringVar(&f.excludeFrom, "exclude-from", "", "file containing patterns to exclude from sync (one pattern per line)") + cmd.Flags().BoolVar(&f.dryRun, "dry-run", false, "simulate sync execution without making actual changes") // Wrapper for [root.MustWorkspaceClient] that disables loading authentication configuration from a bundle. mustWorkspaceClient := func(cmd *cobra.Command, args []string) error { From 7b10afee618e34a6186b0e5fac7d4f2c866f7539 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:49:25 +0200 Subject: [PATCH 11/16] fix acceptance tests --- acceptance/bundle/sync/output.txt | 12 +----------- cmd/bundle/sync.go | 1 + 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/acceptance/bundle/sync/output.txt b/acceptance/bundle/sync/output.txt index 6c4ca6b852..cd140e4fbe 100644 --- a/acceptance/bundle/sync/output.txt +++ b/acceptance/bundle/sync/output.txt @@ -7,14 +7,4 @@ Uploaded dryrun Uploaded dryrun/databricks.yml Uploaded ignored-folder/folder1 Uploaded ignored-folder/folder1/script.py - ->>> [CLI] bundle sync --output text -Deleted ignored-folder -Deleted ignored-folder/folder1 -Deleted ignored-folder/folder1/script.py -Deleted ignored-folder/script.py -Initial Sync Complete -Uploaded project-folder -Uploaded project-folder/app.py -Uploaded project-folder/app.yaml -Uploaded project-folder/query.sql +Uploaded ignored-folder/script.py diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index f5bba0add7..65cda6d6c9 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -48,6 +48,7 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) opts.Full = f.full opts.PollInterval = f.interval + opts.DryRun = f.dryRun return opts, nil } From 1ec76c9e7a33223080a64147f2cc4c6a15a5520d Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:56:48 +0200 Subject: [PATCH 12/16] move acceptance test for sync-from-file one level up --- .../{sync/from-file => sync-from-file}/output.txt | 4 ++-- .../cmd/{sync/from-file => sync-from-file}/script | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) rename acceptance/cmd/{sync/from-file => sync-from-file}/output.txt (57%) rename acceptance/cmd/{sync/from-file => sync-from-file}/script (68%) diff --git a/acceptance/cmd/sync/from-file/output.txt b/acceptance/cmd/sync-from-file/output.txt similarity index 57% rename from acceptance/cmd/sync/from-file/output.txt rename to acceptance/cmd/sync-from-file/output.txt index ab69a28358..b705d105c8 100644 --- a/acceptance/cmd/sync/from-file/output.txt +++ b/acceptance/cmd/sync-from-file/output.txt @@ -1,8 +1,8 @@ ->>> [CLI] sync . /Users/[USERNAME] --exclude-from .databricksignore +>>> [CLI] sync . /Users/[USERNAME] --exclude-from ignore.test Initial Sync Complete -Uploaded .databricksignore Uploaded .gitignore +Uploaded ignore.test Uploaded project-folder Uploaded project-folder/app.py Uploaded project-folder/app.yaml diff --git a/acceptance/cmd/sync/from-file/script b/acceptance/cmd/sync-from-file/script similarity index 68% rename from acceptance/cmd/sync/from-file/script rename to acceptance/cmd/sync-from-file/script index c00bb1a1eb..1fe44d41e1 100644 --- a/acceptance/cmd/sync/from-file/script +++ b/acceptance/cmd/sync-from-file/script @@ -1,16 +1,7 @@ mkdir "project-folder" "project-folder/blob" "ignored-folder" "ignored-folder/folder1" ".git" touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" "project-folder/blob/1" "project-folder/blob/2" touch "ignored-folder/script.py" "ignored-folder/folder1/script.py" "ignored-folder/folder1/script.yaml" "ignored-folder/folder1/big-blob" -cat > .gitignore << EOF -ignored-folder/ -script -output.txt -repls.json -EOF -cat > .databricksignore << EOF -project-folder/blob/* -project-folder/*.sql -EOF +mv gitignore.test .gitignore cleanup() { rm -rf project-folder ignored-folder .git .databricksignore @@ -18,4 +9,4 @@ cleanup() { trap cleanup EXIT # Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out -trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from '.databricksignore' | grep -v "^Action: " | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from 'ignore.test' | grep -v "^Action: " | sort From 4cd963ba68c591a0d2e8cb9990b012ef2da28ec1 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:01:04 +0200 Subject: [PATCH 13/16] remove empty line and comments checks --- acceptance/acceptance_test.go | 4 ++++ cmd/sync/sync.go | 5 +---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 917561e16a..b786e44a7e 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -85,6 +85,10 @@ func TestAccept(t *testing.T) { testAccept(t, InprocessMode, "") } +func TestAcceptLocal(t *testing.T) { + testAccept(t, InprocessMode, "cmd/sync-from-file") +} + func TestInprocessMode(t *testing.T) { if InprocessMode && !Forcerun { t.Skip("Already tested by TestAccept") diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 948f21d0fd..d965d5a6fc 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -51,10 +51,7 @@ func (f *syncFlags) readExcludeFrom(ctx context.Context) ([]string, error) { scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) - // Skip empty lines and comments - if line != "" && !strings.HasPrefix(line, "#") { - patterns = append(patterns, line) - } + patterns = append(patterns, line) } if err := scanner.Err(); err != nil { From a63bc505825aeb8b706cec98f961da2e96daa47e Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:08:07 +0200 Subject: [PATCH 14/16] fix unit test --- cmd/sync/sync_test.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/cmd/sync/sync_test.go b/cmd/sync/sync_test.go index bb7360731f..e1d6bfd332 100644 --- a/cmd/sync/sync_test.go +++ b/cmd/sync/sync_test.go @@ -79,8 +79,6 @@ func TestExcludeFromFlag(t *testing.T) { excludePatterns := []string{ "*.log", "build/", - "# This is a comment", - "", "temp/*.tmp", } require.NoError(t, os.WriteFile(excludeFromPath, []byte(strings.Join(excludePatterns, "\n")), 0o644)) @@ -90,20 +88,12 @@ func TestExcludeFromFlag(t *testing.T) { cmd := New() cmd.SetContext(cmdctx.SetWorkspaceClient(context.Background(), nil)) - // Test syncOptionsFromArgs - opts, err := f.syncOptionsFromArgs(cmd, []string{local, remote}) - require.NoError(t, err) - - // Expected patterns (should skip comments and empty lines) - expected := []string{"*.log", "build/", "temp/*.tmp"} - assert.ElementsMatch(t, expected, opts.Exclude) - // Test with both exclude flag and exclude-from flag f.exclude = []string{"node_modules/"} - opts, err = f.syncOptionsFromArgs(cmd, []string{local, remote}) + opts, err := f.syncOptionsFromArgs(cmd, []string{local, remote}) require.NoError(t, err) // Should include both exclude flag and exclude-from patterns - expected = []string{"node_modules/", "*.log", "build/", "temp/*.tmp"} + expected := []string{"node_modules/", "*.log", "build/", "temp/*.tmp"} assert.ElementsMatch(t, expected, opts.Exclude) } From 34c8d43e7c940cc3f1a53b75b28a6a6dce9eb7ec Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:22:28 +0200 Subject: [PATCH 15/16] more conditions for the test ignore file: 1) refers to exact file (existing / missing) 2) refers to exact directory (existing / missing) 3) contains "**" --- acceptance/acceptance_test.go | 4 ---- acceptance/cmd/sync-from-file/gitignore.test-fixture | 4 ++++ acceptance/cmd/sync-from-file/ignore.test-fixture | 5 +++++ acceptance/cmd/sync-from-file/output.txt | 4 ++-- acceptance/cmd/sync-from-file/script | 11 ++++++----- 5 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 acceptance/cmd/sync-from-file/gitignore.test-fixture create mode 100644 acceptance/cmd/sync-from-file/ignore.test-fixture diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index b786e44a7e..917561e16a 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -85,10 +85,6 @@ func TestAccept(t *testing.T) { testAccept(t, InprocessMode, "") } -func TestAcceptLocal(t *testing.T) { - testAccept(t, InprocessMode, "cmd/sync-from-file") -} - func TestInprocessMode(t *testing.T) { if InprocessMode && !Forcerun { t.Skip("Already tested by TestAccept") diff --git a/acceptance/cmd/sync-from-file/gitignore.test-fixture b/acceptance/cmd/sync-from-file/gitignore.test-fixture new file mode 100644 index 0000000000..eb48311860 --- /dev/null +++ b/acceptance/cmd/sync-from-file/gitignore.test-fixture @@ -0,0 +1,4 @@ +ignored-folder/ +script +output.txt +repls.json diff --git a/acceptance/cmd/sync-from-file/ignore.test-fixture b/acceptance/cmd/sync-from-file/ignore.test-fixture new file mode 100644 index 0000000000..d03fce8340 --- /dev/null +++ b/acceptance/cmd/sync-from-file/ignore.test-fixture @@ -0,0 +1,5 @@ +project-folder/blob/* +project-folder/static/**/*.txt +project-folder/*.sql +project-folder/app2.py +ignored-folder2/ diff --git a/acceptance/cmd/sync-from-file/output.txt b/acceptance/cmd/sync-from-file/output.txt index b705d105c8..6c4047bc1f 100644 --- a/acceptance/cmd/sync-from-file/output.txt +++ b/acceptance/cmd/sync-from-file/output.txt @@ -1,8 +1,8 @@ ->>> [CLI] sync . /Users/[USERNAME] --exclude-from ignore.test +>>> [CLI] sync . /Users/[USERNAME] --exclude-from ignore.test-fixture Initial Sync Complete Uploaded .gitignore -Uploaded ignore.test +Uploaded ignore.test-fixture Uploaded project-folder Uploaded project-folder/app.py Uploaded project-folder/app.yaml diff --git a/acceptance/cmd/sync-from-file/script b/acceptance/cmd/sync-from-file/script index 1fe44d41e1..3b91cc6cf0 100644 --- a/acceptance/cmd/sync-from-file/script +++ b/acceptance/cmd/sync-from-file/script @@ -1,12 +1,13 @@ -mkdir "project-folder" "project-folder/blob" "ignored-folder" "ignored-folder/folder1" ".git" -touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" "project-folder/blob/1" "project-folder/blob/2" +mkdir "project-folder" "project-folder/blob" "project-folder/static" "project-folder/static/folder1" "project-folder/static/folder2" "ignored-folder" "ignored-folder/folder1" "ignored-folder2" ".git" +touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/app2.py" "project-folder/query.sql" "project-folder/blob/1" "project-folder/blob/2" "ignored-folder2/app.py" +touch "project-folder/static/folder1/1.txt" "project-folder/static/folder2/2.txt" touch "ignored-folder/script.py" "ignored-folder/folder1/script.py" "ignored-folder/folder1/script.yaml" "ignored-folder/folder1/big-blob" -mv gitignore.test .gitignore +mv gitignore.test-fixture .gitignore cleanup() { - rm -rf project-folder ignored-folder .git .databricksignore + rm -rf project-folder ignored-folder ignored-folder2 .git } trap cleanup EXIT # Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out -trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from 'ignore.test' | grep -v "^Action: " | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from 'ignore.test-fixture' | grep -v "^Action: " | sort From a91804738c95bc81fcaf27ce3884e5a80c5d7162 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:44:07 +0200 Subject: [PATCH 16/16] add include-from flag support to cmd sync command --- NEXT_CHANGELOG.md | 2 +- acceptance/cmd/sync-from-file/output.txt | 14 +++++++++++++ acceptance/cmd/sync-from-file/script | 1 + cmd/sync/sync.go | 25 ++++++++++++++++++------ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 8a5e5fbf93..670ceec69c 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,7 +7,7 @@ ### Dependency updates ### CLI -* Added exclude-from flag support to bundle sync command ([#2660](https://github.com/databricks/cli/pull/2660)) +* Added `exclude-from` and `include-from` flags support to sync command ([#2660](https://github.com/databricks/cli/pull/2660)) ### Bundles diff --git a/acceptance/cmd/sync-from-file/output.txt b/acceptance/cmd/sync-from-file/output.txt index 6c4047bc1f..5d3c722b74 100644 --- a/acceptance/cmd/sync-from-file/output.txt +++ b/acceptance/cmd/sync-from-file/output.txt @@ -6,3 +6,17 @@ Uploaded ignore.test-fixture Uploaded project-folder Uploaded project-folder/app.py Uploaded project-folder/app.yaml + +>>> [CLI] sync . /Users/[USERNAME] --include-from ignore.test-fixture +Initial Sync Complete +Uploaded ignored-folder2 +Uploaded ignored-folder2/app.py +Uploaded project-folder/app2.py +Uploaded project-folder/blob +Uploaded project-folder/blob/1 +Uploaded project-folder/blob/2 +Uploaded project-folder/query.sql +Uploaded project-folder/static/folder1 +Uploaded project-folder/static/folder1/1.txt +Uploaded project-folder/static/folder2 +Uploaded project-folder/static/folder2/2.txt diff --git a/acceptance/cmd/sync-from-file/script b/acceptance/cmd/sync-from-file/script index 3b91cc6cf0..1e1adce81b 100644 --- a/acceptance/cmd/sync-from-file/script +++ b/acceptance/cmd/sync-from-file/script @@ -11,3 +11,4 @@ trap cleanup EXIT # Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out trace $CLI sync . /Users/$CURRENT_USER_NAME --exclude-from 'ignore.test-fixture' | grep -v "^Action: " | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --include-from 'ignore.test-fixture' | grep -v "^Action: " | sort diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index d965d5a6fc..727750c5c4 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -35,14 +35,15 @@ type syncFlags struct { include []string dryRun bool excludeFrom string + includeFrom string } -func (f *syncFlags) readExcludeFrom(ctx context.Context) ([]string, error) { - if f.excludeFrom == "" { +func readPatternsFile(filePath string) ([]string, error) { + if filePath == "" { return nil, nil } - data, err := os.ReadFile(f.excludeFrom) + data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read exclude-from file: %w", err) } @@ -71,7 +72,12 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b * return nil, fmt.Errorf("cannot get sync options: %w", err) } - excludePatterns, err := f.readExcludeFrom(cmd.Context()) + excludePatterns, err := readPatternsFile(f.excludeFrom) + if err != nil { + return nil, err + } + + includePatterns, err := readPatternsFile(f.includeFrom) if err != nil { return nil, err } @@ -82,6 +88,7 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b * opts.Exclude = append(opts.Exclude, f.exclude...) opts.Exclude = append(opts.Exclude, excludePatterns...) opts.Include = append(opts.Include, f.include...) + opts.Include = append(opts.Include, includePatterns...) opts.DryRun = f.dryRun return opts, nil } @@ -113,7 +120,12 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn log.Warnf(ctx, "Running in dry-run mode. No actual changes will be made.") } - excludePatterns, err := f.readExcludeFrom(ctx) + excludePatterns, err := readPatternsFile(f.excludeFrom) + if err != nil { + return nil, err + } + + includePatterns, err := readPatternsFile(f.includeFrom) if err != nil { return nil, err } @@ -136,7 +148,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn WorktreeRoot: worktreeRoot, LocalRoot: localRoot, Paths: []string{"."}, - Include: f.include, + Include: append(f.include, includePatterns...), Exclude: append(f.exclude, excludePatterns...), RemotePath: args[1], @@ -174,6 +186,7 @@ func New() *cobra.Command { cmd.Flags().StringSliceVar(&f.exclude, "exclude", nil, "patterns to exclude from sync (can be specified multiple times)") cmd.Flags().StringSliceVar(&f.include, "include", nil, "patterns to include in sync (can be specified multiple times)") cmd.Flags().StringVar(&f.excludeFrom, "exclude-from", "", "file containing patterns to exclude from sync (one pattern per line)") + cmd.Flags().StringVar(&f.includeFrom, "include-from", "", "file containing patterns to include to sync (one pattern per line)") cmd.Flags().BoolVar(&f.dryRun, "dry-run", false, "simulate sync execution without making actual changes") // Wrapper for [root.MustWorkspaceClient] that disables loading authentication configuration from a bundle.