0.0
No release in over 3 years
Rails::Worktrees is a Ruby gem intended to support working with git worktrees in Rails development workflows.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 7.1, < 8.2
 Project Readme

Rails::Worktrees

rails-worktrees adds Rails-friendly bin/wt and bin/ob commands for creating Git worktrees and opening the right local browser URL for each worktree.

Requirements

  • Ruby >= 3.2.0
  • Rails >= 7.1, < 8.2
  • Git

Installation

bundle add rails-worktrees --group development
bin/rails generate worktrees:install
# or, to also generate bin/ob without the yolo follow-ups:
bin/rails generate worktrees:install --browser
# or, to apply the common Procfile.dev + Puma + mise follow-ups automatically:
bin/rails generate worktrees:install --yolo

The generated initializer writes app config under Rails.application.config.x.rails_worktrees, so the file stays safe to load even in environments like test where the gem is only bundled for :development. When rails-worktrees is loaded, the gem applies those settings for both the CLI and in-process Rails usage.

The installer adds:

  • bin/wt — a thin wrapper that executes the gem-owned CLI
  • bin/ob — an optional browser helper generated by --browser or --yolo
  • config/initializers/rails_worktrees.rb — optional configuration
  • Procfile.dev.worktree.example — a copy-paste helper for ${DEV_PORT:-3000} in Procfile.dev on regular installs
  • a safe update to config/database.yml for common development/test database names

With --yolo, the installer also:

  • generates bin/ob
  • skips Procfile.dev.worktree.example
  • replaces the existing web: entry in Procfile.dev with the DEV_PORT-aware command when Procfile.dev already exists
  • updates config/puma.rb to use port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000) when it still uses a supported default PORT binding
  • updates mise.toml or .mise.toml to load .env from [env] when either file already exists

Usage

bin/wt                          # auto-pick a name from bundled *.txt lists
bin/wt my-feature               # use an explicit worktree name
bin/wt --skip-setup my-feature  # create now, run setup later
bin/wt --dry-run my-feature     # preview the full setup without changing anything
bin/wt --print-env my-feature   # preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
bin/wt setup                    # rerun setup for the current checkout/worktree
bin/wt setup my-feature         # rerun setup for a managed worktree by name
bin/wt setup ../my-project.worktrees/my-feature # setup a specific checkout without cd-ing into it
bin/wt doctor                   # audit install/config drift and basic worktree health
bin/wt update --dry-run         # preview safe maintenance fixes
bin/wt update                   # apply safe maintenance fixes for managed files
bin/wt remove my-feature        # remove a worktree and delete its local branch
bin/wt delete my-feature        # alias for `bin/wt remove`
bin/wt remove --force my-feature # also delete an unmerged local branch
bin/wt prune                    # remove merged worktrees created by wt

bin/ob                          # open http://localhost:$DEV_PORT/
bin/ob contact                  # open http://localhost:$DEV_PORT/contact
bin/ob '/contact?from=nav'      # open a local route with query params
bin/ob --print-url '?from=nav'  # print the resolved URL without opening a browser

Options

Flag Description
-h, --help Show the help message
-v, --version Show the script version
--dry-run [name] Preview worktree creation or cleanup without changing anything
--skip-setup Create a worktree without running setup steps
--force Force branch deletion for bin/wt remove / bin/wt delete
--env, --print-env <name> Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX

Default behavior

By default bin/wt:

  • creates a sibling directory next to your app: workspace/my-project.worktrees/<name>
  • uses a branch named 🚂/<name>
  • creates new branches from origin's default branch
  • auto-picks names from bundled .txt files when no explicit name is given
  • retires bundled names so they are not picked twice
  • bootstraps a worktree-local .env with deterministic DEV_PORT and WORKTREE_DATABASE_SUFFIX values
  • 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
workspace/
├── my-project/
└── my-project.worktrees/
    ├── feature-auth/
    ├── bugfix-123/
    └── experiment/

WT_WORKSPACES_ROOT or config.workspace_root overrides the destination root and uses the layout <root>/<project>/<name>.

Setup command

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.

If the checkout was created by bin/wt, you can also point at it by managed worktree name from the main app checkout:

bin/wt setup my-feature

If you do not want to cd first, pass an explicit checkout path:

bin/wt setup ../my-project.worktrees/my-feature

This is the recovery path when you want to create first and bootstrap later:

bin/wt --skip-setup my-feature
cd ../my-project.worktrees/my-feature
bin/wt setup

bin/wt setup --dry-run previews the same steps without changing files.

Cleanup commands

bin/wt also supports cleanup commands for worktrees it manages:

  • bin/wt remove <name> — remove the named worktree and delete its local branch
  • bin/wt delete <name> — alias for bin/wt remove <name>
  • bin/wt prune — remove merged worktrees created by bin/wt in bulk

bin/wt remove and bin/wt prune can be run from the main checkout or any sibling worktree in the same repository family.

bin/wt remove refuses to remove the worktree you're currently in, and by default only deletes a local branch after confirming it is already merged into origin's default branch. Use bin/wt remove --force <name> when you intentionally want to delete an unmerged local branch too.

bin/wt prune only targets linked worktrees created by bin/wt whose local branches are already merged into origin's default branch. It skips the main checkout, skips the checkout you're currently in, and asks for confirmation before deleting the batch.

If you want to preview a single removal first, use --dry-run with bin/wt remove:

bin/wt remove --dry-run feature-auth

If you want to see what bin/wt prune would clean up before saying yes, use --dry-run:

bin/wt prune --dry-run

Maintenance commands

bin/wt also includes a small maintenance surface for installer drift and checkout health:

  • bin/wt doctor — audit managed files plus basic Git/worktree health without changing anything
  • bin/wt update --dry-run — preview safe file-based fixes
  • bin/wt update — apply safe file-based fixes for supported managed files

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.

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.

Interactive prompts

bin/wt handles several edge cases interactively:

  • Branch already exists locally — asks whether to attach a new worktree to it
  • Branch already exists on origin — asks whether to create a local tracking worktree
  • Target directory already exists with matching branch — asks whether to reuse it
  • Target directory already exists with a different branch — asks whether to remove and recreate it
  • Prune found merged worktrees created by wt — asks whether to delete the batch
  • Retired bundled name used explicitly — rejects it and suggests running wt with no argument

Name validation

Worktree names must not contain / or whitespace, must not be . or .., and must be a valid Git ref component.

Configuration

The installer generates config/initializers/rails_worktrees.rb where you can override:

The initializer stays app-owned and gem-agnostic; rails-worktrees reads those settings whenever the gem is present in the current bundle groups.

Option Default Description
bootstrap_env true Write .env when creating a worktree
workspace_root nil Override the destination root (sibling layout when nil)
dev_port_range 3000..3999 Port range for deterministic DEV_PORT allocation
branch_prefix 🚂 Prefix for worktree branch names
name_sources_path bundled names/ Directory containing .txt name lists
used_names_file ~/.local/state/rails-worktrees/used-names.tsv TSV tracking retired names
worktree_database_suffix_max_length 18 Max length for generated database suffixes

Database naming

The installer adds WORKTREE_DATABASE_SUFFIX to common development and test database names in config/database.yml. For a multi-database app, the target shape is:

development:
  primary:
    database: my_app_development<%= ENV.fetch('WORKTREE_DATABASE_SUFFIX', '') %>_primary

test:
  primary:
    database: my_app_test<%= ENV.fetch('WORKTREE_DATABASE_SUFFIX', '') %>_primary

If your database.yml is too custom to patch safely, the installer leaves it alone and tells you what to update manually.

Environment bootstrap

When bin/wt creates a worktree it writes a worktree-local .env with:

  • DEV_PORT — deterministic port derived from the worktree name via CRC32, rotated through dev_port_range, skipping ports already claimed by peer worktrees
  • 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.

Existing .env values are never overwritten.

Browser helper

If you install with --browser or --yolo, the installer generates bin/ob.

bin/ob reads DEV_PORT from the current app or worktree's .env, falls back to ENV['DEV_PORT'], then falls back to 3000, and opens http://localhost:$DEV_PORT/... in your default browser.

Route examples:

bin/ob
bin/ob contact
bin/ob admin/users
bin/ob '/contact?from=footer'
bin/ob --print-url '?from=footer'

bin/ob accepts a single optional local route argument. Query-only routes such as ?from=footer resolve to the app root, and full URLs such as https://example.com are intentionally rejected.

If DEV_PORT is missing from .env, bin/ob tells you when it falls back to ENV['DEV_PORT'] or 3000.

Browser opening currently uses:

  • open on macOS
  • xdg-open on Linux and other Unix-like environments that provide it

For scripts and debugging, bin/ob --print-url [route] prints the resolved localhost URL without opening a browser.

By default, the installer does not edit your Procfile.dev, config/puma.rb, or mise config. It generates Procfile.dev.worktree.example with a ready-to-copy line:

web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0 -p ${DEV_PORT:-3000}

If you run bin/rails generate worktrees:install --yolo, the installer applies the three common follow-ups for you when the files already exist:

  • skip generating Procfile.dev.worktree.example
  • replace the existing web: entry in Procfile.dev
  • update config/puma.rb to port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000) when it still uses a supported default PORT binding
  • add _.file = ".env" to the [env] section of mise.toml or .mise.toml

On a regular install, the follow-up message also suggests the same config/puma.rb edit when Puma still uses the default PORT binding.

Use a project-local env loader like mise with _.file = ".env" to keep values scoped per-worktree.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Smoke testing

RSpec remains the main automated test suite. For installer and integration changes, you can also run a disposable Rails app smoke test:

bundle exec rake smoke_test

This smoke test:

  • creates a temporary Rails app from a compatible Rails version
  • installs rails-worktrees from the current checkout path
  • runs bin/rails generate worktrees:install --yolo
  • verifies bin/wt, bin/ob, the generated initializer, that --yolo skips the Procfile example, yolo updates to Procfile.dev, config/puma.rb, and mise.toml, config/database.yml patching, and worktree .env bootstrapping
  • creates a temporary bare origin, confirms bin/wt smoke-branch creates a real worktree, and confirms bin/wt remove smoke-branch can remove that merged worktree from a sibling worktree checkout

By default, the script cleans up all temp directories after the run. Set KEEP_SMOKE_TEST_ARTIFACTS=1 to keep them around for debugging, or set RAILS_WORKTREES_SMOKE_RAILS_VERSION to try a different compatible Rails version.

There is also a manually triggered GitHub Actions workflow named Smoke Test for running the same disposable-app verification in CI without slowing down the default pull request checks.

The workflow accepts optional ruby_version, rails_version, keep_artifacts, and verbose inputs. Enable keep_artifacts when you want the disposable app, bare origin, and worktree directories uploaded from CI for debugging, and enable verbose when you want shell tracing in the smoke-test log.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/asjer/rails-worktrees.

License

The gem is available as open source under the terms of the MIT License.