module Git::Repository::Merging
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}.
@api private
Constants
- MERGE_ALLOWED_OPTS
-
Option keys accepted by {#merge}
Derived from the 4.x option map for ‘Git::Lib#merge`.
- MERGE_BASE_ALLOWED_OPTS
-
Option keys accepted by {#merge_base}
Derived from the 4.x option map for ‘Git::Lib#merge_base`.
- MERGE_INTO_ALLOWED_OPTS
-
Option keys accepted by {#merge_into}
The keys accepted by {#merge} except ‘:no_commit`. A merge stopped before its commit leaves `target_branch` unchanged, and the restore checkout would carry the staged merge result onto the original branch instead.
- REVERT_ALLOWED_OPTS
-
Option keys accepted by {#revert}
Derived from the 4.x option map for ‘Git::Lib#revert`.
Public Instance Methods
Source
# 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
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.
@example 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
@return [Array<String>] the list of unmerged file paths
@raise [Git::FailedError] when ‘git diff –cached` exits outside the
allowed range (exit code > 2)
@yield [file, your_version, their_version] passes conflict details for
each unmerged file
@yieldparam file [String] path to the conflicting file, relative to the
working tree
@yieldparam your_version [String] path to a temporary file containing the
stage-2 (ours) content for the conflicting file
@yieldparam their_version [String] path to a temporary file containing the
stage-3 (theirs) content for the conflicting file
@yieldreturn [void]
@deprecated Use {#each_conflict} instead
Source
# 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
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.
@example 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
@return [Array<String>] the list of unmerged file paths
@raise [Git::FailedError] when ‘git diff –cached` exits outside the
allowed range (exit code > 2)
@yield [file, your_version, their_version] passes conflict details for
each unmerged file
@yieldparam file [String] path to the conflicting file, relative to the
working tree
@yieldparam your_version [String] path to a temporary file containing the
stage-2 (ours) content for the conflicting file
@yieldparam their_version [String] path to a temporary file containing the
stage-3 (theirs) content for the conflicting file
@yieldreturn [void]
Source
# 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 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`.
@example Merge a single branch
repo.merge('feature')
@example Merge a branch with a no-fast-forward commit message
repo.merge('feature', 'Merge feature into main', no_ff: true)
@example Octopus merge of multiple branches
repo.merge(%w[feature-a feature-b])
@example Merge without committing
repo.merge('feature', nil, no_commit: true)
@param 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., {Git::BranchInfo}) is coerced to a String via `#to_s`.
@param message [String, 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.
@param opts [Hash] additional options forwarded to ‘git merge`
@option opts [Boolean, nil] :no_commit (nil) stop before creating the merge commit
(`--no-commit`)
@option opts [Boolean, nil] :no_ff (nil) create a merge commit even when
fast-forward is possible (`--no-ff`)
@option opts [String] :message (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.
@option opts [String] :m (nil) commit message (‘-m` flag)
@return [String] git’s stdout from the merge command
@raise [ArgumentError] when unsupported options are provided
@raise [Git::FailedError] when git exits with a non-zero exit status
Source
# 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
Find common ancestor commit(s) for use in a merge
@example Find the common ancestor of two branches
repo.merge_base('main', 'feature') #=> ["abc123def456..."]
@example Find all common ancestors of two branches
repo.merge_base('branch-a', 'branch-b', all: true)
@example Find the fork point of a branch (consults the reflog)
repo.merge_base('main', 'feature', fork_point: true)
@example Find independent commits not reachable from each other
repo.merge_base('abc1234', 'main', 'feature', independent: true)
@overload merge_base(*commits, options = {})
@param commits [Array<String>] two or more commit SHAs, branch names, or refs to find the common ancestor(s) of @param options [Hash] merge-base options @option options [Boolean, nil] :octopus (nil) compute the best common ancestor for an n-way merge (intersection of all merge bases) @option options [Boolean, nil] :independent (nil) list commits not reachable from any other; useful for finding minimal merge points @option options [Boolean, nil] :fork_point (nil) find the fork point where a branch diverged from another, consulting the reflog @option options [Boolean, nil] :all (nil) output all merge bases instead of just the first when multiple equally good bases exist @return [Array<String>] commit SHAs of the common ancestor(s); empty when no common ancestor exists or `--fork-point` finds none @raise [ArgumentError] when unsupported options are provided @raise [Git::FailedError] when `git merge-base` exits outside the allowed range (exit code > 1)
Source
# 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
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 {#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 {#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.
@example Merge a feature branch into main while staying on the current branch
repo.merge_into('main', 'feature')
@example Merge with a no-fast-forward commit message
repo.merge_into('main', 'feature', 'Merge feature into main', no_ff: true)
@example Octopus merge of multiple branches into main
repo.merge_into('main', %w[feature-a feature-b])
@param target_branch [String] the name of an existing local branch to
merge into
@param 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
@param message [String, nil] optional commit message for the merge commit;
see {#merge} for how it interacts with the `:message` and `:m` options
@param opts [Hash] additional options forwarded to {#merge}
@option opts [Boolean, nil] :no_ff (nil) create a merge commit even when
fast-forward is possible (`--no-ff`)
@option opts [String] :message (nil) commit message; prefer the ‘:m` option
@option opts [String] :m (nil) commit message (‘-m` flag)
@return [String] git’s stdout from the merge command
@raise [ArgumentError] when unsupported options (including ‘:no_commit`)
are provided
@raise [ArgumentError] when ‘branch` is `nil` or an empty Array
@raise [ArgumentError] when ‘target_branch` is not an existing local branch
@raise [Git::Error] when HEAD is on an unborn branch
@raise [Git::FailedError] when git exits with a non-zero exit status
Source
# 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
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.
@example Revert the most recent commit
repo.revert('HEAD')
@example Revert a specific commit by SHA
repo.revert('abc1234')
@example Revert a range of commits
repo.revert('HEAD~3..HEAD~1')
@example Revert without suppressing the editor
repo.revert('HEAD', no_edit: false)
@param commitish [String, nil] the commit, ref, or rev range to revert;
see `gitrevisions(7)` for accepted forms; defaults to `'HEAD'` when `nil`
@param opts [Hash] additional options forwarded to ‘git revert`
@option opts [Boolean, nil] :no_edit (true) suppress the commit-message
editor (`--no-edit`); pass `false` to open the editor
@return [String] git’s stdout from the revert command
@raise [ArgumentError] when unsupported options are provided
@raise [Git::FailedError] when git exits with a non-zero exit status
Source
# File lib/git/repository/merging.rb, line 256 def unmerged Private.unmerged_paths(@execution_context) end
Return the paths of files with unresolved merge conflicts
@example List conflicting files after a failed merge
paths = repo.unmerged # => ["config/settings.rb", "lib/git/base.rb"] paths.each { |path| puts "Conflict in #{path}" }
@return [Array<String>] repository-relative paths of files with unresolved
merge conflicts; empty array when the working tree has no conflicts
@raise [Git::FailedError] if git exits outside the allowed range (exit code > 2)
@see each_conflict