rails-worktrees 0.6.0 → 0.7.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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +14 -0
- data/README.md +33 -1
- data/lib/generators/rails/worktrees/mise_follow_up.rb +3 -1
- data/lib/generators/rails/worktrees/puma_follow_up.rb +5 -1
- data/lib/generators/rails/worktrees/templates/bin/wt +64 -4
- data/lib/generators/worktrees/install/install_generator.rb +17 -5
- data/lib/rails/worktrees/browser_command.rb +2 -1
- data/lib/rails/worktrees/cli.rb +22 -2
- data/lib/rails/worktrees/command/environment_support.rb +1 -0
- data/lib/rails/worktrees/command/git_operations.rb +41 -3
- data/lib/rails/worktrees/command/name_picking.rb +4 -2
- data/lib/rails/worktrees/command/output.rb +9 -0
- data/lib/rails/worktrees/command/post_create_support.rb +7 -6
- data/lib/rails/worktrees/command/workspace_paths.rb +15 -3
- data/lib/rails/worktrees/command.rb +206 -15
- data/lib/rails/worktrees/env_bootstrapper.rb +54 -7
- data/lib/rails/worktrees/initializer_updater.rb +4 -1
- data/lib/rails/worktrees/mise_environment.rb +87 -0
- data/lib/rails/worktrees/post_create_runner.rb +31 -7
- data/lib/rails/worktrees/project_configuration_loader.rb +1 -1
- data/lib/rails/worktrees/project_maintenance.rb +11 -6
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 36b6c2a6e2e481803b035ba28c575c1619ba5fb1d158f0e4c6e4180d69aeafee
|
|
4
|
+
data.tar.gz: 9265e0459e03d34cbc4962b181bdd62836ddb4433c392d8aac43b65f445992a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cfecdcffdaeb1e229332c1a8a20ed80f2600886a58deb4f2f9b186c70957f2b70224a950e1c9109b3d9d234db3c18fe7789074ba1109e58d6e1da35236e2923
|
|
7
|
+
data.tar.gz: 977ebc76e219d9e31d05595852ab830355c6ca30f2617d2ead869b4d5ccce18b0bbc0360dd4e533aff6ffab53c7eee7c20480ec2f380881c69d5387e859526b0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.7.1"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.1](https://github.com/asjer/rails-worktrees/compare/v0.7.0...v0.7.1) (2026-07-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **wt:** bootstrap mise before bundler ([24720a1](https://github.com/asjer/rails-worktrees/commit/24720a1cf51ce55676277c478f3b86bbb98e72eb))
|
|
9
|
+
|
|
10
|
+
## [0.7.0](https://github.com/asjer/rails-worktrees/compare/v0.6.0...v0.7.0) (2026-04-05)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **wt:** add rerunnable setup workflow ([a609fcc](https://github.com/asjer/rails-worktrees/commit/a609fcc3c463da1d2ad9922b620f9c517dc6ed74))
|
|
16
|
+
|
|
3
17
|
## [0.6.0](https://github.com/asjer/rails-worktrees/compare/v0.5.1...v0.6.0) (2026-04-03)
|
|
4
18
|
|
|
5
19
|
|
data/README.md
CHANGED
|
@@ -42,8 +42,12 @@ With `--yolo`, the installer also:
|
|
|
42
42
|
```bash
|
|
43
43
|
bin/wt # auto-pick a name from bundled *.txt lists
|
|
44
44
|
bin/wt my-feature # use an explicit worktree name
|
|
45
|
+
bin/wt --skip-setup my-feature # create now, run setup later
|
|
45
46
|
bin/wt --dry-run my-feature # preview the full setup without changing anything
|
|
46
47
|
bin/wt --print-env my-feature # preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
48
|
+
bin/wt setup # rerun setup for the current checkout/worktree
|
|
49
|
+
bin/wt setup my-feature # rerun setup for a managed worktree by name
|
|
50
|
+
bin/wt setup ../my-project.worktrees/my-feature # setup a specific checkout without cd-ing into it
|
|
47
51
|
bin/wt doctor # audit install/config drift and basic worktree health
|
|
48
52
|
bin/wt update --dry-run # preview safe maintenance fixes
|
|
49
53
|
bin/wt update # apply safe maintenance fixes for managed files
|
|
@@ -65,6 +69,7 @@ bin/ob --print-url '?from=nav' # print the resolved URL without opening a brows
|
|
|
65
69
|
| `-h`, `--help` | Show the help message |
|
|
66
70
|
| `-v`, `--version` | Show the script version |
|
|
67
71
|
| `--dry-run [name]` | Preview worktree creation or cleanup without changing anything |
|
|
72
|
+
| `--skip-setup` | Create a worktree without running setup steps |
|
|
68
73
|
| `--force` | Force branch deletion for `bin/wt remove` / `bin/wt delete` |
|
|
69
74
|
| `--env`, `--print-env <name>` | Preview `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` |
|
|
70
75
|
|
|
@@ -78,6 +83,7 @@ By default `bin/wt`:
|
|
|
78
83
|
- auto-picks names from bundled `.txt` files when no explicit name is given
|
|
79
84
|
- retires bundled names so they are not picked twice
|
|
80
85
|
- 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`
|
|
81
87
|
|
|
82
88
|
```text
|
|
83
89
|
workspace/
|
|
@@ -90,6 +96,32 @@ workspace/
|
|
|
90
96
|
|
|
91
97
|
`WT_WORKSPACES_ROOT` or `config.workspace_root` overrides the destination root and uses the layout `<root>/<project>/<name>`.
|
|
92
98
|
|
|
99
|
+
### Setup command
|
|
100
|
+
|
|
101
|
+
`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.
|
|
102
|
+
|
|
103
|
+
If the checkout was created by `bin/wt`, you can also point at it by managed worktree name from the main app checkout:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bin/wt setup my-feature
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If you do not want to `cd` first, pass an explicit checkout path:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bin/wt setup ../my-project.worktrees/my-feature
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This is the recovery path when you want to create first and bootstrap later:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
bin/wt --skip-setup my-feature
|
|
119
|
+
cd ../my-project.worktrees/my-feature
|
|
120
|
+
bin/wt setup
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`bin/wt setup --dry-run` previews the same steps without changing files.
|
|
124
|
+
|
|
93
125
|
### Cleanup commands
|
|
94
126
|
|
|
95
127
|
`bin/wt` also supports cleanup commands for worktrees it manages:
|
|
@@ -181,7 +213,7 @@ If your `database.yml` is too custom to patch safely, the installer leaves it al
|
|
|
181
213
|
When `bin/wt` creates a worktree it writes a worktree-local `.env` with:
|
|
182
214
|
|
|
183
215
|
- `DEV_PORT` — deterministic port derived from the worktree name via CRC32, rotated through `dev_port_range`, skipping ports already claimed by peer worktrees
|
|
184
|
-
- `WORKTREE_DATABASE_SUFFIX` — derived from the worktree name
|
|
216
|
+
- `WORKTREE_DATABASE_SUFFIX` — derived from the best available worktree identity (managed name when known, otherwise the current branch or checkout basename). When a readable suffix is already claimed by a peer checkout, `bin/wt` appends a short `DEV_PORT`-based token to keep the databases isolated.
|
|
185
217
|
|
|
186
218
|
Existing `.env` values are never overwritten.
|
|
187
219
|
|
|
@@ -3,6 +3,8 @@ module Rails
|
|
|
3
3
|
module Generators
|
|
4
4
|
# Detects common mise.toml setups and suggests loading the worktree-local .env.
|
|
5
5
|
module MiseFollowUp
|
|
6
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
7
|
+
|
|
6
8
|
private
|
|
7
9
|
|
|
8
10
|
def follow_up_notes_text
|
|
@@ -26,7 +28,7 @@ module Rails
|
|
|
26
28
|
def mise_env_file_configured?
|
|
27
29
|
return false unless mise_toml_path
|
|
28
30
|
|
|
29
|
-
File.read(mise_toml_path).match?(/^\s*_.file\s*=\s*["']\.env["']\s*(?:#.*)?\s*$/)
|
|
31
|
+
File.read(mise_toml_path, encoding: FILE_ENCODING).match?(/^\s*_.file\s*=\s*["']\.env["']\s*(?:#.*)?\s*$/)
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def mise_toml_path
|
|
@@ -3,6 +3,8 @@ module Rails
|
|
|
3
3
|
module Generators
|
|
4
4
|
# Detects config/puma.rb setups that should prefer the worktree-local DEV_PORT.
|
|
5
5
|
module PumaFollowUp
|
|
6
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
7
|
+
|
|
6
8
|
private
|
|
7
9
|
|
|
8
10
|
def puma_follow_up_notes_text
|
|
@@ -24,7 +26,9 @@ module Rails
|
|
|
24
26
|
def puma_update_result
|
|
25
27
|
return missing_puma_update_result unless existing_puma_config_path
|
|
26
28
|
|
|
27
|
-
::Rails::Worktrees::PumaConfigUpdater.new(
|
|
29
|
+
::Rails::Worktrees::PumaConfigUpdater.new(
|
|
30
|
+
content: File.read(existing_puma_config_path, encoding: FILE_ENCODING)
|
|
31
|
+
).call
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def missing_puma_update_result
|
|
@@ -1,5 +1,65 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
wt_prepend_path() {
|
|
5
|
+
case ":${PATH:-}:" in
|
|
6
|
+
*":$1:"*) ;;
|
|
7
|
+
*) PATH="$1${PATH:+:$PATH}" ;;
|
|
8
|
+
esac
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
wt_prepend_path "/usr/local/bin"
|
|
12
|
+
wt_prepend_path "/opt/homebrew/bin"
|
|
13
|
+
if [ -n "${HOME:-}" ]; then
|
|
14
|
+
wt_prepend_path "$HOME/.local/bin"
|
|
15
|
+
fi
|
|
16
|
+
export PATH
|
|
17
|
+
|
|
18
|
+
wt_locale_needs_utf8() {
|
|
19
|
+
case "${1:-}" in
|
|
20
|
+
""|C|POSIX) return 0 ;;
|
|
21
|
+
*) return 1 ;;
|
|
22
|
+
esac
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
wt_utf8_locale() {
|
|
26
|
+
if command -v locale >/dev/null 2>&1 && locale -a 2>/dev/null | grep -Eiq '^(C|c)[_.-]UTF-?8$'; then
|
|
27
|
+
printf '%s' 'C.UTF-8'
|
|
28
|
+
else
|
|
29
|
+
printf '%s' 'en_US.UTF-8'
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if wt_locale_needs_utf8 "${LANG:-}" || { [ -n "${LC_ALL:-}" ] && wt_locale_needs_utf8 "${LC_ALL:-}"; }; then
|
|
34
|
+
WT_UTF8_LOCALE="${LANG:-}"
|
|
35
|
+
if wt_locale_needs_utf8 "$WT_UTF8_LOCALE"; then
|
|
36
|
+
WT_UTF8_LOCALE="$(wt_utf8_locale)"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if wt_locale_needs_utf8 "${LANG:-}"; then
|
|
40
|
+
export LANG="$WT_UTF8_LOCALE"
|
|
41
|
+
fi
|
|
42
|
+
if [ -n "${LC_ALL:-}" ] && wt_locale_needs_utf8 "${LC_ALL:-}"; then
|
|
43
|
+
export LC_ALL="$WT_UTF8_LOCALE"
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
WT_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
48
|
+
WT_SCRIPT_PATH="$WT_SCRIPT_DIR/$(basename "${BASH_SOURCE[0]}")"
|
|
49
|
+
WT_APP_ROOT="$(cd "$WT_SCRIPT_DIR/.." && pwd -P)"
|
|
50
|
+
WT_MISE_CONFIG=""
|
|
51
|
+
|
|
52
|
+
if [ -f "$WT_APP_ROOT/mise.toml" ]; then
|
|
53
|
+
WT_MISE_CONFIG="$WT_APP_ROOT/mise.toml"
|
|
54
|
+
elif [ -f "$WT_APP_ROOT/.mise.toml" ]; then
|
|
55
|
+
WT_MISE_CONFIG="$WT_APP_ROOT/.mise.toml"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [ -n "$WT_MISE_CONFIG" ] && [ -z "${RAILS_WORKTREES_BOOTSTRAPPED:-}" ] && command -v mise >/dev/null 2>&1; then
|
|
59
|
+
mise trust "$WT_MISE_CONFIG" >/dev/null || true
|
|
60
|
+
export RAILS_WORKTREES_BOOTSTRAPPED=1
|
|
61
|
+
exec mise exec -- "$WT_SCRIPT_PATH" "$@"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
export BUNDLE_GEMFILE="${BUNDLE_GEMFILE:-$WT_APP_ROOT/Gemfile}"
|
|
65
|
+
exec ruby -rbundler/setup -e 'load Gem.bin_path("rails-worktrees", "wt")' -- "$@"
|
|
@@ -36,6 +36,8 @@ module Worktrees
|
|
|
36
36
|
'follow-up edits when safe; also generate bin/ob'
|
|
37
37
|
].join(' ')
|
|
38
38
|
|
|
39
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
40
|
+
|
|
39
41
|
FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
|
|
40
42
|
============================================
|
|
41
43
|
rails-worktrees installed successfully! 🚂
|
|
@@ -130,7 +132,7 @@ module Worktrees
|
|
|
130
132
|
|
|
131
133
|
def database_update_result
|
|
132
134
|
result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
|
|
133
|
-
content: File.read(database_config_path)
|
|
135
|
+
content: File.read(database_config_path, encoding: FILE_ENCODING)
|
|
134
136
|
).call
|
|
135
137
|
|
|
136
138
|
File.write(database_config_path, result.content) if result.changed?
|
|
@@ -181,7 +183,9 @@ module Worktrees
|
|
|
181
183
|
return
|
|
182
184
|
end
|
|
183
185
|
|
|
184
|
-
result = ::Rails::Worktrees::ProcfileUpdater.new(
|
|
186
|
+
result = ::Rails::Worktrees::ProcfileUpdater.new(
|
|
187
|
+
content: File.read(procfile_path, encoding: FILE_ENCODING)
|
|
188
|
+
).call
|
|
185
189
|
File.write(procfile_path, result.content) if result.changed?
|
|
186
190
|
announce_updater_result('Procfile.dev', result)
|
|
187
191
|
end
|
|
@@ -193,7 +197,9 @@ module Worktrees
|
|
|
193
197
|
return
|
|
194
198
|
end
|
|
195
199
|
|
|
196
|
-
result = ::Rails::Worktrees::PumaConfigUpdater.new(
|
|
200
|
+
result = ::Rails::Worktrees::PumaConfigUpdater.new(
|
|
201
|
+
content: File.read(puma_config_path, encoding: FILE_ENCODING)
|
|
202
|
+
).call
|
|
197
203
|
File.write(puma_config_path, result.content) if result.changed?
|
|
198
204
|
announce_updater_result('config/puma.rb', result)
|
|
199
205
|
end
|
|
@@ -203,7 +209,7 @@ module Worktrees
|
|
|
203
209
|
return announce_missing_mise_toml unless path
|
|
204
210
|
|
|
205
211
|
result = ::Rails::Worktrees::MiseTomlUpdater.new(
|
|
206
|
-
content: File.read(path),
|
|
212
|
+
content: File.read(path, encoding: FILE_ENCODING),
|
|
207
213
|
file_name: File.basename(path)
|
|
208
214
|
).call
|
|
209
215
|
|
|
@@ -249,13 +255,19 @@ module Worktrees
|
|
|
249
255
|
|
|
250
256
|
def git_repo?
|
|
251
257
|
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
252
|
-
'git', 'rev-parse', '--is-inside-work-tree',
|
|
258
|
+
git_process_env, 'git', 'rev-parse', '--is-inside-work-tree',
|
|
259
|
+
chdir: destination_root,
|
|
260
|
+
unsetenv_others: true
|
|
253
261
|
)
|
|
254
262
|
status.success?
|
|
255
263
|
rescue Errno::ENOENT
|
|
256
264
|
false
|
|
257
265
|
end
|
|
258
266
|
|
|
267
|
+
def git_process_env
|
|
268
|
+
ENV.to_h.slice('HOME', 'PATH')
|
|
269
|
+
end
|
|
270
|
+
|
|
259
271
|
def conductor_workspace_root
|
|
260
272
|
"File.expand_path('~/Sites/conductor/workspaces')"
|
|
261
273
|
end
|
|
@@ -8,6 +8,7 @@ module Rails
|
|
|
8
8
|
class BrowserCommand
|
|
9
9
|
APP_ROOT_ENV_KEY = 'RAILS_WORKTREES_APP_ROOT'.freeze
|
|
10
10
|
ENV_FILE_NAME = '.env'.freeze
|
|
11
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
11
12
|
|
|
12
13
|
def initialize(argv:, io:, env:, cwd:, host_os: RbConfig::CONFIG['host_os'])
|
|
13
14
|
@argv = argv.dup
|
|
@@ -127,7 +128,7 @@ module Rails
|
|
|
127
128
|
def dev_port_from_env_file
|
|
128
129
|
return unless File.file?(env_path)
|
|
129
130
|
|
|
130
|
-
lines = File.readlines(env_path, chomp: true)
|
|
131
|
+
lines = File.readlines(env_path, chomp: true, encoding: FILE_ENCODING)
|
|
131
132
|
env_value(lines, 'DEV_PORT')
|
|
132
133
|
rescue StandardError => e
|
|
133
134
|
raise Error, "Could not read #{env_path}: #{e.message}"
|
data/lib/rails/worktrees/cli.rb
CHANGED
|
@@ -3,7 +3,8 @@ module Rails
|
|
|
3
3
|
# Shell entrypoint for the wt executable.
|
|
4
4
|
class CLI
|
|
5
5
|
LOADER_OPTIONAL_COMMANDS = %w[doctor update -h --help -v --version].freeze
|
|
6
|
-
LOADER_IGNORED_FLAGS = %w[--dry-run --force].freeze
|
|
6
|
+
LOADER_IGNORED_FLAGS = %w[--dry-run --force --skip-setup].freeze
|
|
7
|
+
SETUP_SUBCOMMAND = 'setup'.freeze
|
|
7
8
|
|
|
8
9
|
def initialize(
|
|
9
10
|
argv: ARGV,
|
|
@@ -29,11 +30,17 @@ module Rails
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
32
|
def load_project_configuration(configuration)
|
|
32
|
-
::Rails::Worktrees::ProjectConfigurationLoader.new(root:
|
|
33
|
+
::Rails::Worktrees::ProjectConfigurationLoader.new(root: configuration_root, configuration: configuration).call
|
|
33
34
|
rescue StandardError, ScriptError => e
|
|
34
35
|
raise ::Rails::Worktrees::Error, "Failed to load worktrees configuration: #{e.class}: #{e.message}"
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def configuration_root
|
|
39
|
+
return expand_setup_target_path if explicit_setup_path_target?
|
|
40
|
+
|
|
41
|
+
@cwd
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
def should_load_project_configuration?
|
|
38
45
|
argv_without_flags.empty? || !loader_optional_command?(argv_without_flags.first)
|
|
39
46
|
end
|
|
@@ -46,6 +53,19 @@ module Rails
|
|
|
46
53
|
LOADER_OPTIONAL_COMMANDS.include?(command)
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
def explicit_setup_path_target?
|
|
57
|
+
argv_without_flags.first == SETUP_SUBCOMMAND && argv_without_flags.length == 2 &&
|
|
58
|
+
path_like_setup_target?(argv_without_flags.last)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def expand_setup_target_path
|
|
62
|
+
File.expand_path(argv_without_flags.last, @cwd)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def path_like_setup_target?(value)
|
|
66
|
+
value.start_with?('/', '.', '~') || value.include?(File::SEPARATOR)
|
|
67
|
+
end
|
|
68
|
+
|
|
49
69
|
def command_for(configuration)
|
|
50
70
|
Command.new(argv: @argv, io: @io, env: @env, cwd: @cwd, configuration: configuration)
|
|
51
71
|
end
|
|
@@ -7,6 +7,26 @@ module Rails
|
|
|
7
7
|
# Shell-level git helpers, branch/worktree queries, and worktree creation.
|
|
8
8
|
# rubocop:disable Metrics/ModuleLength
|
|
9
9
|
module GitOperations
|
|
10
|
+
GIT_REPOSITORY_ENV_KEYS = %w[
|
|
11
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES
|
|
12
|
+
GIT_COMMON_DIR
|
|
13
|
+
GIT_CONFIG
|
|
14
|
+
GIT_CONFIG_COUNT
|
|
15
|
+
GIT_CONFIG_PARAMETERS
|
|
16
|
+
GIT_DIR
|
|
17
|
+
GIT_GRAFT_FILE
|
|
18
|
+
GIT_IMPLICIT_WORK_TREE
|
|
19
|
+
GIT_INDEX_FILE
|
|
20
|
+
GIT_NAMESPACE
|
|
21
|
+
GIT_NO_REPLACE_OBJECTS
|
|
22
|
+
GIT_OBJECT_DIRECTORY
|
|
23
|
+
GIT_PREFIX
|
|
24
|
+
GIT_QUARANTINE_PATH
|
|
25
|
+
GIT_REPLACE_REF_BASE
|
|
26
|
+
GIT_SHALLOW_FILE
|
|
27
|
+
GIT_WORK_TREE
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
10
30
|
private
|
|
11
31
|
|
|
12
32
|
def require_git_repo
|
|
@@ -162,24 +182,42 @@ module Rails
|
|
|
162
182
|
end
|
|
163
183
|
|
|
164
184
|
def git!(*)
|
|
165
|
-
stdout_str, stderr_str, status = Open3.capture3(
|
|
185
|
+
stdout_str, stderr_str, status = Open3.capture3(
|
|
186
|
+
git_process_env, 'git', *, chdir: @cwd, unsetenv_others: true
|
|
187
|
+
)
|
|
188
|
+
stdout_str = command_output(stdout_str)
|
|
189
|
+
stderr_str = command_output(stderr_str)
|
|
166
190
|
return stdout_str if status.success?
|
|
167
191
|
|
|
168
192
|
raise Error, combined_output(stdout_str, stderr_str)
|
|
169
193
|
end
|
|
170
194
|
|
|
171
195
|
def git_capture(*, allow_failure: false)
|
|
172
|
-
stdout_str, stderr_str, status = Open3.capture3(
|
|
196
|
+
stdout_str, stderr_str, status = Open3.capture3(
|
|
197
|
+
git_process_env, 'git', *, chdir: @cwd, unsetenv_others: true
|
|
198
|
+
)
|
|
199
|
+
stdout_str = command_output(stdout_str)
|
|
200
|
+
stderr_str = command_output(stderr_str)
|
|
173
201
|
return stdout_str if status.success? || allow_failure
|
|
174
202
|
|
|
175
203
|
raise Error, combined_output(stdout_str, stderr_str)
|
|
176
204
|
end
|
|
177
205
|
|
|
178
206
|
def git_success?(*)
|
|
179
|
-
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
207
|
+
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
208
|
+
git_process_env, 'git', *, chdir: @cwd, unsetenv_others: true
|
|
209
|
+
)
|
|
180
210
|
status.success?
|
|
181
211
|
end
|
|
182
212
|
|
|
213
|
+
def git_process_env
|
|
214
|
+
@env.to_h.except(*GIT_REPOSITORY_ENV_KEYS)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def command_output(output)
|
|
218
|
+
output.to_s.dup.force_encoding(Encoding::UTF_8).scrub
|
|
219
|
+
end
|
|
220
|
+
|
|
183
221
|
def combined_output(stdout_str, stderr_str)
|
|
184
222
|
output = [stdout_str, stderr_str].map(&:strip).reject(&:empty?).join("\n")
|
|
185
223
|
return output unless output.empty?
|
|
@@ -4,6 +4,8 @@ module Rails
|
|
|
4
4
|
# Auto-picking names from bundled .txt files and tracking retired names.
|
|
5
5
|
# rubocop:disable Metrics/ModuleLength
|
|
6
6
|
module NamePicking
|
|
7
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
8
|
+
|
|
7
9
|
private
|
|
8
10
|
|
|
9
11
|
def resolve_worktree_name(project_name, workspaces)
|
|
@@ -48,7 +50,7 @@ module Rails
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def source_names(source_file)
|
|
51
|
-
File.readlines(source_file, chomp: true).filter_map do |line|
|
|
53
|
+
File.readlines(source_file, chomp: true, encoding: FILE_ENCODING).filter_map do |line|
|
|
52
54
|
name = line.delete_suffix("\r").strip
|
|
53
55
|
next if name.empty? || name.start_with?('#')
|
|
54
56
|
|
|
@@ -74,7 +76,7 @@ module Rails
|
|
|
74
76
|
state_file = retired_names_file
|
|
75
77
|
return false unless state_file
|
|
76
78
|
|
|
77
|
-
File.foreach(state_file).any? { |line| line.split("\t", 2).first == candidate }
|
|
79
|
+
File.foreach(state_file, encoding: FILE_ENCODING).any? { |line| line.split("\t", 2).first == candidate }
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
def record_retired_name(worktree_name, project_name)
|
|
@@ -30,9 +30,11 @@ module Rails
|
|
|
30
30
|
Create and clean up Git worktrees for the current repository.
|
|
31
31
|
|
|
32
32
|
Usage: wt [worktree-name]
|
|
33
|
+
wt [--skip-setup] [worktree-name]
|
|
33
34
|
wt --dry-run [worktree-name]
|
|
34
35
|
wt --print-env <worktree-name>
|
|
35
36
|
wt doctor
|
|
37
|
+
wt setup [--dry-run] [path|name]
|
|
36
38
|
wt update [--dry-run]
|
|
37
39
|
wt remove [--dry-run] [--force] <worktree-name>
|
|
38
40
|
wt delete [--dry-run] [--force] <worktree-name>
|
|
@@ -42,6 +44,7 @@ module Rails
|
|
|
42
44
|
-h, --help Show this help message
|
|
43
45
|
-v, --version Show the script version
|
|
44
46
|
--dry-run [name] Preview worktree creation or cleanup without changing anything
|
|
47
|
+
--skip-setup Create the worktree without running setup steps
|
|
45
48
|
--force Delete an unmerged local branch with wt remove/delete
|
|
46
49
|
--env, --print-env <name> Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
47
50
|
|
|
@@ -49,8 +52,12 @@ module Rails
|
|
|
49
52
|
wt Auto-pick a name from a bundled *.txt list
|
|
50
53
|
wt my-feature Use an explicit worktree name
|
|
51
54
|
wt --dry-run my-feature
|
|
55
|
+
wt --skip-setup my-feature
|
|
52
56
|
wt --print-env my-feature
|
|
53
57
|
wt doctor
|
|
58
|
+
wt setup
|
|
59
|
+
wt setup my-feature
|
|
60
|
+
wt setup ../my-project.worktrees/my-feature
|
|
54
61
|
wt update --dry-run
|
|
55
62
|
wt remove my-feature
|
|
56
63
|
wt remove --force my-feature
|
|
@@ -61,6 +68,8 @@ module Rails
|
|
|
61
68
|
- when workspace_root or WT_WORKSPACES_ROOT is set, creates worktrees in <root>/<project>/<name>
|
|
62
69
|
- always uses the branch name #{@configuration.branch_prefix}/<name>
|
|
63
70
|
- bases new branches on the repository's origin default branch
|
|
71
|
+
- by default wt <name> both creates the worktree and runs setup automatically
|
|
72
|
+
- wt setup reruns setup for the current checkout, a managed worktree name, or a specific checkout path, including manually-created worktrees
|
|
64
73
|
- wt doctor audits install/config drift plus basic worktree health without changing files
|
|
65
74
|
- wt update applies safe file-based fixes for managed installer artifacts and config hints
|
|
66
75
|
- wt remove/delete can run from the main checkout or any sibling worktree, but never remove the worktree you're currently in
|
|
@@ -5,19 +5,20 @@ module Rails
|
|
|
5
5
|
module PostCreateSupport
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def run_post_create_steps(context)
|
|
9
|
-
post_create_runner_for(context).call(dry_run: false)
|
|
8
|
+
def run_post_create_steps(context, bootstrapped_env: nil)
|
|
9
|
+
post_create_runner_for(context, bootstrapped_env: bootstrapped_env).call(dry_run: false)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def preview_post_create_steps(context)
|
|
13
|
-
post_create_runner_for(context).call(dry_run: true)
|
|
12
|
+
def preview_post_create_steps(context, bootstrapped_env: nil)
|
|
13
|
+
post_create_runner_for(context, bootstrapped_env: bootstrapped_env).call(dry_run: true)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def post_create_runner_for(context)
|
|
16
|
+
def post_create_runner_for(context, bootstrapped_env: nil)
|
|
17
17
|
PostCreateRunner.new(
|
|
18
18
|
target_dir: context[:target_dir],
|
|
19
|
-
peer_roots: peer_roots_excluding(context[:target_dir]),
|
|
19
|
+
peer_roots: context.fetch(:peer_roots) { peer_roots_excluding(context[:target_dir]) },
|
|
20
20
|
configuration: @configuration,
|
|
21
|
+
bootstrapped_env: bootstrapped_env,
|
|
21
22
|
io: { stdout: @stdout, stderr: @stderr }
|
|
22
23
|
)
|
|
23
24
|
end
|
|
@@ -7,7 +7,19 @@ module Rails
|
|
|
7
7
|
|
|
8
8
|
def resolve_repository_context
|
|
9
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)
|
|
10
|
+
common_dir = expand_git_path(git_capture('rev-parse', '--git-common-dir').strip, base_dir: @cwd)
|
|
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 resolve_repository_context_for(path)
|
|
17
|
+
expanded_path = File.expand_path(path, @cwd)
|
|
18
|
+
current_root = canonical_path(git_capture('-C', expanded_path, 'rev-parse', '--show-toplevel').strip)
|
|
19
|
+
common_dir = expand_git_path(
|
|
20
|
+
git_capture('-C', expanded_path, 'rev-parse', '--git-common-dir').strip,
|
|
21
|
+
base_dir: expanded_path
|
|
22
|
+
)
|
|
11
23
|
primary_root = primary_checkout_root_for(current_root, common_dir)
|
|
12
24
|
|
|
13
25
|
repository_context_for(current_root, primary_root)
|
|
@@ -66,10 +78,10 @@ module Rails
|
|
|
66
78
|
canonical_path(File.dirname(common_dir))
|
|
67
79
|
end
|
|
68
80
|
|
|
69
|
-
def expand_git_path(path)
|
|
81
|
+
def expand_git_path(path, base_dir: @cwd)
|
|
70
82
|
return path if path.start_with?('/')
|
|
71
83
|
|
|
72
|
-
File.expand_path(path,
|
|
84
|
+
File.expand_path(path, base_dir)
|
|
73
85
|
end
|
|
74
86
|
|
|
75
87
|
def present_path?(path)
|
|
@@ -13,6 +13,7 @@ module Rails
|
|
|
13
13
|
class Command
|
|
14
14
|
REMOVE_SUBCOMMANDS = %w[remove delete].freeze
|
|
15
15
|
DOCTOR_SUBCOMMAND = 'doctor'.freeze
|
|
16
|
+
SETUP_SUBCOMMAND = 'setup'.freeze
|
|
16
17
|
UPDATE_SUBCOMMAND = 'update'.freeze
|
|
17
18
|
|
|
18
19
|
include GitOperations
|
|
@@ -49,9 +50,12 @@ module Rails
|
|
|
49
50
|
|
|
50
51
|
def force? = @force
|
|
51
52
|
|
|
53
|
+
def skip_setup? = @skip_setup
|
|
54
|
+
|
|
52
55
|
def extract_flags!
|
|
53
56
|
@dry_run = extract_flag!('--dry-run')
|
|
54
57
|
@force = extract_flag!('--force')
|
|
58
|
+
@skip_setup = extract_flag!('--skip-setup')
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def extract_flag!(flag)
|
|
@@ -77,20 +81,37 @@ module Rails
|
|
|
77
81
|
@argv.first == DOCTOR_SUBCOMMAND
|
|
78
82
|
end
|
|
79
83
|
|
|
84
|
+
def setup_subcommand?
|
|
85
|
+
@argv.first == SETUP_SUBCOMMAND
|
|
86
|
+
end
|
|
87
|
+
|
|
80
88
|
def update_subcommand?
|
|
81
89
|
@argv.first == UPDATE_SUBCOMMAND
|
|
82
90
|
end
|
|
83
91
|
|
|
84
92
|
def execute_requested_command
|
|
85
|
-
|
|
86
|
-
return
|
|
87
|
-
return
|
|
88
|
-
return execute_prune_command if prune_subcommand?
|
|
89
|
-
return usage_error if @argv.length > 1 || force?
|
|
93
|
+
handler = requested_command_handler
|
|
94
|
+
return handler.call if handler
|
|
95
|
+
return usage_error if invalid_worktree_command?
|
|
90
96
|
|
|
91
97
|
execute_worktree_command
|
|
92
98
|
end
|
|
93
99
|
|
|
100
|
+
def requested_command_handler
|
|
101
|
+
case @argv.first
|
|
102
|
+
when DOCTOR_SUBCOMMAND then -> { execute_doctor_command }
|
|
103
|
+
when SETUP_SUBCOMMAND then -> { execute_setup_command }
|
|
104
|
+
when UPDATE_SUBCOMMAND then -> { execute_update_command }
|
|
105
|
+
when 'prune' then -> { execute_prune_command }
|
|
106
|
+
else
|
|
107
|
+
-> { execute_remove_command } if remove_subcommand?
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def invalid_worktree_command?
|
|
112
|
+
@argv.length > 1 || force?
|
|
113
|
+
end
|
|
114
|
+
|
|
94
115
|
def execute_worktree_command
|
|
95
116
|
require_git_repo
|
|
96
117
|
announce_dry_run if dry_run?
|
|
@@ -107,6 +128,16 @@ module Rails
|
|
|
107
128
|
finish(context)
|
|
108
129
|
end
|
|
109
130
|
|
|
131
|
+
def execute_setup_command
|
|
132
|
+
validate_setup_args!
|
|
133
|
+
announce_dry_run if dry_run?
|
|
134
|
+
|
|
135
|
+
context = resolve_setup_context(target_reference: setup_target_reference)
|
|
136
|
+
validate_setup_target!(context)
|
|
137
|
+
|
|
138
|
+
complete_setup_flow(context, success_message: 'Setup complete')
|
|
139
|
+
end
|
|
140
|
+
|
|
110
141
|
def execute_remove_command
|
|
111
142
|
require_git_repo
|
|
112
143
|
announce_dry_run if dry_run?
|
|
@@ -210,17 +241,26 @@ module Rails
|
|
|
210
241
|
def validate_prune_args!
|
|
211
242
|
raise Error, 'Usage: wt prune' unless @argv.length == 1
|
|
212
243
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
244
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
213
245
|
end
|
|
214
246
|
|
|
215
247
|
def validate_doctor_args!
|
|
216
248
|
raise Error, 'Usage: wt doctor' unless @argv.length == 1
|
|
217
249
|
raise Error, 'wt doctor does not support --dry-run.' if dry_run?
|
|
218
250
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
251
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def validate_setup_args!
|
|
255
|
+
raise Error, 'Usage: wt setup [--dry-run] [path|name]' unless [1, 2].include?(@argv.length)
|
|
256
|
+
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
257
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
219
258
|
end
|
|
220
259
|
|
|
221
260
|
def validate_update_args!
|
|
222
261
|
raise Error, 'Usage: wt update [--dry-run]' unless @argv.length == 1
|
|
223
262
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
263
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
224
264
|
end
|
|
225
265
|
|
|
226
266
|
def announce_prune_candidates(candidates)
|
|
@@ -258,6 +298,60 @@ module Rails
|
|
|
258
298
|
)
|
|
259
299
|
end
|
|
260
300
|
|
|
301
|
+
def resolve_setup_context(target_reference: nil, repository: nil)
|
|
302
|
+
if setup_target_name?(target_reference)
|
|
303
|
+
return resolve_named_setup_context(target_reference, repository: repository)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
resolve_path_setup_context(target_reference, repository: repository)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def resolve_path_setup_context(target_reference, repository: nil)
|
|
310
|
+
target_dir = resolved_setup_target_path(target_reference)
|
|
311
|
+
repository ||= target_reference ? resolve_repository_context_for(target_dir) : resolve_repository_context
|
|
312
|
+
branch_name = current_checkout_branch_at(target_dir)
|
|
313
|
+
|
|
314
|
+
repository.merge(
|
|
315
|
+
worktree_name: setup_identity_for(repository[:current_root], branch_name),
|
|
316
|
+
branch_name: display_branch_name(branch_name, target_dir: repository[:current_root]),
|
|
317
|
+
target_dir: repository[:current_root],
|
|
318
|
+
peer_roots: peer_roots_for(repository[:current_root])
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def resolve_named_setup_context(worktree_name, repository: nil)
|
|
323
|
+
repository ||= resolve_repository_context
|
|
324
|
+
context = resolve_worktree_context(explicit_worktree_name: worktree_name, repository: repository)
|
|
325
|
+
branch_name = resolved_named_setup_branch_name(context)
|
|
326
|
+
|
|
327
|
+
context.merge(
|
|
328
|
+
branch_name: display_branch_name(branch_name, target_dir: context[:target_dir]),
|
|
329
|
+
peer_roots: peer_roots_for(context[:target_dir])
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def resolved_named_setup_branch_name(context)
|
|
334
|
+
return context[:branch_name] unless File.directory?(context[:target_dir])
|
|
335
|
+
|
|
336
|
+
current_checkout_branch_at(context[:target_dir])
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def setup_target_reference = @argv[1]
|
|
340
|
+
|
|
341
|
+
def setup_target_name?(target_reference)
|
|
342
|
+
!target_reference.nil? && !path_like_setup_target?(target_reference)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def resolved_setup_target_path(target_path)
|
|
346
|
+
return @cwd if target_path.nil?
|
|
347
|
+
|
|
348
|
+
File.expand_path(target_path, @cwd)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def path_like_setup_target?(target_reference)
|
|
352
|
+
target_reference.start_with?('/', '.', '~') || target_reference.include?(File::SEPARATOR)
|
|
353
|
+
end
|
|
354
|
+
|
|
261
355
|
def resolved_worktree_name(project_name, workspaces, explicit_worktree_name)
|
|
262
356
|
return validate_worktree_name(explicit_worktree_name) if explicit_worktree_name
|
|
263
357
|
|
|
@@ -285,27 +379,52 @@ module Rails
|
|
|
285
379
|
|
|
286
380
|
def finish(context)
|
|
287
381
|
settle_retired_name(context[:worktree_name], context[:project_name], dry_run: dry_run?)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
382
|
+
complete_setup_flow(
|
|
383
|
+
context,
|
|
384
|
+
success_message: skip_setup? ? 'Worktree created' : 'Worktree ready',
|
|
385
|
+
run_post_create: !skip_setup?
|
|
386
|
+
)
|
|
293
387
|
end
|
|
294
388
|
|
|
295
|
-
def complete_dry_run_after_setup(context, bootstrap_result)
|
|
296
|
-
preview_post_create_steps(context)
|
|
389
|
+
def complete_dry_run_after_setup(context, bootstrap_result, run_post_create: true)
|
|
390
|
+
preview_post_create_steps(context, bootstrapped_env: bootstrap_result&.values) if run_post_create
|
|
391
|
+
info('Would skip setup steps; run `wt setup` inside the checkout when you are ready.') unless run_post_create
|
|
297
392
|
complete_dry_run(context, env_values: bootstrap_result&.values)
|
|
298
393
|
end
|
|
299
394
|
|
|
300
|
-
def complete_created_worktree(context, bootstrap_result)
|
|
301
|
-
|
|
395
|
+
def complete_created_worktree(context, bootstrap_result, success_message:, run_post_create: true)
|
|
396
|
+
unless run_post_create
|
|
397
|
+
return complete_created_worktree_without_setup(context, bootstrap_result, success_message)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
result = run_post_create_steps(context, bootstrapped_env: bootstrap_result&.values)
|
|
302
401
|
return result unless result.zero?
|
|
303
402
|
|
|
304
|
-
success(
|
|
403
|
+
success(success_message)
|
|
305
404
|
print_context_summary(context, env_values: bootstrap_result&.values)
|
|
306
405
|
0
|
|
307
406
|
end
|
|
308
407
|
|
|
408
|
+
def complete_created_worktree_without_setup(context, bootstrap_result, success_message)
|
|
409
|
+
success(success_message)
|
|
410
|
+
print_context_summary(context, env_values: bootstrap_result&.values)
|
|
411
|
+
info('Setup skipped. Run `wt setup` inside the checkout when you are ready.')
|
|
412
|
+
0
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def complete_setup_flow(context, success_message:, run_post_create: true)
|
|
416
|
+
bootstrap_result = bootstrap_worktree_environment(context)
|
|
417
|
+
|
|
418
|
+
return complete_dry_run_after_setup(context, bootstrap_result, run_post_create: run_post_create) if dry_run?
|
|
419
|
+
|
|
420
|
+
complete_created_worktree(
|
|
421
|
+
context,
|
|
422
|
+
bootstrap_result,
|
|
423
|
+
success_message: success_message,
|
|
424
|
+
run_post_create: run_post_create
|
|
425
|
+
)
|
|
426
|
+
end
|
|
427
|
+
|
|
309
428
|
def finish_reuse(context)
|
|
310
429
|
target, branch = context.values_at(:target_dir, :branch_name)
|
|
311
430
|
|
|
@@ -338,6 +457,78 @@ module Rails
|
|
|
338
457
|
"#{@configuration.branch_prefix}/#{worktree_name}"
|
|
339
458
|
end
|
|
340
459
|
|
|
460
|
+
def validate_setup_target!(context)
|
|
461
|
+
raise Error, "Setup target does not exist: #{context[:target_dir]}" unless File.directory?(context[:target_dir])
|
|
462
|
+
|
|
463
|
+
rails_bin = File.join(context[:target_dir], 'bin', 'rails')
|
|
464
|
+
|
|
465
|
+
unless File.exist?(rails_bin)
|
|
466
|
+
raise Error,
|
|
467
|
+
"Setup target does not look like a Rails app. Expected #{rails_bin} to exist before running wt setup."
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
return if registered_worktree_path_for_setup?(context[:target_dir])
|
|
471
|
+
|
|
472
|
+
warning(
|
|
473
|
+
'Current checkout is not registered in git worktree list; peer credential key discovery may be limited.'
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def current_checkout_branch_at(target_dir)
|
|
478
|
+
git_capture('-C', target_dir, 'branch', '--show-current', allow_failure: true).strip
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def registered_worktree_path_for_setup?(target_dir)
|
|
482
|
+
normalized_target = canonical_path(target_dir)
|
|
483
|
+
|
|
484
|
+
worktree_entries_for_checkout(target_dir).any? { |entry| entry[:path] == normalized_target }
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def peer_roots_for(target_dir)
|
|
488
|
+
normalized_target = canonical_path(target_dir)
|
|
489
|
+
|
|
490
|
+
worktree_entries_for_checkout(target_dir)
|
|
491
|
+
.map { |entry| entry[:path] }
|
|
492
|
+
.reject { |path| path == normalized_target }
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def worktree_entries_for_checkout(target_dir)
|
|
496
|
+
git_capture('-C', target_dir, 'worktree', 'list', '--porcelain', allow_failure: true)
|
|
497
|
+
.split("\n\n")
|
|
498
|
+
.filter_map do |block|
|
|
499
|
+
entry = parse_worktree_entry(block)
|
|
500
|
+
entry[:path] ? entry : nil
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def setup_identity_for(target_dir, branch_name)
|
|
505
|
+
stripped_branch = worktree_identity_from_branch(branch_name)
|
|
506
|
+
return stripped_branch unless stripped_branch.empty?
|
|
507
|
+
|
|
508
|
+
File.basename(target_dir)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def worktree_identity_from_branch(branch_name)
|
|
512
|
+
branch_name = branch_name.to_s.strip
|
|
513
|
+
return '' if branch_name.empty?
|
|
514
|
+
|
|
515
|
+
worktree_name_for_branch(branch_name)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def display_branch_name(branch_name, target_dir: nil)
|
|
519
|
+
branch_name = branch_name.to_s.strip
|
|
520
|
+
return branch_name unless branch_name.empty?
|
|
521
|
+
|
|
522
|
+
git_args = if target_dir.to_s.strip.empty?
|
|
523
|
+
['rev-parse', '--short', 'HEAD']
|
|
524
|
+
else
|
|
525
|
+
['-C', target_dir, 'rev-parse', '--short', 'HEAD']
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
short_sha = git_capture(*git_args, allow_failure: true).strip
|
|
529
|
+
short_sha.empty? ? '(detached HEAD)' : "(detached HEAD at #{short_sha})"
|
|
530
|
+
end
|
|
531
|
+
|
|
341
532
|
def ensure_removable!(context, worktree_exists:, branch_exists:)
|
|
342
533
|
ensure_remove_target_exists!(context, worktree_exists: worktree_exists, branch_exists: branch_exists)
|
|
343
534
|
ensure_not_removing_protected_checkout!(context)
|
|
@@ -13,11 +13,13 @@ module Rails
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
ENV_FILE_NAME = '.env'.freeze
|
|
16
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
16
17
|
|
|
17
|
-
def initialize(target_dir:, worktree_name:, configuration:)
|
|
18
|
+
def initialize(target_dir:, worktree_name:, configuration:, peer_roots: nil)
|
|
18
19
|
@target_dir = target_dir
|
|
19
20
|
@worktree_name = worktree_name
|
|
20
21
|
@configuration = configuration
|
|
22
|
+
@peer_roots = peer_roots.nil? ? nil : Array(peer_roots).compact
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def call(dry_run: false)
|
|
@@ -43,13 +45,17 @@ module Rails
|
|
|
43
45
|
result(false, [message], values)
|
|
44
46
|
end
|
|
45
47
|
|
|
46
|
-
def existing_env_lines
|
|
48
|
+
def existing_env_lines
|
|
49
|
+
File.exist?(env_path) ? File.readlines(env_path, chomp: true, encoding: FILE_ENCODING) : []
|
|
50
|
+
end
|
|
47
51
|
|
|
48
52
|
def resolved_values(lines)
|
|
53
|
+
dev_port = (env_value(lines, 'DEV_PORT') || allocate_dev_port).to_s
|
|
54
|
+
|
|
49
55
|
{
|
|
50
|
-
'DEV_PORT' =>
|
|
56
|
+
'DEV_PORT' => dev_port,
|
|
51
57
|
'WORKTREE_DATABASE_SUFFIX' => env_value(lines, 'WORKTREE_DATABASE_SUFFIX') ||
|
|
52
|
-
|
|
58
|
+
allocate_worktree_database_suffix(dev_port: dev_port)
|
|
53
59
|
}
|
|
54
60
|
end
|
|
55
61
|
|
|
@@ -108,8 +114,7 @@ module Rails
|
|
|
108
114
|
end
|
|
109
115
|
|
|
110
116
|
def claimed_peer_ports
|
|
111
|
-
|
|
112
|
-
next unless File.directory?(path)
|
|
117
|
+
peer_paths.filter_map do |path|
|
|
113
118
|
next if File.expand_path(path) == File.expand_path(@target_dir)
|
|
114
119
|
|
|
115
120
|
port = env_value(peer_env_lines(path), 'DEV_PORT')
|
|
@@ -121,13 +126,26 @@ module Rails
|
|
|
121
126
|
|
|
122
127
|
def peer_env_lines(path)
|
|
123
128
|
env_file = File.join(path, ENV_FILE_NAME)
|
|
124
|
-
File.exist?(env_file) ? File.readlines(env_file, chomp: true) : []
|
|
129
|
+
File.exist?(env_file) ? File.readlines(env_file, chomp: true, encoding: FILE_ENCODING) : []
|
|
125
130
|
end
|
|
126
131
|
|
|
127
132
|
def peers_root = File.dirname(@target_dir)
|
|
128
133
|
|
|
134
|
+
def peer_paths
|
|
135
|
+
return @peer_roots unless @peer_roots.nil?
|
|
136
|
+
|
|
137
|
+
Dir.glob(File.join(peers_root, '*')).select { |path| File.directory?(path) }
|
|
138
|
+
end
|
|
139
|
+
|
|
129
140
|
def configured_port_range = @configuration.dev_port_range
|
|
130
141
|
|
|
142
|
+
def allocate_worktree_database_suffix(dev_port:)
|
|
143
|
+
base_candidate = format_worktree_database_suffix(@worktree_name)
|
|
144
|
+
return base_candidate unless claimed_peer_suffixes.include?(base_candidate)
|
|
145
|
+
|
|
146
|
+
suffix_with_token(@worktree_name, dev_port.to_s)
|
|
147
|
+
end
|
|
148
|
+
|
|
131
149
|
def format_worktree_database_suffix(value)
|
|
132
150
|
suffix = value.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/\A_+|_+\z/, '').squeeze('_')
|
|
133
151
|
suffix = suffix[0, @configuration.worktree_database_suffix_max_length]
|
|
@@ -136,6 +154,35 @@ module Rails
|
|
|
136
154
|
"_#{suffix}"
|
|
137
155
|
end
|
|
138
156
|
|
|
157
|
+
def suffix_with_token(value, token)
|
|
158
|
+
slug = normalized_suffix_slug(value)
|
|
159
|
+
normalized_token = token.to_s.downcase.gsub(/[^a-z0-9]+/, '')
|
|
160
|
+
max_length = @configuration.worktree_database_suffix_max_length
|
|
161
|
+
available_slug_length = max_length - normalized_token.length - 1
|
|
162
|
+
|
|
163
|
+
composed_slug = if available_slug_length.positive?
|
|
164
|
+
[slug[0, available_slug_length], normalized_token].reject(&:empty?).join('_')
|
|
165
|
+
else
|
|
166
|
+
normalized_token[0, max_length]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
format_worktree_database_suffix(composed_slug)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def normalized_suffix_slug(value)
|
|
173
|
+
slug = value.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/\A_+|_+\z/, '').squeeze('_')
|
|
174
|
+
slug.empty? ? 'worktree' : slug
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def claimed_peer_suffixes
|
|
178
|
+
peer_paths.each_with_object(Set.new) do |path, suffixes|
|
|
179
|
+
next if File.expand_path(path) == File.expand_path(@target_dir)
|
|
180
|
+
|
|
181
|
+
suffix = env_value(peer_env_lines(path), 'WORKTREE_DATABASE_SUFFIX')
|
|
182
|
+
suffixes << suffix if suffix
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
139
186
|
def formatted_updates(updates) = updates.map { |key, value| "#{key}=#{value}" }.join(', ')
|
|
140
187
|
|
|
141
188
|
def display_path(path)
|
|
@@ -12,6 +12,7 @@ module Rails
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
TEMPLATE_PATH = File.expand_path('../../generators/rails/worktrees/templates/rails_worktrees.rb.tt', __dir__)
|
|
15
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
15
16
|
CURRENT_WRAPPER_CALL = 'Rails.application.config.x.rails_worktrees.tap do |config|'.freeze
|
|
16
17
|
CONFIGURE_CALL = 'Rails::Worktrees.configure do |config|'.freeze
|
|
17
18
|
KNOWN_GUARD_FRAGMENTS = [
|
|
@@ -135,7 +136,9 @@ module Rails
|
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
def render_default_template
|
|
138
|
-
ERB.new(File.read(TEMPLATE_PATH), trim_mode: '-').result(
|
|
139
|
+
ERB.new(File.read(TEMPLATE_PATH, encoding: FILE_ENCODING), trim_mode: '-').result(
|
|
140
|
+
template_context.instance_eval { binding }
|
|
141
|
+
)
|
|
139
142
|
end
|
|
140
143
|
|
|
141
144
|
def template_context
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'open3'
|
|
3
|
+
|
|
4
|
+
module Rails
|
|
5
|
+
module Worktrees
|
|
6
|
+
# Resolves runtime environment variables from mise for setup subprocesses.
|
|
7
|
+
class MiseEnvironment
|
|
8
|
+
Result = Struct.new(:env, :messages)
|
|
9
|
+
|
|
10
|
+
CONFIG_FILES = %w[mise.toml .mise.toml].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(target_dir:, env:)
|
|
13
|
+
@target_dir = target_dir
|
|
14
|
+
@env = env
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
return Result.new(env: {}, messages: []) unless mise_available?
|
|
19
|
+
|
|
20
|
+
messages = []
|
|
21
|
+
trust_config!(messages)
|
|
22
|
+
env = resolved_env
|
|
23
|
+
messages << '🧰 Activating mise toolchain...' if project_config_file || !env.empty?
|
|
24
|
+
|
|
25
|
+
Result.new(env: env, messages: messages.uniq)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def mise_available?
|
|
31
|
+
_stdout_str, _stderr_str, status = Open3.capture3(@env, 'mise', '--version', chdir: @target_dir)
|
|
32
|
+
status.success?
|
|
33
|
+
rescue Errno::ENOENT
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def trust_config!(messages)
|
|
38
|
+
config_file = project_config_file
|
|
39
|
+
return unless config_file
|
|
40
|
+
|
|
41
|
+
messages << '🔐 Trusting mise config...'
|
|
42
|
+
|
|
43
|
+
ensure_trusted!(config_file)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ensure_trusted!(config_file)
|
|
47
|
+
_stdout_str, stderr_str, status = Open3.capture3(
|
|
48
|
+
@env,
|
|
49
|
+
'mise',
|
|
50
|
+
'trust',
|
|
51
|
+
config_file,
|
|
52
|
+
chdir: @target_dir
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return if status.success?
|
|
56
|
+
|
|
57
|
+
raise Error, "mise trust failed: #{command_error(stderr_str)}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resolved_env
|
|
61
|
+
@resolved_env ||= begin
|
|
62
|
+
stdout_str, stderr_str, status = Open3.capture3(@env, 'mise', 'env', '--json', chdir: @target_dir)
|
|
63
|
+
raise Error, "mise env --json failed: #{command_error(stderr_str)}" unless status.success?
|
|
64
|
+
|
|
65
|
+
JSON.parse(stdout_str).each_with_object({}) do |(key, value), env|
|
|
66
|
+
next if value.nil?
|
|
67
|
+
|
|
68
|
+
env[key] = value.to_s
|
|
69
|
+
end
|
|
70
|
+
rescue JSON::ParserError => e
|
|
71
|
+
raise Error, "mise env --json returned invalid JSON: #{e.message}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def project_config_file
|
|
76
|
+
@project_config_file ||= CONFIG_FILES
|
|
77
|
+
.map { |file_name| File.join(@target_dir, file_name) }
|
|
78
|
+
.find { |path| File.file?(path) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def command_error(stderr_str)
|
|
82
|
+
message = stderr_str.to_s.strip
|
|
83
|
+
message.empty? ? 'unknown error' : message
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -4,7 +4,7 @@ module Rails
|
|
|
4
4
|
module Worktrees
|
|
5
5
|
# Runs post-create setup steps in a newly created worktree.
|
|
6
6
|
# Steps run in order: credential linking, bundle install, yarn install,
|
|
7
|
-
# db:prepare (development), db:prepare (test), assets:precompile (test).
|
|
7
|
+
# db:prepare (development), db:prepare (test), assets:precompile (test), assets:clobber.
|
|
8
8
|
# rubocop:disable Metrics/ClassLength
|
|
9
9
|
class PostCreateRunner
|
|
10
10
|
STEPS = [
|
|
@@ -17,7 +17,9 @@ module Rails
|
|
|
17
17
|
{ id: :test_db_prepare, argv: %w[bin/rails db:prepare],
|
|
18
18
|
header: '🗄️ Preparing test database...', env: { 'RAILS_ENV' => 'test' } },
|
|
19
19
|
{ id: :test_assets_precompile, argv: %w[bin/rails assets:precompile],
|
|
20
|
-
header: '🎨 Precompiling test assets...', env: { 'RAILS_ENV' => 'test' } }
|
|
20
|
+
header: '🎨 Precompiling test assets...', env: { 'RAILS_ENV' => 'test' } },
|
|
21
|
+
{ id: :assets_clobber, argv: %w[bin/rails assets:clobber],
|
|
22
|
+
header: '🧹 Clobbering compiled assets...' }
|
|
21
23
|
].freeze
|
|
22
24
|
|
|
23
25
|
STEP_CONFIG = {
|
|
@@ -25,13 +27,15 @@ module Rails
|
|
|
25
27
|
yarn: :run_yarn_install,
|
|
26
28
|
db_prepare: :run_db_prepare,
|
|
27
29
|
test_db_prepare: :run_test_db_prepare,
|
|
28
|
-
test_assets_precompile: :run_test_assets_precompile
|
|
30
|
+
test_assets_precompile: :run_test_assets_precompile,
|
|
31
|
+
assets_clobber: :run_test_assets_precompile
|
|
29
32
|
}.freeze
|
|
30
33
|
|
|
31
|
-
def initialize(target_dir:, peer_roots:, configuration:, io:)
|
|
34
|
+
def initialize(target_dir:, peer_roots:, configuration:, io:, bootstrapped_env: nil)
|
|
32
35
|
@target_dir = target_dir
|
|
33
36
|
@peer_roots = peer_roots
|
|
34
37
|
@configuration = configuration
|
|
38
|
+
@bootstrapped_env = (bootstrapped_env || {}).transform_values(&:to_s)
|
|
35
39
|
@stdout = io.fetch(:stdout)
|
|
36
40
|
@stderr = io.fetch(:stderr)
|
|
37
41
|
end
|
|
@@ -39,9 +43,14 @@ module Rails
|
|
|
39
43
|
def call(dry_run: false)
|
|
40
44
|
return 0 if @configuration.post_create_command == false
|
|
41
45
|
|
|
46
|
+
@runtime_env = resolved_runtime_env(dry_run: dry_run)
|
|
47
|
+
|
|
42
48
|
return run_custom_command(dry_run: dry_run) if custom_command?
|
|
43
49
|
|
|
44
50
|
run_built_in_steps(dry_run: dry_run)
|
|
51
|
+
rescue Error => e
|
|
52
|
+
@stderr.puts("❌ #{e.message}")
|
|
53
|
+
1
|
|
45
54
|
end
|
|
46
55
|
|
|
47
56
|
private
|
|
@@ -118,7 +127,7 @@ module Rails
|
|
|
118
127
|
def capture_shell_command_exit_status(command)
|
|
119
128
|
exit_status = nil
|
|
120
129
|
|
|
121
|
-
Open3.popen2e(
|
|
130
|
+
Open3.popen2e(runtime_env, command, chdir: @target_dir) do |_stdin, output, wait_thread|
|
|
122
131
|
output.each_line { |line| @stdout.print(line) }
|
|
123
132
|
exit_status = wait_thread.value.exitstatus
|
|
124
133
|
end
|
|
@@ -143,7 +152,7 @@ module Rails
|
|
|
143
152
|
1
|
|
144
153
|
end
|
|
145
154
|
|
|
146
|
-
def
|
|
155
|
+
def shell_env
|
|
147
156
|
# Pass through only the essentials so subprocess tools work correctly.
|
|
148
157
|
ENV.to_h.slice('PATH', 'HOME', 'LANG', 'TERM', 'SHELL',
|
|
149
158
|
'BUNDLE_GEMFILE', 'BUNDLE_PATH', 'GEM_HOME', 'GEM_PATH',
|
|
@@ -152,6 +161,21 @@ module Rails
|
|
|
152
161
|
'DEV_PORT', 'WORKTREE_DATABASE_SUFFIX')
|
|
153
162
|
end
|
|
154
163
|
|
|
164
|
+
attr_reader :runtime_env
|
|
165
|
+
|
|
166
|
+
def resolved_runtime_env(dry_run:)
|
|
167
|
+
env = shell_env.merge(@bootstrapped_env)
|
|
168
|
+
return env if dry_run
|
|
169
|
+
|
|
170
|
+
env.merge(toolchain_env)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def toolchain_env
|
|
174
|
+
result = MiseEnvironment.new(target_dir: @target_dir, env: shell_env.merge(@bootstrapped_env)).call
|
|
175
|
+
result.messages.each { |message| info(message) }
|
|
176
|
+
result.env
|
|
177
|
+
end
|
|
178
|
+
|
|
155
179
|
def yarn_lock_present?
|
|
156
180
|
File.exist?(File.join(@target_dir, 'yarn.lock'))
|
|
157
181
|
end
|
|
@@ -163,7 +187,7 @@ module Rails
|
|
|
163
187
|
end
|
|
164
188
|
|
|
165
189
|
def step_command(step)
|
|
166
|
-
[
|
|
190
|
+
[runtime_env.merge(step.fetch(:env, {})), *step.fetch(:argv)]
|
|
167
191
|
end
|
|
168
192
|
|
|
169
193
|
def info(message)
|
|
@@ -22,7 +22,7 @@ module Rails
|
|
|
22
22
|
def call
|
|
23
23
|
return configuration unless initializer_path && File.file?(initializer_path)
|
|
24
24
|
|
|
25
|
-
body = extract_configuration_body(File.read(initializer_path))
|
|
25
|
+
body = extract_configuration_body(File.read(initializer_path, encoding: 'UTF-8'))
|
|
26
26
|
return configuration unless body
|
|
27
27
|
|
|
28
28
|
recorder = AssignmentRecorder.new(configuration)
|
|
@@ -49,6 +49,7 @@ module Rails
|
|
|
49
49
|
# rubocop:enable Style/RedundantStructKeywordInit
|
|
50
50
|
|
|
51
51
|
TEMPLATE_ROOT = File.expand_path('../../generators/rails/worktrees/templates', __dir__)
|
|
52
|
+
FILE_ENCODING = 'UTF-8'.freeze
|
|
52
53
|
|
|
53
54
|
def initialize(root:)
|
|
54
55
|
@root = root
|
|
@@ -100,10 +101,10 @@ module Rails
|
|
|
100
101
|
path = absolute_path(config.fetch(:relative_path))
|
|
101
102
|
return nil if config.fetch(:optional) && !File.exist?(path)
|
|
102
103
|
|
|
103
|
-
desired_content =
|
|
104
|
+
desired_content = read_text(config.fetch(:template_path))
|
|
104
105
|
return template_missing_check(config, desired_content) unless File.exist?(path)
|
|
105
106
|
|
|
106
|
-
current_content =
|
|
107
|
+
current_content = read_text(path)
|
|
107
108
|
if current_content == desired_content
|
|
108
109
|
if executable_mode_current?(config, path)
|
|
109
110
|
return ok_check(config, "#{config.fetch(:relative_path)} is up to date.")
|
|
@@ -168,7 +169,7 @@ module Rails
|
|
|
168
169
|
)
|
|
169
170
|
end
|
|
170
171
|
|
|
171
|
-
updater_result_check(config, InitializerUpdater.new(content:
|
|
172
|
+
updater_result_check(config, InitializerUpdater.new(content: read_text(path)).call)
|
|
172
173
|
end
|
|
173
174
|
|
|
174
175
|
def database_check
|
|
@@ -187,7 +188,7 @@ module Rails
|
|
|
187
188
|
)
|
|
188
189
|
end
|
|
189
190
|
|
|
190
|
-
result = DatabaseConfigUpdater.new(content:
|
|
191
|
+
result = DatabaseConfigUpdater.new(content: read_text(path)).call
|
|
191
192
|
return updated_database_check(config, result) if result.changed?
|
|
192
193
|
|
|
193
194
|
if database_configured?(result)
|
|
@@ -244,7 +245,7 @@ module Rails
|
|
|
244
245
|
path = absolute_path(config.fetch(:relative_path))
|
|
245
246
|
return unless File.exist?(path)
|
|
246
247
|
|
|
247
|
-
updater_result_check(config, yield(
|
|
248
|
+
updater_result_check(config, yield(read_text(path)))
|
|
248
249
|
end
|
|
249
250
|
|
|
250
251
|
def mise_check
|
|
@@ -265,7 +266,7 @@ module Rails
|
|
|
265
266
|
|
|
266
267
|
def mise_updater_result(relative_path)
|
|
267
268
|
MiseTomlUpdater.new(
|
|
268
|
-
content:
|
|
269
|
+
content: read_text(absolute_path(relative_path)),
|
|
269
270
|
file_name: File.basename(relative_path)
|
|
270
271
|
).call
|
|
271
272
|
end
|
|
@@ -319,6 +320,10 @@ module Rails
|
|
|
319
320
|
)
|
|
320
321
|
end
|
|
321
322
|
|
|
323
|
+
def read_text(path)
|
|
324
|
+
File.read(path, encoding: FILE_ENCODING)
|
|
325
|
+
end
|
|
326
|
+
|
|
322
327
|
def absolute_path(relative_path)
|
|
323
328
|
File.join(root, relative_path)
|
|
324
329
|
end
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'pathname'
|
|
|
3
3
|
require_relative 'worktrees/version'
|
|
4
4
|
require_relative 'worktrees/configuration'
|
|
5
5
|
require_relative 'worktrees/application_configuration'
|
|
6
|
+
require_relative 'worktrees/mise_environment'
|
|
6
7
|
require_relative 'worktrees/env_bootstrapper'
|
|
7
8
|
require_relative 'worktrees/credential_key_linker'
|
|
8
9
|
require_relative 'worktrees/post_create_runner'
|
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.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asjer Querido
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- lib/rails/worktrees/database_config_updater.rb
|
|
71
71
|
- lib/rails/worktrees/env_bootstrapper.rb
|
|
72
72
|
- lib/rails/worktrees/initializer_updater.rb
|
|
73
|
+
- lib/rails/worktrees/mise_environment.rb
|
|
73
74
|
- lib/rails/worktrees/mise_toml_updater.rb
|
|
74
75
|
- lib/rails/worktrees/names/cities.txt
|
|
75
76
|
- lib/rails/worktrees/post_create_runner.rb
|