Module: Git::Repository::Branching

Included in:
Git::Repository
Defined in:
lib/git/repository/branching.rb

Overview

Facade methods for branching operations: creating, checking out, querying, deleting, and updating branches

Included by Git::Repository.

Defined Under Namespace

Classes: HeadState

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#nameString (readonly)

Returns the branch name, or 'HEAD' when detached.

Returns:

  • (String)

    the branch name, or 'HEAD' when detached



38
# File 'lib/git/repository/branching.rb', line 38

HeadState = Data.define(:state, :name)

#stateSymbol (readonly)

Returns one of :active, :unborn, or :detached.

Returns:

  • (Symbol)

    one of :active, :unborn, or :detached



38
# File 'lib/git/repository/branching.rb', line 38

HeadState = Data.define(:state, :name)

Instance Method Details

#branch(branch_name = current_branch) ⇒ Git::Branch

Returns a Branch object for the given branch name

Examples:

Get a branch object for 'main'

repo.branch('main')  #=> #<Git::Branch 'main'>

Get a branch object for the current branch

repo.branch  #=> #<Git::Branch 'main'>

Parameters:

  • branch_name (String) (defaults to: current_branch)

    the branch name (defaults to the current branch)

Returns:

Raises:



573
574
575
576
577
578
579
580
581
582
583
# File 'lib/git/repository/branching.rb', line 573

def branch(branch_name = current_branch)
  branch_info = Git::BranchInfo.new(
    refname: branch_name,
    target_oid: nil,
    current: false,
    worktree: false,
    symref: nil,
    upstream: nil
  )
  Git::Branch.new(self, branch_info)
end

#branch?(branch) ⇒ Boolean

Returns true if the named branch exists locally or as a remote-tracking branch

Examples:

Check whether main exists anywhere

repo.branch?('main')  # => true

Parameters:

  • branch (String)

    the branch name to look up

Returns:

  • (Boolean)

    true if the branch exists locally or remotely, false otherwise

Raises:



259
260
261
# File 'lib/git/repository/branching.rb', line 259

def branch?(branch)
  local_branch?(branch) || remote_branch?(branch)
end

#branch_contains(commit, branch_name = '') ⇒ String

Returns the git branch --list --contains stdout for a given commit

The output format is the human-readable git branch listing: each matching branch name appears on its own line, prefixed with two spaces, or * if it is the currently checked-out branch. This is the same format returned by Git::Lib#branch_contains in the 4.x gem series.

Examples:

List all branches that contain a commit

repo.branch_contains('abc1234')
# => "  main\n"

The current branch is marked with an asterisk

repo.branch_contains('abc1234')
# => "* main\n  feature\n"

Limit the search to branches matching a shell wildcard pattern

repo.branch_contains('abc1234', 'feature/*')

Typical usage: check whether any branch contains the commit

repo.branch_contains('abc1234').empty?  # => false

Parameters:

  • commit (String)

    the commit SHA or ref to look up

  • branch_name (String, nil) (defaults to: '')

    a shell wildcard pattern to limit which branches are searched

    When empty or nil, all local branches are searched.

Returns:

  • (String)

    the git branch --list --contains stdout

    Each matching branch appears on its own line, prefixed with two spaces, or * for the currently checked-out branch. Returns an empty string when no matching branch contains the commit.

Raises:



489
490
491
492
493
494
495
# File 'lib/git/repository/branching.rb', line 489

def branch_contains(commit, branch_name = '')
  branch_name = branch_name.to_s
  pattern = branch_name.empty? ? nil : branch_name
  Git::Commands::Branch::List.new(@execution_context)
                             .call(*[pattern].compact, contains: commit, no_color: true)
                             .stdout
end

#branch_delete(*branches, **options) ⇒ String

Delete one or more local or remote-tracking branches

Examples:

Delete a single branch

repo.branch_delete('feature') # => "Deleted branch feature (was abc1234)."

Delete multiple branches at once

repo.branch_delete('feature-1', 'feature-2')

Force-delete an unmerged branch

repo.branch_delete('unmerged-branch', force: true)

Delete a remote-tracking branch

repo.branch_delete('origin/feature', remotes: true)

Parameters:

  • branches (Array<String>)

    the name(s) of the branch(es) to delete

  • options (Hash)

    options for the delete command

Options Hash (**options):

  • :force (Boolean, nil) — default: true

    allow deleting the branch irrespective of its merged status

    Defaults to true to match the 4.x behavior.

  • :remotes (Boolean, nil) — default: nil

    delete remote-tracking branches

    Use together with a remote/branch name.

Returns:

  • (String)

    the stdout output from the delete command, e.g. "Deleted branch feature (was abc1234)."

Raises:

  • (ArgumentError)

    if unsupported options are provided

  • (Git::FailedError)

    if git exits outside the allowed range (exit code > 1)

  • (Git::Error)

    if git reports a deletion failure



409
410
411
412
413
414
415
416
417
418
# File 'lib/git/repository/branching.rb', line 409

def branch_delete(*branches, **options)
  options = { force: true }.merge(options)
  SharedPrivate.assert_valid_opts!(BRANCH_DELETE_ALLOWED_OPTS, **options)

  result = Git::Commands::Branch::Delete.new(@execution_context).call(*branches, **options)

  raise Git::Error, result.stderr.strip unless result.status.success?

  result.stdout.strip
end

#branch_new(branch, start_point = nil, options = {})

This method returns an undefined value.

Create a new branch

Examples:

Create a new branch from the current HEAD

repo.branch_new('feature')

Create a new branch from a specific commit or branch

repo.branch_new('feature', 'main')

Parameters:

  • branch (String)

    the name of the branch to create

  • start_point (String, nil) (defaults to: nil)

    the commit, branch, or tag to start the new branch from; defaults to the current HEAD when nil

  • options (Hash) (defaults to: {})

    reserved; must be empty — no options are currently supported

Raises:

  • (ArgumentError)

    if unsupported options are provided

  • (Git::FailedError)

    if git exits with a non-zero exit status



355
356
357
358
359
360
361
362
363
364
365
# File 'lib/git/repository/branching.rb', line 355

def branch_new(branch, start_point = nil, options = {})
  if start_point.is_a?(Hash) && options.empty?
    options = start_point
    start_point = nil
  end

  SharedPrivate.assert_valid_opts!(BRANCH_NEW_ALLOWED_OPTS, **options)
  Git::Commands::Branch::Create.new(@execution_context).call(branch, start_point, **options)

  nil
end

#branchesGit::Branches

Returns a Branches collection of all branches in the repository

Examples:

List all branches

repo.branches
# => #<Git::Branches ...>

Iterate over all branches

repo.branches.each { |b| puts b.name }

Access local branches only

repo.branches.local

Access remote-tracking branches only

repo.branches.remote

Look up a branch by name

repo.branches['main']  # => #<Git::Branch 'main'>

Returns:

  • (Git::Branches)

    a collection wrapping all local and remote-tracking branches in the repository

Raises:



608
609
610
# File 'lib/git/repository/branching.rb', line 608

def branches
  Git::Branches.new(self)
end

#branches_allArray<Git::BranchInfo>

Returns all local and remote-tracking branches as structured objects

Examples:

List all branches

repo.branches_all
# => [#<data Git::BranchInfo refname="main", current=true, ...>,
#     #<data Git::BranchInfo refname="remotes/origin/main", current=false, ...>]

Find the currently checked-out branch

repo.branches_all.find(&:current)

List only local branches

repo.branches_all.reject(&:remote?)

Returns:

  • (Array<Git::BranchInfo>)

    parsed branch information for every local and remote-tracking branch

    Returns an empty array when the repository has no branches.

Raises:



517
518
519
520
521
522
# File 'lib/git/repository/branching.rb', line 517

def branches_all
  result = Git::Commands::Branch::List.new(@execution_context).call(
    all: true, format: Git::Parsers::Branch::FORMAT_STRING
  )
  Git::Parsers::Branch.parse_list(result.stdout)
end

#change_head_branch(branch_name)

Note:

Pointing HEAD at a branch that does not yet exist places the repository in unborn-branch state. This is intentional for repository initialization workflows — for example, setting a custom default branch name before any commits land — but is unexpected if done by mistake. The repository will appear to have no commits until the first commit is made on the new branch.

This method returns an undefined value.

Writes the HEAD symbolic ref to point at the given branch

Sets HEAD to refs/heads/<branch_name> via git symbolic-ref. This is equivalent to running git symbolic-ref HEAD refs/heads/<branch_name> on the command line and is the mechanism git uses internally for branch renaming and orphan-branch checkout.

Examples:

Change HEAD to point to an existing branch

repo.change_head_branch('main')

Initialize a repository with a custom default branch name (unborn-branch pattern)

repo = Git.init('/path/to/repo')
repo.change_head_branch('my-branch')
# HEAD now points at refs/heads/my-branch before any commits exist

Parameters:

  • branch_name (String)

    the branch name to point HEAD at

Raises:



448
449
450
451
# File 'lib/git/repository/branching.rb', line 448

def change_head_branch(branch_name)
  Git::Commands::SymbolicRef::Update.new(@execution_context).call('HEAD', "refs/heads/#{branch_name}")
  nil
end

#checkout(branch = nil, opts = {}) ⇒ String

Switch branches or restore working tree files

Examples:

Check out an existing branch

repo.checkout('main')

Create and check out a new branch from main

repo.checkout('new-feature', new_branch: true, start_point: 'main')

Create a new branch with a name different from the start point

repo.checkout('main', new_branch: 'new-feature')

Force checkout discarding local changes

repo.checkout('main', force: true)

Parameters:

  • branch (String, nil) (defaults to: nil)

    the branch to check out; defaults to nil (i.e. restore HEAD state)

  • opts (Hash) (defaults to: {})

    options for the checkout command

Options Hash (opts):

  • :force (Boolean, nil) — default: nil

    discard local changes when switching branches

  • :new_branch (Boolean, String, nil) — default: nil

    when true, creates a new branch named branch from :start_point

    When a String, creates a new branch with that name, using branch as the start point.

  • :b (Boolean, String, nil) — default: nil

    alias for :new_branch

  • :f (Boolean, nil) — default: nil

    alias for :force

  • :start_point (String, nil) — default: nil

    the commit or branch to start the new branch from; used together with new_branch: true

Returns:

  • (String)

    git's stdout from the checkout

Raises:

  • (ArgumentError)

    if unsupported options are provided

  • (Git::FailedError)

    if git exits with a non-zero exit status



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/git/repository/branching.rb', line 161

def checkout(branch = nil, opts = {})
  if branch.is_a?(Hash) && opts.empty?
    opts = branch
    branch = nil
  end

  SharedPrivate.assert_valid_opts!(CHECKOUT_ALLOWED_OPTS, **opts)

  target, translated_opts = Private.translate_checkout_opts(branch, opts)
  Git::Commands::Checkout::Branch.new(@execution_context).call(target, **translated_opts).stdout
end

#checkout_file(version, file) ⇒ String

Restore working tree files from a tree-ish

Examples:

Restore README.md to its HEAD state

repo.checkout_file('HEAD', 'README.md')

Parameters:

  • version (String)

    the tree-ish (branch, tag, commit SHA, etc.) to restore the file from

  • file (String)

    the path to the file to restore

Returns:

  • (String)

    git's stdout from the checkout

Raises:



116
117
118
# File 'lib/git/repository/branching.rb', line 116

def checkout_file(version, file)
  Git::Commands::Checkout::Files.new(@execution_context).call(version, pathspec: [file]).stdout
end

#checkout_index(options = {}) ⇒ String

Populate the working tree from the index

Examples:

Check out all files from the index

repo.checkout_index(all: true)

Force check out a specific file

repo.checkout_index(force: true, path_limiter: 'README.md')

Check out files to a staging prefix

repo.checkout_index(prefix: 'tmp/stage/', all: true)

Parameters:

  • options (Hash) (defaults to: {})

    options for the checkout-index command

Options Hash (options):

  • :all (Boolean, nil) — default: nil

    check out all files in the index

  • :force (Boolean, nil) — default: nil

    overwrite existing files

  • :prefix (String, nil) — default: nil

    write files under this path prefix rather than the working directory root

  • :path_limiter (String, Pathname, Array<String, Pathname>, nil) — default: nil

    limit the check out to the given path(s)

Returns:

  • (String)

    git's stdout from the checkout-index command

Raises:

  • (ArgumentError)

    if unsupported options are provided

  • (Git::FailedError)

    if git exits with a non-zero exit status



202
203
204
205
206
207
208
# File 'lib/git/repository/branching.rb', line 202

def checkout_index(options = {})
  SharedPrivate.assert_valid_opts!(CHECKOUT_INDEX_ALLOWED_OPTS, **options)

  paths = Private.normalize_pathspecs(options[:path_limiter], 'path_limiter')
  keyword_opts = options.except(:path_limiter)
  Git::Commands::CheckoutIndex.new(@execution_context).call(*paths.to_a, **keyword_opts).stdout
end

#current_branchString

Returns the name of the current branch

Examples:

Get the current branch name

repo.current_branch  # => "main"

In detached HEAD state

repo.current_branch  # => "HEAD"

Returns:

  • (String)

    the current branch name, or 'HEAD' when in detached HEAD state

Raises:



63
64
65
66
67
# File 'lib/git/repository/branching.rb', line 63

def current_branch
  result = Git::Commands::Branch::ShowCurrent.new(@execution_context).call
  name = result.stdout.strip
  name.empty? ? 'HEAD' : name
end

#current_branch_stateGit::Repository::Branching::HeadState

Returns the current HEAD state as a structured value object

HEAD can be in one of three states:

  • :active — HEAD points to a branch ref that has at least one commit.
  • :unborn — HEAD points to a branch ref that has been created but has no commits yet (e.g. immediately after git init before any commit).
  • :detached — HEAD points directly to a commit SHA rather than a branch.

Examples:

Active branch

repo.current_branch_state
# => #<data Git::Repository::Branching::HeadState state=:active, name="main">

Unborn branch (no commits yet)

repo.current_branch_state
# => #<data Git::Repository::Branching::HeadState state=:unborn, name="main">

Detached HEAD

repo.current_branch_state
# => #<data Git::Repository::Branching::HeadState state=:detached, name="HEAD">

Returns:

Raises:



94
95
96
97
98
99
100
# File 'lib/git/repository/branching.rb', line 94

def current_branch_state
  branch_name = Git::Commands::Branch::ShowCurrent.new(@execution_context).call.stdout.strip
  return HeadState.new(state: :detached, name: 'HEAD') if branch_name.empty?

  state = Private.get_branch_state(@execution_context, branch_name)
  HeadState.new(state: state, name: branch_name)
end

#is_branch?(branch) ⇒ Boolean

Deprecated.

use #branch? instead

Checks whether the named branch exists locally or as a remote-tracking branch

Examples:

Check whether main exists anywhere

repo.is_branch?('main')  # => true

Parameters:

  • branch (String)

    the branch name to look up

Returns:

  • (Boolean)

    true if the branch exists locally or remotely, false otherwise

Raises:



320
321
322
323
324
325
326
# File 'lib/git/repository/branching.rb', line 320

def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
  Git::Deprecation.warn(
    'Git::Repository#is_branch? is deprecated and will be removed in a future version. ' \
    'Use Git::Repository#branch? instead.'
  )
  branch?(branch)
end

#is_local_branch?(branch) ⇒ Boolean

Deprecated.

use #local_branch? instead

Checks whether the named branch exists locally

Examples:

Check whether main exists locally

repo.is_local_branch?('main')  # => true

Parameters:

  • branch (String)

    the local branch name to look up

Returns:

  • (Boolean)

    true if the branch exists locally, false otherwise

Raises:



276
277
278
279
280
281
282
# File 'lib/git/repository/branching.rb', line 276

def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
  Git::Deprecation.warn(
    'Git::Repository#is_local_branch? is deprecated and will be removed in a future version. ' \
    'Use Git::Repository#local_branch? instead.'
  )
  local_branch?(branch)
end

#is_remote_branch?(branch) ⇒ Boolean

Deprecated.

use #remote_branch? instead

Checks whether the named branch exists as a remote-tracking branch

Examples:

Check whether master exists on any remote

repo.is_remote_branch?('master')  # => true

Parameters:

  • branch (String)

    the short branch name to look up across all remotes

Returns:

  • (Boolean)

    true if a remote-tracking branch with that short name exists, false otherwise

Raises:



298
299
300
301
302
303
304
# File 'lib/git/repository/branching.rb', line 298

def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
  Git::Deprecation.warn(
    'Git::Repository#is_remote_branch? is deprecated and will be removed in a future version. ' \
    'Use Git::Repository#remote_branch? instead.'
  )
  remote_branch?(branch)
end

#local_branch?(branch) ⇒ Boolean

Returns true if the named branch exists as a local branch

Examples:

Check whether main exists locally

repo.local_branch?('main')  # => true

Parameters:

  • branch (String)

    the local branch name to look up

Returns:

  • (Boolean)

    true if the branch exists locally, false otherwise

Raises:



221
222
223
224
# File 'lib/git/repository/branching.rb', line 221

def local_branch?(branch)
  result = Git::Commands::Branch::List.new(@execution_context).call(branch, format: '%(refname:short)')
  result.stdout.chomp == branch
end

#remote_branch?(branch) ⇒ Boolean

Returns true if the named branch exists as a remote-tracking branch

The branch argument must be the short branch name (e.g. 'master'), not the combined remote/branch form (e.g. 'origin/master').

Examples:

Check whether master exists on any remote

repo.remote_branch?('master')  # => true

Parameters:

  • branch (String)

    the short branch name to look up across all remotes

Returns:

  • (Boolean)

    true if a remote-tracking branch with that short name exists, false otherwise

Raises:



241
242
243
244
245
# File 'lib/git/repository/branching.rb', line 241

def remote_branch?(branch)
  result = Git::Commands::Branch::List.new(@execution_context)
                                      .call("*/#{branch}", remotes: true, format: '%(refname:lstrip=3)')
  result.stdout.each_line.any? { |line| line.chomp == branch }
end

#update_ref(branch, commit) ⇒ Git::CommandLineResult

Update a branch ref to point to a new commit

Derives the full ref from the branch argument:

  • remotes/<remote>/<name> or refs/remotes/<remote>/<name> → writes to refs/remotes/<remote>/<name> (remote-tracking branch)
  • Any other value → writes to refs/heads/<branch> (local branch)

Examples:

Advance a local branch to the current HEAD

repo.update_ref('feature', repo.rev_parse('HEAD'))

Reset a local branch to an older commit

repo.update_ref('main', 'abc1234def5678')

Update a remote-tracking branch ref

repo.update_ref('remotes/origin/main', 'abc1234def5678')

Parameters:

  • branch (String)

    a local or remote-tracking branch name

    Short local names (e.g. 'main') resolve to refs/heads/<branch>. Remote-tracking names with a remotes/<remote>/ or refs/remotes/<remote>/ prefix (e.g. 'remotes/origin/main') resolve to refs/remotes/<remote>/<name>.

  • commit (String)

    the commit SHA to point the branch at

Returns:

Raises:



554
555
556
557
# File 'lib/git/repository/branching.rb', line 554

def update_ref(branch, commit)
  ref = Private.build_update_ref(branch)
  Git::Commands::UpdateRef::Update.new(@execution_context).call(ref, commit)
end