A minimal 45‑minute clone of Worktrunk using only Rust stdlib
Welcome to another official UVF IT guide! In this quick build you’ll create a tiny CLI that mimics the core of Worktrunk – managing Git worktrees from the command line. We’ll stick to pure Rust and the system Git binary, so no extra services or paid APIs are required.
We’ll walk through scaffolding a Cargo project, adding a simple subcommand to list, add, and remove worktrees, run it locally, and verify it works with a real Git repo.
Create a new Cargo binary project that will hold our CLI. Cargo will generate the basic directory layout and a starter main.rs. Next we add clap – a lightweight argument parser – as a dependency so we can define subcommands later.
All of this is done from the terminal; no IDE configuration is required. Once the dependency is added, Cargo will fetch it the first time you build.
cargo new worktree-cli --bin
cd worktree-cli
cargo add clap --features deriveWe introduce a Worktree struct that mirrors the data we care about: path, branch, and whether it’s the current checkout. Then we write a small helper run_git that spawns the system git binary, captures its output, and returns a Result<String> – this keeps the rest of the code clean and testable.
The helper will be reused for all git commands (list, add, prune). By keeping everything in src/main.rs we stay minimal while still having a clear separation between data and execution logic.
src/main.rs
use std::process::{Command, Stdio};
use std::io::{self, Write};
#[derive(Debug)]
struct Worktree {
path: String,
branch: String,
is_current: bool,
}
/// Executes a git command and returns stdout as a String.
/// On error we forward the command's stderr for easier debugging.
fn run_git(args: &[&str]) -> io::Result<String> {
let output = Command::new("git")
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let err = String::from_utf8_lossy(&output.stderr);
Err(io::Error::new(io::ErrorKind::Other, err))
}
}
fn main() {
// Placeholder: argument parsing will be added in later phases.
// For now we just verify the helper works against the current repo.
match run_git(&["status", "--short"]) {
Ok(status) => println!("Git status output:\n{}", status),
Err(e) => eprintln!("Failed to run git: {}", e),
}
}In this phase we wire up the command‑line interface using the clap crate. We expose three subcommands—list, add, and prune—that map directly to the helper functions you wrote earlier. Each subcommand parses its own arguments, calls the appropriate helper, and prints a friendly message for the user. Keeping the logic thin in main.rs makes the binary easy to extend later.
src/main.rs
use clap::{Parser, Subcommand};
mod helpers; // assumes list_worktrees, add_worktree, prune_worktrees are defined
#[derive(Parser)]
#[command(name = "worktrunk_clone")]
#[command(about = "Minimal Git worktree manager")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Show all worktrees for the repository
List { path: String },
/// Add a new worktree at <branch> under <dest>
Add { repo: String, branch: String, dest: String },
/// Remove stale worktree entries
Prune { path: String },
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::List { path } => {
helpers::list_worktrees(&path).expect("failed to list worktrees");
}
Commands::Add { repo, branch, dest } => {
helpers::add_worktree(&repo, &branch, &dest).expect("failed to add worktree");
}
Commands::Prune { path } => {
helpers::prune_worktrees(&path).expect("failed to prune worktrees");
}
}
}Now we compile the project and run a quick integration test against a temporary Git repository. The script creates a fresh repo, adds an initial commit, and then exercises the list and add commands to prove the clone works end‑to‑end. All steps are performed in /tmp/demo so they leave no trace on your machine after the test finishes.
#!/usr/bin/env bash
set -e
# 1. Build the binary
cargo build --release
# 2. Create a temporary repo
TMPDIR=$(mktemp -d)
cd $TMPDIR
git init .
echo "hello" > README.md
git add README.md
git commit -m "init"
# 3. Test the CLI
../target/release/worktrunk_clone list $TMPDIR
../target/release/worktrunk_clone add $TMPDIR master $TMPDIR/feature1
../target/release/worktrunk_clone list $TMPDIR
# 4. Clean up
cd /
rm -rf $TMPDIRAfter the script runs, you should see the original worktree listed, followed by the newly added feature1 directory. This confirms that the core commands behave as expected. As a next step you can add a --quiet flag to suppress informational output or implement a remove subcommand to delete a worktree cleanly.
#!/usr/bin/env bash
set -e
# Verify that list prints at least one line
output=$(./target/release/worktrunk_clone list /tmp/demo)
lines=$(echo "$output" | wc -l)
if [ $lines -lt 1 ]; then
echo "FAIL: list produced no output"
exit 1
fi
# Verify that the added worktree directory exists
if [ ! -d /tmp/demo/feature1 ]; then
echo "FAIL: added worktree not found"
exit 1
fi
echo "All checks passed!"| Check | Command / Why it matters |
|---|---|
| Cargo builds without errors | cargo build --release returns exit code 0 |
list prints at least one line (the main worktree) | ./target/release/worktrunk_clone list | wc -l >= 1 |
add creates a new worktree directory | ls /tmp/demo/feature1 && echo "exists" |
You now have a functional subset of Worktrunk. Feel free to add more subcommands (remove, checkout), improve error handling, or package the binary for distribution.