rails-worktrees 0.4.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 +12 -9
- data/README.md +20 -1
- data/lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt +19 -15
- data/lib/rails/worktrees/command/output.rb +29 -0
- data/lib/rails/worktrees/command.rb +211 -0
- 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,17 @@
|
|
|
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
|
+
|
|
3
15
|
## [0.4.0](https://github.com/asjer/rails-worktrees/compare/v0.3.0...v0.4.0) (2026-03-30)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -52,12 +64,3 @@
|
|
|
52
64
|
### Features
|
|
53
65
|
|
|
54
66
|
* add wt command and rails installer ([5d798d5](https://github.com/asjer/rails-worktrees/commit/5d798d5129585331780f0259b39061194feb66e3))
|
|
55
|
-
|
|
56
|
-
## [Unreleased]
|
|
57
|
-
|
|
58
|
-
- Add a gem-managed `wt` CLI for creating Rails worktrees.
|
|
59
|
-
- Add an optional gem-managed `ob` CLI plus generated `bin/ob` wrapper for opening `localhost:$DEV_PORT` routes.
|
|
60
|
-
- Add a Rails installer generator that creates `bin/wt` and `config/initializers/rails_worktrees.rb`.
|
|
61
|
-
- Add conservative `config/database.yml` patching for common development/test database names.
|
|
62
|
-
- Add a manual-dispatch GitHub Actions workflow for the disposable Rails smoke test.
|
|
63
|
-
- 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,9 @@ 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
|
|
45
50
|
bin/wt remove my-feature # remove a worktree and delete its local branch
|
|
46
51
|
bin/wt delete my-feature # alias for `bin/wt remove`
|
|
47
52
|
bin/wt remove --force my-feature # also delete an unmerged local branch
|
|
@@ -111,6 +116,18 @@ If you want to see what `bin/wt prune` would clean up before saying yes, use `--
|
|
|
111
116
|
bin/wt prune --dry-run
|
|
112
117
|
```
|
|
113
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
|
+
|
|
114
131
|
|
|
115
132
|
### Interactive prompts
|
|
116
133
|
|
|
@@ -131,6 +148,8 @@ Worktree names must not contain `/` or whitespace, must not be `.` or `..`, and
|
|
|
131
148
|
|
|
132
149
|
The installer generates `config/initializers/rails_worktrees.rb` where you can override:
|
|
133
150
|
|
|
151
|
+
The initializer becomes a no-op whenever `rails-worktrees` is not loaded in the current bundle groups.
|
|
152
|
+
|
|
134
153
|
| Option | Default | Description |
|
|
135
154
|
|--------|---------|-------------|
|
|
136
155
|
| `bootstrap_env` | `true` | Write `.env` when creating a worktree |
|
|
@@ -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
|
|
@@ -32,6 +32,8 @@ module Rails
|
|
|
32
32
|
Usage: wt [worktree-name]
|
|
33
33
|
wt --dry-run [worktree-name]
|
|
34
34
|
wt --print-env <worktree-name>
|
|
35
|
+
wt doctor
|
|
36
|
+
wt update [--dry-run]
|
|
35
37
|
wt remove [--dry-run] [--force] <worktree-name>
|
|
36
38
|
wt delete [--dry-run] [--force] <worktree-name>
|
|
37
39
|
wt prune [--dry-run]
|
|
@@ -48,6 +50,8 @@ module Rails
|
|
|
48
50
|
wt my-feature Use an explicit worktree name
|
|
49
51
|
wt --dry-run my-feature
|
|
50
52
|
wt --print-env my-feature
|
|
53
|
+
wt doctor
|
|
54
|
+
wt update --dry-run
|
|
51
55
|
wt remove my-feature
|
|
52
56
|
wt remove --force my-feature
|
|
53
57
|
wt prune
|
|
@@ -57,6 +61,8 @@ module Rails
|
|
|
57
61
|
- when workspace_root or WT_WORKSPACES_ROOT is set, creates worktrees in <root>/<project>/<name>
|
|
58
62
|
- always uses the branch name #{@configuration.branch_prefix}/<name>
|
|
59
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
|
|
60
66
|
- wt remove/delete can run from the main checkout or any sibling worktree, but never remove the worktree you're currently in
|
|
61
67
|
- wt prune removes merged worktrees created by wt while skipping the main checkout and the checkout you're in
|
|
62
68
|
- auto-discovers bundled *.txt files from #{@configuration.name_sources_path}
|
|
@@ -147,6 +153,29 @@ module Rails
|
|
|
147
153
|
0
|
|
148
154
|
end
|
|
149
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
|
+
|
|
150
179
|
def warning(message)
|
|
151
180
|
@stderr.puts("⚠️ #{message}")
|
|
152
181
|
end
|
|
@@ -11,6 +11,8 @@ module Rails
|
|
|
11
11
|
# rubocop:disable Metrics/ClassLength
|
|
12
12
|
class Command
|
|
13
13
|
REMOVE_SUBCOMMANDS = %w[remove delete].freeze
|
|
14
|
+
DOCTOR_SUBCOMMAND = 'doctor'.freeze
|
|
15
|
+
UPDATE_SUBCOMMAND = 'update'.freeze
|
|
14
16
|
|
|
15
17
|
include GitOperations
|
|
16
18
|
include EnvironmentSupport
|
|
@@ -69,7 +71,17 @@ module Rails
|
|
|
69
71
|
@argv.first == 'prune'
|
|
70
72
|
end
|
|
71
73
|
|
|
74
|
+
def doctor_subcommand?
|
|
75
|
+
@argv.first == DOCTOR_SUBCOMMAND
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def update_subcommand?
|
|
79
|
+
@argv.first == UPDATE_SUBCOMMAND
|
|
80
|
+
end
|
|
81
|
+
|
|
72
82
|
def execute_requested_command
|
|
83
|
+
return execute_doctor_command if doctor_subcommand?
|
|
84
|
+
return execute_update_command if update_subcommand?
|
|
73
85
|
return execute_remove_command if remove_subcommand?
|
|
74
86
|
return execute_prune_command if prune_subcommand?
|
|
75
87
|
return usage_error if @argv.length > 1 || force?
|
|
@@ -144,11 +156,71 @@ module Rails
|
|
|
144
156
|
0
|
|
145
157
|
end
|
|
146
158
|
|
|
159
|
+
# rubocop:disable Metrics/MethodLength
|
|
160
|
+
def execute_doctor_command
|
|
161
|
+
validate_doctor_args!
|
|
162
|
+
|
|
163
|
+
unless git_success?('rev-parse', '--is-inside-work-tree')
|
|
164
|
+
checks = [doctor_check(category: :git, status: :warning,
|
|
165
|
+
headline: 'Run wt doctor from inside a Git repository.')]
|
|
166
|
+
print_doctor_report(checks)
|
|
167
|
+
return 1
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
repository = resolve_repository_context
|
|
171
|
+
checks = project_maintenance_report(repository[:current_root]).checks + doctor_worktree_checks(repository)
|
|
172
|
+
print_doctor_report(checks)
|
|
173
|
+
|
|
174
|
+
checks.any? { |check| check.fixable? || check.warning? } ? 1 : 0
|
|
175
|
+
end
|
|
176
|
+
# rubocop:enable Metrics/MethodLength
|
|
177
|
+
|
|
178
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
179
|
+
def execute_update_command
|
|
180
|
+
require_git_repo
|
|
181
|
+
validate_update_args!
|
|
182
|
+
announce_dry_run if dry_run?
|
|
183
|
+
|
|
184
|
+
current_root = resolve_repository_context[:current_root]
|
|
185
|
+
report = project_maintenance_report(current_root)
|
|
186
|
+
updated_count = 0
|
|
187
|
+
identical_count = 0
|
|
188
|
+
skipped_count = 0
|
|
189
|
+
|
|
190
|
+
report.checks.each do |check|
|
|
191
|
+
if check.fixable?
|
|
192
|
+
apply_maintenance_check(check, current_root: current_root)
|
|
193
|
+
updated_count += 1
|
|
194
|
+
elsif check.ok?
|
|
195
|
+
identical_count += 1
|
|
196
|
+
info(check.headline)
|
|
197
|
+
else
|
|
198
|
+
skipped_count += 1
|
|
199
|
+
warning(check.headline)
|
|
200
|
+
check.messages.each { |message| info(message) }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
complete_update(updated_count:, identical_count:, skipped_count:)
|
|
205
|
+
end
|
|
206
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
207
|
+
|
|
147
208
|
def validate_prune_args!
|
|
148
209
|
raise Error, 'Usage: wt prune' unless @argv.length == 1
|
|
149
210
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
150
211
|
end
|
|
151
212
|
|
|
213
|
+
def validate_doctor_args!
|
|
214
|
+
raise Error, 'Usage: wt doctor' unless @argv.length == 1
|
|
215
|
+
raise Error, 'wt doctor does not support --dry-run.' if dry_run?
|
|
216
|
+
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def validate_update_args!
|
|
220
|
+
raise Error, 'Usage: wt update [--dry-run]' unless @argv.length == 1
|
|
221
|
+
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
222
|
+
end
|
|
223
|
+
|
|
152
224
|
def announce_prune_candidates(candidates)
|
|
153
225
|
info("Found #{candidates.length} merged worktree#{'s' unless candidates.length == 1} created by wt:")
|
|
154
226
|
end
|
|
@@ -343,6 +415,145 @@ module Rails
|
|
|
343
415
|
def worktree_name_for_branch(branch_name)
|
|
344
416
|
branch_name.delete_prefix("#{@configuration.branch_prefix}/")
|
|
345
417
|
end
|
|
418
|
+
|
|
419
|
+
def project_maintenance_report(root)
|
|
420
|
+
::Rails::Worktrees::ProjectMaintenance.new(root: root).call
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def doctor_worktree_checks(repository)
|
|
424
|
+
[
|
|
425
|
+
doctor_check(
|
|
426
|
+
category: :git,
|
|
427
|
+
status: :ok,
|
|
428
|
+
headline: "Git repository detected at #{repository[:current_root]}."
|
|
429
|
+
),
|
|
430
|
+
default_branch_doctor_check,
|
|
431
|
+
stale_worktree_doctor_check
|
|
432
|
+
]
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def default_branch_doctor_check
|
|
436
|
+
doctor_check(
|
|
437
|
+
category: :git,
|
|
438
|
+
status: :ok,
|
|
439
|
+
headline: "origin default branch resolves to '#{resolve_default_branch}'."
|
|
440
|
+
)
|
|
441
|
+
rescue Error => e
|
|
442
|
+
doctor_check(category: :git, status: :warning, headline: e.message)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# rubocop:disable Metrics/MethodLength
|
|
446
|
+
def stale_worktree_doctor_check
|
|
447
|
+
stale_paths = worktree_entries.reject { |entry| File.directory?(entry[:path]) }
|
|
448
|
+
if stale_paths.empty?
|
|
449
|
+
doctor_check(category: :worktree, status: :ok, headline: 'No stale registered worktree paths found.')
|
|
450
|
+
else
|
|
451
|
+
doctor_check(
|
|
452
|
+
category: :worktree,
|
|
453
|
+
status: :warning,
|
|
454
|
+
headline: "Found #{stale_paths.length} stale registered worktree " \
|
|
455
|
+
"path#{'s' unless stale_paths.length == 1}.",
|
|
456
|
+
messages: stale_paths.map { |entry| entry[:path] }
|
|
457
|
+
)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
# rubocop:enable Metrics/MethodLength
|
|
461
|
+
|
|
462
|
+
# rubocop:disable Metrics/MethodLength
|
|
463
|
+
def doctor_check(category:, status:, headline:, messages: [])
|
|
464
|
+
::Rails::Worktrees::ProjectMaintenance::Check.new(
|
|
465
|
+
identifier: nil,
|
|
466
|
+
category: category,
|
|
467
|
+
status: status,
|
|
468
|
+
headline: headline,
|
|
469
|
+
messages: messages,
|
|
470
|
+
relative_path: nil,
|
|
471
|
+
updated_content: nil,
|
|
472
|
+
make_executable: false,
|
|
473
|
+
apply_messages: []
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
# rubocop:enable Metrics/MethodLength
|
|
477
|
+
|
|
478
|
+
# rubocop:disable Metrics/AbcSize
|
|
479
|
+
def apply_maintenance_check(check, current_root:)
|
|
480
|
+
if dry_run?
|
|
481
|
+
info("Would update #{check.relative_path}")
|
|
482
|
+
Array(check.apply_messages).each { |message| info(message) }
|
|
483
|
+
return
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
path = maintenance_destination_path(check.relative_path, current_root)
|
|
487
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
488
|
+
File.write(path, check.updated_content)
|
|
489
|
+
FileUtils.chmod(0o755, path) if check.make_executable
|
|
490
|
+
|
|
491
|
+
Array(check.apply_messages).each { |message| info(message) }
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def maintenance_destination_path(relative_path, current_root)
|
|
495
|
+
root_path = File.realpath(current_root)
|
|
496
|
+
path = File.expand_path(relative_path, root_path)
|
|
497
|
+
assert_within_root!(path, root_path, "Refusing to write outside of repository root: #{path}")
|
|
498
|
+
parent_path = nearest_existing_parent(File.dirname(path))
|
|
499
|
+
assert_parent_within_root!(parent_path, root_path, path)
|
|
500
|
+
assert_not_symlink!(path)
|
|
501
|
+
path
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def assert_within_root!(path, root_path, message)
|
|
505
|
+
raise Error, message unless within_root?(path, root_path)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def assert_parent_within_root!(parent_path, root_path, path)
|
|
509
|
+
assert_within_root!(
|
|
510
|
+
parent_path,
|
|
511
|
+
root_path,
|
|
512
|
+
"Refusing to write through symlinked directory outside repository root: #{path}"
|
|
513
|
+
)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def assert_not_symlink!(path)
|
|
517
|
+
raise Error, "Refusing to overwrite symlinked path: #{path}" if File.symlink?(path)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def within_root?(path, root_path)
|
|
521
|
+
path == root_path || path.start_with?("#{root_path}#{File::SEPARATOR}")
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def nearest_existing_parent(path)
|
|
525
|
+
candidate = path
|
|
526
|
+
until File.exist?(candidate)
|
|
527
|
+
parent = File.dirname(candidate)
|
|
528
|
+
return candidate if parent == candidate
|
|
529
|
+
|
|
530
|
+
candidate = parent
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
File.realpath(candidate)
|
|
534
|
+
end
|
|
535
|
+
# rubocop:enable Metrics/AbcSize
|
|
536
|
+
|
|
537
|
+
# rubocop:disable Metrics/MethodLength
|
|
538
|
+
def complete_update(updated_count:, identical_count:, skipped_count:)
|
|
539
|
+
if dry_run?
|
|
540
|
+
success('Dry run complete')
|
|
541
|
+
info("Would update #{updated_count} file#{'s' unless updated_count == 1}.")
|
|
542
|
+
info('No changes were made.')
|
|
543
|
+
return 0
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
success('Update complete')
|
|
547
|
+
info(
|
|
548
|
+
[
|
|
549
|
+
"updated: #{updated_count}",
|
|
550
|
+
"already up to date: #{identical_count}",
|
|
551
|
+
"skipped: #{skipped_count}"
|
|
552
|
+
].join(', ')
|
|
553
|
+
)
|
|
554
|
+
0
|
|
555
|
+
end
|
|
556
|
+
# rubocop:enable Metrics/MethodLength
|
|
346
557
|
end
|
|
347
558
|
# rubocop:enable Metrics/ClassLength
|
|
348
559
|
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
module Worktrees
|
|
5
|
+
# Safely updates the generated initializer to use the current gem-loading guard.
|
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
|
7
|
+
class InitializerUpdater
|
|
8
|
+
Result = Struct.new(:content, :changed, :status, :messages) do
|
|
9
|
+
def changed?
|
|
10
|
+
changed
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
TEMPLATE_PATH = File.expand_path('../../generators/rails/worktrees/templates/rails_worktrees.rb.tt', __dir__)
|
|
15
|
+
CURRENT_GUARD_LINES = [
|
|
16
|
+
"if Gem.loaded_specs.key?('rails-worktrees') &&",
|
|
17
|
+
' defined?(Rails::Worktrees) &&',
|
|
18
|
+
' Rails::Worktrees.respond_to?(:configure)'
|
|
19
|
+
].freeze
|
|
20
|
+
KNOWN_GUARD_FRAGMENTS = [
|
|
21
|
+
"Gem.loaded_specs.key?('rails-worktrees')",
|
|
22
|
+
'defined?(Rails::Worktrees)',
|
|
23
|
+
'Rails::Worktrees.respond_to?(:configure)'
|
|
24
|
+
].freeze
|
|
25
|
+
CONFIGURE_CALL = 'Rails::Worktrees.configure do |config|'.freeze
|
|
26
|
+
LEGACY_GUARD = /\Aif defined\?\(Rails::Worktrees\)\n(?<body>.*)\nend\z/m
|
|
27
|
+
|
|
28
|
+
def self.default_content = new(content: '').send(:render_default_template)
|
|
29
|
+
|
|
30
|
+
def initialize(content:) = @content = content
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
return identical_result if current_guard_present?
|
|
34
|
+
return updated_result(self.class.default_content) if blank_content?
|
|
35
|
+
|
|
36
|
+
body = wrapped_body
|
|
37
|
+
return skip_result unless body
|
|
38
|
+
|
|
39
|
+
updated_result(rebuild_content(body))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def identical_result
|
|
45
|
+
Result.new(
|
|
46
|
+
@content,
|
|
47
|
+
false,
|
|
48
|
+
:identical,
|
|
49
|
+
['config/initializers/rails_worktrees.rb already uses the current safety guard.']
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def updated_result(content)
|
|
54
|
+
Result.new(
|
|
55
|
+
content,
|
|
56
|
+
content != @content,
|
|
57
|
+
:updated,
|
|
58
|
+
['Updated config/initializers/rails_worktrees.rb to use the current safety guard.']
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def skip_result
|
|
63
|
+
Result.new(
|
|
64
|
+
@content,
|
|
65
|
+
false,
|
|
66
|
+
:skip,
|
|
67
|
+
['config/initializers/rails_worktrees.rb is too custom to update automatically; review it manually.']
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def blank_content? = @content.to_s.strip.empty?
|
|
72
|
+
|
|
73
|
+
def current_guard_present?
|
|
74
|
+
!extract_known_guard_body(@content.to_s.strip.lines, required_guard_lines: CURRENT_GUARD_LINES).nil?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def wrapped_body = extract_existing_body&.then { |body| normalize_body(body) }
|
|
78
|
+
|
|
79
|
+
def extract_existing_body
|
|
80
|
+
stripped = @content.to_s.strip
|
|
81
|
+
return if stripped.empty?
|
|
82
|
+
|
|
83
|
+
return Regexp.last_match[:body] if stripped.match(LEGACY_GUARD)
|
|
84
|
+
|
|
85
|
+
body = extract_known_guard_body(stripped.lines)
|
|
86
|
+
return body if body
|
|
87
|
+
|
|
88
|
+
body = extract_plain_configure_body(stripped.lines)
|
|
89
|
+
return body if body
|
|
90
|
+
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
95
|
+
def extract_known_guard_body(lines, required_guard_lines: nil)
|
|
96
|
+
return if lines.empty? || lines.last.strip != 'end'
|
|
97
|
+
|
|
98
|
+
configure_index = lines.index { |line| line.strip == CONFIGURE_CALL }
|
|
99
|
+
return unless configure_index
|
|
100
|
+
|
|
101
|
+
guard_lines = lines[0...configure_index].reject { |line| line.strip.empty? }.map(&:rstrip)
|
|
102
|
+
return unless guard_lines.all? { |line| known_guard_line?(line) }
|
|
103
|
+
return if required_guard_lines && guard_lines != required_guard_lines
|
|
104
|
+
|
|
105
|
+
lines[configure_index...-1].join.rstrip
|
|
106
|
+
end
|
|
107
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
108
|
+
|
|
109
|
+
def extract_plain_configure_body(lines)
|
|
110
|
+
return unless lines.first&.strip == CONFIGURE_CALL && lines.last&.strip == 'end'
|
|
111
|
+
|
|
112
|
+
lines.join.rstrip
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def known_guard_line?(line)
|
|
116
|
+
stripped = line.strip
|
|
117
|
+
return true if stripped.empty?
|
|
118
|
+
|
|
119
|
+
normalized = stripped.delete_suffix('&&').strip.sub(/\Aif\s+/, '')
|
|
120
|
+
KNOWN_GUARD_FRAGMENTS.include?(normalized)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def normalize_body(body)
|
|
124
|
+
body_lines = body.rstrip.lines
|
|
125
|
+
return '' if body_lines.empty?
|
|
126
|
+
|
|
127
|
+
return body.rstrip if body_lines.first.start_with?(' ')
|
|
128
|
+
|
|
129
|
+
body_lines.map { |line| line.strip.empty? ? line : " #{line}" }.join.rstrip
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def rebuild_content(body)
|
|
133
|
+
content = [CURRENT_GUARD_LINES.join("\n"), body, 'end'].join("\n")
|
|
134
|
+
@content.end_with?("\n") || @content.empty? ? "#{content}\n" : content
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def render_default_template
|
|
138
|
+
ERB.new(File.read(TEMPLATE_PATH), trim_mode: '-').result(template_context.instance_eval { binding })
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def template_context
|
|
142
|
+
Struct.new(:options, :conductor_workspace_root).new({ 'conductor' => false },
|
|
143
|
+
"File.expand_path('~/Sites/conductor/workspaces')")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
# rubocop:enable Metrics/ClassLength
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Worktrees
|
|
3
|
+
# Audits and prepares safe file-based maintenance updates for the current checkout.
|
|
4
|
+
# rubocop:disable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize
|
|
5
|
+
class ProjectMaintenance
|
|
6
|
+
# rubocop:disable Style/RedundantStructKeywordInit
|
|
7
|
+
Check = Struct.new(
|
|
8
|
+
:identifier,
|
|
9
|
+
:category,
|
|
10
|
+
:headline,
|
|
11
|
+
:messages,
|
|
12
|
+
:relative_path,
|
|
13
|
+
:status,
|
|
14
|
+
:updated_content,
|
|
15
|
+
:make_executable,
|
|
16
|
+
:apply_messages,
|
|
17
|
+
keyword_init: true
|
|
18
|
+
) do
|
|
19
|
+
def ok?
|
|
20
|
+
status == :ok
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fixable?
|
|
24
|
+
status == :fixable
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warning?
|
|
28
|
+
status == :warning
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def updatable?
|
|
32
|
+
!updated_content.nil?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Report = Struct.new(:checks, keyword_init: true) do
|
|
37
|
+
def fixable_checks
|
|
38
|
+
checks.select(&:fixable?)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def warning_checks
|
|
42
|
+
checks.select(&:warning?)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ok?
|
|
46
|
+
fixable_checks.empty? && warning_checks.empty?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
# rubocop:enable Style/RedundantStructKeywordInit
|
|
50
|
+
|
|
51
|
+
TEMPLATE_ROOT = File.expand_path('../../generators/rails/worktrees/templates', __dir__)
|
|
52
|
+
|
|
53
|
+
def initialize(root:)
|
|
54
|
+
@root = root
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def call
|
|
58
|
+
Report.new(checks: maintenance_checks.compact)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
attr_reader :root
|
|
64
|
+
|
|
65
|
+
def maintenance_checks
|
|
66
|
+
[
|
|
67
|
+
template_file_check(wrapper_config),
|
|
68
|
+
initializer_check,
|
|
69
|
+
database_check,
|
|
70
|
+
optional_updater_check(procfile_config) { |content| ProcfileUpdater.new(content: content).call },
|
|
71
|
+
optional_updater_check(puma_config) { |content| PumaConfigUpdater.new(content: content).call },
|
|
72
|
+
mise_check,
|
|
73
|
+
template_file_check(browser_wrapper_config)
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def wrapper_config
|
|
78
|
+
{
|
|
79
|
+
identifier: :bin_wt,
|
|
80
|
+
category: :install,
|
|
81
|
+
relative_path: 'bin/wt',
|
|
82
|
+
template_path: File.join(TEMPLATE_ROOT, 'bin/wt'),
|
|
83
|
+
make_executable: true,
|
|
84
|
+
optional: false
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def browser_wrapper_config
|
|
89
|
+
{
|
|
90
|
+
identifier: :bin_ob,
|
|
91
|
+
category: :install,
|
|
92
|
+
relative_path: 'bin/ob',
|
|
93
|
+
template_path: File.join(TEMPLATE_ROOT, 'bin/ob'),
|
|
94
|
+
make_executable: true,
|
|
95
|
+
optional: true
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def template_file_check(config)
|
|
100
|
+
path = absolute_path(config.fetch(:relative_path))
|
|
101
|
+
return nil if config.fetch(:optional) && !File.exist?(path)
|
|
102
|
+
|
|
103
|
+
desired_content = File.read(config.fetch(:template_path))
|
|
104
|
+
return template_missing_check(config, desired_content) unless File.exist?(path)
|
|
105
|
+
|
|
106
|
+
current_content = File.read(path)
|
|
107
|
+
if current_content == desired_content
|
|
108
|
+
if executable_mode_current?(config, path)
|
|
109
|
+
return ok_check(config, "#{config.fetch(:relative_path)} is up to date.")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
return executable_permission_check(config, desired_content)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
fixable_check(
|
|
116
|
+
config,
|
|
117
|
+
"#{config.fetch(:relative_path)} differs from the managed template.",
|
|
118
|
+
updated_content: desired_content,
|
|
119
|
+
apply_messages: ["Updated #{config.fetch(:relative_path)} to match the managed template."],
|
|
120
|
+
make_executable: config.fetch(:make_executable, false)
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def executable_mode_current?(config, path)
|
|
125
|
+
!config.fetch(:make_executable, false) || File.executable?(path)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def executable_permission_check(config, desired_content)
|
|
129
|
+
relative_path = config.fetch(:relative_path)
|
|
130
|
+
fixable_check(
|
|
131
|
+
config,
|
|
132
|
+
"#{relative_path} needs its executable bit restored.",
|
|
133
|
+
updated_content: desired_content,
|
|
134
|
+
apply_messages: ["Restored executable permissions on #{relative_path}."],
|
|
135
|
+
make_executable: true
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def template_missing_check(config, desired_content)
|
|
140
|
+
fixable_check(
|
|
141
|
+
config,
|
|
142
|
+
"#{config.fetch(:relative_path)} is missing.",
|
|
143
|
+
updated_content: desired_content,
|
|
144
|
+
apply_messages: ["Created #{config.fetch(:relative_path)} from the managed template."],
|
|
145
|
+
make_executable: config.fetch(:make_executable, false)
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def initializer_check
|
|
150
|
+
config = {
|
|
151
|
+
identifier: :initializer,
|
|
152
|
+
category: :install,
|
|
153
|
+
relative_path: 'config/initializers/rails_worktrees.rb',
|
|
154
|
+
identical_headline: 'config/initializers/rails_worktrees.rb already uses the current safety guard.',
|
|
155
|
+
fixable_headline: 'config/initializers/rails_worktrees.rb can be updated automatically.',
|
|
156
|
+
warning_headline: 'config/initializers/rails_worktrees.rb needs manual review.'
|
|
157
|
+
}
|
|
158
|
+
path = absolute_path(config.fetch(:relative_path))
|
|
159
|
+
|
|
160
|
+
unless File.exist?(path)
|
|
161
|
+
return fixable_check(
|
|
162
|
+
config,
|
|
163
|
+
"#{config.fetch(:relative_path)} is missing.",
|
|
164
|
+
updated_content: InitializerUpdater.default_content,
|
|
165
|
+
apply_messages: ['Created config/initializers/rails_worktrees.rb with the current safety guard.']
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
updater_result_check(config, InitializerUpdater.new(content: File.read(path)).call)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def database_check
|
|
173
|
+
config = {
|
|
174
|
+
identifier: :database,
|
|
175
|
+
category: :config,
|
|
176
|
+
relative_path: 'config/database.yml'
|
|
177
|
+
}
|
|
178
|
+
path = absolute_path(config.fetch(:relative_path))
|
|
179
|
+
|
|
180
|
+
unless File.exist?(path)
|
|
181
|
+
return warning_check(
|
|
182
|
+
config,
|
|
183
|
+
'config/database.yml is missing.',
|
|
184
|
+
['Add WORKTREE_DATABASE_SUFFIX support manually if this app uses a custom database setup.']
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
result = DatabaseConfigUpdater.new(content: File.read(path)).call
|
|
189
|
+
return updated_database_check(config, result) if result.changed?
|
|
190
|
+
|
|
191
|
+
if database_configured?(result)
|
|
192
|
+
return ok_check(
|
|
193
|
+
config,
|
|
194
|
+
'config/database.yml already includes WORKTREE_DATABASE_SUFFIX in supported entries.'
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
warning_check(
|
|
199
|
+
config,
|
|
200
|
+
result.messages.first || 'config/database.yml needs manual review.',
|
|
201
|
+
result.messages.drop(1)
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def updated_database_check(config, result)
|
|
206
|
+
fixable_check(
|
|
207
|
+
config,
|
|
208
|
+
'config/database.yml can be updated automatically.',
|
|
209
|
+
messages: result.messages.drop(1),
|
|
210
|
+
updated_content: result.content,
|
|
211
|
+
apply_messages: result.messages
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def database_configured?(result)
|
|
216
|
+
result.messages.first == 'Development/test database names already include WORKTREE_DATABASE_SUFFIX.'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def procfile_config
|
|
220
|
+
{
|
|
221
|
+
identifier: :procfile,
|
|
222
|
+
category: :config,
|
|
223
|
+
relative_path: 'Procfile.dev',
|
|
224
|
+
identical_headline: 'Procfile.dev already uses the DEV_PORT-aware web entry.',
|
|
225
|
+
fixable_headline: 'Procfile.dev can be updated automatically.',
|
|
226
|
+
warning_headline: 'Procfile.dev needs manual review.'
|
|
227
|
+
}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def puma_config
|
|
231
|
+
{
|
|
232
|
+
identifier: :puma,
|
|
233
|
+
category: :config,
|
|
234
|
+
relative_path: 'config/puma.rb',
|
|
235
|
+
identical_headline: 'config/puma.rb already prefers DEV_PORT.',
|
|
236
|
+
fixable_headline: 'config/puma.rb can be updated automatically.',
|
|
237
|
+
warning_headline: 'config/puma.rb needs manual review.'
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def optional_updater_check(config)
|
|
242
|
+
path = absolute_path(config.fetch(:relative_path))
|
|
243
|
+
return unless File.exist?(path)
|
|
244
|
+
|
|
245
|
+
updater_result_check(config, yield(File.read(path)))
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def mise_check
|
|
249
|
+
relative_path = %w[mise.toml .mise.toml].find { |candidate| File.exist?(absolute_path(candidate)) }
|
|
250
|
+
return unless relative_path
|
|
251
|
+
|
|
252
|
+
config = {
|
|
253
|
+
identifier: :mise,
|
|
254
|
+
category: :config,
|
|
255
|
+
relative_path: relative_path,
|
|
256
|
+
identical_headline: "#{relative_path} already loads .env from [env].",
|
|
257
|
+
fixable_headline: "#{relative_path} can be updated automatically.",
|
|
258
|
+
warning_headline: "#{relative_path} needs manual review."
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
updater_result_check(config, mise_updater_result(relative_path))
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def mise_updater_result(relative_path)
|
|
265
|
+
MiseTomlUpdater.new(
|
|
266
|
+
content: File.read(absolute_path(relative_path)),
|
|
267
|
+
file_name: File.basename(relative_path)
|
|
268
|
+
).call
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def updater_result_check(config, result)
|
|
272
|
+
case result.status
|
|
273
|
+
when :identical
|
|
274
|
+
ok_check(config, config.fetch(:identical_headline))
|
|
275
|
+
when :updated
|
|
276
|
+
fixable_check(
|
|
277
|
+
config,
|
|
278
|
+
config.fetch(:fixable_headline),
|
|
279
|
+
updated_content: result.content,
|
|
280
|
+
apply_messages: result.messages
|
|
281
|
+
)
|
|
282
|
+
else
|
|
283
|
+
warning_check(config, config.fetch(:warning_headline), result.messages)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def ok_check(config, headline)
|
|
288
|
+
build_check(config, status: :ok, headline: headline)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def warning_check(config, headline, messages = [])
|
|
292
|
+
build_check(config, status: :warning, headline: headline, messages: messages)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def fixable_check(config, headline, updated_content:, apply_messages:, **attributes)
|
|
296
|
+
build_check(
|
|
297
|
+
config,
|
|
298
|
+
status: :fixable,
|
|
299
|
+
headline: headline,
|
|
300
|
+
updated_content: updated_content,
|
|
301
|
+
apply_messages: apply_messages,
|
|
302
|
+
**attributes
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def build_check(config, status:, headline:, **attributes)
|
|
307
|
+
Check.new(
|
|
308
|
+
identifier: config.fetch(:identifier),
|
|
309
|
+
category: config.fetch(:category),
|
|
310
|
+
relative_path: config.fetch(:relative_path),
|
|
311
|
+
status: status,
|
|
312
|
+
headline: headline,
|
|
313
|
+
messages: attributes.fetch(:messages, []),
|
|
314
|
+
updated_content: attributes.fetch(:updated_content, nil),
|
|
315
|
+
make_executable: attributes.fetch(:make_executable, false),
|
|
316
|
+
apply_messages: attributes.fetch(:apply_messages, [])
|
|
317
|
+
)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def absolute_path(relative_path)
|
|
321
|
+
File.join(root, relative_path)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
# rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize
|
|
325
|
+
end
|
|
326
|
+
end
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -7,9 +7,11 @@ require_relative 'worktrees/command'
|
|
|
7
7
|
require_relative 'worktrees/cli'
|
|
8
8
|
require_relative 'worktrees/browser_command'
|
|
9
9
|
require_relative 'worktrees/database_config_updater'
|
|
10
|
+
require_relative 'worktrees/initializer_updater'
|
|
10
11
|
require_relative 'worktrees/procfile_updater'
|
|
11
12
|
require_relative 'worktrees/mise_toml_updater'
|
|
12
13
|
require_relative 'worktrees/puma_config_updater'
|
|
14
|
+
require_relative 'worktrees/project_maintenance'
|
|
13
15
|
|
|
14
16
|
module Rails
|
|
15
17
|
# Rails-specific git worktree helpers and installer support.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-worktrees
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asjer Querido
|
|
@@ -66,9 +66,11 @@ files:
|
|
|
66
66
|
- lib/rails/worktrees/configuration.rb
|
|
67
67
|
- lib/rails/worktrees/database_config_updater.rb
|
|
68
68
|
- lib/rails/worktrees/env_bootstrapper.rb
|
|
69
|
+
- lib/rails/worktrees/initializer_updater.rb
|
|
69
70
|
- lib/rails/worktrees/mise_toml_updater.rb
|
|
70
71
|
- lib/rails/worktrees/names/cities.txt
|
|
71
72
|
- lib/rails/worktrees/procfile_updater.rb
|
|
73
|
+
- lib/rails/worktrees/project_maintenance.rb
|
|
72
74
|
- lib/rails/worktrees/puma_config_updater.rb
|
|
73
75
|
- lib/rails/worktrees/railtie.rb
|
|
74
76
|
- lib/rails/worktrees/version.rb
|