Class: Git::BranchInfo

Inherits:
Data
  • Object
show all
Defined in:
lib/git/branch_info.rb

Overview

Value object representing branch metadata from git branch output

This is a lightweight, immutable data structure returned by branch listing commands. It contains only the data parsed from git output without any repository context or operations.

Examples:

Local branch with upstream tracking

info = Git::BranchInfo.new(
  refname: 'refs/heads/main',
  target_oid: 'abc123def456789012345678901234567890abcd',
  current: true,
  worktree_path: nil,
  symref: nil,
  upstream: 'refs/remotes/origin/main'
)
info.current?     #=> true
info.remote?      #=> false
info.short_name   #=> 'main'
info.upstream     #=> 'refs/remotes/origin/main'

Remote-tracking branch

info = Git::BranchInfo.new(
  refname: 'refs/remotes/origin/main',
  target_oid: 'abc123def456789012345678901234567890abcd',
  current: false,
  worktree_path: nil,
  symref: nil,
  upstream: nil
)
info.remote?      #=> true
info.remote_name  #=> 'origin'
info.short_name   #=> 'main'

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, remote_name: REMOTE_NAME_NOT_GIVEN) ⇒ BranchInfo

Returns a new instance of BranchInfo.

Parameters:

  • refname (String)

    the full branch refname

  • remote_name (String, nil) (defaults to: REMOTE_NAME_NOT_GIVEN)

    resolved remote name, nil for local branches, or omitted to derive from refname

  • target_oid (String, nil)

    the commit object ID, or nil for unborn branches

  • current (Boolean)

    whether this branch is currently checked out

  • worktree_path (String, nil)

    path to another linked worktree, or nil

  • symref (String, nil)

    symbolic reference target, or nil

  • upstream (String, nil)

    upstream refname, or nil



165
166
167
168
169
170
171
# File 'lib/git/branch_info.rb', line 165

def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
               remote_name: REMOTE_NAME_NOT_GIVEN)
  remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
  self.class.validate_remote_name!(refname, remote_name)

  super
end

Instance Attribute Details

#currentBoolean (readonly)

Note:

A branch can be current (#current? true) or in another worktree (#other_worktree? true), but never both. A branch not checked out anywhere has both false.

Whether this branch is currently checked out in the current worktree

Returns:

  • (Boolean)

    true if this is the current branch



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

#refnameString (readonly)

The full reference name of the branch

Must be the full refname as returned by git (e.g., 'refs/heads/main', 'refs/remotes/origin/main') because the short name alone is not guaranteed to be unique (e.g., 'main' could exist as both a local and remote branch).

Returns:

  • (String)

    the branch refname (e.g., 'refs/heads/main', 'refs/remotes/origin/main')



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

#remote_nameString? (readonly)

Returns the resolved or fallback-derived remote name, or nil for local branches.

Returns:

  • (String, nil)

    the resolved or fallback-derived remote name, or nil for local branches



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

#symrefString? (readonly)

The target reference if this is a symbolic reference

Returns:

  • (String, nil)

    the target ref (e.g., 'refs/heads/main'), or nil if not a symref



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

#target_oidString? (readonly)

The commit object ID (SHA) that this branch points to (aka HEAD)

unborn (no commits yet)

Returns:

  • (String, nil)

    the full 40-character object ID, or nil if branch is



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

#upstreamString? (readonly)

Note:

Remote-tracking branches (e.g., 'refs/remotes/origin/main') have upstream: nil

Note:

This is the raw refname snapshot from when the branch list was read. It does not reflect live git state after the snapshot was taken.

The configured upstream/tracking branch refname as reported by git

Returns:

  • (String, nil)

    the raw upstream refname from %(upstream) (e.g., 'refs/remotes/origin/main'), or nil if no upstream is configured



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

#worktree_pathString? (readonly)

The absolute path of the other linked worktree this branch is checked out in, or nil.

This is nil in two distinct cases:

  • The branch is the current branch in this worktree (use #current? to distinguish that case)
  • The branch is not checked out in any worktree

This path is suppressed for the current branch even though git reports it via %(worktreepath), because the current worktree's path is already known from the repository object and storing it here would make #other_worktree? incorrect.

Returns:

  • (String, nil)

    the absolute path of the linked worktree root directory (e.g., '/home/user/projects/my-repo-hotfix'), or nil if the branch is not checked out in a different linked worktree



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/git/branch_info.rb', line 149

BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
  # @param refname [String] the full branch refname
  #
  # @param remote_name [String, nil] resolved remote name, nil for local branches,
  #   or omitted to derive from `refname`
  #
  # @param target_oid [String, nil] the commit object ID, or nil for unborn branches
  #
  # @param current [Boolean] whether this branch is currently checked out
  #
  # @param worktree_path [String, nil] path to another linked worktree, or nil
  #
  # @param symref [String, nil] symbolic reference target, or nil
  #
  # @param upstream [String, nil] upstream refname, or nil
  #
  def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
                 remote_name: REMOTE_NAME_NOT_GIVEN)
    remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
    self.class.validate_remote_name!(refname, remote_name)

    super
  end

  # @param refname [String] the branch refname to validate
  #
  # @param remote_name [String, nil] the remote name to validate
  #
  # @return [void]
  #
  # @raise [ArgumentError] if the remote name contradicts the refname type
  def self.validate_remote_name!(refname, remote_name)
    if remote_tracking_refname?(refname)
      unless remote_name.is_a?(String) && !remote_name.empty?
        raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
      end

      remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
      raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
    elsif !remote_name.nil?
      raise ArgumentError, 'remote_name must be nil for local branch refname'
    end
  end

  # @param refname [String] the branch refname to parse
  #
  # @return [String, nil] the regex-derived remote name, or nil for local branches
  def self.fallback_remote_name(refname)
    refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
  end

  # @param refname [String] the branch refname to inspect
  #
  # @return [Boolean] true if the refname is a remote-tracking refname
  def self.remote_tracking_refname?(refname)
    refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
  end

  # @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
  def detached? = false

  # @return [Boolean] true if this is an unborn branch (no commits yet)
  def unborn? = target_oid.nil?

  # @return [String] the short branch name without any remote or heads prefix
  #   (e.g., 'main' or 'feature/foo')
  def short_name
    return refname.delete_prefix('refs/heads/') if remote_name.nil?

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    refname.sub(remote_ref_prefix, '')
  end

  # @return [Boolean] true if this is the currently checked out branch
  def current? = current

  # @return [Boolean] true if this branch is checked out in another linked worktree
  def other_worktree? = !worktree_path.nil?

  # @return [Boolean] true if this is a symbolic reference
  def symref? = !symref.nil?

  # @return [Boolean] true if this is a remote-tracking branch
  def remote? = !remote_name.nil?

  # @return [String] string representation (the full refname)
  def to_s = refname
end

Class Method Details

.fallback_remote_name(refname) ⇒ String?

Returns the regex-derived remote name, or nil for local branches.

Parameters:

  • refname (String)

    the branch refname to parse

Returns:

  • (String, nil)

    the regex-derived remote name, or nil for local branches



196
197
198
# File 'lib/git/branch_info.rb', line 196

def self.fallback_remote_name(refname)
  refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
end

.remote_tracking_refname?(refname) ⇒ Boolean

Returns true if the refname is a remote-tracking refname.

Parameters:

  • refname (String)

    the branch refname to inspect

Returns:

  • (Boolean)

    true if the refname is a remote-tracking refname



203
204
205
# File 'lib/git/branch_info.rb', line 203

def self.remote_tracking_refname?(refname)
  refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
end

.validate_remote_name!(refname, remote_name)

This method returns an undefined value.

Parameters:

  • refname (String)

    the branch refname to validate

  • remote_name (String, nil)

    the remote name to validate

Raises:

  • (ArgumentError)

    if the remote name contradicts the refname type



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/git/branch_info.rb', line 180

def self.validate_remote_name!(refname, remote_name)
  if remote_tracking_refname?(refname)
    unless remote_name.is_a?(String) && !remote_name.empty?
      raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
    end

    remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
    raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
  elsif !remote_name.nil?
    raise ArgumentError, 'remote_name must be nil for local branch refname'
  end
end

Instance Method Details

#current?Boolean

Returns true if this is the currently checked out branch.

Returns:

  • (Boolean)

    true if this is the currently checked out branch



223
# File 'lib/git/branch_info.rb', line 223

def current? = current

#detached?Boolean

Returns always false for BranchInfo (see DetachedHeadInfo for detached state).

Returns:

  • (Boolean)

    always false for BranchInfo (see DetachedHeadInfo for detached state)



208
# File 'lib/git/branch_info.rb', line 208

def detached? = false

#other_worktree?Boolean

Returns true if this branch is checked out in another linked worktree.

Returns:

  • (Boolean)

    true if this branch is checked out in another linked worktree



226
# File 'lib/git/branch_info.rb', line 226

def other_worktree? = !worktree_path.nil?

#remote?Boolean

Returns true if this is a remote-tracking branch.

Returns:

  • (Boolean)

    true if this is a remote-tracking branch



232
# File 'lib/git/branch_info.rb', line 232

def remote? = !remote_name.nil?

#short_nameString

Returns the short branch name without any remote or heads prefix (e.g., 'main' or 'feature/foo').

Returns:

  • (String)

    the short branch name without any remote or heads prefix (e.g., 'main' or 'feature/foo')



215
216
217
218
219
220
# File 'lib/git/branch_info.rb', line 215

def short_name
  return refname.delete_prefix('refs/heads/') if remote_name.nil?

  remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
  refname.sub(remote_ref_prefix, '')
end

#symref?Boolean

Returns true if this is a symbolic reference.

Returns:

  • (Boolean)

    true if this is a symbolic reference



229
# File 'lib/git/branch_info.rb', line 229

def symref? = !symref.nil?

#to_sString

Returns string representation (the full refname).

Returns:

  • (String)

    string representation (the full refname)



235
# File 'lib/git/branch_info.rb', line 235

def to_s = refname

#unborn?Boolean

Returns true if this is an unborn branch (no commits yet).

Returns:

  • (Boolean)

    true if this is an unborn branch (no commits yet)



211
# File 'lib/git/branch_info.rb', line 211

def unborn? = target_oid.nil?