rails-worktree 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e8888a53c51f48da7d20802a75aa290fedafb9a4facb449276ac98f03a35a2ca
4
+ data.tar.gz: a6609cd5f4d380f0c62acb8a7bae58079e9c995c032ef04efb2b815386edc612
5
+ SHA512:
6
+ metadata.gz: 28b6e35bbe05918a9ae07a794ce883e4c791e20f71d3fb1956b46a1b4c410ed7ee3f5c5382e914fcc2c90a5f78b25428b8a2a03a00a29c4b1191dce5df8d2706
7
+ data.tar.gz: d64ef8946b28c67a3f34e6602eed6903a8a2bb27492f576167453e2269ede2235e351ce985153495cb780e4469206da6a12d4274acf74a39b66fd81296e2d2e5
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Rails Worktree
2
+
3
+ A Ruby gem for managing git worktrees in Rails projects with isolated databases and configurations.
4
+
5
+ ## Features
6
+
7
+ - Create git worktrees with automatic branch creation
8
+ - Isolated database per worktree (separate database name)
9
+ - Automatic configuration file copying (.env, database.yml, etc.)
10
+ - Copy node_modules from main worktree
11
+ - Easy cleanup with database dropping and directory removal
12
+ - Works across any Rails project (no hardcoded paths)
13
+
14
+ ## Installation
15
+
16
+ ### In your Rails project Gemfile
17
+
18
+ ```ruby
19
+ # Add to Gemfile (development group)
20
+ group :development do
21
+ gem "rails-worktree"
22
+ end
23
+ ```
24
+
25
+ Then run:
26
+
27
+ ```bash
28
+ bundle install
29
+ ```
30
+
31
+ The gem will automatically install a binstub to `bin/worktree` when Rails loads in development mode.
32
+
33
+ ### Manual binstub installation
34
+
35
+ If needed, you can manually install or reinstall the binstub:
36
+
37
+ ```bash
38
+ bundle exec rake worktree:install
39
+ ```
40
+
41
+ To uninstall:
42
+
43
+ ```bash
44
+ bundle exec rake worktree:uninstall
45
+ ```
46
+
47
+ ### Global installation (optional)
48
+
49
+ If you want to use `worktree` command globally:
50
+
51
+ ```bash
52
+ gem install rails-worktree
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ### Create a new worktree
58
+
59
+ ```bash
60
+ bin/worktree feature-branch
61
+ # Creates worktree from current branch
62
+
63
+ bin/worktree feature-branch main
64
+ # Creates worktree from main branch
65
+ ```
66
+
67
+ This will:
68
+ 1. Create a new git worktree in `../feature-branch`
69
+ 2. Create a new branch called `feature-branch`
70
+ 3. Copy configuration files from main worktree
71
+ 4. Set up a separate database (e.g., `myapp_feature-branch`)
72
+ 5. Copy node_modules
73
+ 6. Run migrations and seed the database
74
+
75
+ ### Close a worktree
76
+
77
+ ```bash
78
+ # From main repository:
79
+ bin/worktree --close feature-branch
80
+
81
+ # From within the worktree:
82
+ bin/worktree --close
83
+ ```
84
+
85
+ This will:
86
+ 1. Drop the worktree's database
87
+ 2. Remove the worktree directory
88
+ 3. Delete the branch
89
+ 4. Clean up git worktree references
90
+
91
+ ### Manual initialization (advanced)
92
+
93
+ If you need to manually initialize a worktree:
94
+
95
+ ```bash
96
+ cd ../feature-branch
97
+ worktree --init feature-branch
98
+ ```
99
+
100
+ ## How it works
101
+
102
+ ### Database isolation
103
+
104
+ The gem automatically:
105
+ - Detects your main database name from `config/database.yml`
106
+ - Creates a new database with pattern: `{app_name}_{worktree_name}`
107
+ - Updates the worktree's `.env` to set `DATABASE_NAME`
108
+ - Modifies `database.yml` to use the `DATABASE_NAME` environment variable
109
+
110
+ ### Configuration files
111
+
112
+ The following files are copied from the main worktree:
113
+ - `.env`
114
+ - `config/database.yml`
115
+ - `Procfile.dev`
116
+ - `config/credentials/development.key`
117
+
118
+ ### Node modules
119
+
120
+ If `node_modules` exists in the main worktree, it will be copied to the new worktree.
121
+
122
+ ## Requirements
123
+
124
+ - Ruby >= 2.6.0
125
+ - Git with worktree support
126
+ - Rails project with standard structure
127
+
128
+ ## Project structure
129
+
130
+ This gem works with Rails projects that follow the standard structure:
131
+
132
+ ```
133
+ your-project/
134
+ ├── config/
135
+ │ ├── database.yml
136
+ │ └── credentials/
137
+ │ └── development.key
138
+ ├── .env
139
+ ├── Procfile.dev
140
+ └── bin/
141
+ ├── rails
142
+ └── dev
143
+ ```
144
+
145
+ ## Development
146
+
147
+ To work on the gem:
148
+
149
+ ```bash
150
+ # Clone or create the gem
151
+ cd worktree
152
+
153
+ # Make changes to lib/worktree/**
154
+
155
+ # Test locally
156
+ gem build worktree.gemspec
157
+ gem install ./worktree-0.1.0.gem
158
+
159
+ # Test in a Rails project
160
+ cd /path/to/rails/project
161
+ worktree test-branch
162
+ ```
163
+
164
+ ## License
165
+
166
+ MIT
167
+
168
+ ## Contributing
169
+
170
+ 1. Fork it
171
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
172
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
173
+ 4. Push to the branch (`git push origin my-new-feature`)
174
+ 5. Create new Pull Request
data/exe/worktree ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "worktree"
4
+
5
+ Worktree::CLI.run(ARGV)
@@ -0,0 +1,12 @@
1
+ require_relative "worktree/version"
2
+ require_relative "worktree/cli"
3
+ require_relative "worktree/commands/create"
4
+ require_relative "worktree/commands/init"
5
+ require_relative "worktree/commands/close"
6
+
7
+ # Load Railtie only if Rails is available
8
+ require_relative "worktree/railtie" if defined?(Rails::Railtie)
9
+
10
+ module Worktree
11
+ class Error < StandardError; end
12
+ end
@@ -0,0 +1,59 @@
1
+ module Worktree
2
+ class CLI
3
+ def self.run(args)
4
+ new(args).run
5
+ end
6
+
7
+ def initialize(args)
8
+ @args = args
9
+ end
10
+
11
+ def run
12
+ if @args.empty?
13
+ print_usage
14
+ exit 1
15
+ end
16
+
17
+ case @args[0]
18
+ when "--close", "close"
19
+ @args.shift
20
+ Commands::Close.new(@args).run
21
+ when "--init", "init"
22
+ @args.shift
23
+ Commands::Init.new(@args).run
24
+ when "--help", "-h", "help"
25
+ print_usage
26
+ else
27
+ # Default: create worktree
28
+ Commands::Create.new(@args).run
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def print_usage
35
+ puts <<~USAGE
36
+ Usage: worktree <name> [base-branch]
37
+ worktree --close [worktree-name]
38
+ worktree --init <worktree-name>
39
+
40
+ Creates a new git worktree and initializes it with configuration
41
+
42
+ Commands:
43
+ <name> Create a new worktree with the given name
44
+ --close [name] Close and remove a worktree
45
+ --init <name> Initialize a worktree (usually called automatically)
46
+ --help, -h Show this help message
47
+
48
+ Arguments:
49
+ <name> Name of the worktree (required)
50
+ [base-branch] Branch to create worktree from (default: current branch)
51
+
52
+ Examples:
53
+ worktree feature-branch # Create new worktree
54
+ worktree --close feature-branch # Close worktree from main repo
55
+ worktree --close # Close worktree from within it
56
+ USAGE
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,118 @@
1
+ require "fileutils"
2
+
3
+ module Worktree
4
+ module Commands
5
+ class Close
6
+ def initialize(args)
7
+ @worktree_name = args[0]
8
+ @main_worktree = get_main_worktree
9
+ @current_dir = Dir.pwd
10
+ end
11
+
12
+ def run
13
+ detect_worktree_name unless @worktree_name
14
+
15
+ @db_prefix = get_db_prefix
16
+ @database_name = "#{@db_prefix}_#{@worktree_name}"
17
+
18
+ detect_paths
19
+
20
+ puts "Closing worktree '#{@worktree_name}'..."
21
+ puts "Main worktree: #{@main_worktree}"
22
+ puts ""
23
+
24
+ drop_database
25
+ remove_worktree
26
+ prune_worktrees
27
+ delete_branch
28
+
29
+ puts ""
30
+ puts "✓ Worktree '#{@worktree_name}' closed successfully!"
31
+ puts " Database #{@database_name} dropped"
32
+ puts " Worktree removed from #{@worktree_path}"
33
+ puts " Branch #{@worktree_name} deleted"
34
+ end
35
+
36
+ private
37
+
38
+ def get_main_worktree
39
+ output = `git worktree list --porcelain`
40
+ output.lines.grep(/^worktree /).first&.split(" ", 2)&.last&.strip
41
+ end
42
+
43
+ def detect_worktree_name
44
+ if @current_dir == @main_worktree
45
+ puts "Error: You must specify a worktree name when running from the main repository"
46
+ puts "Usage: worktree --close <worktree-name>"
47
+ puts " or: cd to the worktree and run: worktree --close"
48
+ exit 1
49
+ else
50
+ @worktree_name = File.basename(@current_dir)
51
+ puts "Detected worktree name: #{@worktree_name}"
52
+ end
53
+ end
54
+
55
+ def get_db_prefix
56
+ database_yml = File.join(@main_worktree, "config/database.yml")
57
+ return nil unless File.exist?(database_yml)
58
+
59
+ content = File.read(database_yml)
60
+ match = content.match(/database:\s*(\w+)_development/)
61
+ match ? match[1] : nil
62
+ end
63
+
64
+ def detect_paths
65
+ if @current_dir == @main_worktree
66
+ @in_main_repo = true
67
+ @worktree_path = File.join(File.dirname(@main_worktree), @worktree_name)
68
+ @worktree_dir = @worktree_path
69
+ else
70
+ @in_main_repo = false
71
+ @worktree_path = @current_dir
72
+ @worktree_dir = "."
73
+ end
74
+ end
75
+
76
+ def drop_database
77
+ puts "Dropping database #{@database_name}..."
78
+
79
+ Dir.chdir(@worktree_dir) do
80
+ env_file = ".env"
81
+ if File.exist?(env_file) && File.read(env_file).match?(/^DATABASE_NAME=#{@database_name}/)
82
+ system("RAILS_ENV=development bin/rails db:drop 2>/dev/null") ||
83
+ puts("Warning: Could not drop database #{@database_name}")
84
+ else
85
+ puts "Warning: DATABASE_NAME not set to #{@database_name} in .env, skipping database drop"
86
+ end
87
+ end
88
+ end
89
+
90
+ def remove_worktree
91
+ puts "Removing worktree..."
92
+
93
+ # Change back to main repo if needed
94
+ Dir.chdir(@main_worktree) unless @in_main_repo
95
+
96
+ if system("git worktree remove #{@worktree_path} --force 2>/dev/null")
97
+ puts "Worktree removed successfully via git"
98
+ else
99
+ puts "Git worktree remove failed, deleting directory manually..."
100
+ if Dir.exist?(@worktree_path)
101
+ FileUtils.rm_rf(@worktree_path)
102
+ puts "Directory deleted: #{@worktree_path}"
103
+ end
104
+ end
105
+ end
106
+
107
+ def prune_worktrees
108
+ system("git worktree prune")
109
+ end
110
+
111
+ def delete_branch
112
+ puts "Deleting branch #{@worktree_name}..."
113
+ system("git branch -D #{@worktree_name} 2>/dev/null") ||
114
+ puts("Warning: Could not delete branch #{@worktree_name}")
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,42 @@
1
+ module Worktree
2
+ module Commands
3
+ class Create
4
+ def initialize(args)
5
+ @worktree_name = args[0]
6
+ @base_branch = args[1]
7
+ end
8
+
9
+ def run
10
+ unless @worktree_name
11
+ puts "Error: Worktree name is required"
12
+ exit 1
13
+ end
14
+
15
+ @base_branch ||= current_branch
16
+ worktree_path = "../#{@worktree_name}"
17
+
18
+ puts "Creating worktree '#{@worktree_name}' from branch '#{@base_branch}' at #{worktree_path}..."
19
+
20
+ unless system("git worktree add -b #{@worktree_name} #{worktree_path} #{@base_branch}")
21
+ puts "Failed to create worktree"
22
+ exit 1
23
+ end
24
+
25
+ puts ""
26
+ puts "✓ Worktree created at #{worktree_path}"
27
+ puts ""
28
+ puts "Initializing worktree..."
29
+
30
+ Dir.chdir(worktree_path) do
31
+ Init.new([@worktree_name]).run
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def current_branch
38
+ `git branch --show-current`.strip
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,130 @@
1
+ require "fileutils"
2
+
3
+ module Worktree
4
+ module Commands
5
+ class Init
6
+ def initialize(args)
7
+ @worktree_name = args[0]
8
+ end
9
+
10
+ def run
11
+ unless @worktree_name
12
+ puts "Error: Worktree name is required"
13
+ puts "Usage: worktree --init <worktree-name>"
14
+ exit 1
15
+ end
16
+
17
+ @main_worktree = get_main_worktree
18
+ @db_prefix = get_db_prefix
19
+ @database_name = "#{@db_prefix}_#{@worktree_name}"
20
+
21
+ puts "Initializing worktree '#{@worktree_name}'..."
22
+ puts "Main worktree: #{@main_worktree}"
23
+ puts "Database: #{@database_name}"
24
+
25
+ copy_config_files
26
+ set_database_name
27
+ update_database_yml
28
+ copy_node_modules
29
+ setup_database
30
+
31
+ puts ""
32
+ puts "✓ Worktree initialized successfully!"
33
+ puts " Database: #{@database_name}"
34
+ puts " Configuration files copied"
35
+ puts ""
36
+ puts "To start the development server: bin/dev"
37
+ end
38
+
39
+ private
40
+
41
+ def get_main_worktree
42
+ output = `git worktree list --porcelain`
43
+ output.lines.grep(/^worktree /).first&.split(" ", 2)&.last&.strip
44
+ end
45
+
46
+ def get_db_prefix
47
+ database_yml = File.join(@main_worktree, "config/database.yml")
48
+ return nil unless File.exist?(database_yml)
49
+
50
+ content = File.read(database_yml)
51
+ match = content.match(/database:\s*(\w+)_development/)
52
+ match ? match[1] : nil
53
+ end
54
+
55
+ def copy_config_files
56
+ puts "Copying configuration files..."
57
+
58
+ files_to_copy = [
59
+ ".env",
60
+ "config/database.yml",
61
+ "Procfile.dev",
62
+ "config/credentials/development.key"
63
+ ]
64
+
65
+ files_to_copy.each do |file|
66
+ source = File.join(@main_worktree, file)
67
+ if File.exist?(source)
68
+ FileUtils.mkdir_p(File.dirname(file)) unless File.directory?(File.dirname(file))
69
+ FileUtils.cp(source, file)
70
+ else
71
+ puts "Warning: #{file} not found, skipping"
72
+ end
73
+ end
74
+ end
75
+
76
+ def set_database_name
77
+ puts "Setting DATABASE_NAME=#{@database_name} in .env..."
78
+
79
+ env_file = ".env"
80
+ return unless File.exist?(env_file)
81
+
82
+ content = File.read(env_file)
83
+ if content.match?(/^DATABASE_NAME=/)
84
+ content.gsub!(/^DATABASE_NAME=.*$/, "DATABASE_NAME=#{@database_name}")
85
+ else
86
+ content += "\nDATABASE_NAME=#{@database_name}\n"
87
+ end
88
+
89
+ File.write(env_file, content)
90
+ end
91
+
92
+ def update_database_yml
93
+ puts "Updating database.yml to use DATABASE_NAME..."
94
+
95
+ database_yml = "config/database.yml"
96
+ return unless File.exist?(database_yml)
97
+
98
+ content = File.read(database_yml)
99
+ content.gsub!(
100
+ /database:\s*#{@db_prefix}_development/,
101
+ "database: <%= ENV.fetch(\"DATABASE_NAME\", \"#{@db_prefix}_development\") %>"
102
+ )
103
+
104
+ File.write(database_yml, content)
105
+ end
106
+
107
+ def copy_node_modules
108
+ source = File.join(@main_worktree, "node_modules")
109
+ dest = "node_modules"
110
+
111
+ if Dir.exist?(source) && !Dir.exist?(dest)
112
+ puts "Copying node_modules from main worktree..."
113
+ FileUtils.cp_r(source, dest)
114
+ puts "Note: node_modules copied."
115
+ end
116
+ end
117
+
118
+ def setup_database
119
+ puts "Creating database #{@database_name}..."
120
+ system("RAILS_ENV=development bin/rails db:create") || puts("Warning: Could not create database")
121
+
122
+ puts "Running migrations..."
123
+ system("RAILS_ENV=development bin/rails db:migrate") || puts("Warning: Could not run migrations")
124
+
125
+ puts "Seeding database..."
126
+ system("RAILS_ENV=development bin/rails db:seed") || puts("Warning: Could not seed database")
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,34 @@
1
+ require "rails/railtie"
2
+
3
+ module Worktree
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :worktree
6
+
7
+ rake_tasks do
8
+ load "worktree/tasks.rb"
9
+ end
10
+
11
+ initializer "worktree.install_binstub" do
12
+ # Install binstub automatically when Rails loads in development
13
+ if Rails.env.development?
14
+ binstub_path = Rails.root.join("bin/worktree")
15
+
16
+ unless File.exist?(binstub_path)
17
+ Rails.logger.info "Installing worktree binstub to bin/worktree..."
18
+
19
+ File.write(binstub_path, <<~RUBY)
20
+ #!/usr/bin/env ruby
21
+
22
+ require "bundler/setup"
23
+ require "worktree"
24
+
25
+ Worktree::CLI.run(ARGV)
26
+ RUBY
27
+
28
+ FileUtils.chmod("+x", binstub_path)
29
+ Rails.logger.info "✓ Worktree binstub installed! Use: bin/worktree <name>"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ namespace :worktree do
2
+ desc "Install worktree binstub to bin/worktree"
3
+ task :install do
4
+ binstub_path = File.join(Dir.pwd, "bin/worktree")
5
+
6
+ if File.exist?(binstub_path)
7
+ puts "Binstub already exists at bin/worktree"
8
+ exit 0
9
+ end
10
+
11
+ File.write(binstub_path, <<~RUBY)
12
+ #!/usr/bin/env ruby
13
+
14
+ require "bundler/setup"
15
+ require "worktree"
16
+
17
+ Worktree::CLI.run(ARGV)
18
+ RUBY
19
+
20
+ File.chmod(0755, binstub_path)
21
+
22
+ puts "✓ Worktree binstub installed to bin/worktree"
23
+ puts "Usage: bin/worktree <name>"
24
+ end
25
+
26
+ desc "Uninstall worktree binstub from bin/worktree"
27
+ task :uninstall do
28
+ binstub_path = File.join(Dir.pwd, "bin/worktree")
29
+
30
+ if File.exist?(binstub_path)
31
+ File.delete(binstub_path)
32
+ puts "✓ Worktree binstub removed from bin/worktree"
33
+ else
34
+ puts "Binstub not found at bin/worktree"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Worktree
2
+ VERSION = "0.1.0"
3
+ end
data/lib/worktree.rb ADDED
@@ -0,0 +1,12 @@
1
+ require_relative "worktree/version"
2
+ require_relative "worktree/cli"
3
+ require_relative "worktree/commands/create"
4
+ require_relative "worktree/commands/init"
5
+ require_relative "worktree/commands/close"
6
+
7
+ # Load Railtie only if Rails is available
8
+ require_relative "worktree/railtie" if defined?(Rails::Railtie)
9
+
10
+ module Worktree
11
+ class Error < StandardError; end
12
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-worktree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Ulleberg
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Easily create, initialize, and close git worktrees with isolated databases
13
+ and configurations for Rails projects
14
+ email:
15
+ - martin@fasttravel.com
16
+ executables:
17
+ - worktree
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - exe/worktree
23
+ - lib/rails-worktree.rb
24
+ - lib/worktree.rb
25
+ - lib/worktree/cli.rb
26
+ - lib/worktree/commands/close.rb
27
+ - lib/worktree/commands/create.rb
28
+ - lib/worktree/commands/init.rb
29
+ - lib/worktree/railtie.rb
30
+ - lib/worktree/tasks.rb
31
+ - lib/worktree/version.rb
32
+ homepage: https://github.com/FastTravelAS/rails-worktree
33
+ licenses:
34
+ - MIT
35
+ metadata:
36
+ homepage_uri: https://github.com/FastTravelAS/rails-worktree
37
+ source_code_uri: https://github.com/FastTravelAS/rails-worktree
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.6.0
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.6.9
53
+ specification_version: 4
54
+ summary: Git worktree management for Rails projects
55
+ test_files: []