From 232f21fc4167f04df0e62722397d747fde0e3654 Mon Sep 17 00:00:00 2001 From: "j.seto" Date: Tue, 30 May 2023 15:26:24 -0400 Subject: [PATCH] Update tree and blob resolvers to accept ref_type Use a ref_type parameter to distinguish whether a branch or tag is requested. Propagate ref_type to blob and tree presenters so frontend code can continue depending on the rendered routes Contribute to https://gitlab.com/gitlab-org/gitlab/-/issues/20526 Changelog: other --- app/graphql/resolvers/blobs_resolver.rb | 14 ++- app/graphql/resolvers/last_commit_resolver.rb | 3 +- .../resolvers/paginated_tree_resolver.rb | 12 ++- app/graphql/resolvers/tree_resolver.rb | 8 +- app/graphql/types/ref_type_enum.rb | 11 +++ app/models/blob.rb | 1 + app/models/repository.rb | 5 +- app/models/tree.rb | 16 +++- app/presenters/blob_presenter.rb | 29 ++++-- app/presenters/tree_entry_presenter.rb | 17 +++- doc/api/graphql/reference/index.md | 12 +++ lib/extracts_ref.rb | 30 +++++-- lib/gitlab/git/tree.rb | 2 +- spec/graphql/resolvers/blobs_resolver_spec.rb | 89 +++++++++++++++++-- .../resolvers/last_commit_resolver_spec.rb | 24 +++++ spec/lib/extracts_ref_spec.rb | 59 ++++++++++++ spec/presenters/blob_presenter_spec.rb | 26 ++++++ spec/presenters/tree_entry_presenter_spec.rb | 16 ++++ 18 files changed, 338 insertions(+), 36 deletions(-) create mode 100644 app/graphql/types/ref_type_enum.rb diff --git a/app/graphql/resolvers/blobs_resolver.rb b/app/graphql/resolvers/blobs_resolver.rb index 0b8180dbce70e1..546eeb76ff53f4 100644 --- a/app/graphql/resolvers/blobs_resolver.rb +++ b/app/graphql/resolvers/blobs_resolver.rb @@ -17,6 +17,10 @@ class BlobsResolver < BaseResolver required: false, default_value: nil, description: 'Commit ref to get the blobs from. Default value is HEAD.' + argument :ref_type, Types::RefTypeEnum, + required: false, + default_value: nil, + description: 'Type of ref.' # We fetch blobs from Gitaly efficiently but it still scales O(N) with the # number of paths being fetched, so apply a scaling limit to that. @@ -24,7 +28,7 @@ def self.resolver_complexity(args, child_complexity:) super + (args[:paths] || []).size end - def resolve(paths:, ref:) + def resolve(paths:, ref:, ref_type:) authorize!(repository.container) return [] if repository.empty? @@ -32,7 +36,13 @@ def resolve(paths:, ref:) ref ||= repository.root_ref validate_ref(ref) - repository.blobs_at(paths.map { |path| [ref, path] }) + ref = ExtractsRef.qualify_ref(ref, ref_type) + + repository.blobs_at(paths.map { |path| [ref, path] }).tap do |blobs| + blobs.each do |blob| + blob.ref_type = ref_type + end + end end private diff --git a/app/graphql/resolvers/last_commit_resolver.rb b/app/graphql/resolvers/last_commit_resolver.rb index 00c43bdfee647e..acf7826ab13880 100644 --- a/app/graphql/resolvers/last_commit_resolver.rb +++ b/app/graphql/resolvers/last_commit_resolver.rb @@ -11,7 +11,8 @@ class LastCommitResolver < BaseResolver def resolve(**args) # Ensure merge commits can be returned by sending nil to Gitaly instead of '/' path = tree.path == '/' ? nil : tree.path - commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path, literal_pathspec: true) + commit = Gitlab::Git::Commit.last_for_path(tree.repository, + ExtractsRef.qualify_ref(tree.sha, tree.ref_type), path, literal_pathspec: true) ::Commit.new(commit, tree.repository.project) if commit end diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb index 8fd80b1a9b99b4..de48fbafb044c3 100644 --- a/app/graphql/resolvers/paginated_tree_resolver.rb +++ b/app/graphql/resolvers/paginated_tree_resolver.rb @@ -18,6 +18,9 @@ class PaginatedTreeResolver < BaseResolver argument :ref, GraphQL::Types::String, required: false, description: 'Commit ref to get the tree for. Default value is HEAD.' + argument :ref_type, Types::RefTypeEnum, + required: false, + description: 'Type of ref.' alias_method :repository, :object @@ -25,7 +28,6 @@ def resolve(**args) return if repository.empty? cursor = args.delete(:after) - args[:ref] ||= :head pagination_params = { limit: @field.max_page_size || 100, @@ -33,9 +35,11 @@ def resolve(**args) } tree = repository.tree( - args[:ref], args[:path], recursive: args[:recursive], - skip_flat_paths: false, - pagination_params: pagination_params + args[:ref].presence || :head, + args[:path], recursive: args[:recursive], + skip_flat_paths: false, + pagination_params: pagination_params, + ref_type: args[:ref_type] ) next_cursor = tree.cursor&.next_cursor diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb index 553f9aa6cd97c4..6b88f120d1bc20 100644 --- a/app/graphql/resolvers/tree_resolver.rb +++ b/app/graphql/resolvers/tree_resolver.rb @@ -17,14 +17,18 @@ class TreeResolver < BaseResolver argument :ref, GraphQL::Types::String, required: false, description: 'Commit ref to get the tree for. Default value is HEAD.' + argument :ref_type, Types::RefTypeEnum, + required: false, + description: 'Type of ref.' alias_method :repository, :object def resolve(**args) return unless repository.exists? - args[:ref] ||= :head - repository.tree(args[:ref], args[:path], recursive: args[:recursive]) + ref = (args[:ref].presence || :head) + + repository.tree(ref, args[:path], recursive: args[:recursive], ref_type: args[:ref_type]) end end end diff --git a/app/graphql/types/ref_type_enum.rb b/app/graphql/types/ref_type_enum.rb new file mode 100644 index 00000000000000..f56d4cd512acca --- /dev/null +++ b/app/graphql/types/ref_type_enum.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + class RefTypeEnum < BaseEnum + graphql_name 'RefType' + description 'Type of ref' + + value 'HEADS', description: 'Ref type for branches.', value: 'heads' + value 'TAGS', description: 'Ref type for tags.', value: 'tags' + end +end diff --git a/app/models/blob.rb b/app/models/blob.rb index e6496e21175fb5..7c88833b19dcd3 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -71,6 +71,7 @@ class Blob < SimpleDelegator ].freeze attr_reader :container + attr_accessor :ref_type delegate :repository, to: :container, allow_nil: true delegate :project, to: :repository, allow_nil: true diff --git a/app/models/repository.rb b/app/models/repository.rb index acb795f174dc05..b21df6baf0e69b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -691,7 +691,7 @@ def head_tree(skip_flat_paths: true) @head_tree ||= Tree.new(self, root_ref, nil, skip_flat_paths: skip_flat_paths) end - def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil) + def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil, ref_type: nil) if sha == :head return if empty? || root_ref.nil? @@ -699,10 +699,11 @@ def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagin return head_tree(skip_flat_paths: skip_flat_paths) else sha = head_commit.sha + ref_type = nil end end - Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params) + Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params, ref_type: ref_type) end def blob_at_branch(branch_name, path) diff --git a/app/models/tree.rb b/app/models/tree.rb index c6adf5c263ccae..8622eb793c16ec 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -3,17 +3,25 @@ class Tree include Gitlab::Utils::StrongMemoize - attr_accessor :repository, :sha, :path, :entries, :cursor + attr_accessor :repository, :sha, :path, :entries, :cursor, :ref_type - def initialize(repository, sha, path = '/', recursive: false, skip_flat_paths: true, pagination_params: nil) + def initialize( + repository, sha, path = '/', recursive: false, skip_flat_paths: true, pagination_params: nil, + ref_type: nil) path = '/' if path.blank? @repository = repository @sha = sha @path = path - + @ref_type = ExtractsRef.ref_type(ref_type) git_repo = @repository.raw_repository - @entries, @cursor = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive, skip_flat_paths, pagination_params) + + ref = ExtractsRef.qualify_ref(@sha, ref_type) + + @entries, @cursor = Gitlab::Git::Tree.where(git_repo, ref, @path, recursive, skip_flat_paths, pagination_params) + @entries.each do |entry| + entry.ref_type = self.ref_type + end end def readme_path diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index f25436c54bedef..cd473152b41c14 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -56,23 +56,23 @@ def raw_plain_data end def web_url - url_helpers.project_blob_url(project, ref_qualified_path) + url_helpers.project_blob_url(*path_params) end def web_path - url_helpers.project_blob_path(project, ref_qualified_path) + url_helpers.project_blob_path(*path_params) end def edit_blob_path - url_helpers.project_edit_blob_path(project, ref_qualified_path) + url_helpers.project_edit_blob_path(*path_params) end def raw_path - url_helpers.project_raw_path(project, ref_qualified_path) + url_helpers.project_raw_path(*path_params) end def replace_path - url_helpers.project_update_blob_path(project, ref_qualified_path) + url_helpers.project_update_blob_path(*path_params) end def pipeline_editor_path @@ -164,6 +164,18 @@ def project_blob_path_root private + def path_params + if ref_type.present? + [project, ref_qualified_path, { ref_type: ref_type }] + else + [project, ref_qualified_path] + end + end + + def ref_type + blob.ref_type + end + def url_helpers Gitlab::Routing.url_helpers end @@ -179,7 +191,12 @@ def project end def ref_qualified_path - File.join(blob.commit_id, blob.path) + # If `ref_type` is present the commit_id will include the ref qualifier e.g. `refs/heads/`. + # We only accept/return unqualified refs so we need to remove the qualifier from the `commit_id`. + + commit_id = ExtractsRef.unqualify_ref(blob.commit_id, ref_type) + + File.join(commit_id, blob.path) end def load_all_blob_data diff --git a/app/presenters/tree_entry_presenter.rb b/app/presenters/tree_entry_presenter.rb index 0b313d81360190..3f4a9f13c36338 100644 --- a/app/presenters/tree_entry_presenter.rb +++ b/app/presenters/tree_entry_presenter.rb @@ -4,10 +4,23 @@ class TreeEntryPresenter < Gitlab::View::Presenter::Delegated presents nil, as: :tree def web_url - Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, File.join(tree.commit_id, tree.path)) + Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, ref_qualified_path, + ref_type: tree.ref_type) end def web_path - Gitlab::Routing.url_helpers.project_tree_path(tree.repository.project, File.join(tree.commit_id, tree.path)) + Gitlab::Routing.url_helpers.project_tree_path(tree.repository.project, ref_qualified_path, + ref_type: tree.ref_type) + end + + private + + def ref_qualified_path + # If `ref_type` is present the commit_id will include the ref qualifier e.g. `refs/heads/`. + # We only accept/return unqualified refs so we need to remove the qualifier from the `commit_id`. + + commit_id = ExtractsRef.unqualify_ref(tree.commit_id, ref_type) + + File.join(commit_id, tree.path) end end diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c6e1363b50544b..ae4cf7765a8fe1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -21689,6 +21689,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | ---- | ---- | ----------- | | `paths` | [`[String!]!`](#string) | Array of desired blob paths. | | `ref` | [`String`](#string) | Commit ref to get the blobs from. Default value is HEAD. | +| `refType` | [`RefType`](#reftype) | Type of ref. | ##### `Repository.branchNames` @@ -21733,6 +21734,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | `path` | [`String`](#string) | Path to get the tree for. Default value is the root of the repository. | | `recursive` | [`Boolean`](#boolean) | Used to get a recursive tree. Default is false. | | `ref` | [`String`](#string) | Commit ref to get the tree for. Default value is HEAD. | +| `refType` | [`RefType`](#reftype) | Type of ref. | ##### `Repository.tree` @@ -21747,6 +21749,7 @@ Returns [`Tree`](#tree). | `path` | [`String`](#string) | Path to get the tree for. Default value is the root of the repository. | | `recursive` | [`Boolean`](#boolean) | Used to get a recursive tree. Default is false. | | `ref` | [`String`](#string) | Commit ref to get the tree for. Default value is HEAD. | +| `refType` | [`RefType`](#reftype) | Type of ref. | ### `RepositoryBlob` @@ -26000,6 +26003,15 @@ Project member relation. | `INVITED_GROUPS` | Invited Groups members. | | `SHARED_INTO_ANCESTORS` | Shared Into Ancestors members. | +### `RefType` + +Type of ref. + +| Value | Description | +| ----- | ----------- | +| `HEADS` | Ref type for branches. | +| `TAGS` | Ref type for tags. | + ### `RegistryState` State of a Geo registry. diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb index 5f73b474956d47..49ec564eb8d64a 100644 --- a/lib/extracts_ref.rb +++ b/lib/extracts_ref.rb @@ -7,6 +7,28 @@ module ExtractsRef InvalidPathError = Class.new(StandardError) BRANCH_REF_TYPE = 'heads' TAG_REF_TYPE = 'tags' + REF_TYPES = [BRANCH_REF_TYPE, TAG_REF_TYPE].freeze + + def self.ref_type(type) + return unless REF_TYPES.include?(type) + + type + end + + def self.qualify_ref(ref, type) + validated_type = ref_type(type) + return ref unless validated_type + + %(refs/#{validated_type}/#{ref}) + end + + def self.unqualify_ref(ref, type) + validated_type = ref_type(type) + return ref unless validated_type + + ref.sub(%r{^refs/#{validated_type}/}, '') + end + # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. # @@ -60,7 +82,6 @@ def extract_ref(id) # # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. - # # rubocop:disable Gitlab/ModuleWithInstanceVariables def assign_ref_vars @id, @ref, @path = extract_ref_path @@ -70,7 +91,7 @@ def assign_ref_vars return unless @ref.present? @commit = if ref_type - @fully_qualified_ref = %(refs/#{ref_type}/#{@ref}) + @fully_qualified_ref = ExtractsRef.qualify_ref(@ref, ref_type) @repo.commit(@fully_qualified_ref) else @repo.commit(@ref) @@ -90,9 +111,7 @@ def extract_ref_path end def ref_type - return unless params[:ref_type].present? - - params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE + ExtractsRef.ref_type(params[:ref_type]) end private @@ -156,6 +175,7 @@ def repository_container raise NotImplementedError end + # deprecated in favor of ExtractsRef::RequestedRef def ambiguous_ref?(project, ref) return false unless ref return true if project.repository.ambiguous_ref?(ref) diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index e437f99dab3986..df3d8165ef2c9e 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -6,7 +6,7 @@ class Tree include Gitlab::EncodingHelper extend Gitlab::Git::WrapsGitalyErrors - attr_accessor :id, :type, :mode, :commit_id, :submodule_url + attr_accessor :id, :type, :mode, :commit_id, :submodule_url, :ref_type attr_writer :name, :path, :flat_path class << self diff --git a/spec/graphql/resolvers/blobs_resolver_spec.rb b/spec/graphql/resolvers/blobs_resolver_spec.rb index 26eb6dc0abe310..0d725f00d43193 100644 --- a/spec/graphql/resolvers/blobs_resolver_spec.rb +++ b/spec/graphql/resolvers/blobs_resolver_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Resolvers::BlobsResolver do +RSpec.describe Resolvers::BlobsResolver, feature_category: :source_code_management do include GraphqlHelpers + include RepoHelpers describe '.resolver_complexity' do it 'adds one per path being resolved' do @@ -59,15 +60,89 @@ end end - context 'specifying a different ref' do + context 'when specifying a branch ref' do let(:ref) { 'add-pdf-file' } + let(:args) { { paths: paths, ref: ref, ref_type: ref_type } } let(:paths) { ['files/pdf/test.pdf', 'README.md'] } - it 'returns the specified blobs for that ref' do - is_expected.to contain_exactly( - have_attributes(path: 'files/pdf/test.pdf'), - have_attributes(path: 'README.md') - ) + context 'and no ref_type is specified' do + let(:ref_type) { nil } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'files/pdf/test.pdf'), + have_attributes(path: 'README.md') + ) + end + + context 'and a tag with the same name exists' do + let(:ref) { SecureRandom.uuid } + + before do + project.repository.create_branch(ref) + create_file_in_repo(project, ref, ref, 'branch_file', 'Test file', commit_message: 'Add new content') + project.repository.add_tag(project.owner, sample_commit.id, ref) + end + + it 'returns the specified blobs for the tag' do + is_expected.to contain_exactly( + have_attributes(path: 'README.md') + ) + end + end + end + + context 'and ref_type is for branches' do + let(:args) { { paths: paths, ref: ref, ref_type: 'heads' } } + + it 'returns nothing' do + is_expected.to contain_exactly( + have_attributes(path: 'files/pdf/test.pdf'), + have_attributes(path: 'README.md') + ) + end + end + + context 'and ref_type is for tags' do + let(:args) { { paths: paths, ref: ref, ref_type: 'tags' } } + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + + context 'when specifying a tag ref' do + let(:ref) { 'v1.0.0' } + + let(:args) { { paths: paths, ref: ref, ref_type: ref_type } } + + context 'and no ref_type is specified' do + let(:ref_type) { nil } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'README.md') + ) + end + end + + context 'and ref_type is for tags' do + let(:ref_type) { 'tags' } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'README.md') + ) + end + end + + context 'and ref_type is for branches' do + let(:ref_type) { 'heads' } + + it 'returns nothing' do + is_expected.to be_empty + end end end diff --git a/spec/graphql/resolvers/last_commit_resolver_spec.rb b/spec/graphql/resolvers/last_commit_resolver_spec.rb index 5ac6ad59864611..82bbdd4487c580 100644 --- a/spec/graphql/resolvers/last_commit_resolver_spec.rb +++ b/spec/graphql/resolvers/last_commit_resolver_spec.rb @@ -61,5 +61,29 @@ expect(commit).to be_nil end end + + context 'when the ref is ambiguous' do + let(:ambiguous_ref) { 'v1.0.0' } + + before do + project.repository.create_branch(ambiguous_ref) + end + + context 'when tree is for a tag' do + let(:tree) { repository.tree(ambiguous_ref, ref_type: 'tags') } + + it 'resolves commit' do + expect(commit.id).to eq(repository.find_tag(ambiguous_ref).dereferenced_target.id) + end + end + + context 'when tree is for a branch' do + let(:tree) { repository.tree(ambiguous_ref, ref_type: 'heads') } + + it 'resolves commit' do + expect(commit.id).to eq(repository.find_branch(ambiguous_ref).target) + end + end + end end end diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb index 93a09bf5a0a7d2..ac403ad642a3fe 100644 --- a/spec/lib/extracts_ref_spec.rb +++ b/spec/lib/extracts_ref_spec.rb @@ -57,5 +57,64 @@ end end + describe '#ref_type' do + let(:params) { ActionController::Parameters.new(ref_type: 'heads') } + + it 'delegates to .ref_type' do + expect(described_class).to receive(:ref_type).with('heads') + ref_type + end + end + + describe '.ref_type' do + subject { described_class.ref_type(ref_type) } + + context 'when ref_type is nil' do + let(:ref_type) { nil } + + it { is_expected.to eq(nil) } + end + + context 'when ref_type is heads' do + let(:ref_type) { 'heads' } + + it { is_expected.to eq('heads') } + end + + context 'when ref_type is tags' do + let(:ref_type) { 'tags' } + + it { is_expected.to eq('tags') } + end + + context 'when ref_type is invalid' do + let(:ref_type) { 'invalid' } + + it { is_expected.to eq(nil) } + end + end + + describe '.qualify_ref' do + subject { described_class.qualify_ref(ref, ref_type) } + + context 'when ref_type is nil' do + let(:ref_type) { nil } + + it { is_expected.to eq(ref) } + end + + context 'when ref_type valid' do + let(:ref_type) { 'heads' } + + it { is_expected.to eq("refs/#{ref_type}/#{ref}") } + end + + context 'when ref_type is invalid' do + let(:ref_type) { 'invalid' } + + it { is_expected.to eq(ref) } + end + end + it_behaves_like 'extracts refs' end diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index f10150b819af90..e776716bd2d724 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -31,6 +31,32 @@ it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}") } end + context 'when blob has ref_type' do + before do + blob.ref_type = 'heads' + end + + describe '#web_url' do + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#web_path' do + it { expect(presenter.web_path).to eq("/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#edit_blob_path' do + it { expect(presenter.edit_blob_path).to eq("/#{project.full_path}/-/edit/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#raw_path' do + it { expect(presenter.raw_path).to eq("/#{project.full_path}/-/raw/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + + describe '#replace_path' do + it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/update/#{blob.commit_id}/#{blob.path}?ref_type=heads") } + end + end + describe '#can_current_user_push_to_branch' do let(:branch_exists) { true } diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb index de84f36c5e6144..0abf372b70458e 100644 --- a/spec/presenters/tree_entry_presenter_spec.rb +++ b/spec/presenters/tree_entry_presenter_spec.rb @@ -17,4 +17,20 @@ describe '#web_path' do it { expect(presenter.web_path).to eq("/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}") } end + + context 'when blob has ref_type' do + before do + tree.ref_type = 'heads' + end + + describe '.web_url' do + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}?ref_type=heads") } + end + + describe '#web_path' do + it { + expect(presenter.web_path).to eq("/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}?ref_type=heads") + } + end + end end -- GitLab