diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 11fcf272bef3ba64ecad39e8c425e233fbeae23e..1e4c138931f0ec0eb37cef497f2928367bdc28a8 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -115,7 +115,9 @@ def audit_download(build, filename) def extract_ref_name_and_path return unless params[:ref_name_and_path] - @ref_name, @path = extract_ref(params[:ref_name_and_path]) + ref_extractor = ExtractsRef::RefExtractor.new(@project, {}) + + @ref_name, @path = ref_extractor.extract_ref(params[:ref_name_and_path]) end def artifacts_params diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index e68c13fa7d46279f3891ca594f566d69719b8f71..5eb56a36628c6cfc4f6b8daec4683acd3b8ae025 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -169,8 +169,10 @@ def redirect_renamed_default_branch? end def assign_blob_vars + ref_extractor = ExtractsRef::RefExtractor.new(@project, {}) @id = params[:id] - @ref, @path = extract_ref(@id) + + @ref, @path = ref_extractor.extract_ref(@id) rescue InvalidPathError render_404 end diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb index d5655d404291407bff1348754d2b9ce9e3d40ef6..1c278fa87541a32944d7b12904b21145b30150fb 100644 --- a/app/controllers/projects/build_artifacts_controller.rb +++ b/app/controllers/projects/build_artifacts_controller.rb @@ -39,7 +39,9 @@ def validate_artifacts! def extract_ref_name_and_path return unless params[:ref_name_and_path] - @ref_name, @path = extract_ref(params[:ref_name_and_path]) + ref_extractor = ExtractsRef::RefExtractor.new(@project, {}) + + @ref_name, @path = ref_extractor.extract_ref(params[:ref_name_and_path]) end def job diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index d0a80c6aa07b39154508700e44072b17ecbcf252..9466e88e1fdcfa4456f074a7f78cb95396aac4e8 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -25,13 +25,6 @@ def show private - def set_ref_and_path - # This bypasses assign_ref_vars to avoid a Gitaly FindCommit lookup. - # We don't need to find the commit to either rate limit or send the - # blob. - @ref, @path = extract_ref(get_id) - end - def check_show_rate_limit! check_rate_limit!(:raw_blob, scope: [@project, @path]) do render plain: _('You cannot access the raw file. Please wait a minute.'), status: :too_many_requests diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 170eb3c388bb3440d7d7540dd6a8e98b994039b0..4b7f3deaeb0768cb5b19068bf4bb9d0f01031a62 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -1,6 +1,5 @@ - action = current_action?(:edit, :update) ? 'edit' : 'create' -- file_name = params[:id].split("/").last ||= "" -- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name) +- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(local_assigns[:path]) .file-holder.file.gl-mb-3 .js-file-title.file-title.gl-display-flex.gl-align-items-center.gl-rounded-top-base{ data: { current_action: action } } diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 74c1d2347fa42f863c0ee89831130b92410ad380..c9ac5acb441a91118e3c8f601de564d1dbd68014 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -6,7 +6,7 @@ = _('New file') .file-editor = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths(@project, 'post')) do - = render 'projects/blob/editor', ref: @ref + = render 'projects/blob/editor', ref: @ref, path: @path = render 'shared/new_commit_form', placeholder: "Add new file" = hidden_field_tag 'content', '', id: 'file-content' diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb index 355fd8486fffaa2fe9c47e68b163564014dfa640..9ca80d8d87f938e070a7df6cc89d1948c89cc379 100644 --- a/lib/extracts_ref.rb +++ b/lib/extracts_ref.rb @@ -15,52 +15,6 @@ module ExtractsRef TAG_REF_TYPE = ExtractsRef::RefExtractor::TAG_REF_TYPE REF_TYPES = ExtractsRef::RefExtractor::REF_TYPES - def self.ref_type(type) - ExtractsRef::RefExtractor.ref_type(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. - # - # Expects a repository_container method that returns the active repository object. This is - # used to check the input against a list of valid repository refs. - # - # Examples - # - # # No repository_container available - # extract_ref('master') - # # => ['', ''] - # - # extract_ref('master') - # # => ['master', ''] - # - # extract_ref("f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG") - # # => ['f4b14494ef6abf3d144c28e4af0c20143383e062', 'CHANGELOG'] - # - # extract_ref("v2.0.0/README.md") - # # => ['v2.0.0', 'README.md'] - # - # extract_ref('master/app/models/project.rb') - # # => ['master', 'app/models/project.rb'] - # - # extract_ref('issues/1234/app/models/project.rb') - # # => ['issues/1234', 'app/models/project.rb'] - # - # # Given an invalid branch, we fall back to just splitting on the first slash - # extract_ref('non/existent/branch/README.md') - # # => ['non', 'existent/branch/README.md'] - # - # Returns an Array where the first value is the tree-ish and the second is the - # path - def extract_ref(id) - pair = extract_raw_ref(id) - - [ - pair[0].strip, - pair[1].delete_prefix('/').delete_suffix('/') - ] - end - # Assigns common instance variables for views working with Git tree-ish objects # # Assignments are: @@ -94,60 +48,11 @@ def tree end def ref_type - ExtractsRef.ref_type(params[:ref_type]) + ExtractsRef::RefExtractor.ref_type(params[:ref_type]) end private - def extract_raw_ref(id) - return ['', ''] unless repository_container - - # If the ref appears to be a SHA, we're done, just split the string - return $~.captures if id =~ /^(\h{40}\h{24}?)(.*)/ - - # No slash means we must have a ref and no path - return [id, ''] unless id.include?('/') - - # Otherwise, attempt to detect the ref using a list of the - # repository_container's branches and tags - - # Append a trailing slash if we only get a ref and no file path - id = [id, '/'].join unless id.ends_with?('/') - first_path_segment, rest = id.split('/', 2) - - return [first_path_segment, rest] if use_first_path_segment?(first_path_segment) - - valid_refs = ref_names.select { |v| id.start_with?("#{v}/") } - - # No exact ref match, so just try our best - return id.match(%r{([^/]+)(.*)}).captures if valid_refs.empty? - - # There is a distinct possibility that multiple refs prefix the ID. - # Use the longest match to maximize the chance that we have the - # right ref. - best_match = valid_refs.max_by(&:length) - - # Partition the string into the ref and the path, ignoring the empty first value - id.partition(best_match)[1..] - end - - def use_first_path_segment?(ref) - return false unless repository_container - return false if repository_container.repository.has_ambiguous_refs? - - repository_container.repository.branch_names_include?(ref) || - repository_container.repository.tag_names_include?(ref) - end - - # overridden in subclasses, do not remove - def get_id - allowed_params = params.permit(:id, :ref, :path) - - id = [allowed_params[:id] || allowed_params[:ref]] - id << ("/" + allowed_params[:path]) unless allowed_params[:path].blank? - id.join - end - def ref_names return [] unless repository_container @@ -157,14 +62,4 @@ def ref_names 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) - return false unless ref.starts_with?(%r{(refs|heads|tags)/}) - - unprefixed_ref = ref.sub(%r{^(refs/)?(heads|tags)/}, '') - project.repository.commit(unprefixed_ref).present? - end end diff --git a/lib/extracts_ref/ref_extractor.rb b/lib/extracts_ref/ref_extractor.rb index b67548ef01e35cc4655cb305506cdd8539cb320d..ca4013da02d83cd5a8c7e508013968e4df902746 100644 --- a/lib/extracts_ref/ref_extractor.rb +++ b/lib/extracts_ref/ref_extractor.rb @@ -112,6 +112,12 @@ def extract_ref(id) ] end + def ref_type + self.class.ref_type(params[:ref_type]) + end + + private + def extract_ref_path id = extract_id_from_params ref, path = extract_ref(id) @@ -119,12 +125,6 @@ def extract_ref_path [id, ref, path] end - def ref_type - self.class.ref_type(params[:ref_type]) - end - - private - def extract_raw_ref(id) return ['', ''] unless repository_container diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index a10ff60a24915d3bd1b334746ef9987ac4cb1170..c95f0073bca8ebff30eff7d87d3744407104220b 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -203,8 +203,6 @@ def default_url_options end end - it_behaves_like 'extracts refs' - describe '#extract_ref_without_atom' do it 'ignores any matching refs suffixed with atom' do expect(extract_ref_without_atom('master.atom')).to eq('master') diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb index c71860116549be31ac9aa687f38b1289f68efb9f..559a6544285877248c9a418cefc728a5d7a4c6a5 100644 --- a/spec/lib/extracts_ref_spec.rb +++ b/spec/lib/extracts_ref_spec.rb @@ -58,53 +58,44 @@ end describe '#ref_type' do - let(:params) { ActionController::Parameters.new(ref_type: 'heads') } + subject { ref_type } - 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) } + let(:params) { ActionController::Parameters.new(ref_type: ref) } context 'when ref_type is nil' do - let(:ref_type) { nil } + let(:ref) { nil } it { is_expected.to eq(nil) } end context 'when ref_type is heads' do - let(:ref_type) { 'heads' } + let(:ref) { 'heads' } it { is_expected.to eq('heads') } end context 'when ref_type is tags' do - let(:ref_type) { 'tags' } + let(:ref) { 'tags' } it { is_expected.to eq('tags') } end context 'when case does not match' do - let(:ref_type) { 'tAgS' } + let(:ref) { 'tAgS' } it { is_expected.to(eq('tags')) } end context 'when ref_type is invalid' do - let(:ref_type) { 'invalid' } + let(:ref) { 'invalid' } it { is_expected.to eq(nil) } end context 'when ref_type is a hash' do - let(:ref_type) { { 'just' => 'hash' } } + let(:ref) { { 'just' => 'hash' } } it { is_expected.to eq(nil) } end end - - it_behaves_like 'extracts refs' end diff --git a/spec/requests/projects/build_artifacts_controller_spec.rb b/spec/requests/projects/build_artifacts_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a50ce7a8c871cb7e5418df2cd004cc5bad25b94b --- /dev/null +++ b/spec/requests/projects/build_artifacts_controller_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::BuildArtifactsController, feature_category: :build_artifacts do + let_it_be(:project) { create(:project, :public) } + let_it_be(:ci_build) { create(:ci_build, :artifacts, project: project) } + + describe '#browse' do + it 'redirects' do + get browse_project_build_artifacts_path(project, ci_build, ref_name_and_path: 'test') + + expect(response).to redirect_to browse_project_job_artifacts_path(project, ci_build) + end + end +end diff --git a/spec/support/shared_examples/path_extraction_shared_examples.rb b/spec/support/shared_examples/path_extraction_shared_examples.rb index 8f2aa829f18ff4fd1e6ad06c4635299bebef33c2..48fbc3197f70d88f8e66bee2b478017cd62538ab 100644 --- a/spec/support/shared_examples/path_extraction_shared_examples.rb +++ b/spec/support/shared_examples/path_extraction_shared_examples.rb @@ -49,131 +49,4 @@ expect(@path).to eq(path) end end - - context 'subclass overrides get_id' do - it 'uses ref returned by get_id' do - allow_next_instance_of(self.class) do |instance| - allow(instance).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } - end - - assign_ref_vars - - expect(@id).to eq(get_id) - end - end -end - -RSpec.shared_examples 'extracts refs' do - describe '#extract_ref' do - it 'returns an empty pair when no repository_container is set' do - allow_any_instance_of(described_class).to receive(:repository_container).and_return(nil) - expect(extract_ref('master/CHANGELOG')).to eq(['', '']) - end - - context 'without a path' do - it 'extracts a valid branch' do - expect(extract_ref('master')).to eq(['master', '']) - end - - it 'extracts a valid tag' do - expect(extract_ref('v2.0.0')).to eq(['v2.0.0', '']) - end - - it 'extracts a valid commit SHA1 ref without a path' do - expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062')).to eq( - ['f4b14494ef6abf3d144c28e4af0c20143383e062', ''] - ) - end - - it 'extracts a valid commit SHA256 ref without a path' do - expect(extract_ref('34627760127d5ff2a644771225af09bbd79f28a54a0a4c03c1881bf2c26dc13c')).to eq( - ['34627760127d5ff2a644771225af09bbd79f28a54a0a4c03c1881bf2c26dc13c', ''] - ) - end - - it 'falls back to a primitive split for an invalid ref' do - expect(extract_ref('stable')).to eq(['stable', '']) - end - - it 'does not fetch ref names when there is no slash' do - expect(self).not_to receive(:ref_names) - - extract_ref('master') - end - - it 'fetches ref names when there is a slash' do - expect(self).to receive(:ref_names).and_call_original - - extract_ref('release/app/v1.0.0') - end - end - - context 'with a path' do - it 'extracts a valid branch' do - expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq( - ['foo/bar/baz', 'CHANGELOG']) - end - - it 'extracts a valid tag' do - expect(extract_ref('v2.0.0/CHANGELOG')).to eq(['v2.0.0', 'CHANGELOG']) - end - - it 'extracts a valid commit SHA1' do - expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG')).to eq( - %w[f4b14494ef6abf3d144c28e4af0c20143383e062 CHANGELOG] - ) - end - - it 'extracts a valid commit SHA256' do - expect(extract_ref('34627760127d5ff2a644771225af09bbd79f28a54a0a4c03c1881bf2c26dc13c/CHANGELOG')).to eq( - %w[34627760127d5ff2a644771225af09bbd79f28a54a0a4c03c1881bf2c26dc13c CHANGELOG] - ) - end - - it 'falls back to a primitive split for an invalid ref' do - expect(extract_ref('stable/CHANGELOG')).to eq(%w[stable CHANGELOG]) - end - - it 'extracts the longest matching ref' do - expect(extract_ref('release/app/v1.0.0/README.md')).to eq( - ['release/app/v1.0.0', 'README.md']) - end - - context 'when the repository does not have ambiguous refs' do - before do - allow(container.repository).to receive(:has_ambiguous_refs?).and_return(false) - end - - it 'does not fetch all ref names when the first path component is a ref' do - expect(self).not_to receive(:ref_names) - expect(container.repository).to receive(:branch_names_include?).with('v1.0.0').and_return(false) - expect(container.repository).to receive(:tag_names_include?).with('v1.0.0').and_return(true) - - expect(extract_ref('v1.0.0/doc/README.md')).to eq(['v1.0.0', 'doc/README.md']) - end - - it 'fetches all ref names when the first path component is not a ref' do - expect(self).to receive(:ref_names).and_call_original - expect(container.repository).to receive(:branch_names_include?).with('release').and_return(false) - expect(container.repository).to receive(:tag_names_include?).with('release').and_return(false) - - expect(extract_ref('release/app/doc/README.md')).to eq(['release/app', 'doc/README.md']) - end - end - - context 'when the repository has ambiguous refs' do - before do - allow(container.repository).to receive(:has_ambiguous_refs?).and_return(true) - end - - it 'always fetches all ref names' do - expect(self).to receive(:ref_names).and_call_original - expect(container.repository).not_to receive(:branch_names_include?) - expect(container.repository).not_to receive(:tag_names_include?) - - expect(extract_ref('v1.0.0/doc/README.md')).to eq(['v1.0.0', 'doc/README.md']) - end - end - end - end end