projects_controller.rb 11.8 KB
Newer Older
1
class ProjectsController < Projects::ApplicationController
2
  include IssuableCollections
3
  include ExtractsPath
4
  include PreviewMarkdown
5
  prepend EE::ProjectsController
6

7
  before_action :whitelist_query_limiting, only: [:create]
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
8
  before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
9
  before_action :redirect_git_extension, only: [:show]
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
10 11
  before_action :project, except: [:index, :new, :create]
  before_action :repository, except: [:index, :new, :create]
12
  before_action :assign_ref_vars, only: [:show], if: :repo_exists?
13
  before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?]
14
  before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
15
  before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
16
  before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
gitlabhq's avatar
gitlabhq committed
17 18

  # Authorize
19
  before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
20
  before_action :event_filter, only: [:show, :activity]
gitlabhq's avatar
gitlabhq committed
21

22
  layout :determine_layout
Cyril's avatar
Cyril committed
23

24
  def index
25
    redirect_to(current_user ? root_path : explore_root_path)
26 27
  end

gitlabhq's avatar
gitlabhq committed
28
  def new
29 30 31 32
    namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
    return access_denied! if namespace && !can?(current_user, :create_projects, namespace)

    @project = Project.new(namespace_id: namespace&.id)
gitlabhq's avatar
gitlabhq committed
33 34 35
  end

  def edit
36
    render 'edit'
gitlabhq's avatar
gitlabhq committed
37 38 39
  end

  def create
40
    @project = ::Projects::CreateService.new(current_user, project_params).execute
gitlabhq's avatar
gitlabhq committed
41

42
    if @project.saved?
43 44
      cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) }

Vinnie Okada's avatar
Vinnie Okada committed
45
      redirect_to(
46
        project_path(@project, custom_import_params),
47
        notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name }
Vinnie Okada's avatar
Vinnie Okada committed
48
      )
49
    else
Eric Eastwood's avatar
Eric Eastwood committed
50
      render 'new', locals: { active_tab: active_new_project_tab }
gitlabhq's avatar
gitlabhq committed
51 52
    end
  end
gitlabhq's avatar
gitlabhq committed
53

gitlabhq's avatar
gitlabhq committed
54
  def update
55
    result = ::Projects::UpdateService.new(@project, current_user, project_params).execute
56

57
    # Refresh the repo in case anything changed
58
    @repository = @project.repository
59

gitlabhq's avatar
gitlabhq committed
60
    respond_to do |format|
61
      if result[:status] == :success
62
        flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name }
63

Vinnie Okada's avatar
Vinnie Okada committed
64
        format.html do
65
          redirect_to(edit_project_path(@project))
Vinnie Okada's avatar
Vinnie Okada committed
66
        end
gitlabhq's avatar
gitlabhq committed
67
      else
68
        flash.now[:alert] = result[:message]
69

70
        format.html { render 'edit' }
gitlabhq's avatar
gitlabhq committed
71
      end
72 73

      format.js
gitlabhq's avatar
gitlabhq committed
74
    end
75
  end
76

77
  def transfer
78 79
    return access_denied! unless can?(current_user, :change_namespace, @project)

80 81 82 83 84
    namespace = Namespace.find_by(id: params[:new_namespace_id])
    ::Projects::TransferService.new(project, current_user).execute(namespace)

    if @project.errors[:new_namespace].present?
      flash[:alert] = @project.errors[:new_namespace].first
skv-headless's avatar
skv-headless committed
85
    end
gitlabhq's avatar
gitlabhq committed
86 87
  end

88
  def remove_fork
89 90
    return access_denied! unless can?(current_user, :remove_fork_project, @project)

91
    if ::Projects::UnlinkForkService.new(@project, current_user).execute
92
      flash[:notice] = _('The fork relationship has been removed.')
93 94 95
    end
  end

96 97 98 99 100 101 102
  def activity
    respond_to do |format|
      format.html
      format.json do
        load_events
        pager_json('events/_events', @events.count)
      end
skv-headless's avatar
skv-headless committed
103
    end
gitlabhq's avatar
gitlabhq committed
104 105 106
  end

  def show
107
    if @project.import_in_progress?
108
      redirect_to project_import_path(@project, custom_import_params)
109 110 111
      return
    end

112
    if @project.pending_delete?
113
      flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
114 115
    end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
116
    respond_to do |format|
Nihad Abbasov's avatar
Nihad Abbasov committed
117
      format.html do
118
        @notification_setting = current_user.notification_settings_for(@project) if current_user
119 120
        @project = @project.present(current_user: current_user)

121
        render_landing_page
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
122
      end
123

124 125
      format.atom do
        load_events
126
        render layout: 'xml.atom'
127
      end
128 129 130
    end
  end

gitlabhq's avatar
gitlabhq committed
131
  def destroy
132
    return access_denied! unless can?(current_user, :remove_project, @project)
133

Stan Hu's avatar
Stan Hu committed
134
    ::Projects::DestroyService.new(@project, current_user, {}).async_execute
135
    flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name }
gitlabhq's avatar
gitlabhq committed
136

137
    redirect_to dashboard_projects_path, status: 302
138
  rescue Projects::DestroyService::DestroyError => ex
139
    redirect_to edit_project_path(@project), status: 302, alert: ex.message
gitlabhq's avatar
gitlabhq committed
140
  end
141

142
  def new_issuable_address
143 144 145
    return render_404 unless Gitlab::IncomingEmail.supports_issue_creation?

    current_user.reset_incoming_email_token!
146
    render json: { new_address: @project.new_issuable_address(current_user, params[:issuable_type]) }
147 148
  end

149
  def archive
150
    return access_denied! unless can?(current_user, :archive_project, @project)
Douwe Maan's avatar
Douwe Maan committed
151

152
    @project.archive!
153 154

    respond_to do |format|
155
      format.html { redirect_to project_path(@project) }
156 157 158 159
    end
  end

  def unarchive
160
    return access_denied! unless can?(current_user, :archive_project, @project)
Douwe Maan's avatar
Douwe Maan committed
161

162
    @project.unarchive!
163 164

    respond_to do |format|
165
      format.html { redirect_to project_path(@project) }
166 167 168
    end
  end

169
  def housekeeping
170
    ::Projects::HousekeepingService.new(@project).execute
171

172 173
    redirect_to(
      project_path(@project),
174
      notice: _("Housekeeping successfully started")
175 176 177 178 179 180
    )
  rescue ::Projects::HousekeepingService::LeaseTaken => ex
    redirect_to(
      edit_project_path(@project),
      alert: ex.to_s
    )
181
  end
182

183
  def export
184
    @project.add_export_job(current_user: current_user)
185 186

    redirect_to(
James Lopez's avatar
James Lopez committed
187
      edit_project_path(@project),
188
      notice: _("Project export started. A download link will be sent by email.")
189 190 191
    )
  end

James Lopez's avatar
James Lopez committed
192
  def download_export
193 194
    export_project_path = @project.export_project_path

James Lopez's avatar
James Lopez committed
195 196 197
    if export_project_path
      send_file export_project_path, disposition: 'attachment'
    else
198 199
      redirect_to(
        edit_project_path(@project),
200
        alert: _("Project export link has expired. Please generate a new export from your project settings.")
201 202 203 204 205 206
      )
    end
  end

  def remove_export
    if @project.remove_exports
207
      flash[:notice] = _("Project export has been deleted.")
208
    else
209
      flash[:alert] = _("Project export could not be deleted.")
210
    end
211

212 213 214 215 216 217
    redirect_to(edit_project_path(@project))
  end

  def generate_new_export
    if @project.remove_exports
      export
218 219 220
    else
      redirect_to(
        edit_project_path(@project),
221
        alert: _("Project export could not be deleted.")
222
      )
James Lopez's avatar
James Lopez committed
223
    end
James Lopez's avatar
James Lopez committed
224 225
  end

Ciro Santilli's avatar
Ciro Santilli committed
226 227
  def toggle_star
    current_user.toggle_star(@project)
228
    @project.reload
229 230

    render json: {
231
      star_count: @project.star_count
232
    }
Ciro Santilli's avatar
Ciro Santilli committed
233 234
  end

235
  def refs
236
    find_refs = params['find']
237

238 239 240
    find_branches = true
    find_tags = true
    find_commits = true
Luke "Jared" Bennett's avatar
Luke "Jared" Bennett committed
241 242

    unless find_refs.nil?
Douwe Maan's avatar
Douwe Maan committed
243 244 245
      find_branches = find_refs.include?('branches')
      find_tags = find_refs.include?('tags')
      find_commits = find_refs.include?('commits')
246
    end
247

248 249 250
    options = {}

    if find_branches
Douwe Maan's avatar
Douwe Maan committed
251
      branches = BranchesFinder.new(@repository, params).execute.take(100).map(&:name)
252
      options['Branches'] = branches
253
    end
254

Douwe Maan's avatar
Douwe Maan committed
255 256
    if find_tags && @repository.tag_count.nonzero?
      tags = TagsFinder.new(@repository, params).execute.take(100).map(&:name)
257

258
      options['Tags'] = tags
Phil Hughes's avatar
Phil Hughes committed
259 260
    end

261
    # If reference is commit id - we should add it to branch/tag selectbox
262
    ref = Addressable::URI.unescape(params[:ref])
Douwe Maan's avatar
Douwe Maan committed
263
    if find_commits && ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
264
      options['Commits'] = [ref]
265 266 267 268 269
    end

    render json: options.to_json
  end

270 271
  private

272 273 274 275 276
  # Render project landing depending of which features are available
  # So if page is not availble in the list it renders the next page
  #
  # pages list order: repository readme, wiki home, issues list, customize workflow
  def render_landing_page
277
    if can?(current_user, :download_code, @project)
278
      return render 'projects/no_repo' unless @project.repository_exists?
279

280 281
      render 'projects/empty' if @project.empty_repo?
    else
282
      if can?(current_user, :read_wiki, @project)
283 284
        @project_wiki = @project.wiki
        @wiki_home = @project_wiki.find_page('home', params[:version_id])
285
      elsif @project.feature_available?(:issues, current_user)
286
        @issues = issuables_collection.page(params[:page])
287 288
        @collection_type = 'Issue'
        @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
289 290 291 292 293 294
      end

      render :show
    end
  end

295 296 297 298
  def finder_type
    IssuesFinder
  end

299 300 301 302 303 304 305 306
  def determine_layout
    if [:new, :create].include?(action_name.to_sym)
      'application'
    elsif [:edit, :update].include?(action_name.to_sym)
      'project_settings'
    else
      'project'
    end
307
  end
308

309
  def load_events
310 311 312 313 314
    projects = Project.where(id: @project.id)

    @events = EventCollection
      .new(projects, offset: params[:offset].to_i, filter: event_filter)
      .to_a
315 316

    Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
317 318
  end

319
  def project_params
320
    params.require(:project)
321
      .permit(project_params_attributes)
322
  end
323

324
  def project_params_attributes
325 326 327 328
    [
      :avatar,
      :build_allow_git_fetch,
      :build_coverage_regex,
329
      :build_timeout_human_readable,
330
      :resolve_outdated_diff_discussions,
331
      :container_registry_enabled,
332 333 334 335 336 337 338 339 340
      :default_branch,
      :description,
      :import_url,
      :issues_tracker,
      :issues_tracker_id,
      :last_activity_at,
      :lfs_enabled,
      :name,
      :namespace_id,
341
      :only_allow_merge_if_all_discussions_are_resolved,
342
      :only_allow_merge_if_pipeline_succeeds,
343
      :printing_merge_request_link_enabled,
344 345 346 347 348 349
      :path,
      :public_builds,
      :request_access_enabled,
      :runners_token,
      :tag_list,
      :visibility_level,
350
      :template_name,
351
      :merge_method,
352 353 354 355 356 357 358 359 360 361 362 363

      project_feature_attributes: %i[
        builds_access_level
        issues_access_level
        merge_requests_access_level
        repository_access_level
        snippets_access_level
        wiki_access_level
      ]
    ]
  end

364 365 366 367
  def custom_import_params
    {}
  end

Eric Eastwood's avatar
Eric Eastwood committed
368 369 370 371
  def active_new_project_tab
    project_params[:import_url].present? ? 'import' : 'blank'
  end

372
  def repo_exists?
373
    project.repository_exists? && !project.empty_repo?
374 375 376 377 378

  rescue Gitlab::Git::Repository::NoRepository
    project.repository.expire_exists_cache

    false
379 380
  end

381
  def project_view_files?
382 383 384 385 386
    if current_user
      current_user.project_view == 'files'
    else
      project_view_files_allowed?
    end
387 388
  end

389
  # Override extract_ref from ExtractsPath, which returns the branch and file path
Douwe Maan's avatar
Douwe Maan committed
390
  # for the blob/tree, which in this case is just the root of the default branch.
391 392 393 394 395 396
  # This way we avoid to access the repository.ref_names.
  def extract_ref(_id)
    [get_id, '']
  end

  # Override get_id from ExtractsPath in this case is just the root of the default branch.
397 398 399
  def get_id
    project.repository.root_ref
  end
400 401 402 403 404 405

  # ExtractsPath will set @id = project.path on the show route, but it has to be the
  # branch name for the tree view to work correctly.
  def assign_tree_vars
    @id = get_id
    tree
Nick Thomas's avatar
Nick Thomas committed
406 407
  end

408 409
  def project_view_files_allowed?
    !project.empty_repo? && can?(current_user, :download_code, project)
410
  end
411 412 413 414 415

  def build_canonical_path(project)
    params[:namespace_id] = project.namespace.to_param
    params[:id] = project.to_param

416
    url_for(safe_params)
417
  end
418 419

  def project_export_enabled
420
    render_404 unless Gitlab::CurrentSettings.project_export_enabled?
421
  end
422 423 424 425 426 427 428

  def redirect_git_extension
    # Redirect from
    #   localhost/group/project.git
    # to
    #   localhost/group/project
    #
429
    redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
430
  end
431 432 433 434

  def whitelist_query_limiting
    Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
  end
gitlabhq's avatar
gitlabhq committed
435
end