Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/commands/config/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -177,7 +177,9 @@ fn check_user_config() -> anyhow::Result<Option<UpdateCandidate>> {
}))
}

fn check_project_config() -> anyhow::Result<Option<UpdateCandidate>> {
/// `print` is `--print`, which renders the migrated content to stdout and
/// writes nothing.
fn check_project_config(print: bool) -> anyhow::Result<Option<UpdateCandidate>> {
let repo = match Repository::current() {
Ok(repo) => repo,
Err(_) => return Ok(None),
Expand All @@ -192,14 +194,22 @@ fn check_project_config() -> anyhow::Result<Option<UpdateCandidate>> {
}

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 <main> 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")?;

let result = worktrunk::config::check_and_migrate(
&config_path,
&original,
!is_linked, // only actionable from main worktree
actionable,
ConfigFileKind::Project,
Some(&repo),
false,
Expand All @@ -209,7 +219,7 @@ fn check_project_config() -> anyhow::Result<Option<UpdateCandidate>> {
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));
Expand Down
59 changes: 59 additions & 0 deletions tests/integration_tests/config_show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5286,3 +5286,62 @@ 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 <main> 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!(
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,
"--print must not modify the project config file"
);
Comment thread
worktrunk-bot marked this conversation as resolved.
}
Loading