diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index d5af0341d18fda13ee340f78d1b840ffa6d53186..a6167e9dc6cfeaa427284e793340ec1d3ce063b7 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,6 +1,9 @@ class Projects::RepositoriesController < Projects::ApplicationController + include ExtractsPath + # Authorize before_action :require_non_empty_project, except: :create + before_action :assign_archive_vars, only: :archive before_action :authorize_download_code! before_action :authorize_admin_project!, only: :create @@ -11,9 +14,21 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - send_git_archive @repository, ref: params[:ref], format: params[:format] + append_sha = params[:append_sha] + + shortname = "#{@project.path}-#{@ref.tr('/', '-')}" + append_sha = false if @filename == shortname + + send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha rescue => ex logger.error("#{self.class.name}: #{ex}") return git_not_found! end + + def assign_archive_vars + @id = params[:id] + @ref, @filename = extract_ref(@id) + rescue InvalidPathError + render_404 + end end diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index fa9a9bfc8f707dc543e39e04a335522ddcd8724f..f49f6e630d2ac3dac6fa4f472fbc418f342c1dc5 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,6 +1,7 @@ - pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) } - if !project.empty_repo? && can?(current_user, :download_code, project) + - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}" .project-action-button.dropdown.inline> %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') } = sprite_icon('download') @@ -10,16 +11,16 @@ %li.dropdown-header #{ _('Source code') } %li - = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do + = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do %span= _('Download zip') %li - = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do + = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do %span= _('Download tar.gz') %li - = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do + = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do %span= _('Download tar.bz2') %li - = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do + = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do %span= _('Download tar') - if pipeline && pipeline.latest_builds_with_artifacts.any? diff --git a/changelogs/unreleased/jramsay-38830-tarball.yml b/changelogs/unreleased/jramsay-38830-tarball.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d40c30561433c491c32a706b3b679147b6597e1 --- /dev/null +++ b/changelogs/unreleased/jramsay-38830-tarball.yml @@ -0,0 +1,5 @@ +--- +title: Add alternate archive route for simplified packaging +merge_request: 17225 +author: +type: added diff --git a/config/routes/project.rb b/config/routes/project.rb index 48ba8ef06f91cf31e0343e3d2342d812a91c3814..aa071d28f9709a47c7d1f9e431e97507db3728d2 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -249,6 +249,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end scope '-' do + get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive' + resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all diff --git a/config/routes/repository.rb b/config/routes/repository.rb index eace3a615b42f7ba2e7941324b7bbb8fa82b8063..9e506a1a43adba9df127c24a99e59abe292c6085 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -2,10 +2,11 @@ resource :repository, only: [:create] do member do - get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive' - # deprecated since GitLab 9.5 - get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative' + get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative', defaults: { append_sha: true } + + # deprecated since GitLab 10.7 + get ':id/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+/ }, action: 'archive', as: 'archive_deprecated', defaults: { append_sha: true } end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index a928a05f03f3816735bdf4acbdee1e1eaa3de422..aedefbd0a4148ed79165c6eaeab7900cccfbc22d 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -406,7 +406,7 @@ module Gitlab prefix_segments.join('-') end - def archive_metadata(ref, storage_path, format = "tar.gz", append_sha: true) + def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:) ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) return {} if commit.nil? diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index bdb20060cc079372bee83a6291587fea0a191045..91b469d9bc5b05ea01fbcecef354ef388ef1939f 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -63,10 +63,10 @@ module Gitlab ] end - def send_git_archive(repository, ref:, format:, append_sha: true) + def send_git_archive(repository, ref:, format:, append_sha:) format ||= 'tar.gz' format.downcase! - params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha) + params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha) raise "Repository or ref not found" if params.empty? if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 04d16e9891380c18e1e5f484a540f64b9458e0af..31b1b52fdd1952f27b5c1370a95ebef798a71bee 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -6,7 +6,7 @@ describe Projects::RepositoriesController do describe "GET archive" do context 'as a guest' do it 'responds with redirect in correct format' do - get :archive, namespace_id: project.namespace, project_id: project, format: "zip", ref: 'master' + get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip" expect(response.header["Content-Type"]).to start_with('text/html') expect(response).to be_redirect @@ -22,18 +22,25 @@ describe Projects::RepositoriesController do end it "uses Gitlab::Workhorse" do - get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip" + get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip" expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") end + it 'responds with redirect to the short name archive if fully qualified' do + get :archive, namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master", format: "zip" + + expect(assigns(:ref)).to eq("master") + expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") + end + context "when the service raises an error" do before do allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed") end it "renders Not Found" do - get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip" + get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip" expect(response).to have_gitlab_http_status(404) end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 2bb8aa69defe6c4ddd95f164c018f1d570d91f66..382a22b93a3250c41240dfcb0d045f19b3a02a80 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -247,13 +247,13 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'returns parameterised string for a ref containing slashes' do - prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: true) + prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: nil) expect(prefix).to eq("#{project_name}-test-branch-SHA") end it 'returns correct string for a ref containing dots' do - prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: true) + prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: nil) expect(prefix).to eq("#{project_name}-test.branch-SHA") end @@ -266,25 +266,25 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#archive' do - let(:metadata) { repository.archive_metadata('master', '/tmp') } + let(:metadata) { repository.archive_metadata('master', '/tmp', append_sha: true) } it_should_behave_like 'archive check', '.tar.gz' end describe '#archive_zip' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') } + let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip', append_sha: true) } it_should_behave_like 'archive check', '.zip' end describe '#archive_bz2' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') } + let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2', append_sha: true) } it_should_behave_like 'archive check', '.tar.bz2' end describe '#archive_fallback' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') } + let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup', append_sha: true) } it_should_behave_like 'archive check', '.tar.gz' end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 2b3ffb2d7c0f29d17ca91052f8367c87b0ad14cf..d64ea72e3464a1c5866ed72674cca571f418ae2a 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Workhorse do let(:ref) { 'master' } let(:format) { 'zip' } let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path } - let(:base_params) { repository.archive_metadata(ref, storage_path, format) } + let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) } let(:gitaly_params) do base_params.merge( 'GitalyServer' => { @@ -29,7 +29,7 @@ describe Gitlab::Workhorse do let(:cache_disabled) { false } subject do - described_class.send_git_archive(repository, ref: ref, format: format) + described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil) end before do diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index fb1281a6b428af8333d5f0de0eb693269911f9d0..e1b4e6180922738c2d8eac0d13ccd6cd45534b9d 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -164,20 +164,36 @@ describe 'project routing' do # archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive # edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit describe Projects::RepositoriesController, 'routing' do - it 'to #archive' do - expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'master') - end - it 'to #archive format:zip' do - expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', ref: 'master') + expect(get('/gitlab/gitlabhq/-/archive/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master/archive') end it 'to #archive format:tar.bz2' do - expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', ref: 'master') + expect(get('/gitlab/gitlabhq/-/archive/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master/archive') end it 'to #archive with "/" in route' do - expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'improve/awesome') + expect(get('/gitlab/gitlabhq/-/archive/improve/awesome/gitlabhq-improve-awesome.tar.gz')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.gz', id: 'improve/awesome/gitlabhq-improve-awesome') + end + + it 'to #archive_alternative' do + expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', append_sha: true) + end + + it 'to #archive_deprecated' do + expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', append_sha: true) + end + + it 'to #archive_deprecated format:zip' do + expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master', append_sha: true) + end + + it 'to #archive_deprecated format:tar.bz2' do + expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master', append_sha: true) + end + + it 'to #archive_deprecated with "/" in route' do + expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'improve/awesome', append_sha: true) end end