brunch 0.0.1 → 0.8.1
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 +5 -5
- data/CHANGELOG.md +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +355 -0
- data/brunch.webp +0 -0
- data/exe/brunch +6 -0
- data/lib/brunch/cli.rb +145 -0
- data/lib/brunch/hooks.rb +89 -0
- data/lib/brunch/managers.rb +267 -0
- data/lib/brunch/version.rb +5 -0
- data/lib/brunch/worktree_mode.rb +310 -0
- data/lib/brunch.rb +7 -0
- metadata +96 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 318ff646dd610a9647612d8ea0fcc446e5122215f4517d665fc639b5bfca740d
|
|
4
|
+
data.tar.gz: e4aeddec12c82d9b0d33caddf6a366840e3db8badd45fec4114f57bd034c3d86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d334e9cc379f39f4866626e8801adbdc9cbe8aace2d0249b8b4a6a81020b34ec35eca8b8dcf742622e06545d58086966ed087784b077460bdc7e56e5ec150f1c
|
|
7
|
+
data.tar.gz: b34f7f1baf8dbbf6a56669ccc310846eab6aa23d891fa05e4adbe541e1ab1ccea954bfdb46c087fba7a47e3601f45f967efb1de8ff93d7678cb361abaec6eb1c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.8.1 - 2026-09-29
|
|
6
|
+
|
|
7
|
+
- Reorganize the README into a step-by-step guide for branches, parallel
|
|
8
|
+
worktrees, managers, and everyday commands.
|
|
9
|
+
|
|
10
|
+
## 0.8.0 - 2026-09-29
|
|
11
|
+
|
|
12
|
+
- Use one worktree-based lifecycle for both branch switches and parallel
|
|
13
|
+
worktrees. Branches within a worktree reuse its port; separate worktrees
|
|
14
|
+
receive distinct host ports.
|
|
15
|
+
- Run applications from the live worktree and replace mode-specific port
|
|
16
|
+
settings with an optional `preferred_port`.
|
|
17
|
+
- Document installation and everyday use, and add a compact illustration to
|
|
18
|
+
the README.
|
|
19
|
+
- Make CI lint only project files, install the `ghe` alias, and initialize the
|
|
20
|
+
test repository on `main`.
|
|
21
|
+
- Detect running Podman Compose containers by their project label.
|
|
22
|
+
|
|
23
|
+
## 0.7.1 - 2026-09-29
|
|
24
|
+
|
|
25
|
+
- Use a compatible Podman Compose provider and CLI arguments in integration
|
|
26
|
+
tests and at runtime.
|
|
27
|
+
|
|
28
|
+
## 0.7.0 - 2026-09-29
|
|
29
|
+
|
|
30
|
+
- Add parallel worktree mode with per-worktree ports, live source directories,
|
|
31
|
+
isolated projects and volumes, and branch-specific environments.
|
|
32
|
+
- Handle git-hooks-ext worktree create, remove, move, prune, and repair events;
|
|
33
|
+
reconcile plain Git worktree operations during cleanup.
|
|
34
|
+
- Preserve control files for teardown after a worktree disappears, and use
|
|
35
|
+
short repository locks plus per-worktree lifecycle locks.
|
|
36
|
+
|
|
37
|
+
## 0.6.0 - 2026-09-29
|
|
38
|
+
|
|
39
|
+
- Add shared and unique port modes; shared port `3000` is now the default.
|
|
40
|
+
|
|
41
|
+
## 0.5.0 - 2026-09-28
|
|
42
|
+
|
|
43
|
+
- Add `ports` and script-friendly `port` commands.
|
|
44
|
+
|
|
45
|
+
## 0.4.1 - 2026-09-28
|
|
46
|
+
|
|
47
|
+
- Strengthen `doctor` hook and port validation.
|
|
48
|
+
|
|
49
|
+
## 0.4.0 - 2026-09-28
|
|
50
|
+
|
|
51
|
+
- Add `doctor`, `exec`, and argument forwarding for `logs`.
|
|
52
|
+
- Serialize commands with a repository-local lock, save state atomically, and
|
|
53
|
+
recover interrupted environment setup on the next activation.
|
|
54
|
+
- Add conditional Docker Compose and Podman Compose integration tests to CI.
|
|
55
|
+
|
|
56
|
+
## 0.3.0 - 2026-09-28
|
|
57
|
+
|
|
58
|
+
- Add a native `local_process` manager for `bin/dev`-style commands, with PID
|
|
59
|
+
tracking, process-group shutdown, and snapshot-local logs.
|
|
60
|
+
|
|
61
|
+
## 0.2.0 - 2026-09-28
|
|
62
|
+
|
|
63
|
+
- Add pluggable environment managers. Docker Compose remains the default and a
|
|
64
|
+
command-based manager supports other project-specific runtimes.
|
|
65
|
+
- Add an optional `create` lifecycle command for provisioning custom-manager
|
|
66
|
+
environments before they start.
|
|
67
|
+
- Add the Podman Compose manager, configurable `switch_only` and `active_only`
|
|
68
|
+
lifecycle modes, and `status`, `stop`, `restart`, and `logs` commands.
|
|
69
|
+
- Validate `brunch.yml` and expose manager status and health checks.
|
|
70
|
+
|
|
71
|
+
## 0.1.0 - 2026-09-28
|
|
72
|
+
|
|
73
|
+
- Initial public release.
|
|
74
|
+
- Isolated Docker Compose environments activated by Git branch checkout.
|
|
75
|
+
- `git-hooks-ext` integration and stale-environment cleanup.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maciej Ciemborowicz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Brunch
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Brunch runs isolated development environments for Git branches and worktrees.
|
|
6
|
+
|
|
7
|
+
Each branch gets its own environment. With Compose, that means a separate project, network, containers, and named volumes. Branches checked out in the same worktree share its host port, while additional worktrees get different ports and can run in parallel.
|
|
8
|
+
|
|
9
|
+
Docker Compose is the default manager. Podman Compose, local processes, and custom project commands are also supported.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Ruby 3.1+
|
|
14
|
+
- Git 2.28+
|
|
15
|
+
- Docker Compose, Podman Compose, or another supported manager
|
|
16
|
+
- [git-hooks-ext](https://github.com/ciembor/git-hooks-ext)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Install [git-hooks-ext](https://github.com/ciembor/git-hooks-ext) so that `ghe` is available on your `PATH`, then install Brunch:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
gem install brunch
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Inside each repository you want Brunch to manage:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
brunch install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Brunch installs its hooks through `git-hooks-ext` and will not overwrite hooks owned by another tool.
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Add `brunch.yml` to the repository root.
|
|
37
|
+
|
|
38
|
+
For Docker Compose:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
compose_file: compose.yaml
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Expose the application through `BRUNCH_PORT`:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
services:
|
|
48
|
+
web:
|
|
49
|
+
ports:
|
|
50
|
+
- "127.0.0.1:${BRUNCH_PORT}:3000"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The first activated worktree prefers port `3000`. Additional worktrees receive another free port.
|
|
54
|
+
|
|
55
|
+
To prefer a different starting port:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
preferred_port: 4000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Port assignments are persisted per worktree in `.git/brunch/state.json`.
|
|
62
|
+
|
|
63
|
+
## Getting started
|
|
64
|
+
|
|
65
|
+
Commit `brunch.yml` and the application configuration first. The repository must contain at least one commit.
|
|
66
|
+
|
|
67
|
+
Then run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
brunch install
|
|
71
|
+
brunch doctor
|
|
72
|
+
brunch activate
|
|
73
|
+
brunch status
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`brunch activate` starts the environment for the currently checked-out branch.
|
|
77
|
+
|
|
78
|
+
To print only the current worktree's port:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
brunch port
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Open the application on the printed port, for example:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
http://127.0.0.1:3000
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With Compose, the application must listen on the container port mapped in the Compose file.
|
|
91
|
+
|
|
92
|
+
## Branch switching
|
|
93
|
+
|
|
94
|
+
Once the hooks are installed, normal Git branch switches automatically stop the previous branch environment and start the new one:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
git switch -c feature/login
|
|
98
|
+
git switch main
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Branches checked out in the same worktree reuse that worktree's host port.
|
|
102
|
+
|
|
103
|
+
With Compose, each branch keeps its own Compose project and named volumes, so persistent resources remain isolated between branches.
|
|
104
|
+
|
|
105
|
+
To manually start the environment after `brunch stop`:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
brunch activate
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
To rebuild and restart it:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
brunch restart
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Compose builds use files from the live worktree, including uncommitted changes.
|
|
118
|
+
|
|
119
|
+
If you want source edits to appear in an already running container without rebuilding, configure a bind mount or another reload mechanism in your Compose setup.
|
|
120
|
+
|
|
121
|
+
## Parallel worktrees
|
|
122
|
+
|
|
123
|
+
Additional worktrees run independently on different host ports.
|
|
124
|
+
|
|
125
|
+
Create them with `git-hooks-ext`:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
ghe worktree add -b feature-a ../feature-a
|
|
129
|
+
ghe worktree add -b feature-b ../feature-b
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cd ../feature-a
|
|
136
|
+
|
|
137
|
+
brunch port
|
|
138
|
+
brunch ports
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`brunch port` prints the current worktree's port.
|
|
142
|
+
|
|
143
|
+
`brunch ports` lists ports assigned to all worktrees.
|
|
144
|
+
|
|
145
|
+
Switching branches inside one worktree does not affect environments running in other worktrees.
|
|
146
|
+
|
|
147
|
+
If you create a worktree with plain Git:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
git worktree add -b feature-c ../feature-c
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
activate Brunch manually inside it:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
cd ../feature-c
|
|
157
|
+
brunch activate
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
After moving or removing worktrees with plain Git, run:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
brunch cleanup
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Brunch never deletes the worktree source directory.
|
|
167
|
+
|
|
168
|
+
## Managers
|
|
169
|
+
|
|
170
|
+
### Docker Compose
|
|
171
|
+
|
|
172
|
+
Docker Compose is the default manager:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
compose_file: compose.yaml
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Podman Compose
|
|
179
|
+
|
|
180
|
+
Podman Compose uses the same Compose file contract:
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
manager: podman_compose
|
|
184
|
+
compose_file: compose.yaml
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Local process
|
|
188
|
+
|
|
189
|
+
Use `local_process` for a development command such as `bin/dev`:
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
manager: local_process
|
|
193
|
+
command: bin/dev
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Brunch starts and stops the process together with the branch environment and stores its output under `.git/brunch/controls`.
|
|
197
|
+
|
|
198
|
+
### Custom commands
|
|
199
|
+
|
|
200
|
+
Use the `command` manager to integrate Brunch with project-specific tooling:
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
manager: command
|
|
204
|
+
commands:
|
|
205
|
+
create: bin/environment create
|
|
206
|
+
start: bin/environment start
|
|
207
|
+
stop: bin/environment stop
|
|
208
|
+
remove: bin/environment remove
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Brunch runs these commands from the live worktree with:
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
BRUNCH_REF
|
|
215
|
+
BRUNCH_PORT
|
|
216
|
+
BRUNCH_PROJECT
|
|
217
|
+
BRUNCH_SNAPSHOT
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`BRUNCH_SNAPSHOT` points to the live worktree for compatibility with manager adapters.
|
|
221
|
+
|
|
222
|
+
`create` is optional.
|
|
223
|
+
|
|
224
|
+
`start` must return after launching the environment.
|
|
225
|
+
|
|
226
|
+
`stop` is called when switching away from the active branch.
|
|
227
|
+
|
|
228
|
+
`remove` is called when the corresponding worktree environment is deleted and should remove manager-owned persistent resources.
|
|
229
|
+
|
|
230
|
+
Optional commands:
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
commands:
|
|
234
|
+
status: bin/environment status
|
|
235
|
+
health: bin/environment health
|
|
236
|
+
logs: bin/environment logs
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
These power the corresponding Brunch operations.
|
|
240
|
+
|
|
241
|
+
## Worktree lifecycle
|
|
242
|
+
|
|
243
|
+
A branch checkout stops the previous environment in that worktree and starts the new one on the same host port.
|
|
244
|
+
|
|
245
|
+
Other worktrees continue running.
|
|
246
|
+
|
|
247
|
+
Removing a worktree removes its Brunch environments. Brunch keeps the information needed to shut down manager resources under `.git/brunch/controls`, so cleanup can still run after the worktree itself is gone.
|
|
248
|
+
|
|
249
|
+
If a custom `remove` command depends on project files, those files must be committed because cleanup after worktree removal uses the last committed state.
|
|
250
|
+
|
|
251
|
+
`brunch cleanup` removes environments belonging to worktrees that no longer exist.
|
|
252
|
+
|
|
253
|
+
Git does not expose every branch deletion workflow reliably to hooks, so stopped branch environments may remain until their worktree is removed.
|
|
254
|
+
|
|
255
|
+
## Commands
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
brunch install
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Install Brunch hooks for the repository.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
brunch activate
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Start the environment for the current branch.
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
brunch status
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Show the current environment, manager status, and port.
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
brunch port
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Print the current worktree's port.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
brunch ports
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
List ports assigned to all worktrees.
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
brunch stop
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Stop the active environment without removing it.
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
brunch restart
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Recreate and start the active environment.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
brunch logs
|
|
301
|
+
brunch logs --follow
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Show environment logs.
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
brunch run -- bin/rails console
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Run a command on the host from the live worktree.
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
brunch doctor
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Check Git, installed hooks, configuration, manager availability, and ports.
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
brunch cleanup
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Remove environments belonging to deleted worktrees.
|
|
323
|
+
|
|
324
|
+
## Upgrading from older configurations
|
|
325
|
+
|
|
326
|
+
Before upgrading from an older branch-only configuration, stop its running environment and clean up its legacy state.
|
|
327
|
+
|
|
328
|
+
Brunch will not reinterpret an existing non-empty branch-only state as worktree state.
|
|
329
|
+
|
|
330
|
+
## Development
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
bundle install
|
|
334
|
+
bin/install-pre-commit
|
|
335
|
+
bundle exec rake quality
|
|
336
|
+
gem build brunch.gemspec
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Run container integration tests with Docker Compose:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
BRUNCH_INTEGRATION_MANAGER=docker_compose bundle exec rake test
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Or Podman Compose:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
BRUNCH_INTEGRATION_MANAGER=podman_compose bundle exec rake test
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Without `BRUNCH_INTEGRATION_MANAGER`, container integration tests are skipped by the local quality check.
|
|
352
|
+
|
|
353
|
+
The pre-commit hook runs RuboCop with automatic corrections, Reek, and the full test suite. SimpleCov requires 100% line and branch coverage for `lib/**/*.rb`.
|
|
354
|
+
|
|
355
|
+
If RuboCop modifies a file, review and stage the changes before committing.
|
data/brunch.webp
ADDED
|
Binary file
|
data/exe/brunch
ADDED
data/lib/brunch/cli.rb
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "yaml"
|
|
7
|
+
|
|
8
|
+
module Brunch
|
|
9
|
+
class CLI
|
|
10
|
+
include WorktreeMode
|
|
11
|
+
|
|
12
|
+
PORT_RANGE = (1024..49_151)
|
|
13
|
+
CONFIGURATION_KEYS = %w[manager compose_file command commands preferred_port].freeze
|
|
14
|
+
COMMAND_KEYS = %w[create start stop remove status health logs].freeze
|
|
15
|
+
|
|
16
|
+
class ConfigurationError < StandardError; end
|
|
17
|
+
class OperationError < StandardError; end
|
|
18
|
+
|
|
19
|
+
def self.start(arguments) = new.start(arguments)
|
|
20
|
+
|
|
21
|
+
def start(arguments)
|
|
22
|
+
Dir.chdir(repo_root) unless Dir.pwd == repo_root
|
|
23
|
+
case arguments
|
|
24
|
+
in ["install"] then Hooks.install
|
|
25
|
+
in ["version"] | ["--version"] | ["-v"] then puts Brunch::VERSION
|
|
26
|
+
else return worktree_command(arguments)
|
|
27
|
+
end
|
|
28
|
+
0
|
|
29
|
+
rescue ConfigurationError => e
|
|
30
|
+
warn e.message
|
|
31
|
+
64
|
|
32
|
+
rescue OperationError => e
|
|
33
|
+
warn e.message
|
|
34
|
+
1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def git_output(*arguments)
|
|
40
|
+
output, status = Open3.capture2("git", *arguments)
|
|
41
|
+
abort "Git command failed: git #{arguments.join(' ')}" unless status.success?
|
|
42
|
+
|
|
43
|
+
output.strip
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def repo_root
|
|
47
|
+
@repo_root ||= git_output("rev-parse", "--show-toplevel")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def state_root
|
|
51
|
+
common_dir = git_output("rev-parse", "--git-common-dir")
|
|
52
|
+
@state_root ||= File.join(File.expand_path(common_dir, repo_root), "brunch")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def state_path = File.join(state_root, "state.json")
|
|
56
|
+
|
|
57
|
+
def state
|
|
58
|
+
return { "worktrees" => {} } unless File.file?(state_path)
|
|
59
|
+
|
|
60
|
+
data = JSON.parse(File.read(state_path))
|
|
61
|
+
raise ConfigurationError, "Invalid Brunch state: expected a JSON object." unless data.is_a?(Hash)
|
|
62
|
+
if data.fetch("environments", {}).any? || data.key?("active_ref")
|
|
63
|
+
raise ConfigurationError, "Legacy branch state found in .git/brunch/state.json; stop old environments before upgrading."
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
data
|
|
67
|
+
rescue JSON::ParserError => e
|
|
68
|
+
raise ConfigurationError, "Invalid Brunch state: #{e.message}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def save_state(value)
|
|
72
|
+
FileUtils.mkdir_p(state_root)
|
|
73
|
+
temporary_path = "#{state_path}.#{Process.pid}.tmp"
|
|
74
|
+
File.open(temporary_path, "w", 0o600) do |file|
|
|
75
|
+
file.write(JSON.pretty_generate(value))
|
|
76
|
+
file.flush
|
|
77
|
+
file.fsync
|
|
78
|
+
end
|
|
79
|
+
File.rename(temporary_path, state_path)
|
|
80
|
+
ensure
|
|
81
|
+
FileUtils.rm_f(temporary_path) if temporary_path
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def configuration(root: repo_root)
|
|
85
|
+
path = File.join(root, "brunch.yml")
|
|
86
|
+
configured = File.file?(path) ? YAML.safe_load_file(path, permitted_classes: [], aliases: false) || {} : {}
|
|
87
|
+
validate_configuration!(configured)
|
|
88
|
+
{ "manager" => "docker_compose", "compose_file" => "compose.yaml", "preferred_port" => 3000 }.merge(configured)
|
|
89
|
+
rescue Psych::Exception => e
|
|
90
|
+
raise ConfigurationError, "Invalid brunch.yml: #{e.message}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def validate_configuration!(configured)
|
|
94
|
+
raise ConfigurationError, "brunch.yml must contain a mapping." unless configured.is_a?(Hash)
|
|
95
|
+
|
|
96
|
+
unknown = configured.keys - CONFIGURATION_KEYS
|
|
97
|
+
raise ConfigurationError, "Unknown brunch.yml key(s): #{unknown.join(', ')}." unless unknown.empty?
|
|
98
|
+
|
|
99
|
+
value = { "manager" => "docker_compose", "compose_file" => "compose.yaml", "preferred_port" => 3000 }.merge(configured)
|
|
100
|
+
unless %w[docker_compose podman_compose local_process command].include?(value["manager"])
|
|
101
|
+
raise ConfigurationError, "Unknown Brunch manager: #{value['manager']}."
|
|
102
|
+
end
|
|
103
|
+
unless value["preferred_port"].is_a?(Integer) && PORT_RANGE.cover?(value["preferred_port"])
|
|
104
|
+
raise ConfigurationError, "preferred_port must be a number from #{PORT_RANGE.begin} to #{PORT_RANGE.end}."
|
|
105
|
+
end
|
|
106
|
+
if %w[docker_compose podman_compose].include?(value["manager"]) && (!value["compose_file"].is_a?(String) || value["compose_file"].empty?)
|
|
107
|
+
raise ConfigurationError, "compose_file must be a non-empty string."
|
|
108
|
+
end
|
|
109
|
+
if value["manager"] == "local_process" && (!value["command"].is_a?(String) || value["command"].empty?)
|
|
110
|
+
raise ConfigurationError, "local_process manager requires a non-empty command."
|
|
111
|
+
end
|
|
112
|
+
return unless value["manager"] == "command"
|
|
113
|
+
|
|
114
|
+
commands = value["commands"]
|
|
115
|
+
raise ConfigurationError, "command manager requires a commands mapping." unless commands.is_a?(Hash)
|
|
116
|
+
|
|
117
|
+
unknown = commands.keys - COMMAND_KEYS
|
|
118
|
+
raise ConfigurationError, "Unknown commands key(s): #{unknown.join(', ')}." unless unknown.empty?
|
|
119
|
+
|
|
120
|
+
%w[start stop remove].each do |action|
|
|
121
|
+
raise ConfigurationError, "command manager requires commands.#{action}." unless commands[action].is_a?(String) && !commands[action].empty?
|
|
122
|
+
end
|
|
123
|
+
%w[create status health logs].each do |action|
|
|
124
|
+
raise ConfigurationError, "commands.#{action} must be a string." if commands.key?(action) && !commands[action].is_a?(String)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def preferred_port = configuration.fetch("preferred_port")
|
|
129
|
+
|
|
130
|
+
def archive_ref(ref, destination)
|
|
131
|
+
FileUtils.rm_rf(destination)
|
|
132
|
+
FileUtils.mkdir_p(destination)
|
|
133
|
+
environment = { "LC_ALL" => "C", "LANG" => "C" }
|
|
134
|
+
statuses = Open3.pipeline([environment, "git", "archive", "--format=tar", ref],
|
|
135
|
+
[environment, "tar", "-x", "-C", destination])
|
|
136
|
+
abort "Could not create a control copy for #{ref}." unless statuses.all?(&:success?)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def color(text, code)
|
|
140
|
+
return text unless $stdout.tty?
|
|
141
|
+
|
|
142
|
+
"\e[#{code}m#{text}\e[0m"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|