diff --git a/CHANGELOG.md b/CHANGELOG.md index da1dd17..6be48c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - `upsert: ` + `with: ` — replaces if pattern matches, otherwise appends - MFA required to publish this gem +### ⚠️ Behavior changes + +- `.worktree.yml` in the linked worktree now drives **all** setup steps, not just the tail end. Previously `copy:` and `link:` always came from the main worktree's config while the rest came from the linked one if present — split that produced surprising results. Now: linked config wins entirely if present; main is the fallback. No effect unless you have a linked `.worktree.yml` whose `copy:`/`link:` differs from main's. + ## 0.6.0 ### ✨ Features diff --git a/README.md b/README.md index f5286a0..eba066e 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,17 @@ setup: mise exec -- bin/setup `bonchi create` auto-runs setup when `.worktree.yml` exists. Skip with `--no-setup`. +If `.worktree.yml` exists in the linked worktree, it takes precedence over the main worktree's — useful for trying out config changes in a single worktree without touching main, or for committing a per-branch config. The main worktree's config is the fallback when no local config is present. + +To pin every new worktree to its own copy of the config (full isolation, no propagation of later edits in main), add it to `copy:`: + +```yaml +copy: + - .worktree.yml +``` + +Default is to leave it out, so edits to main's `.worktree.yml` automatically apply to every subsequent `bonchi setup` run. + ### Edit Use `edit` to modify files during setup. Three actions are available; entries run in order, so you can interleave them. Env vars (`$VAR`) are expanded in replacement values. diff --git a/lib/bonchi/setup.rb b/lib/bonchi/setup.rb index 0ed3455..d190ea4 100644 --- a/lib/bonchi/setup.rb +++ b/lib/bonchi/setup.rb @@ -21,8 +21,13 @@ def run(args = [], upto: nil) abort "#{color(:red)}Error:#{reset} already in the main worktree" end - config = Config.from_main_worktree - abort "#{color(:red)}Error:#{reset} .worktree.yml not found in main worktree" unless config + config = Config.from_worktree(@worktree) + if config + puts "Using .worktree.yml from linked worktree" + else + config = Config.from_main_worktree + abort "#{color(:red)}Error:#{reset} .worktree.yml not found in main worktree" unless config + end last_step = upto || STEPS.last run_steps = STEPS[0..STEPS.index(last_step)] @@ -37,14 +42,6 @@ def run(args = [], upto: nil) copy_files(config.copy) if run_steps.include?("copy") link_files(config.link) if run_steps.include?("link") - - # Prefer linked worktree's .worktree.yml if it was copied or already exists - linked_config = Config.from_worktree(@worktree) - if linked_config - puts "Using .worktree.yml from linked worktree" - config = linked_config - end - allocate_ports(config.ports) if run_steps.include?("ports") && config.ports.any? replace_in_files(config.replace) if run_steps.include?("replace") && config.replace.any? run_pre_setup(config.pre_setup) if run_steps.include?("pre_setup") diff --git a/test/test_setup.rb b/test/test_setup.rb index 92f8f47..990316d 100644 --- a/test/test_setup.rb +++ b/test/test_setup.rb @@ -142,3 +142,84 @@ def test_missing_file_aborts end end end + +class TestSetupConfigSource < Minitest::Test + def setup + @tmpdir = Dir.mktmpdir + @main = File.join(@tmpdir, "main") + @linked = File.join(@tmpdir, "linked") + FileUtils.mkdir_p(@main) + FileUtils.mkdir_p(@linked) + @setup = Bonchi::Setup.allocate + @setup.instance_variable_set(:@worktree, @linked) + @setup.instance_variable_set(:@main_worktree, @main) + ENV["WORKTREE_ROOT"] = @tmpdir + end + + def teardown + FileUtils.remove_entry(@tmpdir) + %w[WORKTREE_ROOT WORKTREE_BRANCH WORKTREE_BRANCH_SLUG WORKTREE_MAIN WORKTREE_LINKED].each { |k| ENV.delete(k) } + end + + def write(dir, file, content) + path = File.join(dir, file) + FileUtils.mkdir_p(File.dirname(path)) + File.write(path, content) + end + + def silenced + out = $stdout + $stdout = StringIO.new + yield + ensure + $stdout = out + end + + def run_setup(upto:) + Bonchi::Git.stub(:main_worktree, @main) do + Bonchi::Git.stub(:current_branch, "feat/x") do + silenced { @setup.run([], upto: upto) } + end + end + end + + def test_linked_copy_takes_precedence_over_main + write(@main, "from-main.txt", "main\n") + write(@main, "from-linked.txt", "linked\n") + write(@main, ".worktree.yml", "copy:\n - from-main.txt\n") + write(@linked, ".worktree.yml", "copy:\n - from-linked.txt\n") + + run_setup(upto: "copy") + + refute File.exist?(File.join(@linked, "from-main.txt")), "main's copy entry should be ignored when linked config exists" + assert_equal "linked\n", File.read(File.join(@linked, "from-linked.txt")) + end + + def test_linked_link_takes_precedence_over_main + write(@main, "from-main", "m\n") + write(@main, "from-linked", "l\n") + write(@main, ".worktree.yml", "link:\n - from-main\n") + write(@linked, ".worktree.yml", "link:\n - from-linked\n") + + run_setup(upto: "link") + + refute File.exist?(File.join(@linked, "from-main")) + assert File.symlink?(File.join(@linked, "from-linked")) + assert_equal File.join(@main, "from-linked"), File.readlink(File.join(@linked, "from-linked")) + end + + def test_falls_back_to_main_when_linked_has_no_config + write(@main, "tool.toml", "m\n") + write(@main, ".worktree.yml", "copy:\n - tool.toml\n") + + run_setup(upto: "copy") + + assert_equal "m\n", File.read(File.join(@linked, "tool.toml")) + end + + def test_aborts_when_neither_worktree_has_config + assert_raises(SystemExit) do + run_setup(upto: "copy") + end + end +end