Skip to content
Merged
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
82 changes: 82 additions & 0 deletions docs/content/step.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ wt step push
- `push` — Fast-forward target to current branch
- [`diff`](#wt-step-diff) — Show all changes since branching (committed, staged, unstaged, untracked)
- [`copy-ignored`](#wt-step-copy-ignored) — Copy gitignored files between worktrees
- [`eval`](#wt-step-eval) — <span class="badge-experimental"></span> Evaluate a template expression
- [`for-each`](#wt-step-for-each) — <span class="badge-experimental"></span> Run a command in every worktree
- [`promote`](#wt-step-promote) — <span class="badge-experimental"></span> Swap a branch into the main worktree
- [`prune`](#wt-step-prune) — Remove worktrees and branches merged into the default branch
Expand Down Expand Up @@ -64,6 +65,7 @@ Usage: <b><span class=c>wt step</span></b> <span class=c>[OPTIONS]</span> <span
<b><span class=c>rebase</span></b> Rebase onto target
<b><span class=c>diff</span></b> Show all changes since branching
<b><span class=c>copy-ignored</span></b> Copy gitignored files to another worktree
<b><span class=c>eval</span></b> [experimental] Evaluate a template expression
<b><span class=c>for-each</span></b> [experimental] Run command in each worktree
<b><span class=c>promote</span></b> [experimental] Swap a branch into the main worktree
<b><span class=c>prune</span></b> [experimental] Remove worktrees merged into the default branch
Expand Down Expand Up @@ -451,6 +453,86 @@ Usage: <b><span class=c>wt step copy-ignored</span></b> <span class=c>[OPTIONS]<
Verbose output (-v: hooks, templates; -vv: debug report)
{% end %}

## wt step eval <span class="badge-experimental"></span>

Evaluate a template expression. Prints the result to stdout for use in scripts and shell substitutions.

Evaluates a template expression in the current worktree context and prints the result to stdout. All [hook template variables and filters](@/hook.md#template-variables) are available.

Output goes to stdout with no decoration, making it suitable for shell substitution and piping.

### Examples

Get the port for the current branch:

```bash
$ wt step eval '{{ branch | hash_port }}'
16066
```

Use in shell substitution:

```bash
$ curl http://localhost:$(wt step eval '{{ branch | hash_port }}')/health
```

Combine multiple values:

```bash
$ wt step eval '{{ branch | hash_port }},{{ ("supabase-api-" ~ branch) | hash_port }}'
16066,16739
```

Use conditionals and filters:

```bash
$ wt step eval '{{ branch | sanitize_db }}'
feature_auth_oauth2_a1b
```

Show available template variables:

```bash
$ wt step eval --dry-run '{{ branch }}'
branch=feature/auth-oauth2
worktree_path=/home/user/projects/myapp-feature-auth-oauth2
...
Result: feature/auth-oauth2
```

Note: This command is experimental and may change in future versions.

### Command reference

{% terminal() %}
wt step eval - [experimental] Evaluate a template expression

Prints the result to stdout for use in scripts and shell substitutions.

Usage: <b><span class=c>wt step eval</span></b> <span class=c>[OPTIONS]</span> <span class=c>&lt;TEMPLATE&gt;</span>

<b><span class=g>Arguments:</span></b>
<span class=c>&lt;TEMPLATE&gt;</span>
Template expression to evaluate

<b><span class=g>Options:</span></b>
<b><span class=c>--dry-run</span></b>
Show template variables and expanded result

<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)

<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command

<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path

<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: hooks, templates; -vv: debug report)
{% end %}

## wt step for-each <span class="badge-experimental"></span>

Run command in each worktree. Executes sequentially with real-time output; continues on failure.
Expand Down
80 changes: 80 additions & 0 deletions skills/worktrunk/reference/step.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ wt step push
- `push` — Fast-forward target to current branch
- [`diff`](#wt-step-diff) — Show all changes since branching (committed, staged, unstaged, untracked)
- [`copy-ignored`](#wt-step-copy-ignored) — Copy gitignored files between worktrees
- [`eval`](#wt-step-eval) — [experimental] Evaluate a template expression
- [`for-each`](#wt-step-for-each) — [experimental] Run a command in every worktree
- [`promote`](#wt-step-promote) — [experimental] Swap a branch into the main worktree
- [`prune`](#wt-step-prune) — Remove worktrees and branches merged into the default branch
Expand All @@ -49,6 +50,7 @@ Usage: <b><span class=c>wt step</span></b> <span class=c>[OPTIONS]</span> <span
<b><span class=c>rebase</span></b> Rebase onto target
<b><span class=c>diff</span></b> Show all changes since branching
<b><span class=c>copy-ignored</span></b> Copy gitignored files to another worktree
<b><span class=c>eval</span></b> [experimental] Evaluate a template expression
<b><span class=c>for-each</span></b> [experimental] Run command in each worktree
<b><span class=c>promote</span></b> [experimental] Swap a branch into the main worktree
<b><span class=c>prune</span></b> [experimental] Remove worktrees merged into the default branch
Expand Down Expand Up @@ -427,6 +429,84 @@ Usage: <b><span class=c>wt step copy-ignored</span></b> <span class=c>[OPTIONS]<
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: hooks, templates; -vv: debug report)

## wt step eval [experimental]

Evaluate a template expression. Prints the result to stdout for use in scripts and shell substitutions.

Evaluates a template expression in the current worktree context and prints the result to stdout. All [hook template variables and filters](https://worktrunk.dev/hook/#template-variables) are available.

Output goes to stdout with no decoration, making it suitable for shell substitution and piping.

### Examples

Get the port for the current branch:

```bash
$ wt step eval '{{ branch | hash_port }}'
16066
```

Use in shell substitution:

```bash
$ curl http://localhost:$(wt step eval '{{ branch | hash_port }}')/health
```

Combine multiple values:

```bash
$ wt step eval '{{ branch | hash_port }},{{ ("supabase-api-" ~ branch) | hash_port }}'
16066,16739
```

Use conditionals and filters:

```bash
$ wt step eval '{{ branch | sanitize_db }}'
feature_auth_oauth2_a1b
```

Show available template variables:

```bash
$ wt step eval --dry-run '{{ branch }}'
branch=feature/auth-oauth2
worktree_path=/home/user/projects/myapp-feature-auth-oauth2
...
Result: feature/auth-oauth2
```

Note: This command is experimental and may change in future versions.

### Command reference

wt step eval - [experimental] Evaluate a template expression

Prints the result to stdout for use in scripts and shell substitutions.

Usage: <b><span class=c>wt step eval</span></b> <span class=c>[OPTIONS]</span> <span class=c>&lt;TEMPLATE&gt;</span>

<b><span class=g>Arguments:</span></b>
<span class=c>&lt;TEMPLATE&gt;</span>
Template expression to evaluate

<b><span class=g>Options:</span></b>
<b><span class=c>--dry-run</span></b>
Show template variables and expanded result

<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)

<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command

<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path

<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: hooks, templates; -vv: debug report)

## wt step for-each [experimental]

Run command in each worktree. Executes sequentially with real-time output; continues on failure.
Expand Down
2 changes: 2 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@ wt step push
- `push` — Fast-forward target to current branch
- [`diff`](#wt-step-diff) — Show all changes since branching (committed, staged, unstaged, untracked)
- [`copy-ignored`](#wt-step-copy-ignored) — Copy gitignored files between worktrees
- [`eval`](#wt-step-eval) — [experimental] Evaluate a template expression
- [`for-each`](#wt-step-for-each) — [experimental] Run a command in every worktree
- [`promote`](#wt-step-promote) — [experimental] Swap a branch into the main worktree
- [`prune`](#wt-step-prune) — Remove worktrees and branches merged into the default branch
Expand All @@ -1048,6 +1049,7 @@ wt step push
<!-- subdoc: squash -->
<!-- subdoc: diff -->
<!-- subdoc: copy-ignored -->
<!-- subdoc: eval -->
<!-- subdoc: for-each -->
<!-- subdoc: promote -->
<!-- subdoc: prune -->
Expand Down
59 changes: 59 additions & 0 deletions src/cli/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,65 @@ The `.worktreeinclude` pattern is shared with [Claude Code on desktop](https://c
force: bool,
},

/// \[experimental\] Evaluate a template expression
///
/// Prints the result to stdout for use in scripts and shell substitutions.
#[command(
after_long_help = r#"Evaluates a template expression in the current worktree context and prints the result to stdout. All [hook template variables and filters](@/hook.md#template-variables) are available.

Output goes to stdout with no decoration, making it suitable for shell substitution and piping.

## Examples

Get the port for the current branch:

```console
$ wt step eval '{{ branch | hash_port }}'
16066
```

Use in shell substitution:

```console
$ curl http://localhost:$(wt step eval '{{ branch | hash_port }}')/health
```

Combine multiple values:

```console
$ wt step eval '{{ branch | hash_port }},{{ ("supabase-api-" ~ branch) | hash_port }}'
16066,16739
```

Use conditionals and filters:

```console
$ wt step eval '{{ branch | sanitize_db }}'
feature_auth_oauth2_a1b
```

Show available template variables:

```console
$ wt step eval --dry-run '{{ branch }}'
branch=feature/auth-oauth2
worktree_path=/home/user/projects/myapp-feature-auth-oauth2
...
Result: feature/auth-oauth2
```

Note: This command is experimental and may change in future versions.
"#
)]
Eval {
/// Template expression to evaluate
template: String,

/// Show template variables and expanded result
#[arg(long)]
dry_run: bool,
},

/// \[experimental\] Run command in each worktree
///
/// Executes sequentially with real-time output; continues on failure.
Expand Down
1 change: 1 addition & 0 deletions src/commands/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const BUILTIN_STEP_COMMANDS: &[&str] = &[
"commit",
"copy-ignored",
"diff",
"eval",
"for-each",
"promote",
"prune",
Expand Down
53 changes: 53 additions & 0 deletions src/commands/eval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! Eval command implementation
//!
//! Evaluates a template expression in the current worktree context and prints
//! the result to stdout. Designed for scripting — output is raw (no shell
//! escaping, no decoration).

use std::collections::HashMap;

use worktrunk::config::{UserConfig, expand_template};
use worktrunk::git::Repository;

use crate::commands::command_executor::{CommandContext, build_hook_context};

/// Evaluate a template expression in the current worktree context.
///
/// Prints the expanded result to stdout with a trailing newline. All hook
/// template variables and filters are available.
///
/// With `dry_run`, prints the template variables and the expanded result
/// With `dry_run`, prints the template variables and the expanded result
Comment on lines +19 to +20
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated line.

Suggested change
/// With `dry_run`, prints the template variables and the expanded result
/// With `dry_run`, prints the template variables and the expanded result
/// With `dry_run`, prints the template variables and the expanded result

/// to stderr — useful for debugging templates.
pub fn step_eval(template: &str, dry_run: bool) -> anyhow::Result<()> {
let repo = Repository::current()?;
let config = UserConfig::load()?;

let wt = repo.current_worktree();
let branch = wt.branch()?;
let worktree_path = wt.root()?;

let ctx = CommandContext::new(&repo, &config, branch.as_deref(), &worktree_path, false);
let context_map = build_hook_context(&ctx, &[])?;

let vars: HashMap<&str, &str> = context_map
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();

// No shell escaping — output is literal for scripting
let result = expand_template(template, &vars, false, &repo, "eval")?;

if dry_run {
let mut keys: Vec<&str> = context_map.keys().map(|k| k.as_str()).collect();
keys.sort();
for key in keys {
eprintln!("{}={}", key, context_map[key]);
}
eprintln!("---");
eprintln!("Result: {result}");
Comment thread
worktrunk-bot marked this conversation as resolved.
} else {
println!("{result}");
}
Ok(())
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) mod commit;
pub(crate) mod config;
pub(crate) mod configure_shell;
pub(crate) mod context;
mod eval;
mod for_each;
mod handle_switch;
mod hook_commands;
Expand Down Expand Up @@ -33,6 +34,7 @@ pub(crate) use config::{
pub(crate) use configure_shell::{
handle_configure_shell, handle_show_theme, handle_unconfigure_shell,
};
pub(crate) use eval::step_eval;
pub(crate) use for_each::step_for_each;
pub(crate) use handle_switch::{SwitchOptions, handle_switch};
pub(crate) use hook_commands::{add_approvals, clear_approvals, handle_hook_show, run_hook};
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use commands::{
handle_rebase, handle_remove, handle_remove_current, handle_show_theme, handle_squash,
handle_state_clear, handle_state_clear_all, handle_state_get, handle_state_set,
handle_state_show, handle_switch, handle_unconfigure_shell, resolve_worktree_arg, run_hook,
step_commit, step_copy_ignored, step_diff, step_for_each, step_prune, step_relocate,
step_commit, step_copy_ignored, step_diff, step_eval, step_for_each, step_prune, step_relocate,
};
use output::handle_remove_output;

Expand Down Expand Up @@ -286,6 +286,7 @@ fn handle_step_command(action: StepCommand) -> anyhow::Result<()> {
dry_run,
force,
} => step_copy_ignored(from.as_deref(), to.as_deref(), dry_run, force),
StepCommand::Eval { template, dry_run } => step_eval(&template, dry_run),
StepCommand::ForEach { args } => step_for_each(args),
StepCommand::Promote { branch } => {
handle_promote(branch.as_deref()).map(|result| match result {
Expand Down
5 changes: 3 additions & 2 deletions tests/integration_tests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,14 +633,15 @@ fn test_complete_step_subcommands(repo: TestRepo) {
"Missing copy-ignored"
);
assert!(subcommands.contains(&"diff"), "Missing diff");
assert!(subcommands.contains(&"eval"), "Missing eval");
assert!(subcommands.contains(&"for-each"), "Missing for-each");
assert!(subcommands.contains(&"promote"), "Missing promote");
assert!(subcommands.contains(&"prune"), "Missing prune");
assert!(subcommands.contains(&"relocate"), "Missing relocate");
assert_eq!(
subcommands.len(),
10,
"Should have exactly 10 step subcommands"
11,
"Should have exactly 11 step subcommands"
);
}

Expand Down
Loading
Loading