diff --git a/changelogs/unreleased/sh-rugged-get-tree-entry.yml b/changelogs/unreleased/sh-rugged-get-tree-entry.yml new file mode 100644 index 00000000000..4d46b764022 --- /dev/null +++ b/changelogs/unreleased/sh-rugged-get-tree-entry.yml @@ -0,0 +1,5 @@ +--- +title: Bring back Rugged implementation of TreeEntry +merge_request: 25706 +author: +type: other diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 259a2b7911a..10df4ed72d9 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -23,6 +23,10 @@ module Gitlab class << self def find(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE) + tree_entry(repository, sha, path, limit) + end + + def tree_entry(repository, sha, path, limit) return unless path path = path.sub(%r{\A/*}, '') @@ -179,3 +183,5 @@ module Gitlab end end end + +Gitlab::Git::Blob.singleton_class.prepend Gitlab::Git::RuggedImpl::Blob::ClassMethods diff --git a/lib/gitlab/git/rugged_impl/blob.rb b/lib/gitlab/git/rugged_impl/blob.rb new file mode 100644 index 00000000000..26c82a1a7e4 --- /dev/null +++ b/lib/gitlab/git/rugged_impl/blob.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +# NOTE: This code is legacy. Do not add/modify code here unless you have +# discussed with the Gitaly team. See +# https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code +# for more details. + +module Gitlab + module Git + module RuggedImpl + module Blob + module ClassMethods + extend ::Gitlab::Utils::Override + + override :tree_entry + def tree_entry(repository, sha, path, limit) + if Feature.enabled?(:rugged_tree_entry) + rugged_tree_entry(repository, sha, path, limit) + else + super + end + end + + private + + def rugged_tree_entry(repository, sha, path, limit) + return unless path + + # Strip any leading / characters from the path + path = path.sub(%r{\A/*}, '') + + rugged_commit = repository.lookup(sha) + root_tree = rugged_commit.tree + + blob_entry = find_entry_by_path(repository, root_tree.oid, *path.split('/')) + + return nil unless blob_entry + + if blob_entry[:type] == :commit + submodule_blob(blob_entry, path, sha) + else + blob = repository.lookup(blob_entry[:oid]) + + if blob + new( + id: blob.oid, + name: blob_entry[:name], + size: blob.size, + # Rugged::Blob#content is expensive; don't call it if we don't have to. + data: limit.zero? ? '' : blob.content(limit), + mode: blob_entry[:filemode].to_s(8), + path: path, + commit_id: sha, + binary: blob.binary? + ) + end + end + rescue Rugged::ReferenceError + nil + end + + # Recursive search of blob id by path + # + # Ex. + # blog/ # oid: 1a + # app/ # oid: 2a + # models/ # oid: 3a + # file.rb # oid: 4a + # + # + # Blob.find_entry_by_path(repo, '1a', 'blog', 'app', 'file.rb') # => '4a' + # + def find_entry_by_path(repository, root_id, *path_parts) + root_tree = repository.lookup(root_id) + + entry = root_tree.find do |entry| + entry[:name] == path_parts[0] + end + + return nil unless entry + + if path_parts.size > 1 + return nil unless entry[:type] == :tree + + path_parts.shift + find_entry_by_path(repository, entry[:oid], *path_parts) + else + [:blob, :commit].include?(entry[:type]) ? entry : nil + end + end + + def submodule_blob(blob_entry, path, sha) + new( + id: blob_entry[:oid], + name: blob_entry[:name], + size: 0, + data: '', + path: path, + commit_id: sha + ) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index a1b5cea88c0..10bc82e24d1 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Git::Blob, :seed_helper do end end - describe '.find' do + shared_examples '.find' do context 'nil path' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, nil) } @@ -128,6 +128,20 @@ describe Gitlab::Git::Blob, :seed_helper do end end + describe '.find with Gitaly enabled' do + it_behaves_like '.find' + end + + describe '.find with Rugged enabled', :enable_rugged do + it 'calls out to the Rugged implementation' do + allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original + + described_class.find(repository, SeedRepo::Commit::ID, 'files/images/6049019_460s.jpg') + end + + it_behaves_like '.find' + end + describe '.raw' do let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) } let(:bad_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::BigCommit::ID) }