From b2c1e4102ab1c286cf8eb38e50840ed0b59fb662 Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Sun, 6 Sep 2026 06:47:09 +0000 Subject: [PATCH 1/2] fix(config): let wt config update --print read project config from a linked worktree The main-worktree requirement in check_project_config exists to keep the rewrite off a linked checkout, but it also gated --print, which writes nothing. From a linked worktree -- where a worktree tool's users mostly stand -- wt config update --print emitted an empty stdout, silently dropping the project config from the pipe, plus a stderr hint naming 'wt -C
config update' (a writing command) in answer to a read. Gate the rewrite on !is_linked as before; gate --print on nothing. --- src/commands/config/update.rs | 18 +++++++-- tests/integration_tests/config_show.rs | 53 ++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/commands/config/update.rs b/src/commands/config/update.rs index 2da0d7d8c..9b5696eb7 100644 --- a/src/commands/config/update.rs +++ b/src/commands/config/update.rs @@ -40,7 +40,7 @@ pub fn handle_config_update(yes: bool, print: bool) -> anyhow::Result<()> { if let Some(candidate) = check_user_config()? { candidates.push(candidate); } - if let Some(candidate) = check_project_config()? { + if let Some(candidate) = check_project_config(print)? { candidates.push(candidate); } @@ -177,7 +177,9 @@ fn check_user_config() -> anyhow::Result> { })) } -fn check_project_config() -> anyhow::Result> { +/// `print` is `--print`, which renders the migrated content to stdout and +/// writes nothing. +fn check_project_config(print: bool) -> anyhow::Result> { let repo = match Repository::current() { Ok(repo) => repo, Err(_) => return Ok(None), @@ -192,6 +194,14 @@ fn check_project_config() -> anyhow::Result> { } let is_linked = repo.current_worktree().is_linked().unwrap_or(true); + // The main-worktree requirement exists to keep the *rewrite* off a linked + // checkout, so `--print` — which writes nothing — is actionable from + // anywhere. Gating it too made `wt config update --print` emit an empty + // stdout plus a stderr hint from a linked worktree, which is where a + // worktree tool's users mostly stand: the pipe silently lost the project + // config, and the hint named `wt -C
config update`, a command that + // writes, in answer to a request that only reads. + let actionable = print || !is_linked; let original = std::fs::read_to_string(&config_path).context("Failed to read project config")?; @@ -199,7 +209,7 @@ fn check_project_config() -> anyhow::Result> { let result = worktrunk::config::check_and_migrate( &config_path, &original, - !is_linked, // only actionable from main worktree + actionable, ConfigFileKind::Project, Some(&repo), false, @@ -209,7 +219,7 @@ fn check_project_config() -> anyhow::Result> { return Ok(None); }; - if is_linked { + if !actionable { let cmd = suggest_command_in_dir(repo.repo_path()?, "config", &["update"], &[]); eprintln!("{}", hint_message("To update project config:")); eprintln!("{}", format_bash_with_gutter(&cmd)); diff --git a/tests/integration_tests/config_show.rs b/tests/integration_tests/config_show.rs index f3e49bffd..f40ad2d3d 100644 --- a/tests/integration_tests/config_show.rs +++ b/tests/integration_tests/config_show.rs @@ -5286,3 +5286,56 @@ fn test_project_config_path_env_var_half_anchored_errors(repo: TestRepo) { ); } } + +/// `wt config update --print` writes nothing, so the main-worktree +/// requirement that guards the *rewrite* must not gate it. Before the fix, +/// running it from a linked worktree emitted an empty stdout — silently +/// dropping the project config from the pipe — plus a stderr hint naming +/// `wt -C
config update`, a writing command, in answer to a read. +#[rstest] +fn test_config_update_print_emits_project_config_from_linked_worktree(repo: TestRepo) { + repo.write_project_config( + r#"pre-start = "ln -sf {{ main_worktree }}/node_modules" +"#, + ); + repo.commit("Add deprecated project config"); + let project_config_path = repo.root_path().join(".config").join("wt.toml"); + let before = fs::read_to_string(&project_config_path).unwrap(); + + let feature_path = repo.root_path().parent().unwrap().join("feature-print"); + repo.run_git(&[ + "worktree", + "add", + feature_path.to_str().unwrap(), + "-b", + "feature-print", + ]); + + let output = repo + .wt_command() + .args(["config", "update", "--print"]) + .current_dir(&feature_path) + .output() + .unwrap(); + + assert!( + output.status.success(), + "config update --print should succeed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("{{ repo }}") && !stdout.contains("{{ main_worktree }}"), + "--print from a linked worktree should emit the migrated project config, got: {stdout}" + ); + assert!( + output.stderr.is_empty(), + "--print must keep stderr empty for pipe-friendliness, got: {}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!( + fs::read_to_string(&project_config_path).unwrap(), + before, + "--print must not modify the project config file" + ); +} From d60ee5b52e00b6a0e5543f4d7b2d6dc8a916c7f1 Mon Sep 17 00:00:00 2001 From: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:03:58 +0000 Subject: [PATCH 2/2] test(config): assert --print leaves the linked worktree's config unmigrated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The write-freedom assertion checked the main worktree's .config/wt.toml, which the command never touches when run from a linked worktree: project_config_path() resolves against the current worktree root, so the file read — and the one a regression would rewrite — is /.config/wt.toml. Assert on that copy too. --- tests/integration_tests/config_show.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration_tests/config_show.rs b/tests/integration_tests/config_show.rs index f40ad2d3d..5cc54dc5c 100644 --- a/tests/integration_tests/config_show.rs +++ b/tests/integration_tests/config_show.rs @@ -5333,6 +5333,12 @@ fn test_config_update_print_emits_project_config_from_linked_worktree(repo: Test "--print must keep stderr empty for pipe-friendliness, got: {}", String::from_utf8_lossy(&output.stderr) ); + assert!( + fs::read_to_string(feature_path.join(".config").join("wt.toml")) + .unwrap() + .contains("{{ main_worktree }}"), + "--print must leave the linked worktree's config — the file it actually read — unmigrated" + ); assert_eq!( fs::read_to_string(&project_config_path).unwrap(), before,