Skip to content

Deprecate Git::Branch and Git::Branches (and Git::Repository#branch/#branches) in favor of the BranchInfo API #1639

Description

@jcouball

Summary

Deprecate the ActiveRecord-style branch objects and their facade entry points in favor of the Git::BranchInfo-based API, following the same pattern used for remotes.

Scope of deprecation:

  • Git::Branch (class)
  • Git::Branches (collection class)
  • Git::Repository#branch — returns Git::Branch
  • Git::Repository#branches — returns Git::Branches

Not deprecated: the rest of Git::Repository::Branching. That module is the replacement — branch_list, checkout, branch_new, branch_delete, branch_contains, update_ref, current_branch, etc. all stay. Only the two methods that hand back AR objects (#branch, #branches) are in scope.

Proposed timeline: deprecate as early as 5.0.0, remove in 6.0.0.

Motivation

Git::BranchInfo (immutable, remote-aware) already exists and is returned by Git::Repository#branch_list. Git::Branch/Git::Branches are the legacy def initialize(base, …) rich objects that both hold data and perform operations. Steering users to branch_list + facade operations aligns branches with the *Info DAO direction (see umbrella #1636) and sidesteps the legacy string-constructor bug in #1631.

Capability audit — no gap

Every Git::Branch operation is already a thin delegate to an existing Git::Repository facade method that accepts a name/string:

Git::Branch method Facade equivalent
checkout Git::Repository#checkout (see note on create-if-missing)
create Git::Repository#branch_new
delete Git::Repository#branch_delete
current Git::Repository#current_branch
contains? Git::Repository#branch_contains
update_ref Git::Repository#update_ref
merge Git::Repository#merge (see note on the merge(branch) overload)
archive Git::Repository#archive
gcommit Git::Repository#gcommit
stashes Git::Repository#stashes_all (tracked separately in #1637)
full / name / remote Git::BranchInfo#refname / #short_name / #remote_name

Git::Branches (collection): each/[]/local/remote/size map onto Git::Repository#branch_list (which returns Array<Git::BranchInfo>) plus array operations.

Preconditions (do before deprecating the composites)

Two Git::Branch methods are multi-step conveniences with no single facade equivalent. Provide replacements first so the deprecation notes can point somewhere real:

  1. in_branch(message) { … } — temp checkout → commit-or-reset → restore original branch. Re-home as a Git::Repository#in_branch facade method (or document a recipe).
  2. merge(branch) overload — "merge X into branch Y without checking out Y first." The bare facade #merge merges into the current branch, so add a facade path or document the recipe.

Semantic wrinkles to document in the deprecation notes

  • checkout auto-creates: Git::Branch#checkout calls check_if_create before checking out; Git::Repository#checkout(name) does not. Call this out so migrators aren't surprised.
  • Return-shape change: #branches returns an enumerable of Git::Branch; branch_list returns Array<Git::BranchInfo>. Access shifts from branch.name to info.short_name, etc.
  • #stashes is a latent-bug method (ignores the branch, returns all repo stashes) — already split out to Deprecate Git::Branch#stashes in favor of Git::Repository#stashes_all #1637.

Per-method deprecation messages (draft)

Each warning should name the exact replacement, e.g.:

  • git.branch('x') → "Use repo.branch_list / repo.branch_contains / name-based facade methods instead."
  • git.branch('x').delete → repo.branch_delete('x')
  • git.branch('x').checkout → repo.checkout('x') (note: no auto-create)
  • git.branches → repo.branch_list
  • git.branches['x'] → repo.branch_list.find { |b| b.short_name == 'x' }

Use the existing Git::Deprecation.warn pattern (as in Git::Repository#stash_list).

Suggested sequencing

  1. Land the in_branch and merge-into-branch facade replacements.
  2. Deprecate the 1:1-replaceable methods/accessors first (#branch, #branches, and the Git::Branch operation methods that already have exact facade equivalents).
  3. Hold deprecation of in_branch / merge(branch) until step 1 ships.
  4. Ensure deprecation messages don't point at other about-to-be-deprecated APIs (e.g., don't route #stashes users through Git::Stashes).

Blast radius

Git::Branch, Git::Branches, #branch, #branches are core, high-traffic, @api public. Expect significant deprecation-warning volume; phasing (above) keeps 5.0.0 warnings honest and each pointed at a real replacement.

Testing

  • Unit specs asserting each deprecation warning fires (mirror the stash_list deprecation test).
  • Specs for the new in_branch / merge-into-branch facade methods.
  • UPGRADING.md migration section with the per-method mapping above.

Affected files (initial)

Related

Activity

  1. jcouball commented on Aug 21, 2026

    @jcouball
    MemberAuthor

    Audit result and one semantic wrinkle

    Two findings that were briefly drafted as ADRs. Neither is a decision, so both belong here instead.

    Git::Branch has no capability gap

    Git::Branch can be deprecated without replicating its behavior somewhere first. Every operation on the class already delegates to a Git::Repository facade method that takes a branch name or any string. Nothing on Git::Branch reaches into git in a way the facade cannot.

    Two composites are the exception, and both wrap existing calls rather than adding capability. in_branch stashes, checks out, yields, and restores. The merge(branch) overload merges the receiver into the current branch. Both are tracked in #1641 as preconditions for the deprecation.

    Worth recording because the opposite conclusion is the intuitive one. A class with twenty methods looks like it must do something the facade cannot, and the next person to pick this up will assume a capability audit is still owed. It is not. This is it.

    #checkout is not a drop-in replacement

    Git::Branch#checkout calls check_if_create first, so it creates the branch when it does not exist and swallows any error from that attempt. Git::Repository#checkout(name) does not. Checking out a branch that does not exist fails.

    A caller who follows a deprecation warning from branch('feature').checkout to checkout('feature') gets a failure the first time the branch is new. The deprecation warning and the upgrade notes have to name this and point at the two-call replacement. Changing the semantics quietly under a deprecation notice is worse than not deprecating the method, because the warning tells the caller the migration is safe.

  2. added this to the 5.2.0 milestone on Aug 23, 2026
  3. jcouball commented on Sep 3, 2026

    @jcouball
    MemberAuthor

    Git::Repository#in_branch and Git::Repository#merge_into landed in #1748 (refs #1641). When this issue deprecates Git::Branch#in_branch and the Git::Branch#merge(branch) overload, the UPGRADING.md entry (following the pattern of the Git::Branch#stashes and Git::Repository#remotes entries) needs to call out these behavior differences between the deprecated methods and their replacements:

    1. Branch creation. g.branch('x').in_branch { ... } created x if it did not exist. g.in_branch('x') { ... } raises ArgumentError unless x is an existing local branch, so callers must call branch_create first. A commit SHA, tag, or remote-tracking name is also rejected before any checkout.
    2. Detached HEAD. Git::Branch#in_branch recorded the literal HEAD and could not restore a detached HEAD to its original commit. g.in_branch and g.merge_into record the SHA and restore it.
    3. Unborn HEAD. Both new methods raise Git::Error before checking anything out when HEAD is on a branch with no commits. The old methods failed later, mid-flow.
    4. Merge overload. g.branch('main').merge('feature') returned stdout from the final restore checkout and ran a hard reset after the merge. g.merge_into('main', 'feature') returns the merge's stdout and does no reset. It also rejects the :no_commit option; callers who need --no-commit use checkout and merge directly.

    The deprecation messages for Git::Branch#in_branch and Git::Branch#merge(branch) should point at Git::Repository#in_branch and Git::Repository#merge_into respectively.

  4. added 9 commits that reference this issue on Sep 3, 2026
    b842854
    9819d72
    50c7fcf
    a7d2b0d
    fb96d30
    5764b72
    17e626b
    a7d5cb4
    1581aa0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions