diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/api/repositories.rb | 33 | ||||
-rw-r--r-- | lib/gitlab/git.rb | 8 | ||||
-rw-r--r-- | lib/gitlab/git/merge_base.rb | 44 |
3 files changed, 82 insertions, 3 deletions
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 33a9646ac3b..79736107bbb 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -123,6 +123,39 @@ module API not_found! end end + + desc 'Get the common ancestor between commits' do + success Entities::Commit + end + params do + # For now we just support 2 refs passed, but `merge-base` supports + # multiple defining this as an Array instead of 2 separate params will + # make sure we don't need to deprecate this API in favor of one + # supporting multiple commits when this functionality gets added to + # Gitaly + requires :refs, type: Array[String] + end + get ':id/repository/merge_base' do + refs = params[:refs] + + unless refs.size == 2 + render_api_error!('Provide exactly 2 refs', 400) + end + + merge_base = Gitlab::Git::MergeBase.new(user_project.repository, refs) + + if merge_base.unknown_refs.any? + ref_noun = 'ref'.pluralize(merge_base.unknown_refs.size) + message = "Could not find #{ref_noun}: #{merge_base.unknown_refs.join(', ')}" + render_api_error!(message, 400) + end + + if merge_base.commit + present merge_base.commit, with: Entities::Commit + else + not_found!("Merge Base") + end + end end end end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 55236a1122f..2913a3e416d 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -10,9 +10,11 @@ module Gitlab TAG_REF_PREFIX = "refs/tags/".freeze BRANCH_REF_PREFIX = "refs/heads/".freeze - CommandError = Class.new(StandardError) - CommitError = Class.new(StandardError) - OSError = Class.new(StandardError) + BaseError = Class.new(StandardError) + CommandError = Class.new(BaseError) + CommitError = Class.new(BaseError) + OSError = Class.new(BaseError) + UnknownRef = Class.new(BaseError) class << self include Gitlab::EncodingHelper diff --git a/lib/gitlab/git/merge_base.rb b/lib/gitlab/git/merge_base.rb new file mode 100644 index 00000000000..b27f7038c26 --- /dev/null +++ b/lib/gitlab/git/merge_base.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Gitlab + module Git + class MergeBase + include Gitlab::Utils::StrongMemoize + + def initialize(repository, refs) + @repository, @refs = repository, refs + end + + # Returns the SHA of the first common ancestor + def sha + if unknown_refs.any? + raise UnknownRef, "Can't find merge base for unknown refs: #{unknown_refs.inspect}" + end + + strong_memoize(:sha) do + @repository.merge_base(*commits_for_refs) + end + end + + # Returns the merge base as a Gitlab::Git::Commit + def commit + return unless sha + + @commit ||= @repository.commit_by(oid: sha) + end + + # Returns the refs passed on initialization that aren't found in + # the repository, and thus cannot be used to find a merge base. + def unknown_refs + @unknown_refs ||= Hash[@refs.zip(commits_for_refs)] + .select { |ref, commit| commit.nil? }.keys + end + + private + + def commits_for_refs + @commits_for_refs ||= @repository.commits_by(oids: @refs) + end + end + end +end |