Skip to content

fix: make re-runs idempotent (don't error when branch already exists) #54

@cdhagmann

Description

@cdhagmann

Background

Re-running gem-contribute fix <gem>/<n> against an issue you've already started errors out instead of being a no-op-ending-in-the-same-place.

Repro:

$ gem-contribute fix -e gem-contribute/5    # first run: forks, clones, branches, opens editor
$ gem-contribute fix -e gem-contribute/5    # second run:
[⠴] Forking cdhagmann/gem-contribute...
fix failed: git -C /path/to/clone checkout -b gem-contribute/issue-5 failed:
  fatal: a branch named 'gem-contribute/issue-5' already exists

The current behavior is documented in lib/gem_contribute/operations/branch.rb:11 as preserving pre-extraction behavior, with a forward-pointer to #10 — but #10 turned out to be a different concern (fork-when-you-own-upstream messaging), so this case has no tracking issue today.

Desired behavior

fix should be idempotent: running it a second time on the same <gem>/<n> lands you in exactly the same state as the first run, without errors.

Concretely:

  • Fork already exists → skip fork (already does this).
  • Clone already exists at <clone_root>/<owner>/<repo> → skip clone (already does this — Operations::Clone::Result.new(reused: true)).
  • Branch already exists locallygit checkout it instead of git checkout -b. This is the new behavior.
  • Announce comment already posted → don't re-post. The "👋 I've started working on this" comment shouldn't get duplicated on every re-run. (See Operations::Announce for current behavior — needs a check before posting, presumably by querying issue comments for the marker we set on the first run.)
  • -e / -a hooks → run on every invocation. The whole point of re-running is "open my editor on this work-in-progress." Running them is the goal, not a side effect to skip.

End state of the second run should be: you're cd'd nowhere new, but the editor/AI tool you asked for is open on the existing clone with the existing branch checked out.

Acceptance

  • Second fix invocation against an in-progress issue exits 0 and runs the post-clone hooks.
  • Branch is checked out (whether or not it existed before).
  • No duplicate announce comment on the issue.
  • All existing fix first-run paths still work unchanged.
  • Test coverage for the re-run path: branch exists, clone exists, announce already posted.

Files likely to touch

  • lib/gem_contribute/operations/branch.rb — replace git checkout -b with "create or switch" semantics; update the doc comment.
  • lib/gem_contribute/operations/announce.rb — add a "has the marker comment already been posted by us?" check.
  • lib/gem_contribute/operations/fix_pipeline.rb — may need to thread the announce-already-done signal through.
  • spec/gem_contribute/operations/branch_spec.rb, spec/gem_contribute/operations/announce_spec.rb — re-run path coverage.

Notes

  • Git#checkout_branch currently shells out to git checkout -b <name>. The fix is either a new Git#switch_or_create_branch method (git switch -c <name> falls back to git switch <name> if the branch exists), or a check-then-act pattern in Operations::Branch.
  • Don't try to reconcile state if the branch exists but isn't checked out at the same base as main/master. That's a "you have local work in progress" case and silently rebasing it would be bad. Just check it out where it is.
  • Output messages should reflect what actually happened: Reusing branch gem-contribute/issue-5 instead of Creating branch gem-contribute/issue-5 when reusing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions