Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
# APP-3709: Auto-Generate Worktree Branch Names
Linear: [APP-3709](https://linear.app/warpdotdev/issue/APP-3709/auto-generate-worktree-branch-names)
## Summary
Replace the placeholder counter-based worktree branch name generator (`worktree-1`, `worktree-2`, …) with a pure function that produces memorable, distinctive names by combining two randomly selected desert/southwest-themed words.
## Problem
The current `generate_worktree_branch_name()` function uses a global `AtomicU32` counter that:
- Produces forgettable, indistinct names (`worktree-1`, `worktree-2`, …).
- Resets to 1 on every app restart, so the same names get reused across sessions.
- Gives no hint about which worktree is which when the user has several open.
Developers often have multiple worktrees active simultaneously. Names like `mesa-coyote` or `obsidian-monsoon` are immediately distinguishable in a tab bar, branch list, or terminal prompt.
## Goals
- Provide a pure, stateless function that returns a random `{word1}-{word2}` branch name drawn from a curated 198-word desert/southwest vocabulary.
- Use this function everywhere a worktree branch name is auto-generated:
1. The "New worktree" modal when the "Autogenerate worktree branch name" checkbox is checked.
2. When a saved worktree tab config with `worktree_name_autogenerated = true` is re-opened from the menu.
- Produce names that are valid git branch names with no further sanitization.
- Guarantee uniqueness: never generate a name that collides with an existing local branch in the target repository.
## Non-goals
- Letting the user configure or extend the word list.
- Changing any UI layout or modal behavior — this is purely a naming-function replacement.
- Persisting a "used names" set across sessions (uniqueness is checked against the repo's actual branch list at generation time).
## Figma
Figma: none (no UI changes).
## User Experience
### Generated name format
Each auto-generated name is two words joined by a hyphen:
```
{word1}-{word2}
```
Both words are drawn uniformly at random from the same 198-word list. The two words must be distinct (no `mesa-mesa`).
Examples: `mesa-coyote`, `obsidian-monsoon`, `saguaro-twilight`, `rimrock-falcon`, `turquoise-arroyo`.
### Word list
198 words across six categories, embedded as a compile-time constant array.
**Landforms & Terrain (50)**
mesa, canyon, arroyo, butte, gulch, plateau, dune, bluff, ridge, ravine, basin, crater, ledge, outcrop, escarpment, badlands, flats, playa, wash, gorge, pinnacle, spire, monolith, arch, chasm, chimney, crag, cuesta, alcove, saddle, rimrock, talus, scree, coulee, caldera, cinder, lava, malpais, pediment, bajada, bolson, inselberg, tepui, mogote, notch, gap, pass, switchback, hogback, caprock
**Desert Plants (35)**
cactus, saguaro, agave, yucca, mesquite, ocotillo, creosote, juniper, pinyon, prickly, cholla, barrel, palo-verde, ironwood, saltbush, brittlebush, lupine, mallow, mariposa, sotol, lechuguilla, candelilla, jojoba, chamisa, rabbitbrush, claret, hedgehog, fishhook, organ-pipe, joshua, tumbleweed, sagebrush, chaparral, manzanita, madrone
**Desert Animals (36)**
armadillo, coyote, roadrunner, jackrabbit, rattler, sidewinder, gila, javelina, pronghorn, bighorn, kit-fox, bobcat, cougar, hawk, vulture, falcon, quail, wren, thrasher, horned-toad, gecko, tortoise, tarantula, scorpion, centipede, kingsnake, coachwhip, racer, ringtail, badger, cottontail, mule-deer, prairie-dog, burrowing-owl, nighthawk, swift
**Minerals
obsidian, flint, granite, quartz, sandstone, limestone, basalt, turquoise, onyx, jasper, agate, garnet, topaz, copper, iron, cobalt, tin, zinc, mica, feldspar, gypsum, calcite, shale, pumice, travertine, petrified, opal, malachite, pyrite, cinnabar
**Southwest Culture & Spanish (24)**
adobe, oz, tinaja, acequia, ramada, portal, ristra, luminaria, mirador, hacienda, viga, latilla, nicho, olla, metate, petroglyph, pictograph, solstice, equinox, siesta, sierra, rio, tierra, cumbre
**Weather & Sky (23)**
monsoon, dust-devil, mirage, sundowner, zephyr, thermal, drought, flash-flood, dry-lightning, haze, shimmer, sundog, corona, twilight, dusk, dawn, starlight, moonrise, ember, smoke, wildfire, brushfire, firestorm
### Where the function is called
1. **New worktree modal** (`new_worktree_modal.rs`): When the user clicks "Open" with the "Autogenerate worktree branch name" checkbox checked, the function is called to produce the branch name for the `git worktree add` command.
2. **Saved worktree tab configs**: When a tab config with `worktree_name_autogenerated = true` on any pane is opened from the menu, the commands' branch-name placeholder is replaced with a freshly generated name so each re-open creates a new worktree.
### Uniqueness guarantee
The generated name must not collide with any existing local branch in the target repository. The function takes the repo path as input and queries the repo's branch list. On collision, it makes up to 2 attempts at the same word count before escalating to more words:
1. Generate a 2-word name (`mesa-coyote`). Up to 2 attempts if taken.
2. If both 2-word attempts collide, generate a 3-word name (`mesa-coyote-obsidian`). Up to 2 attempts.
3. Continue escalating (4 words, up to 5 words max) as needed.
Each additional word exponentially increases the pool of candidates, making exhaustion effectively impossible.
If the repo path is unavailable or the branch list cannot be read (e.g. not a git repo), the function falls back to generating a name without the uniqueness check — the user will see a git error if a collision occurs and can retry.
### Function properties
- **Pure naming core**: The naming function takes a set of existing branch names and returns a name — no I/O. Branch listing is performed by the caller.
- **Stateless**: Uses `rand` for randomness rather than a global counter.
- **Git-safe output**: Every word in the list is already a valid git ref component (lowercase alphanumeric and hyphens only, no leading/trailing hyphens, no consecutive dots or slashes).
## Edge Cases
1. **Hyphenated words**: Words like `palo-verde`, `kit-fox`, `dust-devil` already contain hyphens. A name like `palo-verde-kit-fox` is valid for git and reads naturally.
2. **Deterministic testing**: The function should accept an optional random source so unit tests can assert specific outputs.
3. **Repo with many themed branches**: A user who has generated hundreds of worktrees in the same repo will still have tens of thousands of available pairs. The retry loop handles this transparently.
4. **Non-git directory**: When the repo path doesn't point to a valid git repo, skip the uniqueness check and return a random name. Git will report the error when the worktree command runs.
## Success Criteria
1. `generate_worktree_branch_name()` returns a string matching the pattern `{word}-{word}` where both words come from the 198-word list and are not the same word.
2. Repeated calls produce different names (with overwhelming probability).
3. The generated name does not collide with any existing local branch in the target repository when a repo path is provided.
4. The generated name is used as the branch name in `git worktree add -b {name}` and as the worktree directory name.
5. No global counter or shared mutable state — the function is safe to call from any thread.
6. All 198 words in the list are valid git branch name components.
7. Saved worktree configs with `worktree_name_autogenerated = true` produce a fresh name on each menu open, not the baked-in name from the TOML.
## Validation
- **Unit tests**: Call the function many times, assert format matches `{word}-{word}`, assert both words are in the word list, assert the two words differ.
- **Deterministic test**: Seed the random source and assert a specific output.
- **Uniqueness test**: Mock a set of existing branches, call the function, assert it avoids all of them.
- **Manual test**: Open the "New worktree" modal with autogenerate checked, click "Open" several times — each tab should have a distinct desert-themed branch name visible in the tab title and terminal output.
- **Re-open test**: Save a worktree config, re-open it from the menu — the new tab should use a freshly generated branch name, not the one from the first open.
## Open Questions
(None outstanding.)
+178
View File
@@ -0,0 +1,178 @@
# APP-3709: Auto-Generate Worktree Branch Names — Tech Spec
## Problem
`generate_worktree_branch_name()` in `new_worktree_modal.rs` uses a global `AtomicU32` counter to produce `worktree-1`, `worktree-2`, etc. These names are forgettable, collide across sessions, and provide no uniqueness guarantee against existing branches in the target repo. The product spec calls for a themed naming function that combines two random words from a 198-word desert/southwest vocabulary and guarantees uniqueness by escalating to more words on collision.
## Relevant Code
- `warp_util/src/worktree_names.rs` — pure naming module (word list, `generate_name`, `generate_unique_name`, `generate_worktree_branch_name`)
- `app/src/util/git.rs:57-78``list_local_branches_sync()` (synchronous branch listing)
- `app/src/tab_configs/tab_config.rs:62-91``TabConfigPaneNode` struct (`worktree_name_autogenerated`)
- `app/src/tab_configs/tab_config.rs:139-174``render_tab_config()` (threads `worktree_branch_name` through to resolution)
- `app/src/tab_configs/tab_config.rs:299-324``resolve_pane_node()` leaf branch (template variable substitution)
- `app/src/tab_configs/tab_config.rs:350-403``build_worktree_config_toml()` (TOML generation for both manual and autogenerate modes)
- `app/src/workspace/view.rs:7138-7153``maybe_generate_worktree_name()` (scans panes, fetches branches, generates name)
- `app/src/workspace/view.rs:7163-7230``handle_new_worktree_submit()` (writes TOML, opens config)
- `app/src/workspace/view.rs:4892-4958``open_tab_config_with_params()` / `open_tab_config()` (re-open path)
- `app/src/tab_configs/new_worktree_modal.rs:224-257``try_submit()` (emits `None` for autogenerate, `Some(name)` for manual)
## Current State
**Modal flow**: When the user clicks "Open" with autogenerate checked, `try_submit()` calls `generate_worktree_branch_name()` which returns `worktree-{N}`. The workspace handler then bakes this name directly into the TOML commands (`git worktree add -b worktree-1 ../worktree-1 main`) and writes it to `~/.warp/tab_configs/`.
**Saved config re-open flow**: When a saved worktree config is clicked in the menu, `open_tab_config()``open_tab_config_with_params()``render_tab_config()``resolve_pane_tree()` renders the commands. Currently the branch name is hardcoded in the TOML, so re-opening always tries to create the same branch — which fails if it already exists.
**Branch listing**: `DiffStateModel::get_all_branches()` in `diff_state.rs` runs `git for-each-ref --sort=-committerdate --format=%(refname:short) refs/heads` asynchronously. The `BranchPicker` uses this. There is no synchronous branch-listing utility.
**Rand usage**: The codebase uses `rand::seq::SliceRandom` with `rand::thread_rng()` (e.g. `agent_tips.rs:467`). `rand` is already a workspace dependency in `app/Cargo.toml`.
## Proposed Changes
### 1. Pure naming module in `warp_util`
New file: `warp_util/src/worktree_names.rs`. This module has zero I/O — it takes a set of existing branch names and returns a unique name. Keeping it in `warp_util` isolates it from the `app` crate's compile graph and keeps it trivially testable.
`rand` needs to be added as a dependency of `warp_util` (it's already a workspace dep).
**Word list**: A `const WORDS: &[&str]` array with the 198 desert/southwest words from the product spec, sorted alphabetically within each category section for readability.
**Core function** (pure, deterministic with a seeded RNG):
```rust
fn generate_name(
word_count: usize,
existing_branches: &HashSet<&str>,
rng: &mut impl rand::Rng,
) -> Option<String>
```
- Picks `word_count` distinct words at random from `WORDS`.
- Joins them with `-`.
- If the result is in `existing_branches`, retries (bounded by `MAX_RETRIES_PER_LEVEL = 2`).
- Returns `Some(name)` on success, `None` if all retries collided.
**Escalating uniqueness wrapper** (pure, deterministic with a seeded RNG):
```rust
pub fn generate_unique_name(
existing_branches: &HashSet<&str>,
rng: &mut impl rand::Rng,
) -> String
```
- Starts at `word_count = 2`.
- Calls `generate_name(word_count, existing_branches, rng)`.
- If `None` (all retries collided), increments `word_count` and retries.
- Cap at `word_count = 5` (198^5 ≈ 2.9 × 10^11 possibilities) as a safety bound, then fall back to appending a random numeric suffix.
**Convenience entry point** (uses thread_rng):
```rust
pub fn generate_worktree_branch_name(
existing_branches: &HashSet<&str>,
) -> String
```
- Calls `generate_unique_name(existing_branches, &mut rand::thread_rng())`.
- This is the API call sites use. The `rng`-parameterized version exists for deterministic testing.
### 2. Synchronous git branch listing in `app/src/util/git.rs`
New function alongside the existing async helpers:
```rust
#[cfg(feature = "local_fs")]
pub fn list_local_branches_sync(repo_path: &Path) -> HashSet<String>
```
- Runs `git branch --list --format=%(refname:short)` via `std::process::Command`.
- Returns a `HashSet<String>` of local branch names.
- On failure (not a git repo, git not found, etc.) returns an empty set.
**Why synchronous**: The call sites (`handle_new_worktree_submit`, `maybe_generate_worktree_name` in `open_tab_config`) are synchronous view handlers. `git branch --list` is a local filesystem read (packed-refs + loose refs) that completes in single-digit milliseconds even for repos with thousands of branches. A synchronous `std::process::Command` is the simplest approach and avoids threading async through the view layer.
### 3. Template variable substitution in commands
The `commands` array on `TabConfigPaneNode` already exists. The change is in how commands are rendered at open time.
When `worktree_branch_name` is provided, `render_tab_config` injects it into both the unquoted and quoted Handlebars context maps under the key `autogenerated_branch_name`. The existing `handlebars::render_template(cmd, quoted)` call in `resolve_pane_node` handles substitution — no custom `.replace()` needed. This is consistent with how all other tab config params (`{{branch}}`, `{{repo}}`, etc.) work.
The `commands` array in the TOML uses `{{autogenerated_branch_name}}` as a Handlebars template variable. Users can freely edit the commands — reorder them, add their own (e.g. `gt branch create`, `npm install`), or use `{{autogenerated_branch_name}}` in custom commands. The default commands written by the modal work out-of-the-box without any user editing.
### 4. Plumbing the generated name through `open_tab_config`
Modify `open_tab_config()` in `workspace/view.rs`: before rendering, scan the config's panes for any with `worktree_name_autogenerated = true`. If found:
1. Read the pane's `cwd` to determine the repo path.
2. Call `list_local_branches_sync(repo_path)` to get existing branches.
3. Call `generate_worktree_branch_name(&existing_branches)` to get a fresh name.
4. Pass the generated name into `render_tab_config` (new parameter), which injects it into the Handlebars context so `{{autogenerated_branch_name}}` in commands gets substituted.
`render_tab_config` gains an optional `worktree_branch_name: Option<&str>` parameter. When `Some`, the name is added to the template context; when `None`, no extra context is injected. This keeps the API change minimal for all non-worktree configs.
### 5. TOML generation in `handle_new_worktree_submit`
Change `handle_new_worktree_submit()` in `workspace/view.rs` to write the config with template variables in the commands:
```toml
[[panes]]
id = "main"
type = "terminal"
cwd = "/path/to/repo"
worktree_name_autogenerated = true
commands = [
"git worktree add -b {{autogenerated_branch_name}} ../{{autogenerated_branch_name}} main",
"cd ../{{autogenerated_branch_name}}",
]
```
The `commands` array uses `{{autogenerated_branch_name}}` — the same Handlebars syntax as all other tab config params. Users can edit the TOML to add custom commands (e.g. `gt branch create`, `npm install`) or reorder them.
### 6. Update modal and workspace handler call sites
In `new_worktree_modal.rs`:
- Remove `WORKTREE_COUNTER` and the old `generate_worktree_branch_name()`.
- `try_submit()` emits `worktree_branch_name: None` when autogenerate is on, `Some(name)` when the user typed a name manually. The modal does not generate names — it delegates that to the workspace handler.
In `workspace/view.rs`:
- `handle_new_worktree_submit()` receives `worktree_branch_name: Option<&str>`. When `None`, it calls `list_local_branches_sync` then `generate_worktree_branch_name` to produce a fresh name. This name is used both as the TOML filename hint and as the branch name for the initial open.
- `maybe_generate_worktree_name()` is a shared helper used by both `open_tab_config()` (re-open path) and `handle_tab_config_params_modal_body_event()` (params modal submit). It scans panes for `worktree_name_autogenerated = true`, fetches branches, and generates a name.
### 7. Module registration
Add `pub mod worktree_names;` to `warp_util/src/lib.rs`.
## End-to-End Flow
### Modal flow (new worktree)
1. User clicks "Open" with autogenerate checked.
2. `try_submit()` emits `Submit { repo, branch: "main", worktree_branch_name: None }`.
3. `handle_new_worktree_submit()` receives `None`, calls `list_local_branches_sync(repo_path)` to get existing branches.
4. Passes the branch set to `generate_worktree_branch_name(&branches)`. Naming function generates a 2-word name; on collision, retries at the same word count then escalates.
5. Returns e.g. `mesa-coyote`.
6. Workspace handler writes TOML with `worktree_name_autogenerated = true` and `commands` containing `{{autogenerated_branch_name}}` template variables via `build_worktree_config_toml`.
7. Parses the TOML back into a `TabConfig` and calls `open_tab_config_with_params` directly with the just-generated name — no second generation pass.
### Re-open flow (saved config)
1. User clicks a saved worktree config in the menu.
2. `open_tab_config()` scans panes, finds `worktree_name_autogenerated = true`.
3. Reads `cwd` from the pane to determine the repo path.
4. Calls `list_local_branches_sync(repo_path)``HashSet`.
5. Calls `generate_worktree_branch_name(&branches)` → e.g. `obsidian-monsoon`.
6. Passes the name into `render_tab_config(..., Some("obsidian-monsoon"))`.
7. `render_tab_config` injects `obsidian-monsoon` into the Handlebars context, and `resolve_pane_node` renders `{{autogenerated_branch_name}}``obsidian-monsoon` in all commands via normal Handlebars substitution.
8. New tab opens with the substituted commands.
## Risks and Mitigations
- **Synchronous git call on main thread**: `git branch --list` is a local filesystem read (packed-refs + loose refs). Completes in single-digit milliseconds even for repos with thousands of branches. If this ever becomes a concern, the function can be made async without changing the API surface.
- **Word list staleness**: The 198-word list is a compile-time constant. No runtime mechanism to update it. This is intentional — the list is a curated vocabulary, not a growing dictionary.
## Testing and Validation
- **Unit tests in `warp_util/src/worktree_names.rs`**:
- `generate_name` with a seeded RNG produces deterministic output.
- Generated names match `{word}-{word}` format, both words are in `WORDS`, words are distinct.
- `generate_unique_name` avoids all names in a provided `existing_branches` set.
- Escalation: pre-fill `existing_branches` with enough 2-word combos to force a 3-word name, verify output has 3 words.
- All words in `WORDS` are valid git branch name components (no spaces, no `..`, no control chars, no leading `-`).
- **Unit test for template substitution**: Verify that a pane with `worktree_name_autogenerated = true` and commands containing `{{autogenerated_branch_name}}` produces the expected substituted commands.
- **Unit test for custom commands**: Verify that user-added commands (with and without `{{autogenerated_branch_name}}`) are preserved and substituted correctly.
- **`cargo check`**: Verify no compilation errors after all changes.
## Follow-ups
- Add a test that exercises the synchronous `git branch --list` path against a real temporary git repo.
- Consider supporting a `worktree_path_template` field on the pane for users who want worktrees in a non-default location (e.g. `~/worktrees/{name}` instead of `../{name}`).