# frozen_string_literal: true module Gitlab module Git class CrossRepoComparer attr_reader :source_repo, :target_repo def initialize(source_repo, target_repo) @source_repo = source_repo @target_repo = target_repo end def compare(source_ref, target_ref, straight:) ensuring_ref_in_source(target_ref) do |target_commit_id| Gitlab::Git::Compare.new( source_repo, target_commit_id, source_ref, straight: straight ) end end private def ensuring_ref_in_source(ref, &blk) return yield ref if source_repo == target_repo # If the commit doesn't exist in the target, there's nothing we can do commit_id = target_repo.commit(ref)&.sha return unless commit_id # The commit pointed to by ref may exist in the source even when they # are different repositories. This is particularly true of close forks, # but may also be the case if a temporary ref for this comparison has # already been created in the past, and the result hasn't been GC'd yet. return yield commit_id if source_repo.commit(commit_id) # Worst case: the commit is not in the source repo so we need to fetch # it. Use a temporary ref and clean up afterwards with_commit_in_source_tmp(commit_id, &blk) end # Fetch the ref into source_repo from target_repo, using a temporary ref # name that will be deleted once the method completes. This is a no-op if # fetching the source branch fails def with_commit_in_source_tmp(commit_id, &blk) tmp_ref = "refs/tmp/#{SecureRandom.hex}" yield commit_id if source_repo.fetch_source_branch!(target_repo, commit_id, tmp_ref) ensure source_repo.delete_refs(tmp_ref) # best-effort end end end end