repositories_controller.rb 3.15 KB
Newer Older
1
2
# frozen_string_literal: true

3
class Projects::RepositoriesController < Projects::ApplicationController
4
  include ExtractsPath
5
6
7
  include StaticObjectExternalStorage

  prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
8

9
  # Authorize
10
  before_action :require_non_empty_project, except: :create
11
  before_action :assign_archive_vars, only: :archive
12
  before_action :assign_append_sha, only: :archive
13
14
  before_action :authorize_download_code!
  before_action :authorize_admin_project!, only: :create
15
  before_action :redirect_to_external_storage, only: :archive, if: :static_objects_external_storage_enabled?
16
17
18
19

  def create
    @project.create_repository

20
    redirect_to project_path(@project)
21
  end
22

23
  def archive
24
25
    set_cache_headers
    return if archive_not_modified?
26

27
    send_git_archive @repository, **repo_params
28
29
  rescue => ex
    logger.error("#{self.class.name}: #{ex}")
Lin Jen-Shin's avatar
Lin Jen-Shin committed
30
    git_not_found!
31
  end
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  private

  def repo_params
    @repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha }
  end

  def set_cache_headers
    expires_in cache_max_age(archive_metadata['CommitId']), public: project.public?
    fresh_when(etag: archive_metadata['ArchivePath'])
  end

  def archive_not_modified?
    # Check response freshness (Last-Modified and ETag)
    # against request If-Modified-Since and If-None-Match conditions.
    request.fresh?(response)
  end

  def archive_metadata
    @archive_metadata ||= @repository.archive_metadata(
      @ref,
      '', # Where archives are stored isn't really important for ETag purposes
      repo_params[:format],
      path: repo_params[:path],
      append_sha: @append_sha
    )
  end

  def cache_max_age(commit_id)
    if @ref == commit_id
      # This is a link to an archive by a commit SHA. That means that the archive
      # is immutable. The only reason to invalidate the cache is if the commit
      # was deleted or if the user lost access to the repository.
      Repository::ARCHIVE_CACHE_TIME_IMMUTABLE
    else
      # A branch or tag points at this archive. That means that the expected archive
      # content may change over time.
      Repository::ARCHIVE_CACHE_TIME
    end
  end

  def assign_append_sha
    @append_sha = params[:append_sha]

    if @ref
      shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
      @append_sha = false if @filename == shortname
    end
  end

82
  def assign_archive_vars
83
    if params[:id]
84
      @ref, @filename = extract_ref_and_filename(params[:id])
85
86
87
88
    else
      @ref = params[:ref]
      @filename = nil
    end
89
90
91
  rescue InvalidPathError
    render_404
  end
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

  # path can be of the form:
  # master
  # master/first.zip
  # master/first/second.tar.gz
  # master/first/second/third.zip
  #
  # In the archive case, we know that the last value is always the filename, so we
  # do a greedy match to extract the ref. This avoid having to pull all ref names
  # from Redis.
  def extract_ref_and_filename(id)
    path = id.strip
    data = path.match(/(.*)\/(.*)/)

    if data
      [data[1], data[2]]
    else
      [path, nil]
    end
  end
112
end
113
114

Projects::RepositoriesController.prepend_if_ee('EE::Projects::RepositoriesController')