2020-02-01 01:16:34 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Git
|
2023-03-04 22:38:38 +05:30
|
|
|
class CrossRepo
|
2020-02-01 01:16:34 +05:30
|
|
|
attr_reader :source_repo, :target_repo
|
|
|
|
|
|
|
|
def initialize(source_repo, target_repo)
|
|
|
|
@source_repo = source_repo
|
|
|
|
@target_repo = target_repo
|
|
|
|
end
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
def execute(target_ref, &blk)
|
|
|
|
ensuring_ref_in_source(target_ref, &blk)
|
2020-02-01 01:16:34 +05:30
|
|
|
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)
|
2022-07-23 23:45:48 +05:30
|
|
|
tmp_ref = "refs/#{::Repository::REF_TMP}/#{SecureRandom.hex}"
|
2020-02-01 01:16:34 +05:30
|
|
|
|
|
|
|
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
|