rails-worktrees 0.7.4 → 0.9.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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +21 -0
- data/README.md +102 -6
- data/lib/generators/rails/worktrees/templates/bin/wt +3 -3
- data/lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt +8 -0
- data/lib/generators/worktrees/install/install_generator.rb +59 -1
- data/lib/rails/worktrees/cli.rb +20 -3
- data/lib/rails/worktrees/command/in_repo_worktrees.rb +85 -0
- data/lib/rails/worktrees/command/name_picking.rb +2 -1
- data/lib/rails/worktrees/command/output.rb +15 -5
- data/lib/rails/worktrees/command/workspace_paths.rb +40 -9
- data/lib/rails/worktrees/command.rb +58 -9
- data/lib/rails/worktrees/configuration.rb +6 -2
- data/lib/rails/worktrees/pi_worktree_config_path.rb +44 -0
- data/lib/rails/worktrees/pi_worktree_config_updater.rb +123 -0
- data/lib/rails/worktrees/playwright_browser_check.rb +56 -0
- data/lib/rails/worktrees/post_create_runner.rb +45 -4
- data/lib/rails/worktrees/project_maintenance.rb +30 -3
- data/lib/rails/worktrees/teardown_runner.rb +84 -0
- data/lib/rails/worktrees/teardown_script.rb +115 -0
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +4 -0
- data/sig/rails/worktrees.rbs +1 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f0667430464dedbea994c715a32e64f226048502f6037186f3c90692c5df6e5
|
|
4
|
+
data.tar.gz: 1f47027f6cdc16431356c43e4809d801a7537db1558d3ab9b25c1299874d1389
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81a835017246e1cab5fd6acf6ef311b4f9beef54912675602fd454c8ed2aa56904b514be908f69cdc2b84110ec8c38d9d49c9abbc974eea1b309aec2fed3c3f9
|
|
7
|
+
data.tar.gz: 5aad32825be11a64a0095233ed4068eda727da0af5ca0456657e5a4f0aa33f8aa04c0a6a48929a0e7abe97cbbbff9197aa49845c5b6a4acdf207d6c48e8e0796
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.9.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.0](https://github.com/asjer/rails-worktrees/compare/v0.8.0...v0.9.0) (2026-11-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **config:** support worktrees inside the repo via worktrees_dir ([#61](https://github.com/asjer/rails-worktrees/issues/61)) ([681f325](https://github.com/asjer/rails-worktrees/commit/681f325584b69fc675f7fce045c5cd8baf36eb83))
|
|
9
|
+
* **setup:** install Playwright Chromium for the pinned version ([#66](https://github.com/asjer/rails-worktrees/issues/66)) ([cdbef7c](https://github.com/asjer/rails-worktrees/commit/cdbef7c24f738674dd209af35da25d4f85e038ed))
|
|
10
|
+
* **wt:** add guarded worktree teardown with dry-run ([#63](https://github.com/asjer/rails-worktrees/issues/63)) ([5592830](https://github.com/asjer/rails-worktrees/commit/5592830849b098e0d904d70cf96390dc55083356))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **wt:** reserve help and version instead of using them as names ([#60](https://github.com/asjer/rails-worktrees/issues/60)) ([d52dba8](https://github.com/asjer/rails-worktrees/commit/d52dba83525ef21f11840db0b691174ccf896a33))
|
|
16
|
+
|
|
17
|
+
## [0.8.0](https://github.com/asjer/rails-worktrees/compare/v0.7.4...v0.8.0) (2026-10-24)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **pi:** add opt-in pi-worktree setup hook generation ([ccaa0ea](https://github.com/asjer/rails-worktrees/commit/ccaa0ead9fe31a01ac2c8016a51fd60b1611daef)), closes [#53](https://github.com/asjer/rails-worktrees/issues/53)
|
|
23
|
+
|
|
3
24
|
## [0.7.4](https://github.com/asjer/rails-worktrees/compare/v0.7.3...v0.7.4) (2026-08-15)
|
|
4
25
|
|
|
5
26
|
|
data/README.md
CHANGED
|
@@ -17,6 +17,8 @@ bin/rails generate worktrees:install
|
|
|
17
17
|
bin/rails generate worktrees:install --browser
|
|
18
18
|
# or, to apply the common Procfile.dev + Puma + mise follow-ups automatically:
|
|
19
19
|
bin/rails generate worktrees:install --yolo
|
|
20
|
+
# optionally, to let the pi-worktree extension run bin/wt setup in new Pi worktrees:
|
|
21
|
+
bin/rails generate worktrees:install --pi
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
The generated initializer writes app config under `Rails.application.config.x.rails_worktrees`, so the file stays safe to load even in environments like `test` where the gem is only bundled for `:development`. When `rails-worktrees` is loaded, the gem applies those settings for both the CLI and in-process Rails usage.
|
|
@@ -28,6 +30,7 @@ The installer adds:
|
|
|
28
30
|
- `config/initializers/rails_worktrees.rb` — optional configuration
|
|
29
31
|
- `Procfile.dev.worktree.example` — a copy-paste helper for `${DEV_PORT:-3000}` in `Procfile.dev` on regular installs
|
|
30
32
|
- a safe update to `config/database.yml` for common development/test database names
|
|
33
|
+
- `.pi/worktree.json` — only with `--pi`; see [Pi worktrees](#pi-worktrees-pi-worktree-extension)
|
|
31
34
|
|
|
32
35
|
With `--yolo`, the installer also:
|
|
33
36
|
|
|
@@ -51,10 +54,15 @@ bin/wt setup ../my-project.worktrees/my-feature # setup a specific checkout with
|
|
|
51
54
|
bin/wt doctor # audit install/config drift and basic worktree health
|
|
52
55
|
bin/wt update --dry-run # preview safe maintenance fixes
|
|
53
56
|
bin/wt update # apply safe maintenance fixes for managed files
|
|
54
|
-
bin/wt
|
|
57
|
+
bin/wt update --pi # also add the pi-worktree hook in .pi/worktree.json
|
|
58
|
+
bin/wt teardown --dry-run # preview this worktree's Overmind and development/test databases
|
|
59
|
+
bin/wt teardown # stop Overmind and drop worktree databases; keep checkout/branch
|
|
60
|
+
bin/wt remove my-feature # teardown a worktree, remove it and delete its local branch
|
|
55
61
|
bin/wt delete my-feature # alias for `bin/wt remove`
|
|
56
62
|
bin/wt remove --force my-feature # also delete an unmerged local branch
|
|
57
63
|
bin/wt prune # remove merged worktrees created by wt
|
|
64
|
+
bin/wt help # same as --help; never creates a worktree
|
|
65
|
+
bin/wt version # same as --version
|
|
58
66
|
|
|
59
67
|
bin/ob # open http://localhost:$DEV_PORT/
|
|
60
68
|
bin/ob contact # open http://localhost:$DEV_PORT/contact
|
|
@@ -66,12 +74,13 @@ bin/ob --print-url '?from=nav' # print the resolved URL without opening a brows
|
|
|
66
74
|
|
|
67
75
|
| Flag | Description |
|
|
68
76
|
|------|-------------|
|
|
69
|
-
| `-h`, `--help` | Show the help message |
|
|
70
|
-
| `-v`, `--version` | Show the script version |
|
|
77
|
+
| `-h`, `--help`, `help` | Show the help message |
|
|
78
|
+
| `-v`, `--version`, `version` | Show the script version |
|
|
71
79
|
| `--dry-run [name]` | Preview worktree creation or cleanup without changing anything |
|
|
72
80
|
| `--skip-setup` | Create a worktree without running setup steps |
|
|
73
81
|
| `--force` | Force branch deletion for `bin/wt remove` / `bin/wt delete` |
|
|
74
82
|
| `--env`, `--print-env <name>` | Preview `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` |
|
|
83
|
+
| `--pi` | With `bin/wt update`, create or merge `.pi/worktree.json` for the pi-worktree extension |
|
|
75
84
|
|
|
76
85
|
### Default behavior
|
|
77
86
|
|
|
@@ -83,7 +92,7 @@ By default `bin/wt`:
|
|
|
83
92
|
- auto-picks names from bundled `.txt` files when no explicit name is given
|
|
84
93
|
- retires bundled names so they are not picked twice
|
|
85
94
|
- bootstraps a worktree-local `.env` with deterministic `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` values
|
|
86
|
-
- runs setup automatically after creation: credential linking, `bundle install`, `yarn install` when applicable, both `db:prepare` steps, test asset precompile, and a final `bin/rails assets:clobber`
|
|
95
|
+
- runs setup automatically after creation: credential linking, `bundle install`, `yarn install` when applicable, the Playwright Chromium build for the pinned `playwright` package when it is missing, both `db:prepare` steps, test asset precompile, and a final `bin/rails assets:clobber`
|
|
87
96
|
|
|
88
97
|
```text
|
|
89
98
|
workspace/
|
|
@@ -96,6 +105,32 @@ workspace/
|
|
|
96
105
|
|
|
97
106
|
`WT_WORKSPACES_ROOT` or `config.workspace_root` overrides the destination root and uses the layout `<root>/<project>/<name>`.
|
|
98
107
|
|
|
108
|
+
### Worktrees inside the repository
|
|
109
|
+
|
|
110
|
+
Set `config.worktrees_dir` to keep worktrees inside the main checkout instead, in `<main checkout>/<worktrees_dir>/<name>`. This is the layout the [pi-worktree](https://www.npmjs.com/package/pi-worktree) extension uses, so `bin/wt` and Pi share one place for worktrees:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
Rails.application.config.x.rails_worktrees.tap do |config|
|
|
114
|
+
config.worktrees_dir = '.worktrees'
|
|
115
|
+
config.branch_prefix = 'worktree' # wt and pi-worktree both create worktree/<name>
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
my-project/
|
|
121
|
+
├── app/
|
|
122
|
+
├── config/
|
|
123
|
+
└── .worktrees/
|
|
124
|
+
├── feature-auth/
|
|
125
|
+
└── bugfix-123/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- `worktrees_dir` must be a relative path inside the repository, such as `.worktrees` or `tmp/worktrees`. It always resolves against the main checkout, also when you run `bin/wt` from inside a worktree.
|
|
129
|
+
- If `.gitignore` does not already ignore the directory, `bin/wt` adds `/.worktrees/` to `.git/info/exclude` when it creates a worktree, so `git status` in the main checkout stays clean. When it cannot write that file it prints a warning; add the directory to `.gitignore` yourself.
|
|
130
|
+
- `bin/wt remove`, `bin/wt prune` and `bin/wt doctor` work in this layout too. `bin/wt doctor` warns when the directory is not ignored.
|
|
131
|
+
- `worktrees_dir` cannot be combined with `workspace_root`. `WT_WORKSPACES_ROOT` still overrides both for a single run.
|
|
132
|
+
- Tools that scan the main checkout's file tree (for example RuboCop, file watchers or Tailwind content globs) can pick up files from the worktrees. Exclude the directory there if needed.
|
|
133
|
+
|
|
99
134
|
### Setup command
|
|
100
135
|
|
|
101
136
|
`bin/wt setup` reruns setup for the **current checkout**. Run it from inside a linked worktree created by `bin/wt`, a worktree created manually with `git worktree`, or a checkout prepared by another tool.
|
|
@@ -126,7 +161,8 @@ bin/wt setup
|
|
|
126
161
|
|
|
127
162
|
`bin/wt` also supports cleanup commands for worktrees it manages:
|
|
128
163
|
|
|
129
|
-
- `bin/wt
|
|
164
|
+
- `bin/wt teardown` — stop the current worktree's Overmind and drop its development/test databases, keeping the checkout and branch
|
|
165
|
+
- `bin/wt remove <name>` — run the same teardown, remove the named worktree and delete its local branch
|
|
130
166
|
- `bin/wt delete <name>` — alias for `bin/wt remove <name>`
|
|
131
167
|
- `bin/wt prune` — remove merged worktrees created by `bin/wt` in bulk
|
|
132
168
|
|
|
@@ -136,7 +172,17 @@ bin/wt setup
|
|
|
136
172
|
|
|
137
173
|
`bin/wt prune` only targets linked worktrees created by `bin/wt` whose local branches are already merged into `origin`'s default branch. It skips the main checkout, skips the checkout you're currently in, and asks for confirmation before deleting the batch.
|
|
138
174
|
|
|
139
|
-
|
|
175
|
+
### Teardown command
|
|
176
|
+
|
|
177
|
+
Run `bin/wt teardown` **inside a linked worktree** (including manually created or Pi worktrees). It boots that app separately in test and development (so environment-specific files and database URLs are resolved correctly), checks **all** task-enabled development/test database configurations and Rails' protected-environment metadata, stops Overmind with `overmind quit --socket .overmind.sock` if that local socket exists, and drops only those validated databases through Rails' database tasks. Production databases and the checkout/branch are left alone. Multi-database configurations (primary, cache, queue, cable, etc.) are supported; replicas and configurations with `database_tasks: false` are not dropped.
|
|
178
|
+
|
|
179
|
+
Teardown refuses the main checkout, a missing/blank `WORKTREE_DATABASE_SUFFIX`, and any database whose name does not contain that suffix. For SQLite, the suffix must be in the filename, not merely a parent directory. **All names are validated before Overmind or any database is touched.** A working app bundle for both environments and `bin/rails` are required; install missing gems or fix unsafe database configuration before retrying. Both environments are booted even for a dry-run, so application initializers still execute. Rails' production/environment-mismatch protection cannot be bypassed with `DISABLE_DATABASE_ENVIRONMENT_CHECK`. The resolved test configuration is passed privately to the development process; all safety checks complete before Overmind is stopped or any database is dropped.
|
|
180
|
+
|
|
181
|
+
The worktree's `.env` may define a literal `WORKTREE_DATABASE_SUFFIX` (unquoted or single/double quoted, optionally with `export`). Teardown never generates a suffix. When run inside the worktree it can also use the current environment if `.env` does not define one. `remove` reads the **target's** `.env` and starts Rails with only the caller's OS/Ruby runtime variables, never its suffix, database URLs, application settings or bundle. Any other required application settings/credentials must therefore be loadable by the target app itself (for example with dotenv or Rails credentials). Ensure that target file contains the actual suffix used to create its databases.
|
|
182
|
+
|
|
183
|
+
`bin/wt remove` / `delete` run this same teardown **before** deleting the worktree or branch. If validation, Overmind shutdown, or a database drop fails, removal aborts and keeps the checkout and branch. Database drops are not transactional: databases already dropped before a later failure cannot be restored by this command. If only the branch remains (the worktree directory is already gone), removal only deletes the branch. `prune` retains its existing Git-only behavior; run teardown on its candidates first if they have databases.
|
|
184
|
+
|
|
185
|
+
Use `bin/wt teardown --dry-run` to list the local Overmind socket and exact development/test database names without stopping or dropping them. To preview both teardown and checkout/branch removal, use `--dry-run` with `bin/wt remove`:
|
|
140
186
|
|
|
141
187
|
```bash
|
|
142
188
|
bin/wt remove --dry-run feature-auth
|
|
@@ -160,6 +206,53 @@ bin/wt prune --dry-run
|
|
|
160
206
|
|
|
161
207
|
`bin/wt update` is intentionally conservative: it only applies safe file-based fixes for managed templates and supported config shapes. It does **not** delete branches, remove worktrees, or rewrite ambiguous custom files automatically.
|
|
162
208
|
|
|
209
|
+
`bin/wt doctor` and `bin/wt update` only look at `.pi/worktree.json` once it already runs `bin/wt setup`, or when you pass `bin/wt update --pi`.
|
|
210
|
+
|
|
211
|
+
### Pi worktrees (pi-worktree extension)
|
|
212
|
+
|
|
213
|
+
If you create worktrees from [Pi](https://github.com/badlogic/pi-mono) with `pi --worktree` or `/worktree`, rails-worktrees can configure them to run `bin/wt setup` automatically. This is opt-in:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
bin/rails generate worktrees:install --pi
|
|
217
|
+
# or, in an app that already has rails-worktrees installed:
|
|
218
|
+
bin/wt update --pi
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Both commands create or update `.pi/worktree.json`:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"linkEnvFiles": false,
|
|
226
|
+
"postCreate": [
|
|
227
|
+
"bin/wt setup"
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`.pi/worktree.json` is read by the [pi-worktree](https://www.npmjs.com/package/pi-worktree) npm extension (`pi install pi-worktree`), **not** by Pi core. Without that extension, the file has no effect. pi-worktree places worktrees in `.worktrees/` inside the repository by default. Set `config.worktrees_dir = '.worktrees'` and `config.branch_prefix = 'worktree'` to have `bin/wt` use the same directory and branch names; see [Worktrees inside the repository](#worktrees-inside-the-repository).
|
|
233
|
+
|
|
234
|
+
`linkEnvFiles: false` matters. By default, pi-worktree symlinks the main checkout's gitignored `.env*` files into a new worktree before `postCreate` runs. `bin/wt setup` keeps existing `.env` values, so every Pi worktree would then reuse the main checkout's `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX`, and setup would write through the symlink into the main checkout. With linking disabled, each worktree gets its own `.env` and database identity.
|
|
235
|
+
|
|
236
|
+
Existing configuration is merged, not replaced:
|
|
237
|
+
|
|
238
|
+
- other keys (`dir`, `branchPrefix`, `preRemove`, …) and custom `postCreate` hooks are preserved; `bin/wt setup` is appended once and never duplicated
|
|
239
|
+
- `linkEnvFiles` is only added when it is absent; an explicit `"linkEnvFiles": true`, invalid JSON, or a non-list `postCreate` is left untouched with a message explaining what to change by hand
|
|
240
|
+
- no `preRemove` hook is added automatically, and the worktree directory and branch prefix stay under your control
|
|
241
|
+
|
|
242
|
+
To opt into environment cleanup when Pi's pi-worktree extension destroys a worktree, add `bin/wt teardown` to `preRemove` (preserve any existing hooks):
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"linkEnvFiles": false,
|
|
247
|
+
"postCreate": ["bin/wt setup"],
|
|
248
|
+
"preRemove": ["bin/wt teardown"]
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The extension runs `preRemove` from inside the worktree. Teardown only cleans the environment; the calling tool remains responsible for removing the checkout and branch. Keep `linkEnvFiles: false` so a worktree does not share the main checkout's `.env`. Preview the cleanup inside the worktree with `bin/wt teardown --dry-run` first.
|
|
253
|
+
|
|
254
|
+
pi-worktree runs each `postCreate` step with a 120-second timeout. If a first `bundle install` or `db:prepare` takes longer, run `bin/wt setup` again inside the worktree.
|
|
255
|
+
|
|
163
256
|
### Updating old `bin/wt` wrappers
|
|
164
257
|
|
|
165
258
|
Versions before 0.7.1 generated `bin/wt` as a Ruby shebang wrapper. On hosts where Ruby is provided by mise but not already on `PATH`, that legacy wrapper can fail with `/usr/bin/env: ruby: No such file or directory` before rails-worktrees can activate mise.
|
|
@@ -207,6 +300,8 @@ New installs and repaired apps use a bash bootstrap wrapper that activates mise
|
|
|
207
300
|
|
|
208
301
|
Worktree names must not contain `/` or whitespace, must not be `.` or `..`, and must be a valid Git ref component.
|
|
209
302
|
|
|
303
|
+
These reserved words are commands, so `bin/wt <word>` never creates or reuses a worktree with that name and auto-picking skips them: `help`, `version`, `doctor`, `setup`, `update`, `prune`, `remove` and `delete`. `bin/wt help` and `bin/wt version` behave like `--help` and `--version`. An existing worktree with such a name (for example one created by `bin/wt help` in an older version) can still be cleaned up with `bin/wt remove help` or set up with `bin/wt setup ../<project>.worktrees/help`.
|
|
304
|
+
|
|
210
305
|
### Configuration
|
|
211
306
|
|
|
212
307
|
The installer generates `config/initializers/rails_worktrees.rb` where you can override:
|
|
@@ -217,6 +312,7 @@ The initializer stays app-owned and gem-agnostic; `rails-worktrees` reads those
|
|
|
217
312
|
|--------|---------|-------------|
|
|
218
313
|
| `bootstrap_env` | `true` | Write `.env` when creating a worktree |
|
|
219
314
|
| `workspace_root` | `nil` | Override the destination root (sibling layout when `nil`) |
|
|
315
|
+
| `worktrees_dir` | `nil` | Create worktrees inside the main checkout, e.g. `'.worktrees'`; see [Worktrees inside the repository](#worktrees-inside-the-repository) |
|
|
220
316
|
| `dev_port_range` | `3000..3999` | Port range for deterministic `DEV_PORT` allocation |
|
|
221
317
|
| `branch_prefix` | `🚂` | Prefix for worktree branch names |
|
|
222
318
|
| `name_sources_path` | bundled `names/` | Directory containing `.txt` name lists |
|
|
@@ -77,14 +77,14 @@ exec ruby /dev/fd/3 "$@" 3<<'RUBY'
|
|
|
77
77
|
def rails_worktrees_bootstrap_safe_invocation?(argv)
|
|
78
78
|
args = argv.map(&:to_s)
|
|
79
79
|
return true if args.empty?
|
|
80
|
-
return true if %w[-h --help -v --version].include?(args.first)
|
|
80
|
+
return true if %w[-h --help help -v --version version].include?(args.first)
|
|
81
81
|
return true if %w[--env --print-env].include?(args.first)
|
|
82
82
|
|
|
83
|
-
command_args = args.reject { |arg| %w[--dry-run --skip-setup --env --print-env].include?(arg) }
|
|
83
|
+
command_args = args.reject { |arg| %w[--dry-run --skip-setup --pi --env --print-env].include?(arg) }
|
|
84
84
|
command = command_args.first
|
|
85
85
|
|
|
86
86
|
return true if %w[setup doctor update].include?(command)
|
|
87
|
-
return false if %w[remove delete prune].include?(command)
|
|
87
|
+
return false if %w[remove delete prune teardown].include?(command)
|
|
88
88
|
|
|
89
89
|
command_args.length <= 1
|
|
90
90
|
end
|
|
@@ -5,6 +5,13 @@ Rails.application.config.x.rails_worktrees.tap do |config|
|
|
|
5
5
|
# By default, worktrees go in a sibling "<project>.worktrees" directory.
|
|
6
6
|
# Uncomment to override with a custom parent directory that uses <root>/<project>/<name>.
|
|
7
7
|
# config.workspace_root = File.expand_path('~/worktrees')
|
|
8
|
+
#
|
|
9
|
+
# Or keep worktrees inside this checkout in <app>/.worktrees/<name>, like Pi's pi-worktree
|
|
10
|
+
# extension. wt adds the directory to .git/info/exclude unless .gitignore already covers it.
|
|
11
|
+
# Combine with branch_prefix 'worktree' so wt and Pi share worktree/<name> branch names.
|
|
12
|
+
# Cannot be combined with workspace_root.
|
|
13
|
+
# config.worktrees_dir = '.worktrees'
|
|
14
|
+
# config.branch_prefix = 'worktree'
|
|
8
15
|
<% end -%>
|
|
9
16
|
# config.bootstrap_env = false
|
|
10
17
|
# config.dev_port_range = 3000..3999
|
|
@@ -21,6 +28,7 @@ Rails.application.config.x.rails_worktrees.tap do |config|
|
|
|
21
28
|
# config.post_create_command = nil # nil = built-in steps; false = skip all; 'script/worktree-setup' = custom command
|
|
22
29
|
# config.run_bundle_install = true
|
|
23
30
|
# config.run_yarn_install = true # auto-skipped when yarn.lock is absent
|
|
31
|
+
# config.run_playwright_install = true # installs Playwright Chromium when package.json uses playwright
|
|
24
32
|
# config.run_db_prepare = true
|
|
25
33
|
# config.run_test_db_prepare = true
|
|
26
34
|
# config.run_test_assets_precompile = true
|
|
@@ -7,6 +7,7 @@ require_relative '../../../rails/worktrees/database_config_updater'
|
|
|
7
7
|
require_relative '../../../rails/worktrees/procfile_updater'
|
|
8
8
|
require_relative '../../../rails/worktrees/mise_toml_updater'
|
|
9
9
|
require_relative '../../../rails/worktrees/puma_config_updater'
|
|
10
|
+
require_relative '../../../rails/worktrees/pi_worktree_config_updater'
|
|
10
11
|
|
|
11
12
|
module Worktrees
|
|
12
13
|
module Generators
|
|
@@ -35,8 +36,13 @@ module Worktrees
|
|
|
35
36
|
'Apply common Procfile.dev, config/puma.rb, and mise .env',
|
|
36
37
|
'follow-up edits when safe; also generate bin/ob'
|
|
37
38
|
].join(' ')
|
|
39
|
+
class_option :pi,
|
|
40
|
+
type: :boolean,
|
|
41
|
+
default: false,
|
|
42
|
+
desc: 'Generate .pi/worktree.json so the pi-worktree extension runs bin/wt setup in new worktrees'
|
|
38
43
|
|
|
39
44
|
FILE_ENCODING = 'UTF-8'.freeze
|
|
45
|
+
PI_WORKTREE_CONFIG = ::Rails::Worktrees::PiWorktreeConfigUpdater::RELATIVE_PATH
|
|
40
46
|
|
|
41
47
|
FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
|
|
42
48
|
============================================
|
|
@@ -97,6 +103,21 @@ module Worktrees
|
|
|
97
103
|
announce_database_update(result)
|
|
98
104
|
end
|
|
99
105
|
|
|
106
|
+
def configure_pi_worktree
|
|
107
|
+
return unless options[:pi]
|
|
108
|
+
|
|
109
|
+
path = pi_worktree_config_path
|
|
110
|
+
unsafe_reason = ::Rails::Worktrees::PiWorktreeConfigPath.conflict(destination_root)
|
|
111
|
+
return announce_unsafe_pi_worktree_config(unsafe_reason) if unsafe_reason
|
|
112
|
+
|
|
113
|
+
result = ::Rails::Worktrees::PiWorktreeConfigUpdater.new(
|
|
114
|
+
content: File.exist?(path) ? File.read(path, encoding: FILE_ENCODING) : nil
|
|
115
|
+
).call
|
|
116
|
+
write_pi_worktree_config(path, result.content) if result.changed?
|
|
117
|
+
@pi_worktree_outcome = result.status
|
|
118
|
+
announce_updater_result(PI_WORKTREE_CONFIG, result)
|
|
119
|
+
end
|
|
120
|
+
|
|
100
121
|
def verify_installation
|
|
101
122
|
if git_repo?
|
|
102
123
|
say_status(:ok, 'git repository detected', :green)
|
|
@@ -130,6 +151,21 @@ module Worktrees
|
|
|
130
151
|
]
|
|
131
152
|
end
|
|
132
153
|
|
|
154
|
+
def pi_worktree_config_path
|
|
155
|
+
File.join(destination_root, PI_WORKTREE_CONFIG)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def write_pi_worktree_config(path, content)
|
|
159
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
160
|
+
File.write(path, content)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def announce_unsafe_pi_worktree_config(reason)
|
|
164
|
+
@pi_worktree_outcome = :conflict
|
|
165
|
+
say_status(:skip, PI_WORKTREE_CONFIG, :yellow)
|
|
166
|
+
say("#{PI_WORKTREE_CONFIG} #{reason}; leaving it unchanged. Add the bin/wt setup hook manually.")
|
|
167
|
+
end
|
|
168
|
+
|
|
133
169
|
def database_update_result
|
|
134
170
|
result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
|
|
135
171
|
content: File.read(database_config_path, encoding: FILE_ENCODING)
|
|
@@ -145,7 +181,7 @@ module Worktrees
|
|
|
145
181
|
end
|
|
146
182
|
|
|
147
183
|
def follow_up_notes_text
|
|
148
|
-
[super, browser_follow_up_notes_text, puma_follow_up_notes_text].join
|
|
184
|
+
[super, browser_follow_up_notes_text, puma_follow_up_notes_text, pi_worktree_follow_up_notes_text].join
|
|
149
185
|
end
|
|
150
186
|
|
|
151
187
|
def installed_items_text
|
|
@@ -156,9 +192,31 @@ module Worktrees
|
|
|
156
192
|
items << ' • bin/ob' if install_browser_wrapper?
|
|
157
193
|
items << ' • Procfile.dev.worktree.example' unless options[:yolo]
|
|
158
194
|
items << database_follow_up_line if database_follow_up_line
|
|
195
|
+
items << pi_worktree_follow_up_line if pi_worktree_follow_up_line
|
|
159
196
|
items.join("\n")
|
|
160
197
|
end
|
|
161
198
|
|
|
199
|
+
def pi_worktree_follow_up_line
|
|
200
|
+
case @pi_worktree_outcome
|
|
201
|
+
when :updated, :identical
|
|
202
|
+
" • #{PI_WORKTREE_CONFIG} (pi-worktree postCreate runs bin/wt setup)"
|
|
203
|
+
when :conflict
|
|
204
|
+
" • #{PI_WORKTREE_CONFIG} needs manual review — see the message above"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def pi_worktree_follow_up_notes_text
|
|
209
|
+
return '' unless options[:pi]
|
|
210
|
+
|
|
211
|
+
[
|
|
212
|
+
'',
|
|
213
|
+
' Pi worktrees:',
|
|
214
|
+
" #{PI_WORKTREE_CONFIG} is read by the pi-worktree extension",
|
|
215
|
+
' ($ pi install pi-worktree); it is not native Pi core configuration.',
|
|
216
|
+
' Add the worktree directory (default .worktrees/) to .gitignore.'
|
|
217
|
+
].join("\n")
|
|
218
|
+
end
|
|
219
|
+
|
|
162
220
|
def database_follow_up_line
|
|
163
221
|
case @database_outcome
|
|
164
222
|
when :updated
|
data/lib/rails/worktrees/cli.rb
CHANGED
|
@@ -2,8 +2,10 @@ module Rails
|
|
|
2
2
|
module Worktrees
|
|
3
3
|
# Shell entrypoint for the wt executable.
|
|
4
4
|
class CLI
|
|
5
|
-
LOADER_OPTIONAL_COMMANDS = %w[doctor update -h --help -v --version].freeze
|
|
6
|
-
|
|
5
|
+
LOADER_OPTIONAL_COMMANDS = %w[doctor update -h --help help -v --version version].freeze
|
|
6
|
+
# Commands that use project configuration when it loads, but must keep working when it does not.
|
|
7
|
+
LOADER_BEST_EFFORT_COMMANDS = %w[doctor].freeze
|
|
8
|
+
LOADER_IGNORED_FLAGS = %w[--dry-run --force --skip-setup --pi].freeze
|
|
7
9
|
SETUP_SUBCOMMAND = 'setup'.freeze
|
|
8
10
|
|
|
9
11
|
def initialize(
|
|
@@ -20,7 +22,11 @@ module Rails
|
|
|
20
22
|
|
|
21
23
|
def start
|
|
22
24
|
configuration = ::Rails::Worktrees.configuration
|
|
23
|
-
|
|
25
|
+
if should_load_project_configuration?
|
|
26
|
+
load_project_configuration(configuration)
|
|
27
|
+
elsif best_effort_configuration_command?
|
|
28
|
+
load_project_configuration_best_effort(configuration)
|
|
29
|
+
end
|
|
24
30
|
command_for(configuration).run
|
|
25
31
|
rescue ::Rails::Worktrees::Error => e
|
|
26
32
|
@io.fetch(:stderr).puts("Error: #{e.message}")
|
|
@@ -35,6 +41,17 @@ module Rails
|
|
|
35
41
|
raise ::Rails::Worktrees::Error, "Failed to load worktrees configuration: #{e.class}: #{e.message}"
|
|
36
42
|
end
|
|
37
43
|
|
|
44
|
+
# A broken initializer must not block recovery; wt doctor then reports with default settings.
|
|
45
|
+
def load_project_configuration_best_effort(configuration)
|
|
46
|
+
load_project_configuration(configuration)
|
|
47
|
+
rescue ::Rails::Worktrees::Error
|
|
48
|
+
configuration
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def best_effort_configuration_command?
|
|
52
|
+
LOADER_BEST_EFFORT_COMMANDS.include?(argv_without_flags.first)
|
|
53
|
+
end
|
|
54
|
+
|
|
38
55
|
def configuration_root
|
|
39
56
|
return expand_setup_target_path if explicit_setup_path_target?
|
|
40
57
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
module Worktrees
|
|
5
|
+
class Command
|
|
6
|
+
# Keeps an in-repo worktrees_dir out of the main checkout's untracked files.
|
|
7
|
+
module InRepoWorktrees
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def in_repo_layout?(workspaces) = workspaces[:layout] == :in_repo
|
|
11
|
+
|
|
12
|
+
def ensure_worktrees_dir_ignored(context)
|
|
13
|
+
workspaces = context[:workspaces]
|
|
14
|
+
return unless in_repo_layout?(workspaces)
|
|
15
|
+
return if worktrees_dir_ignored?(context[:primary_root], workspaces)
|
|
16
|
+
|
|
17
|
+
entry = worktrees_dir_exclude_entry(workspaces)
|
|
18
|
+
exclude_path = info_exclude_path(context[:primary_root])
|
|
19
|
+
return info("Would add '#{entry}' to #{exclude_path}") if dry_run?
|
|
20
|
+
|
|
21
|
+
append_exclude_entry(exclude_path, entry)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def worktrees_dir_ignored?(primary_root, workspaces)
|
|
25
|
+
git_success?('-C', primary_root, 'check-ignore', '-q', "#{workspaces[:relative_dir]}/")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Escapes gitignore metacharacters so the entry matches only the literal directory.
|
|
29
|
+
def worktrees_dir_exclude_entry(workspaces)
|
|
30
|
+
escaped = workspaces[:relative_dir]
|
|
31
|
+
.gsub(/[\\*?\[\]]/) { |char| "\\#{char}" }
|
|
32
|
+
.sub(/ +\z/) { |spaces| spaces.gsub(' ', '\ ') }
|
|
33
|
+
"/#{escaped}/"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def info_exclude_path(primary_root)
|
|
37
|
+
git_path = git_capture('-C', primary_root, 'rev-parse', '--git-path', 'info/exclude').strip
|
|
38
|
+
expand_git_path(git_path, base_dir: primary_root)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def append_exclude_entry(exclude_path, entry)
|
|
42
|
+
FileUtils.mkdir_p(File.dirname(exclude_path))
|
|
43
|
+
separator = missing_trailing_newline?(exclude_path) ? "\n" : ''
|
|
44
|
+
File.open(exclude_path, 'a') { |file| file.write("#{separator}#{entry}\n") }
|
|
45
|
+
info("Added '#{entry}' to #{exclude_path} so the main checkout ignores in-repo worktrees")
|
|
46
|
+
rescue SystemCallError => e
|
|
47
|
+
warning(
|
|
48
|
+
"Could not update #{exclude_path} (#{e.class}); add '#{entry}' to .gitignore " \
|
|
49
|
+
'so the main checkout does not show the worktrees as untracked files.'
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def missing_trailing_newline?(path)
|
|
54
|
+
File.file?(path) && !File.empty?(path) && !File.read(path).end_with?("\n")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def in_repo_worktrees_doctor_check(repository)
|
|
58
|
+
workspaces = repository[:workspaces]
|
|
59
|
+
return unless in_repo_layout?(workspaces)
|
|
60
|
+
|
|
61
|
+
if worktrees_dir_ignored?(repository[:primary_root], workspaces)
|
|
62
|
+
ignored_worktrees_dir_check(workspaces)
|
|
63
|
+
else
|
|
64
|
+
unignored_worktrees_dir_check(workspaces)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def ignored_worktrees_dir_check(workspaces)
|
|
69
|
+
doctor_check(category: :worktree, status: :ok,
|
|
70
|
+
headline: "In-repo worktrees directory #{workspaces[:root]} is ignored by Git.")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def unignored_worktrees_dir_check(workspaces)
|
|
74
|
+
doctor_check(
|
|
75
|
+
category: :worktree,
|
|
76
|
+
status: :warning,
|
|
77
|
+
headline: "In-repo worktrees directory #{workspaces[:root]} is not ignored by Git.",
|
|
78
|
+
messages: ["Add '#{worktrees_dir_exclude_entry(workspaces)}' to .gitignore, " \
|
|
79
|
+
'or create a worktree with wt to add it to .git/info/exclude.']
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -45,7 +45,8 @@ module Rails
|
|
|
45
45
|
|
|
46
46
|
def list_available_names(source_file, project_name, workspaces)
|
|
47
47
|
source_names(source_file).select do |candidate|
|
|
48
|
-
|
|
48
|
+
!RESERVED_WORDS.include?(candidate) && valid_auto_name?(candidate) &&
|
|
49
|
+
available_for_auto_pick?(candidate, project_name, workspaces)
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
52
|
|
|
@@ -8,10 +8,10 @@ module Rails
|
|
|
8
8
|
|
|
9
9
|
def handle_meta_command
|
|
10
10
|
case @argv.first
|
|
11
|
-
when '-h', '--help'
|
|
11
|
+
when '-h', '--help', 'help'
|
|
12
12
|
@stdout.print(usage)
|
|
13
13
|
0
|
|
14
|
-
when '-v', '--version'
|
|
14
|
+
when '-v', '--version', 'version'
|
|
15
15
|
@stdout.puts("wt #{Rails::Worktrees::VERSION}")
|
|
16
16
|
0
|
|
17
17
|
when '--env', '--print-env'
|
|
@@ -30,23 +30,26 @@ module Rails
|
|
|
30
30
|
Create and clean up Git worktrees for the current repository.
|
|
31
31
|
|
|
32
32
|
Usage: wt [worktree-name]
|
|
33
|
+
wt help | wt version
|
|
33
34
|
wt [--skip-setup] [worktree-name]
|
|
34
35
|
wt --dry-run [worktree-name]
|
|
35
36
|
wt --print-env <worktree-name>
|
|
36
37
|
wt doctor
|
|
37
38
|
wt setup [--dry-run] [path|name]
|
|
38
|
-
wt update [--dry-run]
|
|
39
|
+
wt update [--dry-run] [--pi]
|
|
40
|
+
wt teardown [--dry-run]
|
|
39
41
|
wt remove [--dry-run] [--force] <worktree-name>
|
|
40
42
|
wt delete [--dry-run] [--force] <worktree-name>
|
|
41
43
|
wt prune [--dry-run]
|
|
42
44
|
|
|
43
45
|
Options:
|
|
44
|
-
-h, --help
|
|
45
|
-
-v, --version
|
|
46
|
+
-h, --help, help Show this help message
|
|
47
|
+
-v, --version, version Show the script version
|
|
46
48
|
--dry-run [name] Preview worktree creation or cleanup without changing anything
|
|
47
49
|
--skip-setup Create the worktree without running setup steps
|
|
48
50
|
--force Delete an unmerged local branch with wt remove/delete
|
|
49
51
|
--env, --print-env <name> Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
52
|
+
--pi With wt update, add the pi-worktree bin/wt setup hook
|
|
50
53
|
|
|
51
54
|
Quick start:
|
|
52
55
|
wt Auto-pick a name from a bundled *.txt list
|
|
@@ -59,6 +62,8 @@ module Rails
|
|
|
59
62
|
wt setup my-feature
|
|
60
63
|
wt setup ../my-project.worktrees/my-feature
|
|
61
64
|
wt update --dry-run
|
|
65
|
+
wt teardown --dry-run
|
|
66
|
+
wt teardown
|
|
62
67
|
wt remove my-feature
|
|
63
68
|
wt remove --force my-feature
|
|
64
69
|
wt prune
|
|
@@ -66,12 +71,17 @@ module Rails
|
|
|
66
71
|
How it works:
|
|
67
72
|
- by default creates worktrees beside the repo in ../<project>.worktrees/<name>
|
|
68
73
|
- when workspace_root or WT_WORKSPACES_ROOT is set, creates worktrees in <root>/<project>/<name>
|
|
74
|
+
- when worktrees_dir is set (for example '.worktrees'), creates worktrees inside the main checkout in <repo>/<worktrees_dir>/<name>
|
|
75
|
+
- reserved words are commands, never new worktree names: #{RESERVED_WORDS.join(', ')}
|
|
69
76
|
- always uses the branch name #{@configuration.branch_prefix}/<name>
|
|
70
77
|
- bases new branches on the repository's origin default branch
|
|
71
78
|
- by default wt <name> both creates the worktree and runs setup automatically
|
|
72
79
|
- wt setup reruns setup for the current checkout, a managed worktree name, or a specific checkout path, including manually-created worktrees
|
|
73
80
|
- wt doctor audits install/config drift plus basic worktree health without changing files
|
|
74
81
|
- wt update applies safe file-based fixes for managed installer artifacts and config hints
|
|
82
|
+
- wt update --pi also writes .pi/worktree.json for the pi-worktree extension (not Pi core)
|
|
83
|
+
- wt teardown stops local Overmind and drops suffix-validated development/test databases, keeping the checkout
|
|
84
|
+
- wt remove/delete run teardown before removing the checkout and local branch
|
|
75
85
|
- wt remove/delete can run from the main checkout or any sibling worktree, but never remove the worktree you're currently in
|
|
76
86
|
- wt prune removes merged worktrees created by wt while skipping the main checkout and the checkout you're in
|
|
77
87
|
- auto-discovers bundled *.txt files from #{@configuration.name_sources_path}
|