rails-worktree 0.2.0 → 0.3.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/README.md +113 -98
- data/lib/rails-worktree.rb +2 -0
- data/lib/worktree/cli.rb +76 -55
- data/lib/worktree/clone_database.rb +23 -0
- data/lib/worktree/commands/close.rb +71 -56
- data/lib/worktree/commands/create.rb +9 -24
- data/lib/worktree/commands/init.rb +240 -123
- data/lib/worktree/config.rb +114 -0
- data/lib/worktree/context.rb +7 -6
- data/lib/worktree/copy_sqlite.rb +7 -0
- data/lib/worktree/database_probe.rb +27 -0
- data/lib/worktree/installer.rb +151 -4
- data/lib/worktree/managed_block.rb +32 -0
- data/lib/worktree/push_hook.rb +15 -2
- data/lib/worktree/railtie.rb +3 -5
- data/lib/worktree/tasks.rb +5 -4
- data/lib/worktree/version.rb +1 -1
- metadata +7 -2
|
@@ -2,20 +2,18 @@ require "fileutils"
|
|
|
2
2
|
|
|
3
3
|
module RailsWorktree
|
|
4
4
|
module Commands
|
|
5
|
+
# Drops the worktree-local databases recorded in .env.*.local, removes the
|
|
6
|
+
# checkout and deletes its branch.
|
|
5
7
|
class Close
|
|
6
8
|
def initialize(args)
|
|
7
9
|
@worktree_name = args[0]
|
|
10
|
+
@named = !@worktree_name.nil?
|
|
8
11
|
@main_worktree = get_main_worktree
|
|
9
12
|
@current_dir = Dir.pwd
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
def run
|
|
13
16
|
detect_worktree_name unless @worktree_name
|
|
14
|
-
|
|
15
|
-
@db_prefix = get_db_prefix
|
|
16
|
-
@dev_database_name = "#{@db_prefix}_#{@worktree_name}_development"
|
|
17
|
-
@test_database_name = "#{@db_prefix}_#{@worktree_name}_test"
|
|
18
|
-
|
|
19
17
|
detect_paths
|
|
20
18
|
|
|
21
19
|
puts "Closing worktree '#{@worktree_name}'..."
|
|
@@ -33,12 +31,10 @@ module RailsWorktree
|
|
|
33
31
|
|
|
34
32
|
puts ""
|
|
35
33
|
puts "✓ Worktree '#{@worktree_name}' closed successfully!"
|
|
36
|
-
puts " Databases dropped: #{@dev_database_name}, #{@test_database_name}"
|
|
37
|
-
puts " node_modules removed"
|
|
38
34
|
puts " Worktree removed from #{@worktree_path}"
|
|
39
|
-
puts " Branch #{@
|
|
35
|
+
puts " Branch #{@branch} deleted" if @branch_deleted
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
if @worktree_path == @current_dir
|
|
42
38
|
puts ""
|
|
43
39
|
puts "Run: cd #{@main_worktree}"
|
|
44
40
|
end
|
|
@@ -53,88 +49,107 @@ module RailsWorktree
|
|
|
53
49
|
|
|
54
50
|
def detect_worktree_name
|
|
55
51
|
if @current_dir == @main_worktree
|
|
56
|
-
|
|
57
|
-
puts "Usage: worktree --close <worktree-name>"
|
|
58
|
-
puts " or: cd to the worktree and run: worktree --close"
|
|
59
|
-
exit 1
|
|
52
|
+
raise Error, "Specify a worktree name when closing from the main repository: bin/worktree close NAME"
|
|
60
53
|
else
|
|
61
54
|
@worktree_name = File.basename(@current_dir)
|
|
62
55
|
puts "Detected worktree name: #{@worktree_name}"
|
|
63
56
|
end
|
|
64
57
|
end
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
# Finds the checkout in git's worktree list by directory name or branch, so
|
|
60
|
+
# worktrees outside .worktrees/ (git worktree add, herdr, Claude Code) close too.
|
|
61
|
+
# An explicit name wins over the current directory, so closing another
|
|
62
|
+
# worktree from inside one leaves the current checkout alone.
|
|
63
|
+
def detect_paths
|
|
64
|
+
@in_main_repo = @current_dir == @main_worktree
|
|
65
|
+
entries = worktree_entries.reject { |entry| entry[:path] == @main_worktree }
|
|
66
|
+
entry = if @named
|
|
67
|
+
entries.find { |candidate| candidate[:path] == File.join(@main_worktree, ".worktrees", @worktree_name) } ||
|
|
68
|
+
entries.find { |candidate| File.basename(candidate[:path]) == @worktree_name || candidate[:branch] == @worktree_name }
|
|
69
|
+
else
|
|
70
|
+
entries.find { |candidate| candidate[:path] == @current_dir }
|
|
73
71
|
end
|
|
72
|
+
raise Error, "No worktree named #{@worktree_name}; see git worktree list." if @named && !entry
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
@worktree_path = entry ? entry[:path] : @current_dir
|
|
75
|
+
@branch = entry ? entry[:branch] : @worktree_name
|
|
77
76
|
end
|
|
78
77
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
def worktree_entries
|
|
79
|
+
`git worktree list --porcelain`.split("\n\n").map do |block|
|
|
80
|
+
lines = block.lines.map(&:strip)
|
|
81
|
+
path = lines.find { |line| line.start_with?("worktree ") }&.delete_prefix("worktree ")
|
|
82
|
+
branch = lines.find { |line| line.start_with?("branch ") }&.delete_prefix("branch refs/heads/")
|
|
83
|
+
{path: path, branch: branch}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def config
|
|
88
|
+
@config ||= Config.load(File.exist?(File.join(@worktree_path, Config::FILE)) ? @worktree_path : @main_worktree)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Databases named in the worktree's .env.development.local and .env.test.local.
|
|
92
|
+
# Without those files the checkout shares the main databases, which stay untouched.
|
|
93
|
+
def worktree_databases
|
|
94
|
+
Init::ENV_FILES.filter_map do |environment, file|
|
|
95
|
+
path = File.join(@worktree_path, file)
|
|
96
|
+
next unless File.file?(path)
|
|
97
|
+
|
|
98
|
+
variable = environment == "test" ? config.test_variable : config.development_variable
|
|
99
|
+
match = File.read(path).match(/^#{Regexp.escape(variable)}=(.+)$/)
|
|
100
|
+
[environment, variable, match[1].strip] if match
|
|
88
101
|
end
|
|
89
102
|
end
|
|
90
103
|
|
|
91
104
|
def drop_databases
|
|
92
|
-
|
|
105
|
+
databases = worktree_databases
|
|
106
|
+
if databases.empty?
|
|
107
|
+
puts "No worktree-local databases in .env.development.local or .env.test.local; nothing to drop"
|
|
108
|
+
return
|
|
109
|
+
end
|
|
110
|
+
unless File.executable?(File.join(@worktree_path, "bin/rails"))
|
|
111
|
+
puts "Warning: bin/rails not found in worktree, skipping database drop"
|
|
112
|
+
return
|
|
113
|
+
end
|
|
93
114
|
|
|
94
115
|
Dir.chdir(@worktree_path) do
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
puts "Warning: bin/rails not found in worktree, skipping database drop"
|
|
116
|
+
databases.each do |environment, variable, database|
|
|
117
|
+
puts "Dropping #{database}..."
|
|
118
|
+
env = {"RAILS_ENV" => environment, variable => database, "DISABLE_DATABASE_ENVIRONMENT_CHECK" => "1"}
|
|
119
|
+
system(env, "bin/rails", "db:drop") || puts("Warning: could not drop #{database}")
|
|
100
120
|
end
|
|
101
121
|
end
|
|
102
122
|
end
|
|
103
123
|
|
|
104
124
|
def remove_node_modules
|
|
105
|
-
|
|
125
|
+
node_modules_path = File.join(@worktree_path, "node_modules")
|
|
126
|
+
return unless File.exist?(node_modules_path) || File.symlink?(node_modules_path)
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
FileUtils.rm_rf(node_modules_path)
|
|
110
|
-
puts "node_modules removed"
|
|
111
|
-
else
|
|
112
|
-
puts "No node_modules to remove"
|
|
113
|
-
end
|
|
128
|
+
puts "Removing node_modules..."
|
|
129
|
+
FileUtils.rm_rf(node_modules_path)
|
|
114
130
|
end
|
|
115
131
|
|
|
116
132
|
def remove_worktree
|
|
117
133
|
puts "Removing worktree..."
|
|
118
|
-
|
|
119
|
-
if system("git worktree remove #{@worktree_path} --force 2>/dev/null")
|
|
134
|
+
if system(Context::GIT_ENVIRONMENT, "git", "worktree", "remove", "--force", @worktree_path, err: File::NULL)
|
|
120
135
|
puts "Worktree removed successfully via git"
|
|
121
|
-
|
|
136
|
+
elsif Dir.exist?(@worktree_path)
|
|
122
137
|
puts "Git worktree remove failed, deleting directory manually..."
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
puts "Directory deleted: #{@worktree_path}"
|
|
126
|
-
end
|
|
138
|
+
FileUtils.rm_rf(@worktree_path)
|
|
139
|
+
puts "Directory deleted: #{@worktree_path}"
|
|
127
140
|
end
|
|
128
141
|
end
|
|
129
142
|
|
|
130
143
|
def prune_worktrees
|
|
131
|
-
system("git worktree prune")
|
|
144
|
+
system("git", "worktree", "prune")
|
|
132
145
|
end
|
|
133
146
|
|
|
134
147
|
def delete_branch
|
|
135
|
-
puts
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
return puts("No branch to delete (detached HEAD)") unless @branch
|
|
149
|
+
|
|
150
|
+
puts "Deleting branch #{@branch}..."
|
|
151
|
+
@branch_deleted = system("git", "branch", "-D", @branch, err: File::NULL)
|
|
152
|
+
puts("Warning: Could not delete branch #{@branch}") unless @branch_deleted
|
|
138
153
|
end
|
|
139
154
|
end
|
|
140
155
|
end
|
|
@@ -2,20 +2,19 @@ require "fileutils"
|
|
|
2
2
|
|
|
3
3
|
module RailsWorktree
|
|
4
4
|
module Commands
|
|
5
|
+
# Creates a worktree under .worktrees/ from an explicit base, then provisions it.
|
|
5
6
|
class Create
|
|
6
|
-
def initialize(args, skip_seeds:
|
|
7
|
+
def initialize(args, level: nil, seed: false, force: false, skip_seeds: nil)
|
|
7
8
|
@worktree_name = args[0]
|
|
8
9
|
@base_branch = args[1]
|
|
9
|
-
@
|
|
10
|
+
@level = level
|
|
11
|
+
@seed = seed
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def run
|
|
13
|
-
unless @worktree_name
|
|
14
|
-
|
|
15
|
-
exit 1
|
|
16
|
-
end
|
|
15
|
+
raise Error, "Worktree name is required: bin/worktree create NAME BASE" unless @worktree_name
|
|
16
|
+
raise Error, "Base branch required: bin/worktree create #{@worktree_name} BASE (for example origin/main)." unless @base_branch
|
|
17
17
|
|
|
18
|
-
raise Error, "Base branch required: bin/worktree #{@worktree_name} BASE (for example origin/main)." unless @base_branch
|
|
19
18
|
context = Context.new
|
|
20
19
|
Dir.chdir(context.root) { create }
|
|
21
20
|
end
|
|
@@ -37,32 +36,18 @@ module RailsWorktree
|
|
|
37
36
|
Installer.ignore_worktrees(Dir.pwd)
|
|
38
37
|
FileUtils.mkdir_p(worktree_dir)
|
|
39
38
|
|
|
40
|
-
puts "Creating worktree '#{@worktree_name}' from
|
|
41
|
-
|
|
39
|
+
puts "Creating worktree '#{@worktree_name}' from #{@base_branch} at #{worktree_path}..."
|
|
42
40
|
unless system(Context::GIT_ENVIRONMENT, "git", "worktree", "add", "-b", @worktree_name, worktree_path, base_commit)
|
|
43
|
-
|
|
44
|
-
exit 1
|
|
41
|
+
raise Error, "Failed to create worktree"
|
|
45
42
|
end
|
|
46
43
|
|
|
47
|
-
puts ""
|
|
48
|
-
puts "✓ Worktree created at #{worktree_path}"
|
|
49
|
-
puts ""
|
|
50
|
-
puts "Initializing worktree..."
|
|
51
|
-
|
|
52
44
|
Dir.chdir(worktree_path) do
|
|
53
|
-
|
|
54
|
-
created.record(@base_branch, base_commit, @worktree_name)
|
|
55
|
-
created.verify
|
|
56
|
-
Installer.install(created.root, replace: true)
|
|
57
|
-
PushHook.install(created.root)
|
|
58
|
-
Init.new([@worktree_name], skip_seeds: @skip_seeds).run
|
|
45
|
+
Init.new([@worktree_name], level: @level, seed: @seed, base: @base_branch).run
|
|
59
46
|
end
|
|
60
47
|
|
|
61
48
|
puts ""
|
|
62
49
|
puts "To switch to the new worktree:"
|
|
63
50
|
puts " cd #{absolute_path}"
|
|
64
|
-
puts ""
|
|
65
|
-
puts "To start the development server: bin/dev"
|
|
66
51
|
end
|
|
67
52
|
end
|
|
68
53
|
end
|