sync-worktrees 3.6.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +388 -263
- package/dist/components/App.d.ts +52 -0
- package/dist/components/App.d.ts.map +1 -0
- package/dist/components/BranchCreationWizard.d.ts +26 -0
- package/dist/components/BranchCreationWizard.d.ts.map +1 -0
- package/dist/components/HelpModal.d.ts +7 -0
- package/dist/components/HelpModal.d.ts.map +1 -0
- package/dist/components/LogPanel.d.ts +10 -0
- package/dist/components/LogPanel.d.ts.map +1 -0
- package/dist/components/LogViewer.d.ts +9 -0
- package/dist/components/LogViewer.d.ts.map +1 -0
- package/dist/components/OpenEditorWizard.d.ts +25 -0
- package/dist/components/OpenEditorWizard.d.ts.map +1 -0
- package/dist/components/StatusBar.d.ts +11 -0
- package/dist/components/StatusBar.d.ts.map +1 -0
- package/dist/components/WorktreeStatusView.d.ts +17 -0
- package/dist/components/WorktreeStatusView.d.ts.map +1 -0
- package/dist/constants.d.ts +112 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/errors/index.d.ts +59 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2327 -1082
- package/dist/index.js.map +4 -4
- package/dist/mcp/context.d.ts +143 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/handlers.d.ts +46 -0
- package/dist/mcp/handlers.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/utils.d.ts +14 -0
- package/dist/mcp/utils.d.ts.map +1 -0
- package/dist/mcp/worktree-summary.d.ts +14 -0
- package/dist/mcp/worktree-summary.d.ts.map +1 -0
- package/dist/mcp-server.js +2513 -691
- package/dist/mcp-server.js.map +4 -4
- package/dist/services/InteractiveUIService.d.ts +85 -0
- package/dist/services/InteractiveUIService.d.ts.map +1 -0
- package/dist/services/branch-created-actions.service.d.ts +27 -0
- package/dist/services/branch-created-actions.service.d.ts.map +1 -0
- package/dist/services/clone-sync.service.d.ts +93 -0
- package/dist/services/clone-sync.service.d.ts.map +1 -0
- package/dist/services/config-loader.service.d.ts +28 -0
- package/dist/services/config-loader.service.d.ts.map +1 -0
- package/dist/services/file-copy.service.d.ts +19 -0
- package/dist/services/file-copy.service.d.ts.map +1 -0
- package/dist/services/git.service.d.ts +92 -0
- package/dist/services/git.service.d.ts.map +1 -0
- package/dist/services/hook-execution.service.d.ts +20 -0
- package/dist/services/hook-execution.service.d.ts.map +1 -0
- package/dist/services/logger.service.d.ts +24 -0
- package/dist/services/logger.service.d.ts.map +1 -0
- package/dist/services/path-resolution.service.d.ts +10 -0
- package/dist/services/path-resolution.service.d.ts.map +1 -0
- package/dist/services/progress-emitter.d.ts +14 -0
- package/dist/services/progress-emitter.d.ts.map +1 -0
- package/dist/services/repo-operation-lock.d.ts +16 -0
- package/dist/services/repo-operation-lock.d.ts.map +1 -0
- package/dist/services/sparse-checkout.service.d.ts +45 -0
- package/dist/services/sparse-checkout.service.d.ts.map +1 -0
- package/dist/services/sync-outcome.d.ts +47 -0
- package/dist/services/sync-outcome.d.ts.map +1 -0
- package/dist/services/sync-retry-policy.d.ts +18 -0
- package/dist/services/sync-retry-policy.d.ts.map +1 -0
- package/dist/services/worktree-metadata.service.d.ts +25 -0
- package/dist/services/worktree-metadata.service.d.ts.map +1 -0
- package/dist/services/worktree-mode-sync-runner.d.ts +36 -0
- package/dist/services/worktree-mode-sync-runner.d.ts.map +1 -0
- package/dist/services/worktree-status.service.d.ts +60 -0
- package/dist/services/worktree-status.service.d.ts.map +1 -0
- package/dist/services/worktree-sync-planner.d.ts +62 -0
- package/dist/services/worktree-sync-planner.d.ts.map +1 -0
- package/dist/services/worktree-sync.service.d.ts +49 -0
- package/dist/services/worktree-sync.service.d.ts.map +1 -0
- package/dist/types/index.d.ts +289 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/sync-metadata.d.ts +16 -0
- package/dist/types/sync-metadata.d.ts.map +1 -0
- package/dist/utils/app-events.d.ts +21 -0
- package/dist/utils/app-events.d.ts.map +1 -0
- package/dist/utils/branch-filter.d.ts +3 -0
- package/dist/utils/branch-filter.d.ts.map +1 -0
- package/dist/utils/cli.d.ts +21 -0
- package/dist/utils/cli.d.ts.map +1 -0
- package/dist/utils/clone-skip-format.d.ts +3 -0
- package/dist/utils/clone-skip-format.d.ts.map +1 -0
- package/dist/utils/config-generator.d.ts +10 -0
- package/dist/utils/config-generator.d.ts.map +1 -0
- package/dist/utils/date-filter.d.ts +10 -0
- package/dist/utils/date-filter.d.ts.map +1 -0
- package/dist/utils/disk-space.d.ts +23 -0
- package/dist/utils/disk-space.d.ts.map +1 -0
- package/dist/utils/file-exists.d.ts +2 -0
- package/dist/utils/file-exists.d.ts.map +1 -0
- package/dist/utils/git-progress.d.ts +25 -0
- package/dist/utils/git-progress.d.ts.map +1 -0
- package/dist/utils/git-url.d.ts +23 -0
- package/dist/utils/git-url.d.ts.map +1 -0
- package/dist/utils/git-validation.d.ts +5 -0
- package/dist/utils/git-validation.d.ts.map +1 -0
- package/dist/utils/interactive.d.ts +3 -0
- package/dist/utils/interactive.d.ts.map +1 -0
- package/dist/utils/lfs-error.d.ts +35 -0
- package/dist/utils/lfs-error.d.ts.map +1 -0
- package/dist/utils/lock-path.d.ts +9 -0
- package/dist/utils/lock-path.d.ts.map +1 -0
- package/dist/utils/path-compare.d.ts +16 -0
- package/dist/utils/path-compare.d.ts.map +1 -0
- package/dist/utils/repo-mode.d.ts +8 -0
- package/dist/utils/repo-mode.d.ts.map +1 -0
- package/dist/utils/retry.d.ts +24 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/sanitize-name.d.ts +2 -0
- package/dist/utils/sanitize-name.d.ts.map +1 -0
- package/dist/utils/shell-escape.d.ts +5 -0
- package/dist/utils/shell-escape.d.ts.map +1 -0
- package/dist/utils/signal-handlers.d.ts +14 -0
- package/dist/utils/signal-handlers.d.ts.map +1 -0
- package/dist/utils/timing.d.ts +24 -0
- package/dist/utils/timing.d.ts.map +1 -0
- package/dist/utils/worktree-list-parser.d.ts +10 -0
- package/dist/utils/worktree-list-parser.d.ts.map +1 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,34 +1,75 @@
|
|
|
1
1
|
# sync-worktrees
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Keep every branch and every repo you work on checked out as predictable directories — no stashing, no re-cloning, no re-orienting your AI assistant.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
**Contents:** [Why](#why-sync-worktrees) · [How it works](#how-it-works) · [Quick start](#quick-start) · [MCP server](#mcp-server) · [Interactive TUI](#interactive-tui) · [CLI options](#cli-options)
|
|
8
|
+
|
|
9
|
+
## Why sync-worktrees
|
|
10
|
+
|
|
11
|
+
If you've ever:
|
|
12
|
+
|
|
13
|
+
- Stashed half-finished work just to check out another branch
|
|
14
|
+
- Lost minutes hunting for where you cloned a sibling repo
|
|
15
|
+
- Switched branches in five repos because one feature spans them all
|
|
16
|
+
- Re-explained to an AI assistant which directory holds which branch
|
|
17
|
+
|
|
18
|
+
…sync-worktrees fixes that. It keeps the **entire branch and repo layout you work in materialized on disk** — one directory per branch, automatically kept in sync with the remote. Switching branches becomes `cd`. Searching across repos becomes `grep -r`. **AI agents see the same shape you do, so "look in the other repo" actually works.**
|
|
19
|
+
|
|
20
|
+
It's also a clean answer to **dev-environment bootstrapping**. One config file describes every repo, branch, and folder layout your team works in. Hand it to a new hire (or a fresh laptop) and `sync-worktrees` lays down the whole workspace in a single command — no day-one cloning checklist, no "where do I put this repo?" Slack threads.
|
|
21
|
+
|
|
22
|
+
Runs as a one-shot, a background daemon, or an interactive TUI — and ships an MCP server so AI assistants can list, create, and inspect worktrees themselves.
|
|
23
|
+
|
|
7
24
|
## How it works
|
|
8
25
|
|
|
9
|
-
|
|
26
|
+
The default — **worktree mode** — gives every remote branch its own directory while sharing one Git database underneath:
|
|
27
|
+
|
|
28
|
+
1. **First run** clones the repo once as a bare repository (just the Git data, no working files).
|
|
29
|
+
2. **Each sync**:
|
|
30
|
+
- Creates a directory for every remote branch (`main`, `develop`, `feature/*`).
|
|
31
|
+
- Fetches latest changes (no merge — your local work stays untouched).
|
|
32
|
+
- Removes directories for branches deleted upstream (preserves dirty trees and unpushed commits).
|
|
33
|
+
|
|
34
|
+
Smallest config that produces this:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// sync-worktrees.config.js
|
|
38
|
+
// @ts-check
|
|
39
|
+
|
|
40
|
+
/** @satisfies {import("sync-worktrees").SyncWorktreesConfig} */
|
|
41
|
+
const config = {
|
|
42
|
+
repositories: [
|
|
43
|
+
{
|
|
44
|
+
name: "my-repo",
|
|
45
|
+
repoUrl: "https://github.com/user/my-repo.git",
|
|
46
|
+
worktreeDir: "./worktrees/my-repo",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default config;
|
|
52
|
+
```
|
|
10
53
|
|
|
11
|
-
|
|
12
|
-
2. **Automatic sync**:
|
|
13
|
-
- Creates a dedicated worktree for **every remote branch** (`main`, `develop`, `feature/*`, etc.)
|
|
14
|
-
- Each branch gets its own isolated directory with a full working copy
|
|
15
|
-
- Fetches latest changes (doesn't merge - preserves your local work)
|
|
16
|
-
- Removes worktrees when remote branches are deleted (preserves local changes)
|
|
54
|
+
Run `sync-worktrees` from the directory holding the config and you get:
|
|
17
55
|
|
|
18
|
-
|
|
56
|
+
```
|
|
57
|
+
.
|
|
58
|
+
├── sync-worktrees.config.js
|
|
59
|
+
├── .bare/
|
|
60
|
+
│ └── my-repo/ # Bare repository (shared Git objects)
|
|
61
|
+
└── worktrees/my-repo/
|
|
62
|
+
├── main/ # Worktree for main branch
|
|
63
|
+
├── feature-1/ # Worktree for feature-1 branch
|
|
64
|
+
└── feature-2/ # Worktree for feature-2 branch
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Clone mode** (`mode: "clone"`) is a first-class alternative: a plain `git clone` of one branch into `worktreeDir`, no bare repo, no per-branch subfolders. Reach for it when you want a repo to live at a fixed path — a dependency sibling, a single-branch dev clone, or any case where one checkout is enough. See [Clone mode](#clone-mode).
|
|
19
68
|
|
|
20
69
|
## Features
|
|
21
70
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- ⏰ Run as a scheduled cron job or one-time execution
|
|
25
|
-
- 🛡️ Safe operations - won't delete worktrees with uncommitted changes or unpushed commits
|
|
26
|
-
- 📝 Clear logging with timestamps and progress indicators
|
|
27
|
-
- 📋 Config file support for managing multiple repositories
|
|
28
|
-
- 🔁 Automatic retry with exponential backoff for network and filesystem errors
|
|
29
|
-
- 🕐 Branch age filtering - only sync branches active within a specified time period
|
|
30
|
-
- 🔀 Smart handling of rebased/force-pushed branches with automatic divergence detection
|
|
31
|
-
- 🤖 MCP server for AI assistants - inspect and manage worktrees from Claude Desktop, Claude Code, Cursor, etc.
|
|
71
|
+
- **Filtering & lifecycle** — branch name globs, age filtering, sparse checkout, automatic divergence detection with `.diverged/` preservation, retry with exponential backoff.
|
|
72
|
+
- **Interactive TUI** — Ink-based UI with wizards for opening worktrees, creating branches, and inspecting status; diverged-directory management; live log streaming; multi-repo filtering.
|
|
32
73
|
|
|
33
74
|
## Installation
|
|
34
75
|
|
|
@@ -36,99 +77,191 @@ sync-worktrees maintains a **separate working directory for each remote branch**
|
|
|
36
77
|
npm install -g sync-worktrees
|
|
37
78
|
```
|
|
38
79
|
|
|
39
|
-
|
|
80
|
+
## Quick start
|
|
81
|
+
|
|
82
|
+
`sync-worktrees` always runs against a config file. Create one once, then run the tool.
|
|
40
83
|
|
|
41
84
|
```bash
|
|
42
|
-
|
|
85
|
+
cd ~/projects/my-sync-dir
|
|
86
|
+
sync-worktrees init # interactive wizard → writes sync-worktrees.config.js
|
|
87
|
+
sync-worktrees # auto-loads the config in the current directory and starts syncing
|
|
43
88
|
```
|
|
44
89
|
|
|
45
|
-
|
|
90
|
+
By default, bare `sync-worktrees` launches the [interactive TUI](#interactive-tui) and keeps syncing on the cron schedule from your config. Press `q` to quit. For a one-shot run (CI, scripts, ad-hoc), add `--runOnce`.
|
|
46
91
|
|
|
47
|
-
|
|
92
|
+
To manage multiple repositories, edit the generated config file and add entries under `repositories`. See [Configuration](#configuration).
|
|
48
93
|
|
|
49
|
-
|
|
50
|
-
- **Subsequent runs** — `sync-worktrees` auto-loads `sync-worktrees.config.js` (or `.mjs` / `.cjs`) from the current directory. No flags needed.
|
|
94
|
+
If the config lives outside the current directory, pass it explicitly:
|
|
51
95
|
|
|
52
96
|
```bash
|
|
53
|
-
|
|
54
|
-
sync-worktrees
|
|
55
|
-
sync-worktrees
|
|
97
|
+
sync-worktrees --config /path/to/sync-worktrees.config.js
|
|
98
|
+
sync-worktrees --config /path/to/sync-worktrees.config.js --runOnce
|
|
99
|
+
sync-worktrees list --config ./config.js --filter "frontend-*"
|
|
56
100
|
```
|
|
57
101
|
|
|
58
|
-
|
|
102
|
+
## MCP server
|
|
59
103
|
|
|
60
|
-
|
|
104
|
+
sync-worktrees ships a [Model Context Protocol](https://modelcontextprotocol.io) server so AI assistants (Claude Desktop, Claude Code, Cursor, Windsurf, etc.) can inspect and operate your workspace directly. Installing the package exposes a second binary, `sync-worktrees-mcp`, that speaks MCP over stdio.
|
|
61
105
|
|
|
62
|
-
|
|
106
|
+
In a single call, an AI assistant can discover every repo and worktree you have configured — so an agent working in `frontend/` can grep across `backend/` and `shared/` without you reorienting it. That call is `detect_context` with `includeAllWorktrees: true`; the response also includes a per-capability `{ available, reason }` block telling the agent which operations are reachable from its current vantage point, so there's no guessing whether `sync` will work. See [Available tools](#available-tools) for the full surface.
|
|
107
|
+
|
|
108
|
+
### Getting started
|
|
109
|
+
|
|
110
|
+
Install the sync-worktrees MCP server with your client.
|
|
111
|
+
|
|
112
|
+
**Standard config** works in most tools:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"mcpServers": {
|
|
117
|
+
"sync-worktrees": {
|
|
118
|
+
"command": "npx",
|
|
119
|
+
"args": ["-y", "-p", "sync-worktrees", "sync-worktrees-mcp"],
|
|
120
|
+
"env": {
|
|
121
|
+
"SYNC_WORKTREES_CONFIG": "/absolute/path/to/sync-worktrees.config.js"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If installed globally, replace `command` with `sync-worktrees-mcp` and drop `args`. `SYNC_WORKTREES_CONFIG` is optional — without it the server runs in **auto-detect mode**: when the client's CWD sits inside a worktree managed by sync-worktrees, the server locates the bare repo, enumerates sibling worktrees, and enables per-worktree operations. `sync` and `initialize` require a loaded config (or call `load_config` at runtime).
|
|
129
|
+
|
|
130
|
+
<details>
|
|
131
|
+
<summary>Claude Code</summary>
|
|
132
|
+
|
|
133
|
+
Use the Claude Code CLI:
|
|
63
134
|
|
|
64
135
|
```bash
|
|
65
|
-
sync-worktrees --
|
|
66
|
-
sync-worktrees --config ./config.js --filter "frontend-*"
|
|
67
|
-
sync-worktrees --config ./config.js --list
|
|
136
|
+
claude mcp add sync-worktrees -- npx -y -p sync-worktrees sync-worktrees-mcp
|
|
68
137
|
```
|
|
69
138
|
|
|
70
|
-
|
|
139
|
+
To pass a config path, append `-e SYNC_WORKTREES_CONFIG=/absolute/path/to/sync-worktrees.config.js` to the command.
|
|
71
140
|
|
|
72
|
-
|
|
141
|
+
</details>
|
|
73
142
|
|
|
74
|
-
|
|
75
|
-
|
|
143
|
+
<details>
|
|
144
|
+
<summary>Claude Desktop</summary>
|
|
76
145
|
|
|
77
|
-
|
|
146
|
+
Edit `claude_desktop_config.json` and paste the **standard config** above into the `mcpServers` block. Default locations:
|
|
78
147
|
|
|
79
|
-
|
|
148
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
149
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
80
150
|
|
|
81
|
-
|
|
82
|
-
|----------|---------|------------------|
|
|
83
|
-
| `SYNC_WORKTREES_TERMINAL` | Override the terminal launcher on any platform. Value is a command string; the tmux invocation is appended via `sh -c`. Example: `SYNC_WORKTREES_TERMINAL="alacritty -e"`. | See per-platform defaults below. |
|
|
84
|
-
| `TERMINAL` | Linux-only fallback when `SYNC_WORKTREES_TERMINAL` is unset. Same format. | Probes `gnome-terminal`, `konsole`, `alacritty`, `kitty`, `xterm` in order. |
|
|
85
|
-
| `EDITOR` / `VISUAL` | Editor mode launcher. | Falls back to `code`. |
|
|
151
|
+
Restart Claude Desktop after editing.
|
|
86
152
|
|
|
87
|
-
|
|
153
|
+
</details>
|
|
88
154
|
|
|
89
|
-
|
|
90
|
-
|
|
155
|
+
<details>
|
|
156
|
+
<summary>Cursor</summary>
|
|
157
|
+
|
|
158
|
+
Edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project). Paste the **standard config** above.
|
|
159
|
+
|
|
160
|
+
Or open `Cursor Settings` → `MCP` → `Add new MCP Server`, pick `command` type, and enter `npx -y -p sync-worktrees sync-worktrees-mcp`.
|
|
161
|
+
|
|
162
|
+
</details>
|
|
163
|
+
|
|
164
|
+
<details>
|
|
165
|
+
<summary>Windsurf</summary>
|
|
166
|
+
|
|
167
|
+
Follow the Windsurf MCP [documentation](https://docs.windsurf.com/windsurf/cascade/mcp) and use the **standard config** above.
|
|
168
|
+
|
|
169
|
+
</details>
|
|
170
|
+
|
|
171
|
+
<details>
|
|
172
|
+
<summary>VS Code</summary>
|
|
173
|
+
|
|
174
|
+
Use the VS Code CLI:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
code --add-mcp '{"name":"sync-worktrees","command":"npx","args":["-y","-p","sync-worktrees","sync-worktrees-mcp"]}'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Or follow the VS Code MCP install [guide](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server) and use the **standard config** above.
|
|
181
|
+
|
|
182
|
+
</details>
|
|
183
|
+
|
|
184
|
+
<details>
|
|
185
|
+
<summary>Codex</summary>
|
|
186
|
+
|
|
187
|
+
Use the Codex CLI:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
codex mcp add sync-worktrees -- npx -y -p sync-worktrees sync-worktrees-mcp
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Or edit `~/.codex/config.toml`:
|
|
194
|
+
|
|
195
|
+
```toml
|
|
196
|
+
[mcp_servers.sync-worktrees]
|
|
197
|
+
command = "npx"
|
|
198
|
+
args = ["-y", "-p", "sync-worktrees", "sync-worktrees-mcp"]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
</details>
|
|
91
202
|
|
|
92
|
-
|
|
203
|
+
<details>
|
|
204
|
+
<summary>Gemini CLI</summary>
|
|
93
205
|
|
|
94
|
-
|
|
206
|
+
Follow the Gemini CLI MCP install [guide](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md#configure-the-mcp-server-in-settingsjson) and use the **standard config** above.
|
|
95
207
|
|
|
96
|
-
|
|
208
|
+
</details>
|
|
97
209
|
|
|
98
|
-
|
|
210
|
+
<details>
|
|
211
|
+
<summary>Cline</summary>
|
|
212
|
+
|
|
213
|
+
Edit `cline_mcp_settings.json` (see [Configuring MCP Servers](https://docs.cline.bot/mcp/configuring-mcp-servers)) and add:
|
|
99
214
|
|
|
100
215
|
```json
|
|
101
216
|
{
|
|
102
217
|
"mcpServers": {
|
|
103
218
|
"sync-worktrees": {
|
|
219
|
+
"type": "stdio",
|
|
104
220
|
"command": "npx",
|
|
105
221
|
"args": ["-y", "-p", "sync-worktrees", "sync-worktrees-mcp"],
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
|
|
222
|
+
"disabled": false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
</details>
|
|
229
|
+
|
|
230
|
+
<details>
|
|
231
|
+
<summary>opencode</summary>
|
|
232
|
+
|
|
233
|
+
Edit `~/.config/opencode/opencode.json`:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"$schema": "https://opencode.ai/config.json",
|
|
238
|
+
"mcp": {
|
|
239
|
+
"sync-worktrees": {
|
|
240
|
+
"type": "local",
|
|
241
|
+
"command": ["npx", "-y", "-p", "sync-worktrees", "sync-worktrees-mcp"],
|
|
242
|
+
"enabled": true
|
|
109
243
|
}
|
|
110
244
|
}
|
|
111
245
|
}
|
|
112
246
|
```
|
|
113
247
|
|
|
114
|
-
|
|
248
|
+
</details>
|
|
115
249
|
|
|
116
|
-
|
|
250
|
+
<details>
|
|
251
|
+
<summary>Warp</summary>
|
|
117
252
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| Claude Code | `claude mcp add sync-worktrees -- npx -y -p sync-worktrees sync-worktrees-mcp` |
|
|
122
|
-
| Cursor | `~/.cursor/mcp.json` (or project-level `.cursor/mcp.json`) |
|
|
253
|
+
Open `Settings` → `AI` → `Manage MCP Servers` → `+ Add` (see [Warp MCP docs](https://docs.warp.dev/knowledge-and-collaboration/mcp#adding-an-mcp-server)) and paste the **standard config** above. Alternatively, run `/add-mcp` in the prompt.
|
|
254
|
+
|
|
255
|
+
</details>
|
|
123
256
|
|
|
124
257
|
### Available tools
|
|
125
258
|
|
|
126
259
|
| Tool | Purpose |
|
|
127
260
|
|------|---------|
|
|
128
|
-
| `detect_context` | Inspect a path, resolve the bare repo, enumerate sibling worktrees, report capabilities. |
|
|
129
|
-
| `list_worktrees` | List worktrees with status label (`clean`/`dirty`/`stale`/`current`), divergence, `safeToRemove`, last sync. |
|
|
261
|
+
| `detect_context` | Inspect a path, resolve the bare repo, enumerate sibling worktrees, report config-driven sibling repositories and capabilities. Pass `includeAllWorktrees: true` to include every configured repo's worktrees keyed by repo name. |
|
|
262
|
+
| `list_worktrees` | List worktrees with status label (`clean`/`dirty`/`stale`/`current`), divergence, `safeToRemove`, last sync. Without `repoName` and with a loaded config, results are grouped across all configured repos. |
|
|
130
263
|
| `get_worktree_status` | Detailed status for one worktree (dirty files, unpushed commits, stashes, operation in progress). |
|
|
131
|
-
| `create_worktree` | Create a worktree for a branch; optionally create the branch from `baseBranch
|
|
264
|
+
| `create_worktree` | Create a worktree for a branch; optionally create the branch from `baseBranch`. Newly created branches are pushed to origin unless `push=false`. |
|
|
132
265
|
| `remove_worktree` | Remove a worktree after safety checks; `force=true` skips validation. |
|
|
133
266
|
| `update_worktree` | Fast-forward one worktree to match upstream. |
|
|
134
267
|
| `sync` | Full sync cycle (fetch, create, prune, update). Requires config. Streams progress notifications. |
|
|
@@ -142,288 +275,280 @@ All tools that target a single repo accept an optional `repoName`. When omitted,
|
|
|
142
275
|
|
|
143
276
|
- `remove_worktree` refuses to delete worktrees with uncommitted changes, unpushed commits, stashes, or operations in progress (merge/rebase/cherry-pick/revert/bisect). Pass `force=true` to override.
|
|
144
277
|
- `create_worktree` rejects sanitized-path collisions (e.g. `feature/foo` vs `feature-foo` both resolving to `feature-foo/`) before touching disk.
|
|
278
|
+
- Branches created by sync-worktrees use `--no-track` first, then publish with `git push -u origin <branch>`, so they do not inherit `origin/main` as their upstream.
|
|
145
279
|
- Path-targeted tools verify the supplied path is a registered worktree of the selected repository.
|
|
146
280
|
|
|
147
|
-
##
|
|
281
|
+
## Interactive TUI
|
|
148
282
|
|
|
149
|
-
|
|
150
|
-
|--------|-------|-------------|---------|
|
|
151
|
-
| `--config` | `-c` | Path to JavaScript config file (auto-detected in CWD when omitted) | - |
|
|
152
|
-
| `--filter` | `-f` | Filter repositories by name (wildcards supported) | - |
|
|
153
|
-
| `--list` | `-l` | List configured repositories and exit | `false` |
|
|
154
|
-
| `--runOnce` | - | Override config to run once and exit | `false` |
|
|
155
|
-
| `--no-update-existing` | - | Disable automatic updates of existing worktrees | `false` |
|
|
156
|
-
| `--debug` | `-d` | Show detailed reasons for skipped cleanups | `false` |
|
|
157
|
-
| `--help` | `-h` | Show help | - |
|
|
283
|
+
Running `sync-worktrees` without `runOnce` drops you into an interactive terminal UI with live log streaming, manual sync triggers, and wizards for the common operations.
|
|
158
284
|
|
|
159
|
-
|
|
285
|
+
### Keybindings
|
|
160
286
|
|
|
161
|
-
|
|
287
|
+
| Key | Action |
|
|
288
|
+
|-----|--------|
|
|
289
|
+
| `s` | Manually trigger sync for all repositories |
|
|
290
|
+
| `c` | Create a new branch (wizard) |
|
|
291
|
+
| `o` | Open a worktree in terminal or editor (wizard) |
|
|
292
|
+
| `w` | View worktree status across repos |
|
|
293
|
+
| `r` | Reload configuration and re-sync |
|
|
294
|
+
| `?` / `h` | Toggle help screen |
|
|
295
|
+
| `q` / `Esc` | Gracefully quit |
|
|
296
|
+
| `j` / `↓` | Scroll log down one line |
|
|
297
|
+
| `k` / `↑` | Scroll log up one line |
|
|
298
|
+
| `gg` | Jump to top of log |
|
|
299
|
+
| `G` | Jump to bottom (re-enables auto-scroll) |
|
|
162
300
|
|
|
163
|
-
|
|
301
|
+
### Wizards
|
|
302
|
+
|
|
303
|
+
- **Open wizard (`o`)** — select a worktree across all configured repos with live filtering (just type to narrow the list). Press `Tab` to flip between **Terminal** mode (launches a new terminal window attached to a `tmux` session in the worktree) and **Editor** mode (launches `$EDITOR` / `$VISUAL`, falling back to `code`). Re-opening the same worktree attaches to the existing tmux session instead of creating a duplicate.
|
|
304
|
+
- **Branch creation wizard (`c`)** — pick a repo, pick a base branch from a live-filtered list, type the new branch name. Names are validated against Git's rules; if the desired name already exists, a numeric suffix (`-2`, `-3`, …) is suggested automatically.
|
|
305
|
+
- **Worktree status view (`w`)** — flat list of every worktree across every configured repo, each tagged with status flags:
|
|
306
|
+
|
|
307
|
+
| Flag | Meaning |
|
|
308
|
+
|------|---------|
|
|
309
|
+
| `✓` | Clean |
|
|
310
|
+
| `M` | Modified / uncommitted changes |
|
|
311
|
+
| `↑` | Unpushed commits |
|
|
312
|
+
| `S` | Stashed changes |
|
|
313
|
+
| `⚠` | Operation in progress (merge/rebase/cherry-pick/revert/bisect) |
|
|
314
|
+
| `⊞` | Modified submodules |
|
|
315
|
+
| `✗` | Upstream branch is gone |
|
|
316
|
+
|
|
317
|
+
Press `Enter` on an entry to expand file/commit/stash counts. The view also surfaces `.diverged/` directories preserved from past force-pushes; press `d` (with `y`/`n` confirmation) to delete one after reviewing.
|
|
318
|
+
|
|
319
|
+
### Terminal mode environment variables
|
|
320
|
+
|
|
321
|
+
| Variable | Purpose | Default behavior |
|
|
322
|
+
|----------|---------|------------------|
|
|
323
|
+
| `SYNC_WORKTREES_TERMINAL` | Override the terminal launcher on any platform. Value is a command string; the tmux invocation is appended via `sh -c`. Example: `SYNC_WORKTREES_TERMINAL="alacritty -e"`. | See per-platform defaults below. |
|
|
324
|
+
| `TERMINAL` | Linux-only fallback when `SYNC_WORKTREES_TERMINAL` is unset. Same format. | Probes `gnome-terminal`, `konsole`, `alacritty`, `kitty`, `xterm` in order. |
|
|
325
|
+
| `EDITOR` / `VISUAL` | Editor mode launcher. | Falls back to `code`. |
|
|
326
|
+
|
|
327
|
+
Per-platform terminal defaults (when no env override is set):
|
|
328
|
+
|
|
329
|
+
- **macOS** — Ghostty if `Ghostty.app` is installed, otherwise Terminal.app via AppleScript.
|
|
330
|
+
- **Linux** — `$TERMINAL` if set; otherwise the first found among the candidates above.
|
|
331
|
+
|
|
332
|
+
Terminal mode requires [`tmux`](https://github.com/tmux/tmux) to be installed.
|
|
333
|
+
|
|
334
|
+
## Configuration
|
|
335
|
+
|
|
336
|
+
Config files are JavaScript ES modules. Relative paths resolve from the config file's location, and you have full access to `process.env` and Node module loading.
|
|
337
|
+
|
|
338
|
+
### Minimal config
|
|
164
339
|
|
|
165
340
|
```javascript
|
|
166
|
-
|
|
167
|
-
|
|
341
|
+
// @ts-check
|
|
342
|
+
|
|
343
|
+
/** @satisfies {import("sync-worktrees").SyncWorktreesConfig} */
|
|
344
|
+
const config = {
|
|
345
|
+
repositories: [
|
|
346
|
+
{
|
|
347
|
+
name: "my-project",
|
|
348
|
+
repoUrl: "https://github.com/user/my-project.git",
|
|
349
|
+
worktreeDir: "./worktrees/my-project",
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export default config;
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Multi-repo config
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
// @ts-check
|
|
361
|
+
|
|
362
|
+
/** @satisfies {import("sync-worktrees").SyncWorktreesConfig} */
|
|
363
|
+
const config = {
|
|
168
364
|
defaults: {
|
|
169
|
-
cronSchedule: "0 * * * *",
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
updateExistingWorktrees: true // Auto-update worktrees that are behind (default: true)
|
|
365
|
+
cronSchedule: "0 * * * *", // hourly
|
|
366
|
+
branchMaxAge: "30d", // ignore stale branches
|
|
367
|
+
branchExclude: ["wip-*", "tmp-*"],
|
|
368
|
+
updateExistingWorktrees: true,
|
|
174
369
|
},
|
|
175
370
|
|
|
176
|
-
// Retry configuration (optional - these are the defaults)
|
|
177
371
|
retry: {
|
|
178
|
-
maxAttempts:
|
|
179
|
-
initialDelayMs: 1000,
|
|
180
|
-
maxDelayMs: 600000,
|
|
181
|
-
backoffMultiplier: 2
|
|
372
|
+
maxAttempts: "unlimited",
|
|
373
|
+
initialDelayMs: 1000,
|
|
374
|
+
maxDelayMs: 600000,
|
|
375
|
+
backoffMultiplier: 2,
|
|
182
376
|
},
|
|
183
377
|
|
|
184
378
|
repositories: [
|
|
185
379
|
{
|
|
186
|
-
name: "frontend",
|
|
380
|
+
name: "frontend",
|
|
187
381
|
repoUrl: "https://github.com/company/frontend.git",
|
|
188
|
-
worktreeDir: "./worktrees/frontend",
|
|
189
|
-
cronSchedule: "*/30 * * * *"
|
|
382
|
+
worktreeDir: "./worktrees/frontend",
|
|
383
|
+
cronSchedule: "*/30 * * * *", // override default
|
|
190
384
|
},
|
|
191
385
|
{
|
|
192
386
|
name: "backend",
|
|
193
|
-
repoUrl: process.env.BACKEND_REPO_URL
|
|
387
|
+
repoUrl: process.env.BACKEND_REPO_URL || "https://github.com/company/backend.git",
|
|
194
388
|
worktreeDir: "/absolute/path/backend-worktrees",
|
|
195
|
-
branchMaxAge: "6m",
|
|
196
|
-
branchInclude: ["feature/*", "release-*", "main"],
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
]
|
|
389
|
+
branchMaxAge: "6m",
|
|
390
|
+
branchInclude: ["feature/*", "release-*", "main"],
|
|
391
|
+
retry: { maxAttempts: 10 }, // per-repo override
|
|
392
|
+
},
|
|
393
|
+
],
|
|
201
394
|
};
|
|
395
|
+
|
|
396
|
+
export default config;
|
|
202
397
|
```
|
|
203
398
|
|
|
204
|
-
|
|
205
|
-
- Relative paths are resolved from the config file location
|
|
206
|
-
- `bareRepoDir` defaults to `.bare/<repo-name>` if not specified
|
|
207
|
-
- Repository-specific settings override defaults
|
|
399
|
+
Notes:
|
|
208
400
|
|
|
209
|
-
|
|
401
|
+
- `bareRepoDir` defaults to `.bare/<repo-name>` if not specified.
|
|
402
|
+
- Repository-specific settings override `defaults`.
|
|
210
403
|
|
|
211
|
-
|
|
404
|
+
### Clone mode
|
|
212
405
|
|
|
213
|
-
|
|
214
|
-
- **Network errors**: Connection timeouts, DNS failures, repository access issues
|
|
215
|
-
- **Filesystem errors**: Busy files, permission issues, race conditions
|
|
406
|
+
Set `mode: "clone"` to clone one checked-out branch directly into `worktreeDir` instead of maintaining one worktree per remote branch:
|
|
216
407
|
|
|
217
|
-
Simple retry examples:
|
|
218
408
|
```javascript
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
repositories: [{
|
|
227
|
-
name: "critical-repo",
|
|
228
|
-
// ... other config ...
|
|
229
|
-
retry: { maxAttempts: 'unlimited', initialDelayMs: 10000 }
|
|
230
|
-
}]
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Git LFS Support
|
|
234
|
-
|
|
235
|
-
For repositories with Git LFS issues or when large files aren't needed:
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
# Skip LFS downloads
|
|
239
|
-
sync-worktrees -u https://github.com/user/repo.git -w ./worktrees --skip-lfs
|
|
240
|
-
|
|
241
|
-
# Or in config file
|
|
242
|
-
defaults: {
|
|
243
|
-
skipLfs: true
|
|
409
|
+
{
|
|
410
|
+
name: "game-platform",
|
|
411
|
+
repoUrl: "ssh://git@example.com/game-platform.git",
|
|
412
|
+
worktreeDir: "./slots/game-platform",
|
|
413
|
+
mode: "clone",
|
|
414
|
+
branch: "main",
|
|
415
|
+
depth: 1, // optional shallow clone
|
|
244
416
|
}
|
|
245
417
|
```
|
|
246
418
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
### Branch Name Filtering
|
|
419
|
+
Clone mode keeps the normal `+refs/heads/*:refs/remotes/origin/*` fetch refspec, so `git branch -r` and `git fetch --all --prune` can see all remote branches. `branch` controls the checked-out branch that sync-worktrees fast-forwards on each sync. Omit `branch` and the remote HEAD is resolved at clone time.
|
|
250
420
|
|
|
251
|
-
|
|
421
|
+
`depth` is valid only for clone-mode repositories and must be a positive safe integer. Shallow clones use `--no-single-branch` so all remote branch refs remain visible at the configured depth. If you later remove `depth` from the config, the next sync unshallows the existing clone with `git fetch --unshallow`.
|
|
252
422
|
|
|
253
|
-
|
|
254
|
-
- `feature/*` - matches `feature/login`, `feature/auth/oauth`, etc.
|
|
255
|
-
- `release-*` - matches `release-1.0`, `release-2.0-beta`, etc.
|
|
256
|
-
- `*-hotfix` - matches `urgent-hotfix`, `prod-hotfix`, etc.
|
|
423
|
+
Clone mode rejects `branchInclude`, `branchExclude`, `branchMaxAge`, `updateExistingWorktrees`, and `bareRepoDir` at validation time (whether set directly or inherited via `defaults`) — they have no meaning for a single-branch checkout.
|
|
257
424
|
|
|
258
|
-
|
|
259
|
-
- `branchInclude` - only branches matching at least one pattern are synced
|
|
260
|
-
- `branchExclude` - branches matching any pattern are skipped
|
|
261
|
-
- When both are set, include runs first, then exclude removes from the result
|
|
262
|
-
- The default branch (e.g., `main`) is always retained regardless of filters
|
|
425
|
+
### Sparse checkout
|
|
263
426
|
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
# Command line
|
|
267
|
-
sync-worktrees -u https://github.com/user/repo.git -w ./worktrees \
|
|
268
|
-
--branchInclude "feature/*,release-*"
|
|
427
|
+
For monorepos where you only need a subset of folders, set `sparseCheckout` per repository entry. The tool runs `git worktree add --no-checkout`, configures sparse-checkout, then materializes only the included paths. The same repository URL can be listed multiple times under different `name`s with different sparse patterns to build domain-grouped layouts.
|
|
269
428
|
|
|
270
|
-
|
|
271
|
-
|
|
429
|
+
```javascript
|
|
430
|
+
// @ts-check
|
|
272
431
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
432
|
+
/** @satisfies {import("sync-worktrees").SyncWorktreesConfig} */
|
|
433
|
+
const config = {
|
|
434
|
+
repositories: [
|
|
435
|
+
{
|
|
436
|
+
name: "roulette-game-client",
|
|
437
|
+
repoUrl: "https://github.com/acme/casino-monorepo.git",
|
|
438
|
+
worktreeDir: "/Users/me/game-clients/roulette",
|
|
439
|
+
sparseCheckout: { include: ["game-client"] },
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "roulette-autocue",
|
|
443
|
+
repoUrl: "https://github.com/acme/casino-monorepo.git",
|
|
444
|
+
worktreeDir: "/Users/me/autocues/roulette",
|
|
445
|
+
sparseCheckout: { include: ["autocue"] },
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
};
|
|
277
449
|
|
|
278
|
-
|
|
279
|
-
repositories: [{
|
|
280
|
-
name: "frontend",
|
|
281
|
-
branchInclude: ["feature/*", "release-*"],
|
|
282
|
-
branchExclude: ["feature/wip-*"],
|
|
283
|
-
}]
|
|
450
|
+
export default config;
|
|
284
451
|
```
|
|
285
452
|
|
|
286
|
-
**
|
|
453
|
+
**Modes:**
|
|
287
454
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
sync-worktrees -u https://github.com/user/repo.git -w ./worktrees \
|
|
291
|
-
--branchInclude "feature/*" --branchMaxAge 30d
|
|
292
|
-
```
|
|
455
|
+
- `cone` (default): pass folder names in `include`. Fast and recommended.
|
|
456
|
+
- `no-cone`: pass gitignore-style patterns including `!negation`. Required for `exclude` and any `!`-prefixed include.
|
|
293
457
|
|
|
294
|
-
|
|
458
|
+
If you set `exclude` or `!`-prefixed patterns while `mode: "cone"` is explicit, the tool auto-promotes to `no-cone` and logs a warning.
|
|
295
459
|
|
|
296
|
-
|
|
460
|
+
**Duplicate `repoUrl` handling:** The first entry per `repoUrl` keeps the URL-derived bare path (`.bare/<repo-slug>`). Subsequent duplicate entries auto-derive `bareRepoDir` from `name` (`.bare/<name>`). Pin `bareRepoDir` explicitly on duplicate entries if you want config order to be irrelevant.
|
|
297
461
|
|
|
298
|
-
**
|
|
299
|
-
- `h` - hours (e.g., `24h`)
|
|
300
|
-
- `d` - days (e.g., `30d`)
|
|
301
|
-
- `w` - weeks (e.g., `4w`)
|
|
302
|
-
- `m` - months (e.g., `6m`)
|
|
303
|
-
- `y` - years (e.g., `1y`)
|
|
462
|
+
**Narrowing safety:** When a sync would narrow an existing worktree's sparse patterns (remove a previously included path), it first checks the worktree is clean. If there are uncommitted changes, unpushed commits, or in-progress operations, the sparse update is skipped with a warning.
|
|
304
463
|
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
# Command line
|
|
308
|
-
sync-worktrees -u https://github.com/user/repo.git -w ./worktrees --branchMaxAge 30d
|
|
464
|
+
### Branch filtering
|
|
309
465
|
|
|
310
|
-
|
|
466
|
+
Two filters can be combined:
|
|
467
|
+
|
|
468
|
+
```javascript
|
|
311
469
|
defaults: {
|
|
312
|
-
|
|
470
|
+
branchInclude: ["feature/*", "release-*", "main"],
|
|
471
|
+
branchExclude: ["feature/wip-*"],
|
|
472
|
+
branchMaxAge: "30d",
|
|
313
473
|
}
|
|
314
|
-
|
|
315
|
-
# Config file - per repository
|
|
316
|
-
repositories: [{
|
|
317
|
-
name: "active-project",
|
|
318
|
-
branchMaxAge: "14d", // Very active project - only last 2 weeks
|
|
319
|
-
}, {
|
|
320
|
-
name: "legacy-project",
|
|
321
|
-
branchMaxAge: "1y", // Legacy project - keep branches from last year
|
|
322
|
-
}]
|
|
323
474
|
```
|
|
324
475
|
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
-
|
|
328
|
-
-
|
|
329
|
-
- Only create/maintain worktrees for active branches
|
|
330
|
-
|
|
331
|
-
### Handling Rebased and Force-Pushed Branches
|
|
332
|
-
|
|
333
|
-
sync-worktrees intelligently handles branches that have been rebased or force-pushed to prevent data loss:
|
|
334
|
-
|
|
335
|
-
**Automatic behavior (no configuration needed):**
|
|
476
|
+
- **Name patterns** support `*` wildcards (including across `/`): `feature/*` matches `feature/login` and `feature/auth/oauth`.
|
|
477
|
+
- **`branchInclude`** keeps only matching branches; **`branchExclude`** removes matching branches. When both are set, include runs first, then exclude.
|
|
478
|
+
- **`branchMaxAge`** drops branches whose latest commit is older than the duration (`h`/`d`/`w`/`m`/`y` — e.g. `24h`, `30d`, `6m`, `1y`). Applied after name filtering.
|
|
479
|
+
- The default branch is always retained regardless of filters.
|
|
336
480
|
|
|
337
|
-
|
|
338
|
-
- Automatically resets the worktree to match the upstream
|
|
339
|
-
- No data loss since the content is the same
|
|
481
|
+
### Diverged branches
|
|
340
482
|
|
|
341
|
-
|
|
342
|
-
- Automatically resets to the new upstream state
|
|
343
|
-
- No move to `.diverged` since you have no work to preserve
|
|
344
|
-
- Keeps `.diverged` clean by only preserving actual user work
|
|
483
|
+
When upstream is force-pushed and your worktree contains divergent local commits, sync-worktrees moves the worktree to a hidden `.diverged/` directory before creating a fresh one from the new upstream. No data loss; you can review the old state later.
|
|
345
484
|
|
|
346
|
-
3. **Diverged branches WITH local changes** - When a branch has different content AND you've made local commits:
|
|
347
|
-
- Moves the worktree to `.diverged` directory within your worktrees folder
|
|
348
|
-
- Preserves all your local changes and commits
|
|
349
|
-
- Creates a fresh worktree from the upstream branch
|
|
350
|
-
- Logs clear instructions for reviewing diverged changes
|
|
351
|
-
|
|
352
|
-
**Example diverged structure:**
|
|
353
485
|
```
|
|
354
486
|
my-repo-worktrees/
|
|
355
487
|
├── main/
|
|
356
488
|
├── feature-a/
|
|
357
|
-
|
|
358
|
-
└──
|
|
359
|
-
├── 2024-01-15-feature-x/ # Timestamp + branch name
|
|
360
|
-
│ ├── .diverged-info.json # Metadata about the divergence
|
|
361
|
-
│ └── [all your local files]
|
|
362
|
-
└── 2024-01-16-feature-y/
|
|
489
|
+
└── .diverged/
|
|
490
|
+
└── 2024-01-15-feature-x/
|
|
363
491
|
├── .diverged-info.json
|
|
364
492
|
└── [all your local files]
|
|
365
493
|
```
|
|
366
494
|
|
|
367
|
-
|
|
495
|
+
Reviewing a diverged worktree:
|
|
496
|
+
|
|
368
497
|
```bash
|
|
369
|
-
# See what's different
|
|
370
498
|
cd my-repo-worktrees/.diverged/2024-01-15-feature-x
|
|
371
499
|
git diff origin/feature-x
|
|
372
500
|
|
|
373
|
-
#
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
# If you want to discard and use upstream
|
|
377
|
-
cd ../..
|
|
378
|
-
rm -rf .diverged/2024-01-15-feature-x
|
|
501
|
+
# keep local: git push --force-with-lease
|
|
502
|
+
# discard: cd ../.. && rm -rf .diverged/2024-01-15-feature-x
|
|
379
503
|
```
|
|
380
504
|
|
|
381
|
-
|
|
505
|
+
The TUI's worktree status view (`w`) lists diverged directories and offers a guided delete (`d` with `y`/`n` confirmation) once you've decided.
|
|
382
506
|
|
|
383
|
-
|
|
507
|
+
Clean rebases where file content matches the upstream are auto-applied with no detour through `.diverged/`. Diverged-but-no-local-commits is also handled without preservation, since there's no user work to keep.
|
|
384
508
|
|
|
385
|
-
|
|
509
|
+
### Retry and LFS
|
|
386
510
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
worktreeDir: "/Users/me/game-clients/roulette",
|
|
394
|
-
sparseCheckout: { include: ["game-client"] },
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
name: "roulette-autocue",
|
|
398
|
-
repoUrl: "https://github.com/acme/casino-monorepo.git",
|
|
399
|
-
worktreeDir: "/Users/me/autocues/roulette",
|
|
400
|
-
sparseCheckout: { include: ["autocue"] },
|
|
401
|
-
// bareRepoDir: ".bare/roulette-autocue", // pin to make config reorder-proof
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
};
|
|
511
|
+
The tool retries network errors (timeouts, DNS failures, access issues) and filesystem race conditions automatically:
|
|
512
|
+
|
|
513
|
+
```javascript
|
|
514
|
+
retry: { maxAttempts: 5 } // try 5 times then stop
|
|
515
|
+
retry: { maxAttempts: "unlimited" } // keep trying forever (default)
|
|
516
|
+
retry: { maxDelayMs: 60000 } // cap retry delay at 1 minute
|
|
405
517
|
```
|
|
406
518
|
|
|
407
|
-
|
|
519
|
+
For repositories with Git LFS issues or large files you don't need, set `skipLfs: true` in `defaults` or per repository. The tool also retries LFS-specific failures with LFS disabled (configurable via `retry.maxLfsRetries`).
|
|
408
520
|
|
|
409
|
-
|
|
410
|
-
|
|
521
|
+
### Hooks and file copying
|
|
522
|
+
|
|
523
|
+
Two lifecycle hooks the example config covers in depth:
|
|
524
|
+
|
|
525
|
+
- `hooks.onBranchCreated` — array of shell commands run after a new branch's worktree is created. Placeholders: `{BRANCH_NAME}`, `{WORKTREE_PATH}`, `{REPO_NAME}`, `{BASE_BRANCH}`, `{REPO_URL}`. Fire-and-forget.
|
|
526
|
+
- `filesToCopyOnBranchCreate` — paths copied into every newly created worktree (e.g. `.env.local`, `.npmrc`). Glob patterns are resolved relative to the config file's directory.
|
|
411
527
|
|
|
412
|
-
|
|
528
|
+
In clone mode, `filesToCopyOnBranchCreate` fires once on the initial clone, and `hooks.onBranchCreated` fires only for TUI-initiated branch creation (clone mode tracks a single fixed branch with no later branch-creation events).
|
|
413
529
|
|
|
414
|
-
|
|
530
|
+
For every knob (timeouts, parallelism, jitter, sparse-update behavior, retry tuning), see [`sync-worktrees.config.example.js`](./sync-worktrees.config.example.js).
|
|
415
531
|
|
|
416
|
-
|
|
532
|
+
## CLI options
|
|
533
|
+
|
|
534
|
+
The CLI loads a config file and runs it. Most run-mode settings (branch filters, retry, parallelism, LFS, clone mode, depth, etc.) live in the config file. Use `--runOnce` for an ad-hoc one-shot run without editing config.
|
|
535
|
+
|
|
536
|
+
| Option | Alias | Description | Default |
|
|
537
|
+
|--------|-------|-------------|---------|
|
|
538
|
+
| `--config` | `-c` | Path to JavaScript config file (auto-detected in CWD when omitted) | - |
|
|
539
|
+
| `--runOnce` | - | Run a sync once and exit, overriding config `runOnce` settings for this invocation | `false` |
|
|
540
|
+
| `--help` | `-h` | Show help | - |
|
|
541
|
+
| `--version` | - | Print version | - |
|
|
417
542
|
|
|
418
|
-
|
|
543
|
+
Subcommands:
|
|
419
544
|
|
|
420
|
-
|
|
545
|
+
- `sync-worktrees init [--config <path>] [--force]` — interactive wizard that writes a minimal config file (`./sync-worktrees.config.js` by default). Refuses to overwrite an existing target unless `--force` is passed.
|
|
546
|
+
- `sync-worktrees list [--config <path>] [--filter <pattern>]` — print the resolved repositories and exit.
|
|
421
547
|
|
|
422
548
|
## Requirements
|
|
423
549
|
|
|
424
550
|
- Node.js >= 22.0.0
|
|
425
551
|
- Git
|
|
426
|
-
- [`tmux`](https://github.com/tmux/tmux) (optional, required only for Terminal mode in the TUI)
|
|
427
552
|
- An MCP-capable client (optional, only for the `sync-worktrees-mcp` server)
|
|
428
553
|
|
|
429
554
|
## Contributing
|