From c48e0c1d2e85f6f115ea4924270fb41df60664ca Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Thu, 3 Apr 2025 21:25:44 +0200 Subject: [PATCH 01/16] Add include/exclude flags to sync and bundle-sync commands --- acceptance/bundle/sync/output.txt | 2 ++ acceptance/bundle/sync/sample-dashboard.lvdash.json | 1 + acceptance/cmd/sync/output.txt | 5 +++++ 3 files changed, 8 insertions(+) create mode 100644 acceptance/bundle/sync/sample-dashboard.lvdash.json diff --git a/acceptance/bundle/sync/output.txt b/acceptance/bundle/sync/output.txt index f23e07dc8d..f9f9912bf9 100644 --- a/acceptance/bundle/sync/output.txt +++ b/acceptance/bundle/sync/output.txt @@ -2,6 +2,8 @@ >>> [CLI] bundle sync --output text Initial Sync Complete Uploaded .gitignore +Uploaded app.py +Uploaded app.yaml Uploaded databricks.yml Uploaded ignored-folder/folder1 Uploaded ignored-folder/folder1/script.py diff --git a/acceptance/bundle/sync/sample-dashboard.lvdash.json b/acceptance/bundle/sync/sample-dashboard.lvdash.json new file mode 100644 index 0000000000..464a8475cc --- /dev/null +++ b/acceptance/bundle/sync/sample-dashboard.lvdash.json @@ -0,0 +1 @@ +{"pages":[{"name":"12724bf2","displayName":"Page One"}]} diff --git a/acceptance/cmd/sync/output.txt b/acceptance/cmd/sync/output.txt index 198e997d4b..c38e7b968a 100644 --- a/acceptance/cmd/sync/output.txt +++ b/acceptance/cmd/sync/output.txt @@ -1,5 +1,6 @@ >>> [CLI] sync . /Users/[USERNAME] +Warn: Failed to read git info: stat /.git: no such file or directory Initial Sync Complete Uploaded .gitignore Uploaded project-folder @@ -36,9 +37,13 @@ Uploaded project-folder/app.yaml Uploaded project-folder/query.sql >>> [CLI] sync . /Users/[USERNAME] +Warn: Failed to read git info: stat /.git: no such file or directory Deleted ignored-folder Deleted ignored-folder/folder1 Deleted ignored-folder/folder1/script.py Deleted ignored-folder/folder1/script.yaml Deleted ignored-folder/script.py Initial Sync Complete +Uploaded app.py +Uploaded app.yaml +Uploaded query.sql From 16d11acdeb52904831b0d8d71b147e3b659bbcd1 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:46:56 +0200 Subject: [PATCH 02/16] simplify acceptance tests setup --- acceptance/bundle/sync/output.txt | 2 -- acceptance/cmd/sync/output.txt | 9 ++++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/acceptance/bundle/sync/output.txt b/acceptance/bundle/sync/output.txt index f9f9912bf9..f23e07dc8d 100644 --- a/acceptance/bundle/sync/output.txt +++ b/acceptance/bundle/sync/output.txt @@ -2,8 +2,6 @@ >>> [CLI] bundle sync --output text Initial Sync Complete Uploaded .gitignore -Uploaded app.py -Uploaded app.yaml Uploaded databricks.yml Uploaded ignored-folder/folder1 Uploaded ignored-folder/folder1/script.py diff --git a/acceptance/cmd/sync/output.txt b/acceptance/cmd/sync/output.txt index c38e7b968a..e979329e64 100644 --- a/acceptance/cmd/sync/output.txt +++ b/acceptance/cmd/sync/output.txt @@ -1,6 +1,5 @@ >>> [CLI] sync . /Users/[USERNAME] -Warn: Failed to read git info: stat /.git: no such file or directory Initial Sync Complete Uploaded .gitignore Uploaded project-folder @@ -37,13 +36,13 @@ Uploaded project-folder/app.yaml Uploaded project-folder/query.sql >>> [CLI] sync . /Users/[USERNAME] -Warn: Failed to read git info: stat /.git: no such file or directory Deleted ignored-folder Deleted ignored-folder/folder1 Deleted ignored-folder/folder1/script.py Deleted ignored-folder/folder1/script.yaml Deleted ignored-folder/script.py Initial Sync Complete -Uploaded app.py -Uploaded app.yaml -Uploaded query.sql +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml +Uploaded project-folder/query.sql From af74873bca462c1b684022b404de9b16c1da2e8c Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:16:04 +0200 Subject: [PATCH 03/16] simplify acceptance test --- acceptance/bundle/sync/sample-dashboard.lvdash.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 acceptance/bundle/sync/sample-dashboard.lvdash.json diff --git a/acceptance/bundle/sync/sample-dashboard.lvdash.json b/acceptance/bundle/sync/sample-dashboard.lvdash.json deleted file mode 100644 index 464a8475cc..0000000000 --- a/acceptance/bundle/sync/sample-dashboard.lvdash.json +++ /dev/null @@ -1 +0,0 @@ -{"pages":[{"name":"12724bf2","displayName":"Page One"}]} From 6c25f78da38bd670383c580d98c4666b7188f885 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:28:38 +0200 Subject: [PATCH 04/16] add a negated include example to the acceptance test --- acceptance/cmd/sync/output.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/acceptance/cmd/sync/output.txt b/acceptance/cmd/sync/output.txt index e979329e64..198e997d4b 100644 --- a/acceptance/cmd/sync/output.txt +++ b/acceptance/cmd/sync/output.txt @@ -42,7 +42,3 @@ Deleted ignored-folder/folder1/script.py Deleted ignored-folder/folder1/script.yaml 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 2926b28ead0f274db9d55a849562792323b2690b Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:38:15 +0200 Subject: [PATCH 05/16] add --dry-run flag to sync and bundle-sync commands --- acceptance/bundle/sync/dryrun/databricks.yml | 7 ++++ acceptance/bundle/sync/dryrun/output.txt | 5 +++ acceptance/bundle/sync/dryrun/script | 22 ++++++++++++ acceptance/cmd/sync/dryrun/output.txt | 6 ++++ acceptance/cmd/sync/dryrun/script | 33 +++++++++++++++++ acceptance/cmd/sync/script | 1 + cmd/bundle/sync.go | 13 +++++++ cmd/sync/sync.go | 8 +++++ libs/sync/event.go | 38 ++++++++++++++++---- libs/sync/sync.go | 18 ++++++---- libs/sync/watchdog.go | 36 +++++++++++-------- 11 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 acceptance/bundle/sync/dryrun/databricks.yml create mode 100644 acceptance/bundle/sync/dryrun/output.txt create mode 100644 acceptance/bundle/sync/dryrun/script create mode 100644 acceptance/cmd/sync/dryrun/output.txt create mode 100644 acceptance/cmd/sync/dryrun/script diff --git a/acceptance/bundle/sync/dryrun/databricks.yml b/acceptance/bundle/sync/dryrun/databricks.yml new file mode 100644 index 0000000000..c164f486a0 --- /dev/null +++ b/acceptance/bundle/sync/dryrun/databricks.yml @@ -0,0 +1,7 @@ +bundle: + name: bundle-sync-test + +resources: + dashboards: + dashboard1: + display_name: My dashboard diff --git a/acceptance/bundle/sync/dryrun/output.txt b/acceptance/bundle/sync/dryrun/output.txt new file mode 100644 index 0000000000..2004e90e11 --- /dev/null +++ b/acceptance/bundle/sync/dryrun/output.txt @@ -0,0 +1,5 @@ + +>>> [CLI] bundle sync --dry-run --output text +grep: invalid character range + +Exit code: 2 diff --git a/acceptance/bundle/sync/dryrun/script b/acceptance/bundle/sync/dryrun/script new file mode 100644 index 0000000000..276cf8da5a --- /dev/null +++ b/acceptance/bundle/sync/dryrun/script @@ -0,0 +1,22 @@ +mkdir "project-folder" "ignored-folder" "ignored-folder/folder1" +touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" +touch "ignored-folder/script.py" "ignored-folder/folder1/script.py" +cat > .gitignore << EOF +ignored-folder/ +script +output.txt +repls.json +EOF + +cleanup() { + rm .gitignore + rm -rf project-folder ignored-folder .git .databricks +} +trap cleanup EXIT + +# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out +trace $CLI bundle sync --dry-run --output text | grep -v "^[DRY-RUN] Action: " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^[DRY-RUN] Action: " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^[DRY-RUN] Action: " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^[DRY-RUN] Action: " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^[DRY-RUN] Action: " | sort diff --git a/acceptance/cmd/sync/dryrun/output.txt b/acceptance/cmd/sync/dryrun/output.txt new file mode 100644 index 0000000000..1d8b94fffe --- /dev/null +++ b/acceptance/cmd/sync/dryrun/output.txt @@ -0,0 +1,6 @@ + +>>> [CLI] sync . /Users/[USERNAME] --dry-run +grep: invalid character range +Warn: Running in DRY-RUN mode. No actual changes will be made. + +Exit code: 2 diff --git a/acceptance/cmd/sync/dryrun/script b/acceptance/cmd/sync/dryrun/script new file mode 100644 index 0000000000..67cd9f2aa0 --- /dev/null +++ b/acceptance/cmd/sync/dryrun/script @@ -0,0 +1,33 @@ +mkdir "project-folder" "ignored-folder" "ignored-folder/folder1" ".git" +touch "project-folder/app.yaml" "project-folder/app.py" "project-folder/query.sql" +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 + +cleanup() { + rm .gitignore + rm -rf project-folder ignored-folder .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 --dry-run | grep -v "^[DRY-RUN MODE] Action" | sort + +# excluding by mask: +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' | grep -v "^[DRY-RUN MODE] Action" | sort + +# combining excludes: +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' | grep -v "^[DRY-RUN MODE] Action" | sort + +# combining excludes and includes: +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' | grep -v "^[DRY-RUN MODE] Action" | sort + +# include sub-folders: +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' | grep -v "^[DRY-RUN MODE] Action" | sort + +# use negated include to exclude files from syncing: +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --include 'ignored-folder/**' --include '!ignored-folder/folder1/big-blob' | grep -v "^[DRY-RUN MODE] Action" | sort diff --git a/acceptance/cmd/sync/script b/acceptance/cmd/sync/script index 7a4624912f..5b9bcb2a3e 100644 --- a/acceptance/cmd/sync/script +++ b/acceptance/cmd/sync/script @@ -9,6 +9,7 @@ repls.json EOF cleanup() { + rm .gitignore rm -rf project-folder ignored-folder .git } trap cleanup EXIT diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index 25475206d0..da0ef2888d 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -22,6 +22,9 @@ type syncFlags struct { full bool watch bool output flags.Output + exclude []string + include []string + dryRun bool } func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) (*sync.SyncOptions, error) { @@ -47,6 +50,9 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, b *bundle.Bundle) opts.Full = f.full opts.PollInterval = f.interval + opts.Exclude = append(opts.Exclude, f.exclude...) + opts.Include = append(opts.Include, f.include...) + opts.DryRun = f.dryRun return opts, nil } @@ -62,6 +68,9 @@ func newSyncCommand() *cobra.Command { cmd.Flags().BoolVar(&f.full, "full", false, "perform full synchronization (default is incremental)") 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().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, "show what would be uploaded/deleted without making any changes") cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() @@ -89,6 +98,10 @@ func newSyncCommand() *cobra.Command { log.Infof(ctx, "Remote file sync location: %v", opts.RemotePath) + if opts.DryRun { + log.Infof(ctx, "Running in dry-run mode. No changes will be made.") + } + if f.watch { return s.RunContinuous(ctx) } diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 4646288663..a8c805073c 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -29,6 +29,7 @@ type syncFlags struct { output flags.Output exclude []string include []string + dryRun bool } func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b *bundle.Bundle) (*sync.SyncOptions, error) { @@ -46,6 +47,7 @@ func (f *syncFlags) syncOptionsFromBundle(cmd *cobra.Command, args []string, b * opts.WorktreeRoot = b.WorktreeRoot opts.Exclude = append(opts.Exclude, f.exclude...) opts.Include = append(opts.Include, f.include...) + opts.DryRun = f.dryRun return opts, nil } @@ -72,6 +74,10 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn ctx := cmd.Context() client := cmdctx.WorkspaceClient(ctx) + if f.dryRun { + log.Warnf(ctx, "Running in DRY-RUN mode. No actual changes will be made.") + } + localRoot := vfs.MustNew(args[0]) info, err := git.FetchRepositoryInfo(ctx, localRoot.Native(), client) if err != nil { @@ -105,6 +111,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn WorkspaceClient: client, OutputHandler: outputHandler, + DryRun: f.dryRun, } return &opts, nil } @@ -126,6 +133,7 @@ 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, "show what would be uploaded/deleted without making any changes") // Wrapper for [root.MustWorkspaceClient] that disables loading authentication configuration from a bundle. mustWorkspaceClient := func(cmd *cobra.Command, args []string) error { diff --git a/libs/sync/event.go b/libs/sync/event.go index 510a019546..c7d8c1f7e1 100644 --- a/libs/sync/event.go +++ b/libs/sync/event.go @@ -30,13 +30,15 @@ type EventBase struct { Timestamp time.Time `json:"timestamp"` Seq int `json:"seq"` Type EventType `json:"type"` + DryRun bool `json:"dry_run,omitempty"` } -func newEventBase(seq int, typ EventType) *EventBase { +func newEventBase(seq int, typ EventType, dryRun bool) *EventBase { return &EventBase{ Timestamp: time.Now(), Seq: seq, Type: typ, + DryRun: dryRun, } } @@ -70,12 +72,16 @@ func (e *EventStart) String() string { return "" } + if e.DryRun { + return "[DRY-RUN MODE] Action: " + e.EventChanges.String() + } + return "Action: " + e.EventChanges.String() } -func newEventStart(seq int, put, delete []string) Event { +func newEventStart(seq int, put, delete []string, dryRun bool) Event { return &EventStart{ - EventBase: newEventBase(seq, EventTypeStart), + EventBase: newEventBase(seq, EventTypeStart, dryRun), EventChanges: &EventChanges{Put: put, Delete: delete}, } } @@ -96,6 +102,17 @@ func (e *EventSyncProgress) String() string { return "" } + if e.DryRun { + switch e.Action { + case EventActionPut: + return "[DRY-RUN] Would upload: " + e.Path + case EventActionDelete: + return "[DRY-RUN] Would delete: " + e.Path + default: + panic("invalid action") + } + } + switch e.Action { case EventActionPut: return "Uploaded " + e.Path @@ -106,9 +123,9 @@ func (e *EventSyncProgress) String() string { } } -func newEventProgress(seq int, action EventAction, path string, progress float32) Event { +func newEventProgress(seq int, action EventAction, path string, progress float32, dryRun bool) Event { return &EventSyncProgress{ - EventBase: newEventBase(seq, EventTypeProgress), + EventBase: newEventBase(seq, EventTypeProgress, dryRun), Action: action, Path: path, @@ -123,6 +140,9 @@ type EventSyncComplete struct { func (e *EventSyncComplete) String() string { if e.Seq == 0 { + if e.DryRun { + return "[DRY-RUN] Initial Sync Complete" + } return "Initial Sync Complete" } @@ -130,12 +150,16 @@ func (e *EventSyncComplete) String() string { return "" } + if e.DryRun { + return "[DRY-RUN] Complete" + } + return "Complete" } -func newEventComplete(seq int, put, delete []string) Event { +func newEventComplete(seq int, put, delete []string, dryRun bool) Event { return &EventSyncComplete{ - EventBase: newEventBase(seq, EventTypeComplete), + EventBase: newEventBase(seq, EventTypeComplete, dryRun), EventChanges: &EventChanges{Put: put, Delete: delete}, } } diff --git a/libs/sync/sync.go b/libs/sync/sync.go index 4d14f745a9..dae65c7da3 100644 --- a/libs/sync/sync.go +++ b/libs/sync/sync.go @@ -41,6 +41,8 @@ type SyncOptions struct { Host string OutputHandler OutputHandler + + DryRun bool } type Sync struct { @@ -156,11 +158,11 @@ func (s *Sync) notifyStart(ctx context.Context, d diff) { if s.seq > 0 && d.IsEmpty() { return } - s.notifier.Notify(ctx, newEventStart(s.seq, d.put, d.delete)) + s.notifier.Notify(ctx, newEventStart(s.seq, d.put, d.delete, s.DryRun)) } func (s *Sync) notifyProgress(ctx context.Context, action EventAction, path string, progress float32) { - s.notifier.Notify(ctx, newEventProgress(s.seq, action, path, progress)) + s.notifier.Notify(ctx, newEventProgress(s.seq, action, path, progress, s.DryRun)) } func (s *Sync) notifyComplete(ctx context.Context, d diff) { @@ -168,7 +170,7 @@ func (s *Sync) notifyComplete(ctx context.Context, d diff) { if s.seq > 0 && d.IsEmpty() { return } - s.notifier.Notify(ctx, newEventComplete(s.seq, d.put, d.delete)) + s.notifier.Notify(ctx, newEventComplete(s.seq, d.put, d.delete, s.DryRun)) s.seq++ } @@ -199,10 +201,12 @@ func (s *Sync) RunOnce(ctx context.Context) ([]fileset.File, error) { return files, err } - err = s.snapshot.Save(ctx) - if err != nil { - log.Errorf(ctx, "cannot store snapshot: %s", err) - return files, err + if !s.DryRun { + err = s.snapshot.Save(ctx) + if err != nil { + log.Errorf(ctx, "cannot store snapshot: %s", err) + return files, err + } } s.notifyComplete(ctx, change) diff --git a/libs/sync/watchdog.go b/libs/sync/watchdog.go index cc2ca83c56..4a47acfb83 100644 --- a/libs/sync/watchdog.go +++ b/libs/sync/watchdog.go @@ -17,9 +17,11 @@ const MaxRequestsInFlight = 20 func (s *Sync) applyDelete(ctx context.Context, remoteName string) error { s.notifyProgress(ctx, EventActionDelete, remoteName, 0.0) - err := s.filer.Delete(ctx, remoteName) - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return err + if !s.DryRun { + err := s.filer.Delete(ctx, remoteName) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return err + } } s.notifyProgress(ctx, EventActionDelete, remoteName, 1.0) @@ -30,10 +32,12 @@ func (s *Sync) applyDelete(ctx context.Context, remoteName string) error { func (s *Sync) applyRmdir(ctx context.Context, remoteName string) error { s.notifyProgress(ctx, EventActionDelete, remoteName, 0.0) - err := s.filer.Delete(ctx, remoteName) - if err != nil { - // Directory deletion is opportunistic, so we ignore errors. - log.Debugf(ctx, "error removing directory %s: %s", remoteName, err) + if !s.DryRun { + err := s.filer.Delete(ctx, remoteName) + if err != nil { + // Directory deletion is opportunistic, so we ignore errors. + log.Debugf(ctx, "error removing directory %s: %s", remoteName, err) + } } s.notifyProgress(ctx, EventActionDelete, remoteName, 1.0) @@ -44,9 +48,11 @@ func (s *Sync) applyRmdir(ctx context.Context, remoteName string) error { func (s *Sync) applyMkdir(ctx context.Context, localName string) error { s.notifyProgress(ctx, EventActionPut, localName, 0.0) - err := s.filer.Mkdir(ctx, localName) - if err != nil { - return err + if !s.DryRun { + err := s.filer.Mkdir(ctx, localName) + if err != nil { + return err + } } s.notifyProgress(ctx, EventActionPut, localName, 1.0) @@ -64,10 +70,12 @@ func (s *Sync) applyPut(ctx context.Context, localName string) error { defer localFile.Close() - opts := []filer.WriteMode{filer.CreateParentDirectories, filer.OverwriteIfExists} - err = s.filer.Write(ctx, localName, localFile, opts...) - if err != nil { - return err + if !s.DryRun { + opts := []filer.WriteMode{filer.CreateParentDirectories, filer.OverwriteIfExists} + err = s.filer.Write(ctx, localName, localFile, opts...) + if err != nil { + return err + } } s.notifyProgress(ctx, EventActionPut, localName, 1.0) From ac5fa668c176bd9eb33f8dd4bf4f5f25f53a2885 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:42:56 +0200 Subject: [PATCH 06/16] change flag description --- acceptance/bundle/help/bundle-sync/output.txt | 1 + cmd/bundle/sync.go | 2 +- cmd/sync/sync.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/acceptance/bundle/help/bundle-sync/output.txt b/acceptance/bundle/help/bundle-sync/output.txt index 992138a206..f9bcc40cfa 100644 --- a/acceptance/bundle/help/bundle-sync/output.txt +++ b/acceptance/bundle/help/bundle-sync/output.txt @@ -6,6 +6,7 @@ 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) diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index da0ef2888d..7136ee4dab 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -70,7 +70,7 @@ func newSyncCommand() *cobra.Command { cmd.Flags().Var(&f.output, "output", "type of the 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, "show what would be uploaded/deleted without making any changes") + cmd.Flags().BoolVar(&f.dryRun, "dry-run", false, "simulate sync execution without making actual changes") cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index a8c805073c..de79aae41e 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -133,7 +133,7 @@ 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, "show what would be uploaded/deleted without making any changes") + 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 60a2ad68eb2fca7273e3b7b03255312b6a0889ce Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:08:37 +0200 Subject: [PATCH 07/16] fix event_test.go --- libs/sync/event_test.go | 52 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/libs/sync/event_test.go b/libs/sync/event_test.go index 3fcb0709bc..e0b7b35bfe 100644 --- a/libs/sync/event_test.go +++ b/libs/sync/event_test.go @@ -28,32 +28,32 @@ func jsonEqual(t *testing.T, expected string, e Event) { func TestEventStart(t *testing.T) { var e Event - e = newEventStart(0, []string{"put"}, []string{"delete"}) + e = newEventStart(0, []string{"put"}, []string{"delete"}, false) assert.Equal(t, "Action: PUT: put, DELETE: delete", e.String()) - e = newEventStart(1, []string{"put"}, []string{}) + e = newEventStart(1, []string{"put"}, []string{}, false) assert.Equal(t, "Action: PUT: put", e.String()) - e = newEventStart(2, []string{}, []string{"delete"}) + e = newEventStart(2, []string{}, []string{"delete"}, false) assert.Equal(t, "Action: DELETE: delete", e.String()) - e = newEventStart(3, []string{}, []string{}) + e = newEventStart(3, []string{}, []string{}, false) assert.Equal(t, "", e.String()) } func TestEventStartJSON(t *testing.T) { var e Event - e = newEventStart(0, []string{"put"}, []string{"delete"}) + e = newEventStart(0, []string{"put"}, []string{"delete"}, false) jsonEqual(t, `{"seq": 0, "type": "start", "put": ["put"], "delete": ["delete"]}`, e) - e = newEventStart(1, []string{"put"}, []string{}) + e = newEventStart(1, []string{"put"}, []string{}, false) jsonEqual(t, `{"seq": 1, "type": "start", "put": ["put"]}`, e) - e = newEventStart(2, []string{}, []string{"delete"}) + e = newEventStart(2, []string{}, []string{"delete"}, false) jsonEqual(t, `{"seq": 2, "type": "start", "delete": ["delete"]}`, e) - e = newEventStart(3, []string{}, []string{}) + e = newEventStart(3, []string{}, []string{}, false) jsonEqual(t, `{"seq": 3, "type": "start"}`, e) } @@ -61,70 +61,70 @@ func TestEventProgress(t *testing.T) { var e Event // Empty string if no progress has been made. - e = newEventProgress(0, EventActionPut, "path", 0.0) + e = newEventProgress(0, EventActionPut, "path", 0.0, false) assert.Equal(t, "", e.String()) - e = newEventProgress(1, EventActionPut, "path", 1.0) + e = newEventProgress(1, EventActionPut, "path", 1.0, false) assert.Equal(t, "Uploaded path", e.String()) // Empty string if no progress has been made. - e = newEventProgress(2, EventActionDelete, "path", 0.0) + e = newEventProgress(2, EventActionDelete, "path", 0.0, false) assert.Equal(t, "", e.String()) - e = newEventProgress(3, EventActionDelete, "path", 1.0) + e = newEventProgress(3, EventActionDelete, "path", 1.0, false) assert.Equal(t, "Deleted path", e.String()) } func TestEventProgressJSON(t *testing.T) { var e Event - e = newEventProgress(0, EventActionPut, "path", 0.0) + e = newEventProgress(0, EventActionPut, "path", 0.0, false) jsonEqual(t, `{"seq": 0, "type": "progress", "action": "put", "path": "path", "progress": 0.0}`, e) - e = newEventProgress(0, EventActionPut, "path", 0.5) + e = newEventProgress(0, EventActionPut, "path", 0.5, false) jsonEqual(t, `{"seq": 0, "type": "progress", "action": "put", "path": "path", "progress": 0.5}`, e) - e = newEventProgress(1, EventActionPut, "path", 1.0) + e = newEventProgress(1, EventActionPut, "path", 1.0, false) jsonEqual(t, `{"seq": 1, "type": "progress", "action": "put", "path": "path", "progress": 1.0}`, e) - e = newEventProgress(2, EventActionDelete, "path", 0.0) + e = newEventProgress(2, EventActionDelete, "path", 0.0, false) jsonEqual(t, `{"seq": 2, "type": "progress", "action": "delete", "path": "path", "progress": 0.0}`, e) - e = newEventProgress(2, EventActionDelete, "path", 0.5) + e = newEventProgress(2, EventActionDelete, "path", 0.5, false) jsonEqual(t, `{"seq": 2, "type": "progress", "action": "delete", "path": "path", "progress": 0.5}`, e) - e = newEventProgress(3, EventActionDelete, "path", 1.0) + e = newEventProgress(3, EventActionDelete, "path", 1.0, false) jsonEqual(t, `{"seq": 3, "type": "progress", "action": "delete", "path": "path", "progress": 1.0}`, e) } func TestEventComplete(t *testing.T) { var e Event - e = newEventComplete(0, []string{"put"}, []string{"delete"}) + e = newEventComplete(0, []string{"put"}, []string{"delete"}, false) assert.Equal(t, "Initial Sync Complete", e.String()) - e = newEventComplete(1, []string{"put"}, []string{}) + e = newEventComplete(1, []string{"put"}, []string{}, false) assert.Equal(t, "Complete", e.String()) - e = newEventComplete(2, []string{}, []string{"delete"}) + e = newEventComplete(2, []string{}, []string{"delete"}, false) assert.Equal(t, "Complete", e.String()) - e = newEventComplete(3, []string{}, []string{}) + e = newEventComplete(3, []string{}, []string{}, false) assert.Equal(t, "", e.String()) } func TestEventCompleteJSON(t *testing.T) { var e Event - e = newEventComplete(0, []string{"put"}, []string{"delete"}) + e = newEventComplete(0, []string{"put"}, []string{"delete"}, false) jsonEqual(t, `{"seq": 0, "type": "complete", "put": ["put"], "delete": ["delete"]}`, e) - e = newEventComplete(1, []string{"put"}, []string{}) + e = newEventComplete(1, []string{"put"}, []string{}, false) jsonEqual(t, `{"seq": 1, "type": "complete", "put": ["put"]}`, e) - e = newEventComplete(2, []string{}, []string{"delete"}) + e = newEventComplete(2, []string{}, []string{"delete"}, false) jsonEqual(t, `{"seq": 2, "type": "complete", "delete": ["delete"]}`, e) - e = newEventComplete(3, []string{}, []string{}) + e = newEventComplete(3, []string{}, []string{}, false) jsonEqual(t, `{"seq": 3, "type": "complete"}`, e) } From 33f346e5cacc54a572970166d6b61375158aa295 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:16:00 +0200 Subject: [PATCH 08/16] modify event_test.go to test dry run mode --- libs/sync/event_test.go | 620 ++++++++++++++++++++++++++++++++++------ 1 file changed, 534 insertions(+), 86 deletions(-) diff --git a/libs/sync/event_test.go b/libs/sync/event_test.go index e0b7b35bfe..35d5ccaaf9 100644 --- a/libs/sync/event_test.go +++ b/libs/sync/event_test.go @@ -26,105 +26,553 @@ func jsonEqual(t *testing.T, expected string, e Event) { } func TestEventStart(t *testing.T) { - var e Event - - e = newEventStart(0, []string{"put"}, []string{"delete"}, false) - assert.Equal(t, "Action: PUT: put, DELETE: delete", e.String()) - - e = newEventStart(1, []string{"put"}, []string{}, false) - assert.Equal(t, "Action: PUT: put", e.String()) - - e = newEventStart(2, []string{}, []string{"delete"}, false) - assert.Equal(t, "Action: DELETE: delete", e.String()) - - e = newEventStart(3, []string{}, []string{}, false) - assert.Equal(t, "", e.String()) + tests := []struct { + name string + seq int + put []string + delete []string + dryRun bool + expected string + }{ + { + name: "put and delete without dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: false, + expected: "Action: PUT: put, DELETE: delete", + }, + { + name: "put and delete with dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: true, + expected: "[DRY-RUN MODE] Action: PUT: put, DELETE: delete", + }, + { + name: "only put without dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: false, + expected: "Action: PUT: put", + }, + { + name: "only put with dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: true, + expected: "[DRY-RUN MODE] Action: PUT: put", + }, + { + name: "only delete without dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: false, + expected: "Action: DELETE: delete", + }, + { + name: "only delete with dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: true, + expected: "[DRY-RUN MODE] Action: DELETE: delete", + }, + { + name: "empty without dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: false, + expected: "", + }, + { + name: "empty with dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: true, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := newEventStart(tt.seq, tt.put, tt.delete, tt.dryRun) + assert.Equal(t, tt.expected, e.String()) + }) + } } func TestEventStartJSON(t *testing.T) { - var e Event - - e = newEventStart(0, []string{"put"}, []string{"delete"}, false) - jsonEqual(t, `{"seq": 0, "type": "start", "put": ["put"], "delete": ["delete"]}`, e) - - e = newEventStart(1, []string{"put"}, []string{}, false) - jsonEqual(t, `{"seq": 1, "type": "start", "put": ["put"]}`, e) - - e = newEventStart(2, []string{}, []string{"delete"}, false) - jsonEqual(t, `{"seq": 2, "type": "start", "delete": ["delete"]}`, e) - - e = newEventStart(3, []string{}, []string{}, false) - jsonEqual(t, `{"seq": 3, "type": "start"}`, e) + tests := []struct { + name string + seq int + put []string + delete []string + dryRun bool + expected string + }{ + { + name: "put and delete without dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: false, + expected: `{"seq": 0, "type": "start", "put": ["put"], "delete": ["delete"]}`, + }, + { + name: "put and delete with dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: true, + expected: `{"seq": 0, "type": "start", "dry_run": true, "put": ["put"], "delete": ["delete"]}`, + }, + { + name: "only put without dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: false, + expected: `{"seq": 1, "type": "start", "put": ["put"]}`, + }, + { + name: "only put with dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: true, + expected: `{"seq": 1, "type": "start", "dry_run": true, "put": ["put"]}`, + }, + { + name: "only delete without dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: false, + expected: `{"seq": 2, "type": "start", "delete": ["delete"]}`, + }, + { + name: "only delete with dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: true, + expected: `{"seq": 2, "type": "start", "dry_run": true, "delete": ["delete"]}`, + }, + { + name: "empty without dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: false, + expected: `{"seq": 3, "type": "start"}`, + }, + { + name: "empty with dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: true, + expected: `{"seq": 3, "type": "start", "dry_run": true}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := newEventStart(tt.seq, tt.put, tt.delete, tt.dryRun) + jsonEqual(t, tt.expected, e) + }) + } } func TestEventProgress(t *testing.T) { - var e Event - - // Empty string if no progress has been made. - e = newEventProgress(0, EventActionPut, "path", 0.0, false) - assert.Equal(t, "", e.String()) - - e = newEventProgress(1, EventActionPut, "path", 1.0, false) - assert.Equal(t, "Uploaded path", e.String()) - - // Empty string if no progress has been made. - e = newEventProgress(2, EventActionDelete, "path", 0.0, false) - assert.Equal(t, "", e.String()) - - e = newEventProgress(3, EventActionDelete, "path", 1.0, false) - assert.Equal(t, "Deleted path", e.String()) + tests := []struct { + name string + seq int + action EventAction + path string + progress float32 + dryRun bool + expected string + }{ + { + name: "put no progress without dry run", + seq: 0, + action: EventActionPut, + path: "path", + progress: 0.0, + dryRun: false, + expected: "", + }, + { + name: "put no progress with dry run", + seq: 0, + action: EventActionPut, + path: "path", + progress: 0.0, + dryRun: true, + expected: "", + }, + { + name: "put completed without dry run", + seq: 1, + action: EventActionPut, + path: "path", + progress: 1.0, + dryRun: false, + expected: "Uploaded path", + }, + { + name: "put completed with dry run", + seq: 1, + action: EventActionPut, + path: "path", + progress: 1.0, + dryRun: true, + expected: "[DRY-RUN] Would upload: path", + }, + { + name: "delete no progress without dry run", + seq: 2, + action: EventActionDelete, + path: "path", + progress: 0.0, + dryRun: false, + expected: "", + }, + { + name: "delete no progress with dry run", + seq: 2, + action: EventActionDelete, + path: "path", + progress: 0.0, + dryRun: true, + expected: "", + }, + { + name: "delete completed without dry run", + seq: 3, + action: EventActionDelete, + path: "path", + progress: 1.0, + dryRun: false, + expected: "Deleted path", + }, + { + name: "delete completed with dry run", + seq: 3, + action: EventActionDelete, + path: "path", + progress: 1.0, + dryRun: true, + expected: "[DRY-RUN] Would delete: path", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := newEventProgress(tt.seq, tt.action, tt.path, tt.progress, tt.dryRun) + assert.Equal(t, tt.expected, e.String()) + }) + } } func TestEventProgressJSON(t *testing.T) { - var e Event - - e = newEventProgress(0, EventActionPut, "path", 0.0, false) - jsonEqual(t, `{"seq": 0, "type": "progress", "action": "put", "path": "path", "progress": 0.0}`, e) - - e = newEventProgress(0, EventActionPut, "path", 0.5, false) - jsonEqual(t, `{"seq": 0, "type": "progress", "action": "put", "path": "path", "progress": 0.5}`, e) - - e = newEventProgress(1, EventActionPut, "path", 1.0, false) - jsonEqual(t, `{"seq": 1, "type": "progress", "action": "put", "path": "path", "progress": 1.0}`, e) - - e = newEventProgress(2, EventActionDelete, "path", 0.0, false) - jsonEqual(t, `{"seq": 2, "type": "progress", "action": "delete", "path": "path", "progress": 0.0}`, e) - - e = newEventProgress(2, EventActionDelete, "path", 0.5, false) - jsonEqual(t, `{"seq": 2, "type": "progress", "action": "delete", "path": "path", "progress": 0.5}`, e) - - e = newEventProgress(3, EventActionDelete, "path", 1.0, false) - jsonEqual(t, `{"seq": 3, "type": "progress", "action": "delete", "path": "path", "progress": 1.0}`, e) + tests := []struct { + name string + seq int + action EventAction + path string + progress float32 + dryRun bool + expected string + }{ + { + name: "put no progress without dry run", + seq: 0, + action: EventActionPut, + path: "path", + progress: 0.0, + dryRun: false, + expected: `{"seq": 0, "type": "progress", "action": "put", "path": "path", "progress": 0.0}`, + }, + { + name: "put no progress with dry run", + seq: 0, + action: EventActionPut, + path: "path", + progress: 0.0, + dryRun: true, + expected: `{"seq": 0, "type": "progress", "dry_run": true, "action": "put", "path": "path", "progress": 0.0}`, + }, + { + name: "put half progress without dry run", + seq: 0, + action: EventActionPut, + path: "path", + progress: 0.5, + dryRun: false, + expected: `{"seq": 0, "type": "progress", "action": "put", "path": "path", "progress": 0.5}`, + }, + { + name: "put half progress with dry run", + seq: 0, + action: EventActionPut, + path: "path", + progress: 0.5, + dryRun: true, + expected: `{"seq": 0, "type": "progress", "dry_run": true, "action": "put", "path": "path", "progress": 0.5}`, + }, + { + name: "put completed without dry run", + seq: 1, + action: EventActionPut, + path: "path", + progress: 1.0, + dryRun: false, + expected: `{"seq": 1, "type": "progress", "action": "put", "path": "path", "progress": 1.0}`, + }, + { + name: "put completed with dry run", + seq: 1, + action: EventActionPut, + path: "path", + progress: 1.0, + dryRun: true, + expected: `{"seq": 1, "type": "progress", "dry_run": true, "action": "put", "path": "path", "progress": 1.0}`, + }, + { + name: "delete no progress without dry run", + seq: 2, + action: EventActionDelete, + path: "path", + progress: 0.0, + dryRun: false, + expected: `{"seq": 2, "type": "progress", "action": "delete", "path": "path", "progress": 0.0}`, + }, + { + name: "delete no progress with dry run", + seq: 2, + action: EventActionDelete, + path: "path", + progress: 0.0, + dryRun: true, + expected: `{"seq": 2, "type": "progress", "dry_run": true, "action": "delete", "path": "path", "progress": 0.0}`, + }, + { + name: "delete half progress without dry run", + seq: 2, + action: EventActionDelete, + path: "path", + progress: 0.5, + dryRun: false, + expected: `{"seq": 2, "type": "progress", "action": "delete", "path": "path", "progress": 0.5}`, + }, + { + name: "delete half progress with dry run", + seq: 2, + action: EventActionDelete, + path: "path", + progress: 0.5, + dryRun: true, + expected: `{"seq": 2, "type": "progress", "dry_run": true, "action": "delete", "path": "path", "progress": 0.5}`, + }, + { + name: "delete completed without dry run", + seq: 3, + action: EventActionDelete, + path: "path", + progress: 1.0, + dryRun: false, + expected: `{"seq": 3, "type": "progress", "action": "delete", "path": "path", "progress": 1.0}`, + }, + { + name: "delete completed with dry run", + seq: 3, + action: EventActionDelete, + path: "path", + progress: 1.0, + dryRun: true, + expected: `{"seq": 3, "type": "progress", "dry_run": true, "action": "delete", "path": "path", "progress": 1.0}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := newEventProgress(tt.seq, tt.action, tt.path, tt.progress, tt.dryRun) + jsonEqual(t, tt.expected, e) + }) + } } func TestEventComplete(t *testing.T) { - var e Event - - e = newEventComplete(0, []string{"put"}, []string{"delete"}, false) - assert.Equal(t, "Initial Sync Complete", e.String()) - - e = newEventComplete(1, []string{"put"}, []string{}, false) - assert.Equal(t, "Complete", e.String()) - - e = newEventComplete(2, []string{}, []string{"delete"}, false) - assert.Equal(t, "Complete", e.String()) - - e = newEventComplete(3, []string{}, []string{}, false) - assert.Equal(t, "", e.String()) + tests := []struct { + name string + seq int + put []string + delete []string + dryRun bool + expected string + }{ + { + name: "initial sync without dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: false, + expected: "Initial Sync Complete", + }, + { + name: "initial sync with dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: true, + expected: "[DRY-RUN] Initial Sync Complete", + }, + { + name: "only put without dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: false, + expected: "Complete", + }, + { + name: "only put with dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: true, + expected: "[DRY-RUN] Complete", + }, + { + name: "only delete without dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: false, + expected: "Complete", + }, + { + name: "only delete with dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: true, + expected: "[DRY-RUN] Complete", + }, + { + name: "empty without dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: false, + expected: "", + }, + { + name: "empty with dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: true, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := newEventComplete(tt.seq, tt.put, tt.delete, tt.dryRun) + assert.Equal(t, tt.expected, e.String()) + }) + } } func TestEventCompleteJSON(t *testing.T) { - var e Event - - e = newEventComplete(0, []string{"put"}, []string{"delete"}, false) - jsonEqual(t, `{"seq": 0, "type": "complete", "put": ["put"], "delete": ["delete"]}`, e) - - e = newEventComplete(1, []string{"put"}, []string{}, false) - jsonEqual(t, `{"seq": 1, "type": "complete", "put": ["put"]}`, e) - - e = newEventComplete(2, []string{}, []string{"delete"}, false) - jsonEqual(t, `{"seq": 2, "type": "complete", "delete": ["delete"]}`, e) - - e = newEventComplete(3, []string{}, []string{}, false) - jsonEqual(t, `{"seq": 3, "type": "complete"}`, e) + tests := []struct { + name string + seq int + put []string + delete []string + dryRun bool + expected string + }{ + { + name: "put and delete without dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: false, + expected: `{"seq": 0, "type": "complete", "put": ["put"], "delete": ["delete"]}`, + }, + { + name: "put and delete with dry run", + seq: 0, + put: []string{"put"}, + delete: []string{"delete"}, + dryRun: true, + expected: `{"seq": 0, "type": "complete", "dry_run": true, "put": ["put"], "delete": ["delete"]}`, + }, + { + name: "only put without dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: false, + expected: `{"seq": 1, "type": "complete", "put": ["put"]}`, + }, + { + name: "only put with dry run", + seq: 1, + put: []string{"put"}, + delete: []string{}, + dryRun: true, + expected: `{"seq": 1, "type": "complete", "dry_run": true, "put": ["put"]}`, + }, + { + name: "only delete without dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: false, + expected: `{"seq": 2, "type": "complete", "delete": ["delete"]}`, + }, + { + name: "only delete with dry run", + seq: 2, + put: []string{}, + delete: []string{"delete"}, + dryRun: true, + expected: `{"seq": 2, "type": "complete", "dry_run": true, "delete": ["delete"]}`, + }, + { + name: "empty without dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: false, + expected: `{"seq": 3, "type": "complete"}`, + }, + { + name: "empty with dry run", + seq: 3, + put: []string{}, + delete: []string{}, + dryRun: true, + expected: `{"seq": 3, "type": "complete", "dry_run": true}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := newEventComplete(tt.seq, tt.put, tt.delete, tt.dryRun) + jsonEqual(t, tt.expected, e) + }) + } } From 7ebf82740fed4f377447728c36b8aaa3b7e985e9 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:45:58 +0200 Subject: [PATCH 09/16] fix acceptance tests for dry-run syncs --- acceptance/bundle/sync/dryrun/output.txt | 40 +++++++++++++++++++- acceptance/bundle/sync/dryrun/script | 10 ++--- acceptance/cmd/sync/dryrun/output.txt | 47 +++++++++++++++++++++++- acceptance/cmd/sync/dryrun/script | 12 +++--- 4 files changed, 94 insertions(+), 15 deletions(-) diff --git a/acceptance/bundle/sync/dryrun/output.txt b/acceptance/bundle/sync/dryrun/output.txt index 2004e90e11..d3a61d94e5 100644 --- a/acceptance/bundle/sync/dryrun/output.txt +++ b/acceptance/bundle/sync/dryrun/output.txt @@ -1,5 +1,41 @@ >>> [CLI] bundle sync --dry-run --output text -grep: invalid character range +[DRY-RUN MODE] Action: PUT: .gitignore, databricks.yml, project-folder/app.py, project-folder/app.yaml, project-folder/query.sql +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: databricks.yml +[DRY-RUN] Would upload: project-folder +[DRY-RUN] Would upload: project-folder/app.py +[DRY-RUN] Would upload: project-folder/app.yaml +[DRY-RUN] Would upload: project-folder/query.sql -Exit code: 2 +>>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --output text +[DRY-RUN MODE] Action: PUT: .gitignore, databricks.yml, project-folder/query.sql +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: databricks.yml +[DRY-RUN] Would upload: project-folder +[DRY-RUN] Would upload: project-folder/query.sql + +>>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --output text +[DRY-RUN MODE] Action: PUT: databricks.yml, .gitignore +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: databricks.yml + +>>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py --output text +[DRY-RUN MODE] Action: PUT: databricks.yml, ignored-folder/script.py, .gitignore +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: databricks.yml +[DRY-RUN] Would upload: ignored-folder +[DRY-RUN] Would upload: ignored-folder/script.py + +>>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py --output text +[DRY-RUN MODE] Action: PUT: ignored-folder/folder1/script.py, ignored-folder/script.py, .gitignore, databricks.yml +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: databricks.yml +[DRY-RUN] Would upload: ignored-folder/folder1 +[DRY-RUN] Would upload: ignored-folder/folder1/script.py +[DRY-RUN] Would upload: ignored-folder/script.py diff --git a/acceptance/bundle/sync/dryrun/script b/acceptance/bundle/sync/dryrun/script index 276cf8da5a..c51a180fba 100644 --- a/acceptance/bundle/sync/dryrun/script +++ b/acceptance/bundle/sync/dryrun/script @@ -15,8 +15,8 @@ cleanup() { trap cleanup EXIT # Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out -trace $CLI bundle sync --dry-run --output text | grep -v "^[DRY-RUN] Action: " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^[DRY-RUN] Action: " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^[DRY-RUN] Action: " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^[DRY-RUN] Action: " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^[DRY-RUN] Action: " | sort +trace $CLI bundle sync --dry-run --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort diff --git a/acceptance/cmd/sync/dryrun/output.txt b/acceptance/cmd/sync/dryrun/output.txt index 1d8b94fffe..e6b4943d68 100644 --- a/acceptance/cmd/sync/dryrun/output.txt +++ b/acceptance/cmd/sync/dryrun/output.txt @@ -1,6 +1,49 @@ >>> [CLI] sync . /Users/[USERNAME] --dry-run -grep: invalid character range Warn: Running in DRY-RUN mode. No actual changes will be made. +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: project-folder +[DRY-RUN] Would upload: project-folder/app.py +[DRY-RUN] Would upload: project-folder/app.yaml +[DRY-RUN] Would upload: project-folder/query.sql -Exit code: 2 +>>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* +Warn: Running in DRY-RUN mode. No actual changes will be made. +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: project-folder +[DRY-RUN] Would upload: project-folder/query.sql + +>>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql +Warn: Running in DRY-RUN mode. No actual changes will be made. +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore + +>>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py +Warn: Running in DRY-RUN mode. No actual changes will be made. +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: ignored-folder +[DRY-RUN] Would upload: ignored-folder/script.py + +>>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py +Warn: Running in DRY-RUN mode. No actual changes will be made. +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: ignored-folder/folder1 +[DRY-RUN] Would upload: ignored-folder/folder1/script.py +[DRY-RUN] Would upload: ignored-folder/script.py + +>>> [CLI] sync . /Users/[USERNAME] --dry-run --include ignored-folder/** --include !ignored-folder/folder1/big-blob +Warn: Running in DRY-RUN mode. No actual changes will be made. +[DRY-RUN] Initial Sync Complete +[DRY-RUN] Would upload: .gitignore +[DRY-RUN] Would upload: ignored-folder/folder1 +[DRY-RUN] Would upload: ignored-folder/folder1/script.py +[DRY-RUN] Would upload: ignored-folder/folder1/script.yaml +[DRY-RUN] Would upload: ignored-folder/script.py +[DRY-RUN] Would upload: project-folder +[DRY-RUN] Would upload: project-folder/app.py +[DRY-RUN] Would upload: project-folder/app.yaml +[DRY-RUN] Would upload: project-folder/query.sql diff --git a/acceptance/cmd/sync/dryrun/script b/acceptance/cmd/sync/dryrun/script index 67cd9f2aa0..ed5537c386 100644 --- a/acceptance/cmd/sync/dryrun/script +++ b/acceptance/cmd/sync/dryrun/script @@ -15,19 +15,19 @@ 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 --dry-run | grep -v "^[DRY-RUN MODE] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run | grep -v "^\[DRY-RUN MODE\] Action" | sort # excluding by mask: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' | grep -v "^[DRY-RUN MODE] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' | grep -v "^\[DRY-RUN MODE\] Action" | sort # combining excludes: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' | grep -v "^[DRY-RUN MODE] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' | grep -v "^\[DRY-RUN MODE\] Action" | sort # combining excludes and includes: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' | grep -v "^[DRY-RUN MODE] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' | grep -v "^\[DRY-RUN MODE\] Action" | sort # include sub-folders: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' | grep -v "^[DRY-RUN MODE] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' | grep -v "^\[DRY-RUN MODE\] Action" | sort # use negated include to exclude files from syncing: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --include 'ignored-folder/**' --include '!ignored-folder/folder1/big-blob' | grep -v "^[DRY-RUN MODE] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --include 'ignored-folder/**' --include '!ignored-folder/folder1/big-blob' | grep -v "^\[DRY-RUN MODE\] Action" | sort From 1238676e9c707dfe91c0db4aab7b4fbc3d1758e6 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:00:43 +0200 Subject: [PATCH 10/16] add NEXT_CHANGELOG.md --- NEXT_CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 95acdbc46d..e473c0a76c 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -1,14 +1,20 @@ # NEXT CHANGELOG -## Release v0.248.0 +## Release v0.247.0 ### Notable Changes ### Dependency updates ### CLI +* Added include/exclude flags support to sync command ([#2650](https://github.com/databricks/cli/pull/2650)) +* Added dry-run flag support to sync command ([#2657](https://github.com/databricks/cli/pull/2657)) + ### Bundles +* Added support for model serving endpoints in deployment bind/unbind commands ([#2634](https://github.com/databricks/cli/pull/2634)) +* Added include/exclude flags support to bundle sync command ([#2650](https://github.com/databricks/cli/pull/2650)) +* Added dry-run flag support to bundle sync command ([#2657](https://github.com/databricks/cli/pull/2657)) * Do not exit early when checking incompatible tasks for specified DBR ([#2692](https://github.com/databricks/cli/pull/2692)) * Removed include/exclude flags support from bundle sync command ([#2718](https://github.com/databricks/cli/pull/2718)) From 76eb5de89c4d964198e7e9e34e341d7425bcfb55 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:10:35 +0200 Subject: [PATCH 11/16] fix acceptance tests for sync dry-run --- acceptance/bundle/sync/dryrun/output.txt | 5 ----- acceptance/bundle/sync/dryrun/script | 12 ++++++------ acceptance/cmd/sync/dryrun/script | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/acceptance/bundle/sync/dryrun/output.txt b/acceptance/bundle/sync/dryrun/output.txt index d3a61d94e5..f4de24c498 100644 --- a/acceptance/bundle/sync/dryrun/output.txt +++ b/acceptance/bundle/sync/dryrun/output.txt @@ -1,6 +1,5 @@ >>> [CLI] bundle sync --dry-run --output text -[DRY-RUN MODE] Action: PUT: .gitignore, databricks.yml, project-folder/app.py, project-folder/app.yaml, project-folder/query.sql [DRY-RUN] Initial Sync Complete [DRY-RUN] Would upload: .gitignore [DRY-RUN] Would upload: databricks.yml @@ -10,7 +9,6 @@ [DRY-RUN] Would upload: project-folder/query.sql >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --output text -[DRY-RUN MODE] Action: PUT: .gitignore, databricks.yml, project-folder/query.sql [DRY-RUN] Initial Sync Complete [DRY-RUN] Would upload: .gitignore [DRY-RUN] Would upload: databricks.yml @@ -18,13 +16,11 @@ [DRY-RUN] Would upload: project-folder/query.sql >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --output text -[DRY-RUN MODE] Action: PUT: databricks.yml, .gitignore [DRY-RUN] Initial Sync Complete [DRY-RUN] Would upload: .gitignore [DRY-RUN] Would upload: databricks.yml >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py --output text -[DRY-RUN MODE] Action: PUT: databricks.yml, ignored-folder/script.py, .gitignore [DRY-RUN] Initial Sync Complete [DRY-RUN] Would upload: .gitignore [DRY-RUN] Would upload: databricks.yml @@ -32,7 +28,6 @@ [DRY-RUN] Would upload: ignored-folder/script.py >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py --output text -[DRY-RUN MODE] Action: PUT: ignored-folder/folder1/script.py, ignored-folder/script.py, .gitignore, databricks.yml [DRY-RUN] Initial Sync Complete [DRY-RUN] Would upload: .gitignore [DRY-RUN] Would upload: databricks.yml diff --git a/acceptance/bundle/sync/dryrun/script b/acceptance/bundle/sync/dryrun/script index c51a180fba..87b10dad1f 100644 --- a/acceptance/bundle/sync/dryrun/script +++ b/acceptance/bundle/sync/dryrun/script @@ -14,9 +14,9 @@ cleanup() { } trap cleanup EXIT -# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out -trace $CLI bundle sync --dry-run --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action " | sort +# Note: output line containing "DRY-RUN MODE" lists files in non-deterministic order so we filter it out +trace $CLI bundle sync --dry-run --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort diff --git a/acceptance/cmd/sync/dryrun/script b/acceptance/cmd/sync/dryrun/script index ed5537c386..cd4ae6ee5b 100644 --- a/acceptance/cmd/sync/dryrun/script +++ b/acceptance/cmd/sync/dryrun/script @@ -14,7 +14,7 @@ cleanup() { } trap cleanup EXIT -# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out +# Note: output line containing "DRY-RUN MODE" lists files in non-deterministic order so we filter it out trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run | grep -v "^\[DRY-RUN MODE\] Action" | sort # excluding by mask: From 8cbd68eee778f4c218517d1d6ed9b975f1d3a1e4 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:40:26 +0200 Subject: [PATCH 12/16] update NEXT_CHANGELOG.md --- NEXT_CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index e473c0a76c..924e1491b2 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,14 +7,9 @@ ### Dependency updates ### CLI -* Added include/exclude flags support to sync command ([#2650](https://github.com/databricks/cli/pull/2650)) * Added dry-run flag support to sync command ([#2657](https://github.com/databricks/cli/pull/2657)) - ### Bundles -* Added support for model serving endpoints in deployment bind/unbind commands ([#2634](https://github.com/databricks/cli/pull/2634)) -* Added include/exclude flags support to bundle sync command ([#2650](https://github.com/databricks/cli/pull/2650)) -* Added dry-run flag support to bundle sync command ([#2657](https://github.com/databricks/cli/pull/2657)) * Do not exit early when checking incompatible tasks for specified DBR ([#2692](https://github.com/databricks/cli/pull/2692)) * Removed include/exclude flags support from bundle sync command ([#2718](https://github.com/databricks/cli/pull/2718)) From 00db49f8304520d249fbd671e26ccc9368c8a7a8 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:54:08 +0200 Subject: [PATCH 13/16] remove DRY-RUN mode note from individual file actions --- acceptance/bundle/sync/dryrun/output.txt | 52 ++++++++++---------- acceptance/bundle/sync/dryrun/script | 12 ++--- acceptance/cmd/sync/dryrun/output.txt | 62 ++++++++++++------------ acceptance/cmd/sync/dryrun/script | 14 +++--- libs/sync/event.go | 22 --------- libs/sync/event_test.go | 16 +++--- 6 files changed, 78 insertions(+), 100 deletions(-) diff --git a/acceptance/bundle/sync/dryrun/output.txt b/acceptance/bundle/sync/dryrun/output.txt index f4de24c498..a8287a306b 100644 --- a/acceptance/bundle/sync/dryrun/output.txt +++ b/acceptance/bundle/sync/dryrun/output.txt @@ -1,36 +1,36 @@ >>> [CLI] bundle sync --dry-run --output text -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: databricks.yml -[DRY-RUN] Would upload: project-folder -[DRY-RUN] Would upload: project-folder/app.py -[DRY-RUN] Would upload: project-folder/app.yaml -[DRY-RUN] Would upload: project-folder/query.sql +Initial Sync Complete +Uploaded .gitignore +Uploaded databricks.yml +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml +Uploaded project-folder/query.sql >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --output text -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: databricks.yml -[DRY-RUN] Would upload: project-folder -[DRY-RUN] Would upload: project-folder/query.sql +Initial Sync Complete +Uploaded .gitignore +Uploaded databricks.yml +Uploaded project-folder +Uploaded project-folder/query.sql >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --output text -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: databricks.yml +Initial Sync Complete +Uploaded .gitignore +Uploaded databricks.yml >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py --output text -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: databricks.yml -[DRY-RUN] Would upload: ignored-folder -[DRY-RUN] Would upload: ignored-folder/script.py +Initial Sync Complete +Uploaded .gitignore +Uploaded databricks.yml +Uploaded ignored-folder +Uploaded ignored-folder/script.py >>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py --output text -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: databricks.yml -[DRY-RUN] Would upload: ignored-folder/folder1 -[DRY-RUN] Would upload: ignored-folder/folder1/script.py -[DRY-RUN] Would upload: ignored-folder/script.py +Initial Sync Complete +Uploaded .gitignore +Uploaded databricks.yml +Uploaded ignored-folder/folder1 +Uploaded ignored-folder/folder1/script.py +Uploaded ignored-folder/script.py diff --git a/acceptance/bundle/sync/dryrun/script b/acceptance/bundle/sync/dryrun/script index 87b10dad1f..4aed09e1ac 100644 --- a/acceptance/bundle/sync/dryrun/script +++ b/acceptance/bundle/sync/dryrun/script @@ -14,9 +14,9 @@ cleanup() { } trap cleanup EXIT -# Note: output line containing "DRY-RUN MODE" lists files in non-deterministic order so we filter it out -trace $CLI bundle sync --dry-run --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^\[DRY-RUN MODE\] Action" | sort +# Note: output line starting with Action lists files in non-deterministic order so we filter it out +trace $CLI bundle sync --dry-run --output text | grep -v "^Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^Action" | sort +trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^Action" | sort diff --git a/acceptance/cmd/sync/dryrun/output.txt b/acceptance/cmd/sync/dryrun/output.txt index e6b4943d68..54cd472f5f 100644 --- a/acceptance/cmd/sync/dryrun/output.txt +++ b/acceptance/cmd/sync/dryrun/output.txt @@ -1,49 +1,49 @@ >>> [CLI] sync . /Users/[USERNAME] --dry-run Warn: Running in DRY-RUN mode. No actual changes will be made. -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: project-folder -[DRY-RUN] Would upload: project-folder/app.py -[DRY-RUN] Would upload: project-folder/app.yaml -[DRY-RUN] Would upload: project-folder/query.sql +Initial Sync Complete +Uploaded .gitignore +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml +Uploaded project-folder/query.sql >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* Warn: Running in DRY-RUN mode. No actual changes will be made. -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: project-folder -[DRY-RUN] Would upload: project-folder/query.sql +Initial Sync Complete +Uploaded .gitignore +Uploaded project-folder +Uploaded project-folder/query.sql >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql Warn: Running in DRY-RUN mode. No actual changes will be made. -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore +Initial Sync Complete +Uploaded .gitignore >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py Warn: Running in DRY-RUN mode. No actual changes will be made. -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: ignored-folder -[DRY-RUN] Would upload: ignored-folder/script.py +Initial Sync Complete +Uploaded .gitignore +Uploaded ignored-folder +Uploaded ignored-folder/script.py >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py Warn: Running in DRY-RUN mode. No actual changes will be made. -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: ignored-folder/folder1 -[DRY-RUN] Would upload: ignored-folder/folder1/script.py -[DRY-RUN] Would upload: ignored-folder/script.py +Initial Sync Complete +Uploaded .gitignore +Uploaded ignored-folder/folder1 +Uploaded ignored-folder/folder1/script.py +Uploaded ignored-folder/script.py >>> [CLI] sync . /Users/[USERNAME] --dry-run --include ignored-folder/** --include !ignored-folder/folder1/big-blob Warn: Running in DRY-RUN mode. No actual changes will be made. -[DRY-RUN] Initial Sync Complete -[DRY-RUN] Would upload: .gitignore -[DRY-RUN] Would upload: ignored-folder/folder1 -[DRY-RUN] Would upload: ignored-folder/folder1/script.py -[DRY-RUN] Would upload: ignored-folder/folder1/script.yaml -[DRY-RUN] Would upload: ignored-folder/script.py -[DRY-RUN] Would upload: project-folder -[DRY-RUN] Would upload: project-folder/app.py -[DRY-RUN] Would upload: project-folder/app.yaml -[DRY-RUN] Would upload: project-folder/query.sql +Initial Sync Complete +Uploaded .gitignore +Uploaded ignored-folder/folder1 +Uploaded ignored-folder/folder1/script.py +Uploaded ignored-folder/folder1/script.yaml +Uploaded ignored-folder/script.py +Uploaded project-folder +Uploaded project-folder/app.py +Uploaded project-folder/app.yaml +Uploaded project-folder/query.sql diff --git a/acceptance/cmd/sync/dryrun/script b/acceptance/cmd/sync/dryrun/script index cd4ae6ee5b..75e217400e 100644 --- a/acceptance/cmd/sync/dryrun/script +++ b/acceptance/cmd/sync/dryrun/script @@ -14,20 +14,20 @@ cleanup() { } trap cleanup EXIT -# Note: output line containing "DRY-RUN MODE" lists files in non-deterministic order so we filter it out -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run | grep -v "^\[DRY-RUN MODE\] Action" | sort +# Note: output line starting with Action lists files in non-deterministic order so we filter it out +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run | grep -v "^Action" | sort # excluding by mask: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' | grep -v "^Action" | sort # combining excludes: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' | grep -v "^Action" | sort # combining excludes and includes: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' | grep -v "^Action" | sort # include sub-folders: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' | grep -v "^Action" | sort # use negated include to exclude files from syncing: -trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --include 'ignored-folder/**' --include '!ignored-folder/folder1/big-blob' | grep -v "^\[DRY-RUN MODE\] Action" | sort +trace $CLI sync . /Users/$CURRENT_USER_NAME --dry-run --include 'ignored-folder/**' --include '!ignored-folder/folder1/big-blob' | grep -v "^Action" | sort diff --git a/libs/sync/event.go b/libs/sync/event.go index c7d8c1f7e1..4b488f4ede 100644 --- a/libs/sync/event.go +++ b/libs/sync/event.go @@ -72,10 +72,6 @@ func (e *EventStart) String() string { return "" } - if e.DryRun { - return "[DRY-RUN MODE] Action: " + e.EventChanges.String() - } - return "Action: " + e.EventChanges.String() } @@ -102,17 +98,6 @@ func (e *EventSyncProgress) String() string { return "" } - if e.DryRun { - switch e.Action { - case EventActionPut: - return "[DRY-RUN] Would upload: " + e.Path - case EventActionDelete: - return "[DRY-RUN] Would delete: " + e.Path - default: - panic("invalid action") - } - } - switch e.Action { case EventActionPut: return "Uploaded " + e.Path @@ -140,9 +125,6 @@ type EventSyncComplete struct { func (e *EventSyncComplete) String() string { if e.Seq == 0 { - if e.DryRun { - return "[DRY-RUN] Initial Sync Complete" - } return "Initial Sync Complete" } @@ -150,10 +132,6 @@ func (e *EventSyncComplete) String() string { return "" } - if e.DryRun { - return "[DRY-RUN] Complete" - } - return "Complete" } diff --git a/libs/sync/event_test.go b/libs/sync/event_test.go index 35d5ccaaf9..343bbb21ec 100644 --- a/libs/sync/event_test.go +++ b/libs/sync/event_test.go @@ -48,7 +48,7 @@ func TestEventStart(t *testing.T) { put: []string{"put"}, delete: []string{"delete"}, dryRun: true, - expected: "[DRY-RUN MODE] Action: PUT: put, DELETE: delete", + expected: "Action: PUT: put, DELETE: delete", }, { name: "only put without dry run", @@ -64,7 +64,7 @@ func TestEventStart(t *testing.T) { put: []string{"put"}, delete: []string{}, dryRun: true, - expected: "[DRY-RUN MODE] Action: PUT: put", + expected: "Action: PUT: put", }, { name: "only delete without dry run", @@ -80,7 +80,7 @@ func TestEventStart(t *testing.T) { put: []string{}, delete: []string{"delete"}, dryRun: true, - expected: "[DRY-RUN MODE] Action: DELETE: delete", + expected: "Action: DELETE: delete", }, { name: "empty without dry run", @@ -235,7 +235,7 @@ func TestEventProgress(t *testing.T) { path: "path", progress: 1.0, dryRun: true, - expected: "[DRY-RUN] Would upload: path", + expected: "Uploaded path", }, { name: "delete no progress without dry run", @@ -271,7 +271,7 @@ func TestEventProgress(t *testing.T) { path: "path", progress: 1.0, dryRun: true, - expected: "[DRY-RUN] Would delete: path", + expected: "Deleted path", }, } @@ -434,7 +434,7 @@ func TestEventComplete(t *testing.T) { put: []string{"put"}, delete: []string{"delete"}, dryRun: true, - expected: "[DRY-RUN] Initial Sync Complete", + expected: "Initial Sync Complete", }, { name: "only put without dry run", @@ -450,7 +450,7 @@ func TestEventComplete(t *testing.T) { put: []string{"put"}, delete: []string{}, dryRun: true, - expected: "[DRY-RUN] Complete", + expected: "Complete", }, { name: "only delete without dry run", @@ -466,7 +466,7 @@ func TestEventComplete(t *testing.T) { put: []string{}, delete: []string{"delete"}, dryRun: true, - expected: "[DRY-RUN] Complete", + expected: "Complete", }, { name: "empty without dry run", From c53a2f7f99683b31e97ed7c3d243ac101600f7e2 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Tue, 15 Apr 2025 14:43:27 +0200 Subject: [PATCH 14/16] update acceptance tests to reflect removal of include/exclude flags from bundle sync --- acceptance/bundle/sync/dryrun/output.txt | 27 ------------------------ acceptance/bundle/sync/dryrun/script | 8 ++----- acceptance/bundle/sync/output.txt | 2 ++ cmd/bundle/sync.go | 6 ------ 4 files changed, 4 insertions(+), 39 deletions(-) diff --git a/acceptance/bundle/sync/dryrun/output.txt b/acceptance/bundle/sync/dryrun/output.txt index a8287a306b..cf89a56a4e 100644 --- a/acceptance/bundle/sync/dryrun/output.txt +++ b/acceptance/bundle/sync/dryrun/output.txt @@ -7,30 +7,3 @@ Uploaded project-folder Uploaded project-folder/app.py Uploaded project-folder/app.yaml Uploaded project-folder/query.sql - ->>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --output text -Initial Sync Complete -Uploaded .gitignore -Uploaded databricks.yml -Uploaded project-folder -Uploaded project-folder/query.sql - ->>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --output text -Initial Sync Complete -Uploaded .gitignore -Uploaded databricks.yml - ->>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py --output text -Initial Sync Complete -Uploaded .gitignore -Uploaded databricks.yml -Uploaded ignored-folder -Uploaded ignored-folder/script.py - ->>> [CLI] bundle sync --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py --output text -Initial Sync Complete -Uploaded .gitignore -Uploaded databricks.yml -Uploaded ignored-folder/folder1 -Uploaded ignored-folder/folder1/script.py -Uploaded ignored-folder/script.py diff --git a/acceptance/bundle/sync/dryrun/script b/acceptance/bundle/sync/dryrun/script index 4aed09e1ac..66f1bb6de3 100644 --- a/acceptance/bundle/sync/dryrun/script +++ b/acceptance/bundle/sync/dryrun/script @@ -14,9 +14,5 @@ cleanup() { } trap cleanup EXIT -# Note: output line starting with Action lists files in non-deterministic order so we filter it out -trace $CLI bundle sync --dry-run --output text | grep -v "^Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --output text | grep -v "^Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --output text | grep -v "^Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/*.py' --output text | grep -v "^Action" | sort -trace $CLI bundle sync --dry-run --exclude 'project-folder/app.*' --exclude 'project-folder/query.sql' --include 'ignored-folder/**/*.py' --output text | grep -v "^Action" | sort +# Note: output line starting with "Action: " lists files in non-deterministic order so we filter it out +trace $CLI bundle sync --dry-run --output text | grep -v "^Action: " | sort diff --git a/acceptance/bundle/sync/output.txt b/acceptance/bundle/sync/output.txt index f23e07dc8d..cd140e4fbe 100644 --- a/acceptance/bundle/sync/output.txt +++ b/acceptance/bundle/sync/output.txt @@ -3,6 +3,8 @@ Initial Sync Complete Uploaded .gitignore Uploaded databricks.yml +Uploaded dryrun +Uploaded dryrun/databricks.yml Uploaded ignored-folder/folder1 Uploaded ignored-folder/folder1/script.py Uploaded ignored-folder/script.py diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index 7136ee4dab..30d95b7ac0 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -22,8 +22,6 @@ type syncFlags struct { full bool watch bool output flags.Output - exclude []string - include []string dryRun bool } @@ -50,8 +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, f.exclude...) - opts.Include = append(opts.Include, f.include...) opts.DryRun = f.dryRun return opts, nil } @@ -68,8 +64,6 @@ func newSyncCommand() *cobra.Command { cmd.Flags().BoolVar(&f.full, "full", false, "perform full synchronization (default is incremental)") 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().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") cmd.RunE = func(cmd *cobra.Command, args []string) error { From 9093505a96c8ec3ab88c710129a21ecc3f42d44d Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Tue, 15 Apr 2025 14:55:47 +0200 Subject: [PATCH 15/16] use the same style of messaging in sync and bundle-sync commands --- NEXT_CHANGELOG.md | 2 +- cmd/bundle/sync.go | 2 +- cmd/sync/sync.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 924e1491b2..653d779b3a 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -1,6 +1,6 @@ # NEXT CHANGELOG -## Release v0.247.0 +## Release v0.248.0 ### Notable Changes diff --git a/cmd/bundle/sync.go b/cmd/bundle/sync.go index 30d95b7ac0..65cda6d6c9 100644 --- a/cmd/bundle/sync.go +++ b/cmd/bundle/sync.go @@ -93,7 +93,7 @@ func newSyncCommand() *cobra.Command { log.Infof(ctx, "Remote file sync location: %v", opts.RemotePath) if opts.DryRun { - log.Infof(ctx, "Running in dry-run mode. No changes will be made.") + log.Warnf(ctx, "Running in dry-run mode. No actual changes will be made.") } if f.watch { diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index de79aae41e..3ccae8cbfe 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -75,7 +75,7 @@ func (f *syncFlags) syncOptionsFromArgs(cmd *cobra.Command, args []string) (*syn client := cmdctx.WorkspaceClient(ctx) if f.dryRun { - log.Warnf(ctx, "Running in DRY-RUN mode. No actual changes will be made.") + log.Warnf(ctx, "Running in dry-run mode. No actual changes will be made.") } localRoot := vfs.MustNew(args[0]) From 829bf9e0a785f6ef274bf0bcd10e3ab1f05d4626 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:12:48 +0200 Subject: [PATCH 16/16] fix acceptance tests --- acceptance/bundle/sync/dryrun/output.txt | 1 + acceptance/cmd/sync/dryrun/output.txt | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/acceptance/bundle/sync/dryrun/output.txt b/acceptance/bundle/sync/dryrun/output.txt index cf89a56a4e..765f2f8c6d 100644 --- a/acceptance/bundle/sync/dryrun/output.txt +++ b/acceptance/bundle/sync/dryrun/output.txt @@ -1,5 +1,6 @@ >>> [CLI] bundle sync --dry-run --output text +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore Uploaded databricks.yml diff --git a/acceptance/cmd/sync/dryrun/output.txt b/acceptance/cmd/sync/dryrun/output.txt index 54cd472f5f..091b812701 100644 --- a/acceptance/cmd/sync/dryrun/output.txt +++ b/acceptance/cmd/sync/dryrun/output.txt @@ -1,6 +1,6 @@ >>> [CLI] sync . /Users/[USERNAME] --dry-run -Warn: Running in DRY-RUN mode. No actual changes will be made. +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore Uploaded project-folder @@ -9,26 +9,26 @@ Uploaded project-folder/app.yaml Uploaded project-folder/query.sql >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* -Warn: Running in DRY-RUN mode. No actual changes will be made. +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore Uploaded project-folder Uploaded project-folder/query.sql >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql -Warn: Running in DRY-RUN mode. No actual changes will be made. +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/*.py -Warn: Running in DRY-RUN mode. No actual changes will be made. +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore Uploaded ignored-folder Uploaded ignored-folder/script.py >>> [CLI] sync . /Users/[USERNAME] --dry-run --exclude project-folder/app.* --exclude project-folder/query.sql --include ignored-folder/**/*.py -Warn: Running in DRY-RUN mode. No actual changes will be made. +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore Uploaded ignored-folder/folder1 @@ -36,7 +36,7 @@ Uploaded ignored-folder/folder1/script.py Uploaded ignored-folder/script.py >>> [CLI] sync . /Users/[USERNAME] --dry-run --include ignored-folder/** --include !ignored-folder/folder1/big-blob -Warn: Running in DRY-RUN mode. No actual changes will be made. +Warn: Running in dry-run mode. No actual changes will be made. Initial Sync Complete Uploaded .gitignore Uploaded ignored-folder/folder1