Module: Git::Repository::Merging

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

Overview

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

Included by Git::Repository.

Instance Method Summary collapse

Instance Method Details

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

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:



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

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

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 (String, Array<String>, #to_s)

    the branch or branches to merge into the current branch

    When an Array is given, an octopus merge is performed; a Branch object 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>

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)



141
142
143
144
145
146
# File 'lib/git/repository/merging.rb', line 141

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

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

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



232
233
234
235
236
237
# File 'lib/git/repository/merging.rb', line 232

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