From 47dafd4505e94c84c74bde9586d13a1b121833ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:55:17 +0000 Subject: [PATCH 1/3] Initial plan From bc393a5cf565cf67ac2f1b3b1bb471c3ad53f09c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:05:45 +0000 Subject: [PATCH 2/3] fix: extract default config during dry runs --- defaultconfigs/defaultconfigs.go | 3 +-- internal/projectconfig/config.go | 2 +- internal/projectconfig/config_test.go | 15 +++++++++++ internal/utils/fileutils/copy.go | 36 ++++++++++++++++----------- internal/utils/fileutils/copy_test.go | 15 +++++++++++ 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/defaultconfigs/defaultconfigs.go b/defaultconfigs/defaultconfigs.go index 1445f1c12..8ff1db426 100644 --- a/defaultconfigs/defaultconfigs.go +++ b/defaultconfigs/defaultconfigs.go @@ -31,11 +31,10 @@ var content embed.FS // CopyTo recursively copies the contents of the [content] embedded filesystem to the specified // destination path in the provided filesystem. Returns the path to the root configuration file // to load from the copied directory. -func CopyTo(dryRunnable opctx.DryRunnable, fs opctx.FS, destPath string) (rootConfigFilePath string, err error) { +func CopyTo(fs opctx.FS, destPath string) (rootConfigFilePath string, err error) { sourceFS := fileutils.WrapEmbedFS(&content) err = fileutils.CopyDirRecursiveCrossFS( - dryRunnable, sourceFS, embedfsRootDir, fs, destPath, fileutils.CopyDirOptions{}, diff --git a/internal/projectconfig/config.go b/internal/projectconfig/config.go index b2f71e25b..dd74b3037 100644 --- a/internal/projectconfig/config.go +++ b/internal/projectconfig/config.go @@ -43,7 +43,7 @@ func LoadProjectConfig( return "", nil, fmt.Errorf("failed to create temp dir for default config files:\n%w", err) } - defaultConfigFilePath, err := defaultconfigs.CopyTo(dryRunnable, fs, tempConfigDirPath) + defaultConfigFilePath, err := defaultconfigs.CopyTo(fs, tempConfigDirPath) if err != nil { return "", nil, fmt.Errorf("failed to copy default config files:\n%w", err) } diff --git a/internal/projectconfig/config_test.go b/internal/projectconfig/config_test.go index a31e1d719..e81189681 100644 --- a/internal/projectconfig/config_test.go +++ b/internal/projectconfig/config_test.go @@ -58,6 +58,21 @@ description = "`+testProjectDesc+`" assert.Equal(t, testProjectDesc, config.Project.Description) } +func TestLoadProjectConfig_DryRunWithDefaultConfig(t *testing.T) { + const tempDir = "/test/tmp" + + ctx := newTestCtxWithXDGConfigHome() + ctx.DryRunValue = true + writeProjectConfig(t, ctx, "") + require.NoError(t, fileutils.MkdirAll(ctx.FS(), tempDir)) + + _, config, err := projectconfig.LoadProjectConfig( + ctx, ctx.FS(), ctx.OSEnv(), testProjectDir, false /*disableDefaultConfig*/, tempDir, nil, false, + ) + require.NoError(t, err) + require.NotNil(t, config) +} + // TestLoadProjectConfig_UserConfigOverridesProject verifies that values defined in the // user-level config file override those defined by the project config file. func TestLoadProjectConfig_UserConfigOverridesProject(t *testing.T) { diff --git a/internal/utils/fileutils/copy.go b/internal/utils/fileutils/copy.go index 1f480117c..790822976 100644 --- a/internal/utils/fileutils/copy.go +++ b/internal/utils/fileutils/copy.go @@ -30,22 +30,30 @@ func CopyFile(dryRunnable opctx.DryRunnable, fs opctx.FS, sourcePath, destPath s // Copies the contents of an existing file to a new file at the given destination path, // potentially across two [opctx.FS] filesystem instances. May preserve mode bits if // "options" says so but does *not* preserve ownership, timestamps, or other metadata. -// *Does* follow symlinks. Is *not* enlightened to skip copying in dry-run mode. +// *Does* follow symlinks. Is enlightened to skip copying in dry-run mode. func CopyFileCrossFS( dryRunnable opctx.DryRunnable, sourceFS opctx.FS, sourcePath string, destFS opctx.FS, destPath string, options CopyFileOptions, ) (err error) { - // Use a default read/write mode; umask will further restrict this as appropriate. - const defaultPerms = os.FileMode(0o666) - if dryRunnable.DryRun() { slog.Info("Dry run; would copy file", "source", sourcePath, "dest", destPath) return nil } + return copyFileCrossFS(sourceFS, sourcePath, destFS, destPath, options) +} + +func copyFileCrossFS( + sourceFS opctx.FS, sourcePath string, + destFS opctx.FS, destPath string, + options CopyFileOptions, +) (err error) { + // Use a default read/write mode; umask will further restrict this as appropriate. + const defaultPerms = os.FileMode(0o666) + destFileMode := defaultPerms if options.PreserveFileMode { @@ -127,14 +135,19 @@ func CopyDirRecursive( sourceDirPath, destDirPath string, options CopyDirOptions, ) (err error) { - return CopyDirRecursiveCrossFS(dryRunnable, fs, sourceDirPath, fs, destDirPath, options) + if dryRunnable.DryRun() { + slog.Info("Dry run; would recursively copy dir", "source", sourceDirPath, "dest", destDirPath) + + return nil + } + + return CopyDirRecursiveCrossFS(fs, sourceDirPath, fs, destDirPath, options) } // Recursively copies a directory, potentially across two [opctx.FS] filesystem instances. May preserve // mode bits if "options" says so but does *not* preserve ownership, timestamps, or other metadata. // *Does* follow symlinks. Is *not* enlightened to skip copying in dry-run mode. func CopyDirRecursiveCrossFS( - dryRunnable opctx.DryRunnable, sourceFS opctx.FS, sourceDirPath string, destFS opctx.FS, destDirPath string, options CopyDirOptions, @@ -142,12 +155,6 @@ func CopyDirRecursiveCrossFS( // Use a default read/write/execute mode; umask will further restrict this as appropriate. const defaultDirPerms = os.FileMode(0o777) - if dryRunnable.DryRun() { - slog.Info("Dry run; would recursively copy dir", "source", sourceDirPath, "dest", destDirPath) - - return nil - } - var fileInfo os.FileInfo if fileInfo, err = sourceFS.Stat(sourceDirPath); err != nil { @@ -186,7 +193,7 @@ func CopyDirRecursiveCrossFS( destPath := filepath.Join(destDirPath, entry.Name()) if entry.IsDir() { - err = CopyDirRecursiveCrossFS(dryRunnable, sourceFS, sourcePath, destFS, destPath, options) + err = CopyDirRecursiveCrossFS(sourceFS, sourcePath, destFS, destPath, options) if err != nil { return fmt.Errorf("failed to recursively copy dir '%s' to '%s':\n%w", sourcePath, destPath, err) } @@ -205,8 +212,7 @@ func CopyDirRecursiveCrossFS( } } - err = CopyFileCrossFS( - dryRunnable, + err = copyFileCrossFS( sourceFS, sourcePath, destFS, destPath, options.CopyFileOptions, diff --git a/internal/utils/fileutils/copy_test.go b/internal/utils/fileutils/copy_test.go index 3c202d9f1..0febf757e 100644 --- a/internal/utils/fileutils/copy_test.go +++ b/internal/utils/fileutils/copy_test.go @@ -136,6 +136,21 @@ func TestCopyDirRecursive(t *testing.T) { } }) + t.Run("DryRunDoesNotCopy", func(t *testing.T) { + ctx := testctx.NewCtx() + ctx.DryRunValue = true + + sourceFilePath := filepath.Join(sourceDirPath, "file.txt") + require.NoError(t, fileutils.WriteFile(ctx.FS(), sourceFilePath, []byte(content1), fileperms.PrivateFile)) + + err := fileutils.CopyDirRecursive(ctx, ctx.FS(), sourceDirPath, destDirPath, fileutils.CopyDirOptions{}) + require.NoError(t, err) + + exists, err := fileutils.Exists(ctx.FS(), destDirPath) + require.NoError(t, err) + assert.False(t, exists) + }) + t.Run("ConfirmDoesNotPreserveDirMode", func(t *testing.T) { const ( extraDirPerms = os.ModeSticky From 25dcdec12ef24fad016bae4c186c38ec65dd612b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:04:35 +0000 Subject: [PATCH 3/3] refactor: remove unused config dry runnable --- internal/app/azldev/app.go | 4 +--- internal/projectconfig/config.go | 1 - internal/projectconfig/config_test.go | 13 ++++++------- internal/projectgen/projectgen_test.go | 3 +-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/internal/app/azldev/app.go b/internal/app/azldev/app.go index 33d692e35..78d103057 100644 --- a/internal/app/azldev/app.go +++ b/internal/app/azldev/app.go @@ -404,7 +404,6 @@ func (a *App) initializeEnvOptions() *EnvOptions { // This is responsible for finding the project root, finding and processing the configuration file. func (a *App) initializeProjectConfig(envOptions *EnvOptions, earlyTempDirPath string) error { projectDir, config, err := a.findAndLoadConfig( - envOptions.DryRunnable, earlyTempDirPath, a.configFiles, ) @@ -516,7 +515,7 @@ func (a *App) handParsePrefixedFlags(arg string) { // Initializes the configuration for the azldev CLI. This includes finding the project. // loading configuration, etc. -func (a *App) findAndLoadConfig(dryRunnable opctx.DryRunnable, tempDirPath string, extraConfigFiles []string) ( +func (a *App) findAndLoadConfig(tempDirPath string, extraConfigFiles []string) ( projectDir string, config *projectconfig.ProjectConfig, err error, ) { // If no explicit project dir was specified, then fall back to the current working directory. @@ -531,7 +530,6 @@ func (a *App) findAndLoadConfig(dryRunnable opctx.DryRunnable, tempDirPath strin // Rely on projectconfig package to find all relevant configuration files (including defaults) and // load them into a single project configuration object. projectDir, config, err = projectconfig.LoadProjectConfig( - dryRunnable, a.fsFactory.FS(), a.osEnvFactory.OSEnv(), referenceDir, diff --git a/internal/projectconfig/config.go b/internal/projectconfig/config.go index dd74b3037..a25cf2740 100644 --- a/internal/projectconfig/config.go +++ b/internal/projectconfig/config.go @@ -18,7 +18,6 @@ import ( // for cleaning it up -- but not until after it is done using the loaded configuration. The loaded // configuration may implicitly depend on the contents of the temporary directory. func LoadProjectConfig( - dryRunnable opctx.DryRunnable, fs opctx.FS, osEnv opctx.OSEnv, referenceDir string, diff --git a/internal/projectconfig/config_test.go b/internal/projectconfig/config_test.go index e81189681..d7f0ad0f8 100644 --- a/internal/projectconfig/config_test.go +++ b/internal/projectconfig/config_test.go @@ -51,23 +51,22 @@ description = "`+testProjectDesc+`" `) _, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), nil, false, + ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), nil, false, ) require.NoError(t, err) require.NotNil(t, config) assert.Equal(t, testProjectDesc, config.Project.Description) } -func TestLoadProjectConfig_DryRunWithDefaultConfig(t *testing.T) { +func TestLoadProjectConfig_WithDefaultConfig(t *testing.T) { const tempDir = "/test/tmp" ctx := newTestCtxWithXDGConfigHome() - ctx.DryRunValue = true writeProjectConfig(t, ctx, "") require.NoError(t, fileutils.MkdirAll(ctx.FS(), tempDir)) _, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), testProjectDir, false /*disableDefaultConfig*/, tempDir, nil, false, + ctx.FS(), ctx.OSEnv(), testProjectDir, false /*disableDefaultConfig*/, tempDir, nil, false, ) require.NoError(t, err) require.NotNil(t, config) @@ -94,7 +93,7 @@ output-dir = "/from/user/out" `), fileperms.PublicFile)) _, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), nil, false, + ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), nil, false, ) require.NoError(t, err) require.NotNil(t, config) @@ -135,7 +134,7 @@ description = "`+testUserDesc+`" `), fileperms.PublicFile)) _, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), + ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), []string{extraConfigPath}, false, ) require.NoError(t, err) @@ -166,7 +165,7 @@ description = "`+testUserDesc+`" `), fileperms.PublicFile)) _, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), nil, false, + ctx.FS(), ctx.OSEnv(), testProjectDir, true /*disableDefaultConfig*/, t.TempDir(), nil, false, ) require.NoError(t, err) require.NotNil(t, config) diff --git a/internal/projectgen/projectgen_test.go b/internal/projectgen/projectgen_test.go index d9b82d820..e1200e5e3 100644 --- a/internal/projectgen/projectgen_test.go +++ b/internal/projectgen/projectgen_test.go @@ -29,7 +29,6 @@ func requireProjectHasValidDefaultConfig(t *testing.T, ctx opctx.Ctx, projectPat // Load the config. _, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), projectPath, @@ -67,7 +66,7 @@ default-distro = { name = "other", version = "42.42" } // Load the project. foundProjectDir, config, err := projectconfig.LoadProjectConfig( - ctx, ctx.FS(), ctx.OSEnv(), testProjectPath, false /*disable default config?*/, t.TempDir(), nil, false, + ctx.FS(), ctx.OSEnv(), testProjectPath, false /*disable default config?*/, t.TempDir(), nil, false, ) require.NoError(t, err)