Module: Git::Repository::Merging Private

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

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Facade methods for merge operations: merging branches into the current branch or into another branch, and finding common ancestors between commits

Included by Git::Repository.

Instance Method Summary collapse

Instance Method Details

#conflicts {|file, your_version, their_version| ... } ⇒ Array<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deprecated.

Use #each_conflict instead

Iterate over files with merge conflicts, yielding conflict details for each

For each unmerged file, the staged content for both sides of the conflict (stage 2 "ours" and stage 3 "theirs") is written to temporary files whose paths are yielded alongside the file path. The temporary files are deleted automatically when the block returns.

Examples:

Inspect conflicting files

repo.conflicts do |file, your_version, their_version|
  puts "Conflict in #{file}"
  puts File.read(your_version)
  puts File.read(their_version)
end

Yields:

  • (file, your_version, their_version)

    passes conflict details for each unmerged file

Yield Parameters:

  • file (String)

    path to the conflicting file, relative to the working tree

  • your_version (String)

    path to a temporary file containing the stage-2 (ours) content for the conflicting file

  • their_version (String)

    path to a temporary file containing the stage-3 (theirs) content for the conflicting file

Yield Returns:

  • (void)

Returns:

  • (Array<String>)

    the list of unmerged file paths

Raises:

  • (Git::FailedError)

    when git diff --cached exits outside the allowed range (exit code > 2)



340
341
342
343
344
345
346
# File 'lib/git/repository/merging.rb', line 340

def conflicts(&)
  Git::Deprecation.warn(
    'Git::Repository#conflicts is deprecated and will be removed in v6.0.0. ' \
    'Use Git::Repository#each_conflict instead.'
  )
  each_conflict(&)
end

#each_conflict {|file, your_version, their_version| ... } ⇒ Array<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Iterate over files with merge conflicts, yielding conflict details for each

For each unmerged file, the staged content for both sides of the conflict (stage 2 "ours" and stage 3 "theirs") is written to temporary files whose paths are yielded alongside the file path. The temporary files are deleted automatically when the block returns.

Examples:

Inspect conflicting files

repo.each_conflict do |file, your_version, their_version|
  puts "Conflict in #{file}"
  puts "Your version:"
  puts File.read(your_version)
  puts "Their version:"
  puts File.read(their_version)
end

Yields:

  • (file, your_version, their_version)

    passes conflict details for each unmerged file

Yield Parameters:

  • file (String)

    path to the conflicting file, relative to the working tree

  • your_version (String)

    path to a temporary file containing the stage-2 (ours) content for the conflicting file

  • their_version (String)

    path to a temporary file containing the stage-3 (theirs) content for the conflicting file

Yield Returns:

  • (void)

Returns:

  • (Array<String>)

    the list of unmerged file paths

Raises:

  • (Git::FailedError)

    when git diff --cached exits outside the allowed range (exit code > 2)



295
296
297
298
299
300
301
302
303
# File 'lib/git/repository/merging.rb', line 295

def each_conflict
  Private.unmerged_paths(@execution_context).each do |file_path|
    Private.write_staged_file(@execution_context, file_path, 2) do |your_file|
      Private.write_staged_file(@execution_context, file_path, 3) do |their_file|
        yield(file_path, your_file.path, their_file.path)
      end
    end
  end
end

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Merge one or more branches into the current branch

The merge commit message may be given by the message positional argument, the :message option, or the :m option; if more than one is provided, the precedence is positional argument > :message > :m.

Examples:

Merge a single branch

repo.merge('feature')

Merge a branch with a no-fast-forward commit message

repo.merge('feature', 'Merge feature into main', no_ff: true)

Octopus merge of multiple branches

repo.merge(%w[feature-a feature-b])

Merge without committing

repo.merge('feature', nil, no_commit: true)

Parameters:

  • branch (#to_s, Array<#to_s>)

    the branch or branches to merge into the current branch

    When an Array is given, an octopus merge is performed; each branch-ish object (e.g., BranchInfo) is coerced to a String via #to_s.

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

    optional commit message for the merge commit

    Translated to the -m flag internally. For fast-forward merges git ignores this value; use no_ff: true to ensure a merge commit is created and the message is recorded.

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

    additional options forwarded to git merge

Options Hash (opts):

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

    stop before creating the merge commit (--no-commit)

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

    create a merge commit even when fast-forward is possible (--no-ff)

  • :message (String) — default: nil

    commit message

    Prefer the :m option instead of this one. Translated to the -m flag. Identical to the positional message argument and the :m option.

  • :m (String) — default: nil

    commit message (-m flag)

Returns:

  • (String)

    git's stdout from the merge command

Raises:

  • (ArgumentError)

    when unsupported options are provided

  • (Git::FailedError)

    when git exits with a non-zero exit status



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/git/repository/merging.rb', line 84

def merge(branch, message = nil, opts = {})
  SharedPrivate.assert_valid_opts!(MERGE_ALLOWED_OPTS, **opts)

  # Dup so callers who reuse the same opts hash are not affected
  opts = opts.dup

  # Merge positional message into opts so the rest of the logic is uniform
  opts[:message] = message if message

  # git merge uses -m, not --message; translate the key
  opts[:m] = opts.delete(:message) if opts.key?(:message)

  branches = Array(branch).map(&:to_s)
  Git::Commands::Merge::Start.new(@execution_context).call(*branches, no_edit: true, **opts).stdout
end

#merge_base(*commits, options = {}) ⇒ Array<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Find common ancestor commit(s) for use in a merge

Examples:

Find the common ancestor of two branches

repo.merge_base('main', 'feature') #=> ["abc123def456..."]

Find all common ancestors of two branches

repo.merge_base('branch-a', 'branch-b', all: true)

Find the fork point of a branch (consults the reflog)

repo.merge_base('main', 'feature', fork_point: true)

Find independent commits not reachable from each other

repo.merge_base('abc1234', 'main', 'feature', independent: true)

Returns commit SHAs of the common ancestor(s); empty when no common ancestor exists or --fork-point finds none.

Parameters:

  • commits (Array<String>)

    two or more commit SHAs, branch names, or refs to find the common ancestor(s) of

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

    merge-base options

Options Hash (options):

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

    compute the best common ancestor for an n-way merge (intersection of all merge bases)

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

    list commits not reachable from any other; useful for finding minimal merge points

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

    find the fork point where a branch diverged from another, consulting the reflog

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

    output all merge bases instead of just the first when multiple equally good bases exist

Returns:

  • (Array<String>)

    commit SHAs of the common ancestor(s); empty when no common ancestor exists or --fork-point finds none

Raises:

  • (ArgumentError)

    when unsupported options are provided

  • (Git::FailedError)

    when git merge-base exits outside the allowed range (exit code > 1)



235
236
237
238
239
240
# File 'lib/git/repository/merging.rb', line 235

def merge_base(*args)
  opts = args.last.is_a?(Hash) ? args.pop : {}
  SharedPrivate.assert_valid_opts!(MERGE_BASE_ALLOWED_OPTS, **opts)
  result = Git::Commands::MergeBase.new(@execution_context).call(*args, **opts)
  result.stdout.lines.map(&:strip).reject(&:empty?)
end

#merge_into(target_branch, branch, message = nil, opts = {}) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Merge one or more branches into another branch without leaving the current branch

Records the current branch (or the current commit when HEAD is detached), checks out target_branch, merges branch into it with #merge, then checks out the original branch or commit again. Use #merge directly when the target is the currently checked-out branch.

target_branch must be an existing local branch. Unlike Branching#checkout, a commit SHA, tag, or remote-tracking branch is rejected before any checkout happens: those detach HEAD, and the merge commit made there would be left dangling once the original branch is restored while the named ref stayed unchanged.

HEAD must be on a branch with at least one commit, or detached: an unborn branch (no commits yet) cannot be checked out again by name, so it is rejected before any checkout happens.

Option keys, the source list, and target_branch are checked before any branch is checked out, so those failures never leave the repository on target_branch. Anything rejected later, such as an option value that is not accepted or a source ref that does not exist, surfaces inside #merge after the checkout and follows the Note below.

The :no_commit option is not accepted: a merge stopped before its commit would leave target_branch unchanged and the restore checkout would carry the staged result onto the original branch. To merge without committing, call Branching#checkout and #merge directly.

Note: the restore checkout is not wrapped in ensure. If the merge fails (for example, on a conflict), the repository is left checked out on target_branch with the merge in progress rather than restored to the original branch.

Examples:

Merge a feature branch into main while staying on the current branch

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

Merge with a no-fast-forward commit message

repo.merge_into('main', 'feature', 'Merge feature into main', no_ff: true)

Octopus merge of multiple branches into main

repo.merge_into('main', %w[feature-a feature-b])

Parameters:

  • target_branch (String)

    the name of an existing local branch to merge into

  • branch (#to_s, Array<#to_s>)

    the branch or branches to merge into target_branch; accepts the same forms as #merge, but must name at least one branch

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

    optional commit message for the merge commit; see #merge for how it interacts with the :message and :m options

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

    additional options forwarded to #merge

Options Hash (opts):

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

    create a merge commit even when fast-forward is possible (--no-ff)

  • :message (String) — default: nil

    commit message; prefer the :m option

  • :m (String) — default: nil

    commit message (-m flag)

Returns:

  • (String)

    git's stdout from the merge command

Raises:

  • (ArgumentError)

    when unsupported options (including :no_commit) are provided

  • (ArgumentError)

    when branch is nil or an empty Array

  • (ArgumentError)

    when target_branch is not an existing local branch

  • (Git::Error)

    when HEAD is on an unborn branch

  • (Git::FailedError)

    when git exits with a non-zero exit status



182
183
184
185
186
187
188
189
190
191
192
# File 'lib/git/repository/merging.rb', line 182

def merge_into(target_branch, branch, message = nil, opts = {})
  SharedPrivate.assert_valid_opts!(MERGE_INTO_ALLOWED_OPTS, **opts)
  raise ArgumentError, 'at least one branch to merge is required' if Array(branch).empty?

  SharedPrivate.assert_local_branch!(self, target_branch)
  restore_point = SharedPrivate.head_restore_point(self)
  checkout(target_branch)
  output = merge(branch, message, opts)
  checkout(restore_point)
  output
end

#revert(commitish = nil, opts = {}) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Revert one or more existing commits by creating new commits that undo the changes those commits introduced

The working tree must be clean before calling this method. By default the editor is suppressed (--no-edit) so the commit message is taken from git's default revert message without prompting.

Examples:

Revert the most recent commit

repo.revert('HEAD')

Revert a specific commit by SHA

repo.revert('abc1234')

Revert a range of commits

repo.revert('HEAD~3..HEAD~1')

Revert without suppressing the editor

repo.revert('HEAD', no_edit: false)

Parameters:

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

    the commit, ref, or rev range to revert; see gitrevisions(7) for accepted forms; defaults to 'HEAD' when nil

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

    additional options forwarded to git revert

Options Hash (opts):

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

    suppress the commit-message editor (--no-edit); pass false to open the editor

Returns:

  • (String)

    git's stdout from the revert command

Raises:

  • (ArgumentError)

    when unsupported options are provided

  • (Git::FailedError)

    when git exits with a non-zero exit status



388
389
390
391
392
393
# File 'lib/git/repository/merging.rb', line 388

def revert(commitish = nil, opts = {})
  commitish = 'HEAD' if commitish.nil?
  SharedPrivate.assert_valid_opts!(REVERT_ALLOWED_OPTS, **opts)
  opts = { no_edit: true }.merge(opts)
  Git::Commands::Revert::Start.new(@execution_context).call(commitish, **opts).stdout
end

#unmergedArray<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return the paths of files with unresolved merge conflicts

Examples:

List conflicting files after a failed merge

paths = repo.unmerged
# => ["config/settings.rb", "lib/git/base.rb"]
paths.each { |path| puts "Conflict in #{path}" }

Returns:

  • (Array<String>)

    repository-relative paths of files with unresolved merge conflicts; empty array when the working tree has no conflicts

Raises:

See Also:



256
257
258
# File 'lib/git/repository/merging.rb', line 256

def unmerged
  Private.unmerged_paths(@execution_context)
end