rails-worktrees 0.3.0 → 0.5.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 +19 -9
- data/README.md +55 -3
- data/lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt +19 -15
- data/lib/rails/worktrees/command/git_operations.rb +65 -4
- data/lib/rails/worktrees/command/output.rb +90 -3
- data/lib/rails/worktrees/command/workspace_paths.rb +34 -0
- data/lib/rails/worktrees/command.rb +429 -18
- data/lib/rails/worktrees/initializer_updater.rb +148 -0
- data/lib/rails/worktrees/project_maintenance.rb +326 -0
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f11c54fe45c6061774b7181c80c7a25ba1c824d04fe06668ca0d6fe0bc3c453
|
|
4
|
+
data.tar.gz: 9470d8a967f77f0188679e8f9b50fd5a78c4aebe6f10e268cba00ffaff1ebea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3775b72de293d0cf888f9b94cbbeae5b6031f001c641a1365f8f2662caa38de8b04bd4235a4e5c3f7b81279f6a23ce4637f4b2cfa6d6e1ba9d0fd0619daede2
|
|
7
|
+
data.tar.gz: 0c07249ae8bad61d6ae0cdad68f58752579145175e0d43005772e0d91f2f04b8d672f99a1952ce168e881d04e0b88ab4bbb8603183d4a9a2ace12f5c1767584e
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.5.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0](https://github.com/asjer/rails-worktrees/compare/v0.4.0...v0.5.0) (2026-04-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **maintenance:** add doctor and update maintenance commands ([fce44f7](https://github.com/asjer/rails-worktrees/commit/fce44f757ce9835eb663010f30a192c23f82080c))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **install:** support development-only gem installs ([bf9e742](https://github.com/asjer/rails-worktrees/commit/bf9e74260a88ba37674a5318b48ad5c93f1c0399))
|
|
14
|
+
|
|
15
|
+
## [0.4.0](https://github.com/asjer/rails-worktrees/compare/v0.3.0...v0.4.0) (2026-03-30)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **delete:** add `bin/wt remove` command to delete worktrees and local branches ([5905723](https://github.com/asjer/rails-worktrees/commit/5905723712e3ca11fd6614633e68855e33bb50e3))
|
|
21
|
+
|
|
3
22
|
## [0.3.0](https://github.com/asjer/rails-worktrees/compare/v0.2.2...v0.3.0) (2026-03-30)
|
|
4
23
|
|
|
5
24
|
|
|
@@ -45,12 +64,3 @@
|
|
|
45
64
|
### Features
|
|
46
65
|
|
|
47
66
|
* add wt command and rails installer ([5d798d5](https://github.com/asjer/rails-worktrees/commit/5d798d5129585331780f0259b39061194feb66e3))
|
|
48
|
-
|
|
49
|
-
## [Unreleased]
|
|
50
|
-
|
|
51
|
-
- Add a gem-managed `wt` CLI for creating Rails worktrees.
|
|
52
|
-
- Add an optional gem-managed `ob` CLI plus generated `bin/ob` wrapper for opening `localhost:$DEV_PORT` routes.
|
|
53
|
-
- Add a Rails installer generator that creates `bin/wt` and `config/initializers/rails_worktrees.rb`.
|
|
54
|
-
- Add conservative `config/database.yml` patching for common development/test database names.
|
|
55
|
-
- Add a manual-dispatch GitHub Actions workflow for the disposable Rails smoke test.
|
|
56
|
-
- Add smoke-test workflow debug controls for retained artifacts and verbose output.
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
bundle add rails-worktrees
|
|
14
|
+
bundle add rails-worktrees --group development
|
|
15
15
|
bin/rails generate worktrees:install
|
|
16
16
|
# or, to also generate bin/ob without the yolo follow-ups:
|
|
17
17
|
bin/rails generate worktrees:install --browser
|
|
@@ -19,6 +19,8 @@ bin/rails generate worktrees:install --browser
|
|
|
19
19
|
bin/rails generate worktrees:install --yolo
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
The generated initializer checks that `rails-worktrees` is actually loaded before it calls `Rails::Worktrees.configure`, so the app can still boot in environments like `test` when the gem is only bundled for `:development`.
|
|
23
|
+
|
|
22
24
|
The installer adds:
|
|
23
25
|
|
|
24
26
|
- `bin/wt` — a thin wrapper that executes the gem-owned CLI
|
|
@@ -42,6 +44,13 @@ bin/wt # auto-pick a name from bundled *.txt lists
|
|
|
42
44
|
bin/wt my-feature # use an explicit worktree name
|
|
43
45
|
bin/wt --dry-run my-feature # preview the full setup without changing anything
|
|
44
46
|
bin/wt --print-env my-feature # preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
47
|
+
bin/wt doctor # audit install/config drift and basic worktree health
|
|
48
|
+
bin/wt update --dry-run # preview safe maintenance fixes
|
|
49
|
+
bin/wt update # apply safe maintenance fixes for managed files
|
|
50
|
+
bin/wt remove my-feature # remove a worktree and delete its local branch
|
|
51
|
+
bin/wt delete my-feature # alias for `bin/wt remove`
|
|
52
|
+
bin/wt remove --force my-feature # also delete an unmerged local branch
|
|
53
|
+
bin/wt prune # remove merged worktrees created by wt
|
|
45
54
|
|
|
46
55
|
bin/ob # open http://localhost:$DEV_PORT/
|
|
47
56
|
bin/ob contact # open http://localhost:$DEV_PORT/contact
|
|
@@ -55,7 +64,8 @@ bin/ob --print-url '?from=nav' # print the resolved URL without opening a brows
|
|
|
55
64
|
|------|-------------|
|
|
56
65
|
| `-h`, `--help` | Show the help message |
|
|
57
66
|
| `-v`, `--version` | Show the script version |
|
|
58
|
-
| `--dry-run [name]` | Preview
|
|
67
|
+
| `--dry-run [name]` | Preview worktree creation or cleanup without changing anything |
|
|
68
|
+
| `--force` | Force branch deletion for `bin/wt remove` / `bin/wt delete` |
|
|
59
69
|
| `--env`, `--print-env <name>` | Preview `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` |
|
|
60
70
|
|
|
61
71
|
### Default behavior
|
|
@@ -80,6 +90,45 @@ workspace/
|
|
|
80
90
|
|
|
81
91
|
`WT_WORKSPACES_ROOT` or `config.workspace_root` overrides the destination root and uses the layout `<root>/<project>/<name>`.
|
|
82
92
|
|
|
93
|
+
### Cleanup commands
|
|
94
|
+
|
|
95
|
+
`bin/wt` also supports cleanup commands for worktrees it manages:
|
|
96
|
+
|
|
97
|
+
- `bin/wt remove <name>` — remove the named worktree and delete its local branch
|
|
98
|
+
- `bin/wt delete <name>` — alias for `bin/wt remove <name>`
|
|
99
|
+
- `bin/wt prune` — remove merged worktrees created by `bin/wt` in bulk
|
|
100
|
+
|
|
101
|
+
`bin/wt remove` and `bin/wt prune` can be run from the main checkout or any sibling worktree in the same repository family.
|
|
102
|
+
|
|
103
|
+
`bin/wt remove` refuses to remove the worktree you're currently in, and by default only deletes a local branch after confirming it is already merged into `origin`'s default branch. Use `bin/wt remove --force <name>` when you intentionally want to delete an unmerged local branch too.
|
|
104
|
+
|
|
105
|
+
`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.
|
|
106
|
+
|
|
107
|
+
If you want to preview a single removal first, use `--dry-run` with `bin/wt remove`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bin/wt remove --dry-run feature-auth
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
If you want to see what `bin/wt prune` would clean up before saying yes, use `--dry-run`:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
bin/wt prune --dry-run
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Maintenance commands
|
|
120
|
+
|
|
121
|
+
`bin/wt` also includes a small maintenance surface for installer drift and checkout health:
|
|
122
|
+
|
|
123
|
+
- `bin/wt doctor` — audit managed files plus basic Git/worktree health without changing anything
|
|
124
|
+
- `bin/wt update --dry-run` — preview safe file-based fixes
|
|
125
|
+
- `bin/wt update` — apply safe file-based fixes for supported managed files
|
|
126
|
+
|
|
127
|
+
`bin/wt doctor` checks the generated initializer, `bin/wt`, optional `bin/ob`, supported config files such as `config/database.yml`, `Procfile.dev`, `config/puma.rb`, and `mise.toml`/`.mise.toml`, plus basic repository/worktree conditions like resolving `origin`'s default branch and spotting stale registered worktree paths.
|
|
128
|
+
|
|
129
|
+
`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.
|
|
130
|
+
|
|
131
|
+
|
|
83
132
|
### Interactive prompts
|
|
84
133
|
|
|
85
134
|
`bin/wt` handles several edge cases interactively:
|
|
@@ -88,6 +137,7 @@ workspace/
|
|
|
88
137
|
- **Branch already exists on origin** — asks whether to create a local tracking worktree
|
|
89
138
|
- **Target directory already exists with matching branch** — asks whether to reuse it
|
|
90
139
|
- **Target directory already exists with a different branch** — asks whether to remove and recreate it
|
|
140
|
+
- **Prune found merged worktrees created by `wt`** — asks whether to delete the batch
|
|
91
141
|
- **Retired bundled name used explicitly** — rejects it and suggests running `wt` with no argument
|
|
92
142
|
|
|
93
143
|
### Name validation
|
|
@@ -98,6 +148,8 @@ Worktree names must not contain `/` or whitespace, must not be `.` or `..`, and
|
|
|
98
148
|
|
|
99
149
|
The installer generates `config/initializers/rails_worktrees.rb` where you can override:
|
|
100
150
|
|
|
151
|
+
The initializer becomes a no-op whenever `rails-worktrees` is not loaded in the current bundle groups.
|
|
152
|
+
|
|
101
153
|
| Option | Default | Description |
|
|
102
154
|
|--------|---------|-------------|
|
|
103
155
|
| `bootstrap_env` | `true` | Write `.env` when creating a worktree |
|
|
@@ -197,7 +249,7 @@ This smoke test:
|
|
|
197
249
|
- installs `rails-worktrees` from the current checkout path
|
|
198
250
|
- runs `bin/rails generate worktrees:install --yolo`
|
|
199
251
|
- verifies `bin/wt`, `bin/ob`, the generated initializer, that `--yolo` skips the Procfile example, yolo updates to `Procfile.dev`, `config/puma.rb`, and `mise.toml`, `config/database.yml` patching, and worktree `.env` bootstrapping
|
|
200
|
-
- creates a temporary bare `origin` and confirms `bin/wt smoke-branch`
|
|
252
|
+
- creates a temporary bare `origin`, confirms `bin/wt smoke-branch` creates a real worktree, and confirms `bin/wt remove smoke-branch` can remove that merged worktree from a sibling worktree checkout
|
|
201
253
|
|
|
202
254
|
By default, the script cleans up all temp directories after the run. Set `KEEP_SMOKE_TEST_ARTIFACTS=1` to keep them around for debugging, or set `RAILS_WORKTREES_SMOKE_RAILS_VERSION` to try a different compatible Rails version.
|
|
203
255
|
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
if Gem.loaded_specs.key?('rails-worktrees') &&
|
|
2
|
+
defined?(Rails::Worktrees) &&
|
|
3
|
+
Rails::Worktrees.respond_to?(:configure)
|
|
4
|
+
Rails::Worktrees.configure do |config|
|
|
2
5
|
<% if options['conductor'] -%>
|
|
3
|
-
|
|
6
|
+
config.workspace_root = <%= conductor_workspace_root %>
|
|
4
7
|
<% else -%>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
# By default, worktrees go in a sibling "<project>.worktrees" directory.
|
|
9
|
+
# Uncomment to override with a custom parent directory that uses <root>/<project>/<name>.
|
|
10
|
+
# config.workspace_root = File.expand_path('~/worktrees')
|
|
8
11
|
<% end -%>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
# config.bootstrap_env = false
|
|
13
|
+
# config.dev_port_range = 3000..3999
|
|
14
|
+
# config.worktree_database_suffix_max_length = 18
|
|
15
|
+
# config.branch_prefix = '🚂'
|
|
16
|
+
# config.name_sources_path = Rails.root.join('config/worktree_names').to_s
|
|
17
|
+
# config.used_names_file = File.join(
|
|
18
|
+
# ENV.fetch('XDG_STATE_HOME', File.expand_path('~/.local/state')),
|
|
19
|
+
# 'rails-worktrees',
|
|
20
|
+
# 'used-names.tsv'
|
|
21
|
+
# )
|
|
22
|
+
end
|
|
19
23
|
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
require 'open3'
|
|
2
|
+
require 'fileutils'
|
|
2
3
|
|
|
3
4
|
module Rails
|
|
4
5
|
module Worktrees
|
|
5
6
|
class Command
|
|
6
7
|
# Shell-level git helpers, branch/worktree queries, and worktree creation.
|
|
8
|
+
# rubocop:disable Metrics/ModuleLength
|
|
7
9
|
module GitOperations
|
|
8
10
|
private
|
|
9
11
|
|
|
@@ -40,14 +42,18 @@ module Rails
|
|
|
40
42
|
worktree_branch_checked_out?(branch_name, worktree_list_output)
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
def current_checkout_branch
|
|
46
|
+
git_capture('branch', '--show-current', allow_failure: true).strip
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
def registered_worktree_path?(target_dir)
|
|
44
50
|
normalized_target = canonical_path(target_dir)
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
worktree_entries.any? { |entry| entry[:path] == normalized_target }
|
|
53
|
+
end
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
def worktree_entries_for_branch(branch_name)
|
|
56
|
+
worktree_entries.select { |entry| entry[:branch_name] == branch_name }
|
|
51
57
|
end
|
|
52
58
|
|
|
53
59
|
def worktree_branch_checked_out?(branch_name, output)
|
|
@@ -59,11 +65,38 @@ module Rails
|
|
|
59
65
|
|
|
60
66
|
def worktree_list_output = git_capture('worktree', 'list', '--porcelain')
|
|
61
67
|
|
|
68
|
+
def worktree_entries
|
|
69
|
+
worktree_list_output.split("\n\n").filter_map do |block|
|
|
70
|
+
entry = parse_worktree_entry(block)
|
|
71
|
+
entry[:path] ? entry : nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_worktree_entry(block)
|
|
76
|
+
entry = { branch_name: nil }
|
|
77
|
+
|
|
78
|
+
block.each_line(chomp: true) do |line|
|
|
79
|
+
case line
|
|
80
|
+
when /\Aworktree /
|
|
81
|
+
entry[:path] = canonical_path(line.delete_prefix('worktree '))
|
|
82
|
+
when %r{\Abranch refs/heads/}
|
|
83
|
+
entry[:branch_name] = line.delete_prefix('branch refs/heads/')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
entry
|
|
88
|
+
end
|
|
89
|
+
|
|
62
90
|
def canonical_path(path)
|
|
63
91
|
File.realpath(path)
|
|
64
92
|
rescue Errno::ENOENT then File.expand_path(path)
|
|
65
93
|
end
|
|
66
94
|
|
|
95
|
+
def branch_merged_into_default?(branch_name)
|
|
96
|
+
default_branch = resolve_default_branch
|
|
97
|
+
git_success?('merge-base', '--is-ancestor', branch_name, "origin/#{default_branch}")
|
|
98
|
+
end
|
|
99
|
+
|
|
67
100
|
def create_new_branch_worktree(branch_name, target_dir)
|
|
68
101
|
default_branch = resolve_default_branch
|
|
69
102
|
if dry_run?
|
|
@@ -101,6 +134,33 @@ module Rails
|
|
|
101
134
|
git!('worktree', 'remove', '--force', target_dir)
|
|
102
135
|
end
|
|
103
136
|
|
|
137
|
+
def remove_target_path(target_dir)
|
|
138
|
+
if registered_worktree_path?(target_dir)
|
|
139
|
+
remove_registered_worktree(target_dir)
|
|
140
|
+
else
|
|
141
|
+
info("Removing existing target path '#{target_dir}'")
|
|
142
|
+
FileUtils.rm_rf(target_dir)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def delete_local_branch(branch_name, force: false)
|
|
147
|
+
ensure_local_branch_removable!(branch_name, force: force)
|
|
148
|
+
|
|
149
|
+
info("Deleting local branch '#{branch_name}'")
|
|
150
|
+
git!('branch', force ? '-D' : '-d', branch_name)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def ensure_local_branch_removable!(branch_name, force: false)
|
|
154
|
+
return if force
|
|
155
|
+
|
|
156
|
+
default_branch = resolve_default_branch
|
|
157
|
+
return if branch_merged_into_default?(branch_name)
|
|
158
|
+
|
|
159
|
+
raise Error,
|
|
160
|
+
"Local branch '#{branch_name}' is not merged into origin/#{default_branch}. " \
|
|
161
|
+
'Re-run with --force to delete it.'
|
|
162
|
+
end
|
|
163
|
+
|
|
104
164
|
def git!(*)
|
|
105
165
|
stdout_str, stderr_str, status = Open3.capture3(@env.to_h, 'git', *, chdir: @cwd)
|
|
106
166
|
return stdout_str if status.success?
|
|
@@ -137,6 +197,7 @@ module Rails
|
|
|
137
197
|
git!('worktree', 'add', '--force', target_dir, branch_name)
|
|
138
198
|
end
|
|
139
199
|
end
|
|
200
|
+
# rubocop:enable Metrics/ModuleLength
|
|
140
201
|
end
|
|
141
202
|
end
|
|
142
203
|
end
|
|
@@ -2,6 +2,7 @@ module Rails
|
|
|
2
2
|
module Worktrees
|
|
3
3
|
class Command
|
|
4
4
|
# User-facing output, prompts, and help text.
|
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
|
5
6
|
module Output
|
|
6
7
|
private
|
|
7
8
|
|
|
@@ -26,16 +27,22 @@ module Rails
|
|
|
26
27
|
def usage
|
|
27
28
|
<<~USAGE
|
|
28
29
|
wt #{::Rails::Worktrees::VERSION}
|
|
29
|
-
Create Git worktrees for the current repository.
|
|
30
|
+
Create and clean up Git worktrees for the current repository.
|
|
30
31
|
|
|
31
32
|
Usage: wt [worktree-name]
|
|
32
33
|
wt --dry-run [worktree-name]
|
|
33
34
|
wt --print-env <worktree-name>
|
|
35
|
+
wt doctor
|
|
36
|
+
wt update [--dry-run]
|
|
37
|
+
wt remove [--dry-run] [--force] <worktree-name>
|
|
38
|
+
wt delete [--dry-run] [--force] <worktree-name>
|
|
39
|
+
wt prune [--dry-run]
|
|
34
40
|
|
|
35
41
|
Options:
|
|
36
42
|
-h, --help Show this help message
|
|
37
43
|
-v, --version Show the script version
|
|
38
|
-
--dry-run [name] Preview
|
|
44
|
+
--dry-run [name] Preview worktree creation or cleanup without changing anything
|
|
45
|
+
--force Delete an unmerged local branch with wt remove/delete
|
|
39
46
|
--env, --print-env <name> Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
40
47
|
|
|
41
48
|
Quick start:
|
|
@@ -43,12 +50,21 @@ module Rails
|
|
|
43
50
|
wt my-feature Use an explicit worktree name
|
|
44
51
|
wt --dry-run my-feature
|
|
45
52
|
wt --print-env my-feature
|
|
53
|
+
wt doctor
|
|
54
|
+
wt update --dry-run
|
|
55
|
+
wt remove my-feature
|
|
56
|
+
wt remove --force my-feature
|
|
57
|
+
wt prune
|
|
46
58
|
|
|
47
59
|
How it works:
|
|
48
60
|
- by default creates worktrees beside the repo in ../<project>.worktrees/<name>
|
|
49
61
|
- when workspace_root or WT_WORKSPACES_ROOT is set, creates worktrees in <root>/<project>/<name>
|
|
50
62
|
- always uses the branch name #{@configuration.branch_prefix}/<name>
|
|
51
63
|
- bases new branches on the repository's origin default branch
|
|
64
|
+
- wt doctor audits install/config drift plus basic worktree health without changing files
|
|
65
|
+
- wt update applies safe file-based fixes for managed installer artifacts and config hints
|
|
66
|
+
- wt remove/delete can run from the main checkout or any sibling worktree, but never remove the worktree you're currently in
|
|
67
|
+
- wt prune removes merged worktrees created by wt while skipping the main checkout and the checkout you're in
|
|
52
68
|
- auto-discovers bundled *.txt files from #{@configuration.name_sources_path}
|
|
53
69
|
- retires bundled names in #{@configuration.used_names_file}
|
|
54
70
|
USAGE
|
|
@@ -75,7 +91,7 @@ module Rails
|
|
|
75
91
|
@stdout.puts("→ #{message}")
|
|
76
92
|
end
|
|
77
93
|
|
|
78
|
-
def announce_dry_run = info('Dry run: previewing worktree
|
|
94
|
+
def announce_dry_run = info('Dry run: previewing worktree changes without applying them')
|
|
79
95
|
|
|
80
96
|
def complete_dry_run(context, env_values:)
|
|
81
97
|
success('Dry run complete')
|
|
@@ -90,6 +106,76 @@ module Rails
|
|
|
90
106
|
complete_dry_run(context, env_values: preview_result&.values)
|
|
91
107
|
end
|
|
92
108
|
|
|
109
|
+
def complete_remove_dry_run(context, worktree_exists:, branch_exists:)
|
|
110
|
+
info(remove_dry_run_target_message(context, worktree_exists: worktree_exists))
|
|
111
|
+
info(remove_dry_run_branch_message(context, branch_exists: branch_exists))
|
|
112
|
+
|
|
113
|
+
success('Dry run complete')
|
|
114
|
+
print_context_summary(context, env_values: nil)
|
|
115
|
+
info('No changes were made.')
|
|
116
|
+
0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def remove_dry_run_target_message(context, worktree_exists:)
|
|
120
|
+
return "Would skip worktree removal because '#{context[:target_dir]}' does not exist" unless worktree_exists
|
|
121
|
+
|
|
122
|
+
action = if registered_worktree_path?(context[:target_dir])
|
|
123
|
+
'remove registered worktree at'
|
|
124
|
+
else
|
|
125
|
+
'remove existing target path'
|
|
126
|
+
end
|
|
127
|
+
"Would #{action} '#{context[:target_dir]}'"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def remove_dry_run_branch_message(context, branch_exists:)
|
|
131
|
+
unless branch_exists
|
|
132
|
+
return "Would skip local branch deletion because '#{context[:branch_name]}' does not exist"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
"Would delete local branch '#{context[:branch_name]}'"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def print_prune_candidates(candidates)
|
|
139
|
+
candidates.each do |context|
|
|
140
|
+
info("#{context[:worktree_name]} => #{context[:target_dir]} (#{context[:branch_name]})")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def complete_prune_noop
|
|
145
|
+
info('No merged worktrees created by wt are ready to prune.')
|
|
146
|
+
0
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def complete_prune_dry_run(candidates)
|
|
150
|
+
info("Would prune #{candidates.length} merged worktree#{'s' unless candidates.length == 1}")
|
|
151
|
+
success('Dry run complete')
|
|
152
|
+
info('No changes were made.')
|
|
153
|
+
0
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
157
|
+
def print_doctor_report(checks)
|
|
158
|
+
checks.each do |check|
|
|
159
|
+
printer = check.ok? ? :info : :warning
|
|
160
|
+
send(printer, "#{check.category}: #{check.headline}")
|
|
161
|
+
Array(check.messages).each { |message| info(message) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
fixable_count = checks.count(&:fixable?)
|
|
165
|
+
warning_count = checks.count(&:warning?)
|
|
166
|
+
|
|
167
|
+
if fixable_count.zero? && warning_count.zero?
|
|
168
|
+
success('Doctor found no issues.')
|
|
169
|
+
else
|
|
170
|
+
warning(
|
|
171
|
+
"Doctor found #{fixable_count} fixable issue#{'s' unless fixable_count == 1} and " \
|
|
172
|
+
"#{warning_count} warning#{'s' unless warning_count == 1}."
|
|
173
|
+
)
|
|
174
|
+
info('Run `wt update --dry-run` to preview safe fixes.') if fixable_count.positive?
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
178
|
+
|
|
93
179
|
def warning(message)
|
|
94
180
|
@stderr.puts("⚠️ #{message}")
|
|
95
181
|
end
|
|
@@ -124,6 +210,7 @@ module Rails
|
|
|
124
210
|
@stdout.puts("Suffix: #{suffix}")
|
|
125
211
|
end
|
|
126
212
|
end
|
|
213
|
+
# rubocop:enable Metrics/ModuleLength
|
|
127
214
|
end
|
|
128
215
|
end
|
|
129
216
|
end
|
|
@@ -5,6 +5,28 @@ module Rails
|
|
|
5
5
|
module WorkspacePaths
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
+
def resolve_repository_context
|
|
9
|
+
current_root = canonical_path(git_capture('rev-parse', '--show-toplevel').strip)
|
|
10
|
+
common_dir = expand_git_path(git_capture('rev-parse', '--git-common-dir').strip)
|
|
11
|
+
primary_root = primary_checkout_root_for(current_root, common_dir)
|
|
12
|
+
|
|
13
|
+
repository_context_for(current_root, primary_root)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def repository_context_for(current_root, primary_root)
|
|
17
|
+
project_name = File.basename(primary_root)
|
|
18
|
+
workspaces = resolve_workspaces(primary_root, project_name)
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
current_root: current_root,
|
|
22
|
+
primary_root: primary_root,
|
|
23
|
+
project_name: project_name,
|
|
24
|
+
workspaces: workspaces,
|
|
25
|
+
workspaces_root: workspaces[:root],
|
|
26
|
+
uses_default_workspace_root: workspaces[:uses_default_root]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
8
30
|
def resolve_workspaces(repo_root, project_name)
|
|
9
31
|
explicit_root = configured_workspaces_root
|
|
10
32
|
return { root: explicit_root, uses_default_root: false } if explicit_root
|
|
@@ -38,6 +60,18 @@ module Rails
|
|
|
38
60
|
FileUtils.mkdir_p(parent_dir)
|
|
39
61
|
end
|
|
40
62
|
|
|
63
|
+
def primary_checkout_root_for(current_root, common_dir)
|
|
64
|
+
return current_root unless File.basename(common_dir) == '.git'
|
|
65
|
+
|
|
66
|
+
canonical_path(File.dirname(common_dir))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def expand_git_path(path)
|
|
70
|
+
return path if path.start_with?('/')
|
|
71
|
+
|
|
72
|
+
File.expand_path(path, @cwd)
|
|
73
|
+
end
|
|
74
|
+
|
|
41
75
|
def present_path?(path)
|
|
42
76
|
!path.nil? && !path.empty?
|
|
43
77
|
end
|