diff --git a/e2e/tasks/test_task_double_dash_behavior b/e2e/tasks/test_task_double_dash_behavior index 671e32ccbc..ddfe41d284 100644 --- a/e2e/tasks/test_task_double_dash_behavior +++ b/e2e/tasks/test_task_double_dash_behavior @@ -54,7 +54,7 @@ EOF # Usage spec handles its own flags (parsed by usage spec, not passed as $@) # shellcheck disable=SC2016 -assert "mise run with_usage -x myfile 2>&1" '[with_usage -x myfile] $ echo "custom=$usage_custom file=$usage_file args=$@" +assert "mise run with_usage -x myfile 2>&1" '[with_usage] $ echo "custom=$usage_custom file=$usage_file args=$@" custom=true file=myfile args=' # But mise flags still go to mise (myfile is consumed by usage spec, not passed to $@) diff --git a/e2e/tasks/test_task_prefix_args_disambiguation b/e2e/tasks/test_task_prefix_args_disambiguation new file mode 100644 index 0000000000..7e0e365863 --- /dev/null +++ b/e2e/tasks/test_task_prefix_args_disambiguation @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Test that task output prefixes include args only when the same task runs +# multiple times with different arguments (disambiguation), and omit args +# when there's no conflict. + +cat <<'EOF' >mise.toml +[tasks.greet] +run = 'echo "hello $1"' + +[tasks.build] +run = 'echo "building"' +EOF + +# When the same task runs twice with different args, prefixes should include args +output="$(mise run greet alice ::: greet bob 2>&1)" +assert_contains "echo \"$output\"" "[greet alice]" +assert_contains "echo \"$output\"" "[greet bob]" + +# When a task runs only once, prefix should NOT include args +output="$(mise run greet alice 2>&1)" +assert_not_contains "echo \"$output\"" "[greet alice]" +assert_contains "echo \"$output\"" "[greet]" + +# Different tasks should not trigger disambiguation +output="$(mise run greet alice ::: build 2>&1)" +assert_not_contains "echo \"$output\"" "[greet alice]" +assert_contains "echo \"$output\"" "[greet]" +assert_contains "echo \"$output\"" "[build]" diff --git a/src/cli/run.rs b/src/cli/run.rs index 3ff99bb7fd..583ab634e6 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -527,7 +527,8 @@ impl Run { async fn prepare_tasks(&mut self, config: &Arc, mut tasks: Vec) -> Result { let fetcher = crate::task::task_fetcher::TaskFetcher::new(self.no_cache); fetcher.fetch_tasks(&mut tasks).await?; - let tasks = Deps::new(config, tasks).await?; + let mut tasks = Deps::new(config, tasks).await?; + tasks.mark_ambiguous_prefixes(); self.is_linear = tasks.is_linear(); Ok(tasks) } diff --git a/src/task/deps.rs b/src/task/deps.rs index bcd69f6c90..09bddded46 100644 --- a/src/task/deps.rs +++ b/src/task/deps.rs @@ -122,6 +122,7 @@ impl Deps { for idx in to_remove { deps.graph.remove_node(idx); } + deps.mark_ambiguous_prefixes(); Ok(deps) } @@ -228,6 +229,25 @@ impl Deps { self.graph.node_indices().map(|idx| &self.graph[idx]) } + /// Mark tasks that share a display_name so their prefix includes args + /// for disambiguation (e.g. `[test-docker 4.1]` vs `[test-docker 4.2]`). + pub fn mark_ambiguous_prefixes(&mut self) { + let mut name_to_indices: HashMap> = HashMap::new(); + for idx in self.graph.node_indices() { + name_to_indices + .entry(self.graph[idx].display_name.clone()) + .or_default() + .push(idx); + } + for indices in name_to_indices.values() { + if indices.len() > 1 { + for &idx in indices { + self.graph[idx].show_args_in_prefix = true; + } + } + } + } + pub fn is_linear(&self) -> bool { let mut graph = self.graph.clone(); // pop dependencies off, if we get multiple dependencies at once it's not linear diff --git a/src/task/mod.rs b/src/task/mod.rs index 30eab7f958..e6af8b679e 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -293,6 +293,11 @@ pub struct Task { /// Name of the task template to extend (requires experimental = true) #[serde(default)] pub extends: Option, + + /// When true, include args in the output prefix to disambiguate tasks + /// with the same display_name but different arguments. + #[serde(skip)] + pub show_args_in_prefix: bool, } impl Task { @@ -517,9 +522,13 @@ impl Task { pub fn prefix(&self) -> String { let max_width = 40; - let inner = format!("{} {}", self.display_name, self.args.join(" ")); - let inner = inner.trim(); - format!("[{}]", console::truncate_str(inner, max_width, "…")) + let inner = if self.show_args_in_prefix && !self.args.is_empty() { + let s = format!("{} {}", self.display_name, self.args.join(" ")); + s.trim().to_string() + } else { + self.display_name.clone() + }; + format!("[{}]", console::truncate_str(&inner, max_width, "…")) } pub fn run(&self) -> &Vec { @@ -1267,6 +1276,7 @@ impl Default for Task { timeout: None, remote_file_source: None, extends: None, + show_args_in_prefix: false, } } }