Skip to content

Commit 8bd7f05

Browse files
committed
Add forest system foundation and CLI infrastructure
Establishes the foundation for forest-based stack management with core types, operations interfaces, and CLI integration. Forests are named collections of worktrees that enable unified operations like split, merge, and conflict-preserving reroot across entire stacks. ## Files Changed ### Added (6 files) - `src/erk/cli/commands/forest/README.md` - Comprehensive documentation for forest operations (split, merge, reroot) - `src/erk/cli/commands/forest/__init__.py` - Forest command group (infrastructure ready for subcommands) - `src/erk/core/forest_ops.py` - ForestOps interface with real/fake implementations for metadata persistence - `src/erk/core/forest_types.py` - Core data structures (Forest, ForestMetadata, RerootState) - `src/erk/core/forest_utils.py` - Pure utility functions for forest operations - `tests/fakes/forest_ops.py` - Test fake for forest operations ### Modified (4 files) - `docs/agent/glossary.md` - Added comprehensive forest terminology (Forest, Forest Membership, Empty Forest, Reroot) - `src/erk/cli/cli.py` - Integrated forest command group, reordered split_cmd placement for logical grouping - `src/erk/core/consolidation_utils.py` - Simplified docstrings and refactored function organization - `src/erk/core/context.py` - Added forest_ops dependency injection to ErkContext ### Deleted (1 file) - `tests/core/utils/test_consolidation_utils.py` - Removed in favor of updated testing structure ## Key Changes - **Forest metadata system**: Implements TOML-based storage for forest definitions with automatic membership tracking - **Reroot state management**: JSON state file for tracking conflict-preserving rebase progress across branches - **Dependency injection**: Forest operations properly integrated into context creation with real/fake implementations - **CLI group structure**: Forest commands ready for implementation with consistent interface pattern - **Documentation**: Complete glossary and user-facing command documentation for all planned forest operations ## Critical Notes - Forest subcommands (list, show, split, merge, reroot) are defined but not yet implemented (TODO markers in place) - Reroot functionality requires Graphite to be enabled
1 parent 3662890 commit 8bd7f05

File tree

15 files changed

+1638
-414
lines changed

15 files changed

+1638
-414
lines changed

docs/agent/glossary.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,66 @@ A **managed worktree** created and maintained by the erk tool.
3838

3939
**Example**: `erk create my-feature` creates both a git worktree and an erk.
4040

41+
### Forest
42+
43+
A **named collection of worktrees** belonging to the same Graphite stack.
44+
45+
**Purpose**: Enables unified stack management operations like split, merge, and reroot.
46+
47+
**Key characteristics**:
48+
49+
- Forest names are pure labels stored in `forests.toml` metadata
50+
- Renaming a forest NEVER moves worktrees or changes filesystem paths
51+
- Worktree paths remain stable: `~/.erk/repos/<repo>/worktrees/<original-name>/`
52+
- Membership determined automatically by Graphite stack structure
53+
54+
**Lifecycle**:
55+
56+
- Created automatically when `erk create <name>` called from trunk
57+
- Joined automatically when `erk create -s <name>` called from forest member
58+
- Deleted automatically during `erk sync` when empty (zero worktrees)
59+
60+
**Example**: A forest named "auth" might contain worktrees `auth-redesign`, `add-oauth`, and `add-2fa`.
61+
62+
### Forest Membership
63+
64+
Automatic assignment of a worktree to a forest based on Graphite parent/child relationships.
65+
66+
**Rules**:
67+
68+
- A worktree can only belong to one forest at a time
69+
- If branch's parent is trunk → new forest OR join existing if siblings present
70+
- If branch's parent is in forest → join that forest
71+
72+
**Example**: Creating branch `add-oauth` from `auth-redesign` automatically adds it to the `auth` forest.
73+
74+
### Empty Forest
75+
76+
A forest with zero existing worktrees, automatically deleted during `erk sync`.
77+
78+
**Detection**: Checked by comparing forest metadata against actual worktree directories.
79+
80+
**Cleanup**: Automatic and silent during sync operations.
81+
82+
### Reroot
83+
84+
Conflict-preserving rebase operation for an entire forest.
85+
86+
**Purpose**: Rebase all branches in a stack while preserving conflict states in git history.
87+
88+
**Benefits**:
89+
90+
- Permanent audit trail of conflicts and resolutions
91+
- Better PR reviewability
92+
- Easier debugging of complex merges
93+
94+
**Commit format**:
95+
96+
- `[CONFLICT] Rebase conflicts from <parent> (<sha>)`
97+
- `[RESOLVED] Fix rebase conflicts from <parent> (<sha>)`
98+
99+
**Example**: `erk forest reroot auth` rebases all branches in the auth forest.
100+
41101
### Repo Root
42102

43103
The main git repository directory containing `.git/` directory.

src/erk/cli/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from erk.cli.commands.current import current_cmd
99
from erk.cli.commands.delete import del_cmd, delete_cmd
1010
from erk.cli.commands.down import down_cmd
11+
from erk.cli.commands.forest import forest_group
1112
from erk.cli.commands.goto import goto_cmd
1213
from erk.cli.commands.init import init_cmd
1314
from erk.cli.commands.land_stack import land_stack
@@ -43,19 +44,20 @@ def cli(ctx: click.Context) -> None:
4344
cli.add_command(down_cmd)
4445
cli.add_command(checkout_cmd)
4546
cli.add_command(checkout_cmd, name="co") # Alias
47+
cli.add_command(forest_group)
4648
cli.add_command(goto_cmd)
4749
cli.add_command(land_stack)
4850
cli.add_command(up_cmd)
4951
cli.add_command(list_cmd)
5052
cli.add_command(ls_cmd)
53+
cli.add_command(split_cmd)
5154
cli.add_command(status_cmd)
5255
cli.add_command(init_cmd)
5356
cli.add_command(move_cmd)
5457
cli.add_command(delete_cmd)
5558
cli.add_command(del_cmd)
5659
cli.add_command(rename_cmd)
5760
cli.add_command(config_group)
58-
cli.add_command(split_cmd)
5961
cli.add_command(sync_cmd)
6062
cli.add_command(hidden_shell_cmd)
6163
cli.add_command(prepare_cwd_recovery_cmd)
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
# Forest Commands
2+
3+
Unified stack management for erk worktrees.
4+
5+
## Overview
6+
7+
Forests are named collections of worktrees belonging to the same Graphite stack. They enable:
8+
9+
- **Split**: Convert single worktree into forest of individual worktrees
10+
- **Merge**: Consolidate forest into single worktree
11+
- **Reroot**: Conflict-preserving rebase for entire stack
12+
- **Automatic lifecycle**: Creation, cleanup, and membership management
13+
14+
## Requirements
15+
16+
- Graphite must be enabled: `erk config set use-graphite true`
17+
- All branches must have worktrees (for reroot operations)
18+
- Clean working directory (no uncommitted changes for reroot)
19+
20+
## Commands
21+
22+
### Query Commands
23+
24+
#### `erk forest`
25+
26+
Show forest for current worktree with tree structure.
27+
28+
```bash
29+
$ erk forest
30+
Forest: auth (3 worktrees)
31+
├── auth-redesign [auth-redesign] ← you are here
32+
├── add-oauth [add-oauth]
33+
└── add-2fa [add-2fa]
34+
```
35+
36+
#### `erk forest list`
37+
38+
List all forests in repository with creation dates.
39+
40+
```bash
41+
$ erk forest list
42+
Forests in my-project:
43+
• auth-redesign (3 worktrees) - created 2024-01-15
44+
• perf-work (2 worktrees) - created 2024-01-16
45+
```
46+
47+
#### `erk forest show [NAME]`
48+
49+
Show specific forest (defaults to current worktree's forest).
50+
51+
#### `erk forest rename OLD NEW`
52+
53+
Rename forest (label only, paths unchanged).
54+
55+
**Important**: Renaming changes only the metadata label. Worktree paths remain stable.
56+
57+
### Split Operation
58+
59+
Convert single worktree containing multiple branches into forest with individual worktrees.
60+
61+
```bash
62+
$ erk forest split [FOREST_NAME]
63+
```
64+
65+
**Options**:
66+
67+
- `--up`: Only split upstack branches (toward leaves)
68+
- `--down`: Only split downstack branches (toward trunk)
69+
- `--force`: Skip confirmation
70+
- `--dry-run`: Preview without executing
71+
72+
**Example**:
73+
74+
```bash
75+
$ erk forest split
76+
Current worktree 'auth' contains 3 branches.
77+
Forest 'auth' will contain 3 worktrees:
78+
• auth-redesign [auth-redesign] (current)
79+
• add-oauth [add-oauth] (new)
80+
• add-2fa [add-2fa] (new)
81+
82+
Continue? [y/N]: y
83+
```
84+
85+
### Merge Operation
86+
87+
Consolidate forest into single worktree by removing all worktrees except target.
88+
89+
```bash
90+
$ erk forest merge [FOREST_NAME]
91+
```
92+
93+
**Options**:
94+
95+
- `--into WORKTREE`: Specify target worktree to keep
96+
- `--force`: Skip confirmation
97+
- `--dry-run`: Preview without executing
98+
99+
**Default target**: Current worktree (if in forest) or first worktree.
100+
101+
**Example**:
102+
103+
```bash
104+
$ erk forest merge auth
105+
This will merge forest 'auth' (3 worktrees) into worktree 'auth-redesign'.
106+
Worktrees to be removed:
107+
• add-oauth
108+
• add-2fa
109+
110+
Continue? [y/N]: y
111+
```
112+
113+
### Reroot Operation
114+
115+
Conflict-preserving rebase for entire stack.
116+
117+
#### Starting a Rebase
118+
119+
```bash
120+
$ erk forest reroot [FOREST_NAME]
121+
```
122+
123+
Pre-flight checks:
124+
125+
1. Graphite enabled
126+
2. Clean working directory
127+
3. All branches have worktrees
128+
129+
The command displays stack preview and runs pre-flight checks before confirmation.
130+
131+
#### When Conflicts Occur
132+
133+
Reroot pauses on conflicts and prompts whether to commit conflict state:
134+
135+
```
136+
⚠️ Conflicts detected in branch: feature-1
137+
138+
Conflicted files (3):
139+
- src/main.py (UU - both modified)
140+
- src/utils.py (UU - both modified)
141+
- tests/test_main.py (AU - added by us, modified by them)
142+
143+
Commit conflict state to preserve in history? [Y/n]: y
144+
Creating conflict commit: [CONFLICT] Rebase conflicts from main (abc1234)
145+
✅ Conflict commit created
146+
✅ Progress saved
147+
148+
Next steps:
149+
1. Resolve conflicts in the files listed above
150+
2. Run: erk forest reroot --continue
151+
152+
ℹ️ Current worktree: /Users/you/.erk/repos/myproject/worktrees/feature-1
153+
```
154+
155+
#### Resolving Conflicts
156+
157+
After manually resolving conflicts:
158+
159+
```bash
160+
$ erk forest reroot --continue
161+
```
162+
163+
This creates a `[RESOLVED]` commit and continues with remaining branches.
164+
165+
#### Abort Workflow
166+
167+
To abort an in-progress reroot:
168+
169+
```bash
170+
$ erk forest reroot --abort
171+
```
172+
173+
Runs `git rebase --abort` and cleans up state files.
174+
175+
## Automatic Lifecycle
176+
177+
### Auto-Creation
178+
179+
Forests are created silently when:
180+
181+
1. Creating branch from trunk: `erk create <name>` → new forest
182+
2. Creating branch from forest member: `erk create -s <name>` → joins forest
183+
184+
No explicit forest creation needed.
185+
186+
### Auto-Cleanup
187+
188+
Empty forests (zero worktrees) are automatically deleted during `erk sync`.
189+
190+
## Git History After Rebase
191+
192+
Conflict commits preserve exact conflict state:
193+
194+
```bash
195+
$ git log --oneline
196+
abc1234 [RESOLVED] Fix rebase conflicts from main (abc1234)
197+
def5678 [CONFLICT] Rebase conflicts from main (abc1234)
198+
```
199+
200+
View conflict state:
201+
202+
```bash
203+
$ git show def5678
204+
```
205+
206+
View resolution:
207+
208+
```bash
209+
$ git show abc1234
210+
```
211+
212+
Compare before/after:
213+
214+
```bash
215+
$ git diff def5678 abc1234
216+
```
217+
218+
## Common Errors
219+
220+
### "This command requires Graphite"
221+
222+
**Solution**: `erk config set use-graphite true`
223+
224+
### "Uncommitted changes detected"
225+
226+
**Solution**: Commit or stash changes before reroot
227+
228+
```bash
229+
$ git add . && git commit -m "WIP"
230+
# or
231+
$ git stash
232+
```
233+
234+
### "The following branches do not have worktrees"
235+
236+
**Solution**: Create worktrees for missing branches
237+
238+
```bash
239+
$ erk create feature-1
240+
$ erk create feature-2
241+
```
242+
243+
### "No rebase state found" (on --continue)
244+
245+
**Solution**: Start new rebase instead
246+
247+
```bash
248+
$ erk forest reroot
249+
```
250+
251+
## Design Rationale
252+
253+
### Why forests are labels not filesystem structures
254+
255+
Forest names are pure metadata labels. Renaming never moves worktrees because:
256+
257+
- Prevents breaking tools/scripts that depend on stable paths
258+
- Separation of logical grouping from physical storage
259+
- Simpler consistency model
260+
261+
### Why auto-creation is silent
262+
263+
Forest concept should be invisible until explicitly needed:
264+
265+
- Users just create branches normally
266+
- Forests emerge naturally from stack structure
267+
- Reduces cognitive overhead
268+
269+
### Why conflict commits are optional
270+
271+
User confirmation prompt (not automatic) because:
272+
273+
- Some users may prefer clean history
274+
- Allows case-by-case decisions
275+
- Explicit consent for history modification
276+
277+
## Migration from Split/Consolidate
278+
279+
Old commands have been replaced:
280+
281+
- `erk split``erk forest split`
282+
- `erk consolidate``erk forest merge`
283+
284+
**Key difference**: The `--name` flag is gone. Forest names are managed automatically.

0 commit comments

Comments
 (0)