Files
gitlabhq/app/controllers/projects_controller.rb
T
Douwe MaanandRobert Speicher 834e440f70 Merge branch 'mirror-repository' into 'master'
Add option to mirror an upstream repository.

Closes internal https://dev.gitlab.org/gitlab/gitlab-ee/issues/279

Depends on gitlab-org/gitlab-shell!29

To do:

- [x] Decide on what user should be the author of the activity feed events. The initial project creator perhaps? That makes sense for personal projects, but less so for group projects, where it's even possible that the creator has since left the team.
- [x] Write specs!
- [x] Write documentation
- [x] Port back relevant commits to CE: gitlab-org/gitlab-ce!1822

----

## Mirror status on project homepage

![Screen_Shot_2015-11-12_at_12.48.15](/uploads/39666b0e4a14723407b0f66e2aaddb46/Screen_Shot_2015-11-12_at_12.48.15.png)


## Settings navigation item

![Screen_Shot_2015-11-10_at_17.32.27](/uploads/116ece85f65681bbd3c7c5e282ab3b3c/Screen_Shot_2015-11-10_at_17.32.27.png)

## Mirroring settings

![Screen_Shot_2015-11-17_at_11.17.17](/uploads/ff57c89831524435ec6a063c3c470203/Screen_Shot_2015-11-17_at_11.17.17.png)


## Activity feed with new "pushes" (mirror updates)
![activity_feed](/uploads/04754251f2142fd06b1fddc2aae45232/activity_feed.png)

I have yet to decide on what user should be the author of these events. The initial project creator perhaps? That makes sense for personal projects, but less so for group projects, where it's even possible that the creator has since left the team.

## Warning that branch has diverged from upstream
![diverged](/uploads/ecf7ae6c5709f626c750695f45ca521a/diverged.png)

## Commits heading after failed update
![commits_failure](/uploads/d1b3e1f6796431c839ee9e2f67499283/commits_failure.png)

Link send user to the mirroring settings if they are a project admin.

## Mirroring settings after failed update

![Screen_Shot_2015-11-12_at_12.43.09](/uploads/ece68d5021c9a4c374bf6d973cf9fafa/Screen_Shot_2015-11-12_at_12.43.09.png)

## New project page
![new_project](/uploads/296beb6c7faf390a9b7e1facc9c1d4d6/new_project.png)

## Import form after failed import
(Screenshot outdated)

![new_import_failure](/uploads/ac0ebfc8ec23232603126fe0a6e10171/new_import_failure.png)

Shown when the initial import of a project fails, not when an update of an existing project fails.

cc @dzaporozhets @sytses @JobV

See merge request !51
2015-11-18 16:50:49 -05:00

276 lines
6.7 KiB
Ruby

class ProjectsController < ApplicationController
include ExtractsPath
prepend_before_action :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
def index
redirect_to(current_user ? root_path : explore_root_path)
end
def new
@project = Project.new
end
def edit
render 'edit'
end
def create
@project = ::Projects::CreateService.new(current_user, project_params).execute
if @project.saved?
redirect_to(
project_path(@project),
notice: "Project '#{@project.name}' was successfully created."
)
else
render 'new'
end
end
def update
status = ::Projects::UpdateService.new(@project, current_user, project_params).execute
respond_to do |format|
if status
flash[:notice] = "Project '#{@project.name}' was successfully updated."
format.html do
redirect_to(
edit_project_path(@project),
notice: "Project '#{@project.name}' was successfully updated."
)
end
format.js
else
format.html { render 'edit' }
format.js
end
end
end
def transfer
return access_denied! unless can?(current_user, :change_namespace, @project)
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
end
end
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
if @project.unlink_fork
flash[:notice] = 'The fork relationship has been removed.'
end
end
def activity
respond_to do |format|
format.html
format.json do
load_events
pager_json('events/_events', @events.count)
end
end
end
def show
# If we're importing while we do have a repository, we're simply updating the mirror.
if @project.import_in_progress? && !@project.updating_mirror?
redirect_to namespace_project_import_path(@project.namespace, @project)
return
end
respond_to do |format|
format.html do
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
if current_user
@membership = @project.team.find_member(current_user.id)
end
render :show
end
else
render 'projects/no_repo'
end
end
format.atom do
load_events
render layout: false
end
end
end
def destroy
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
flash[:alert] = "Project '#{@project.name}' was deleted."
redirect_back_or_default(default: dashboard_projects_path, options: {})
rescue Projects::DestroyService::DestroyError => ex
redirect_to edit_project_path(@project), alert: ex.message
end
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
autocomplete = ::Projects::AutocompleteService.new(@project)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
emojis: autocomplete_emojis,
issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests,
members: participants
}
respond_to do |format|
format.json { render json: @suggestions }
end
end
def archive
return access_denied! unless can?(current_user, :archive_project, @project)
@project.archive!
respond_to do |format|
format.html { redirect_to project_path(@project) }
end
end
def unarchive
return access_denied! unless can?(current_user, :archive_project, @project)
@project.unarchive!
respond_to do |format|
format.html { redirect_to project_path(@project) }
end
end
def toggle_star
current_user.toggle_star(@project)
@project.reload
render json: {
html: view_to_html_string("projects/buttons/_star")
}
end
def markdown_preview
text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
ext.analyze(text)
render json: {
body: view_context.markdown(text),
references: {
users: ext.users.map(&:username)
}
}
end
private
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
end
def load_events
@events = @project.events.recent
@events = event_filter.apply_filter(@events).with_associations
limit = (params[:limit] || 20).to_i
@events = @events.limit(limit).offset(params[:offset] || 0)
end
def project_params
params.require(:project).permit(
:avatar,
:builds_enabled,
:default_branch,
:description,
:import_url,
:issues_enabled,
:issues_tracker,
:issues_tracker_id,
:last_activity_at,
:merge_requests_enabled,
:name,
:namespace_id,
:path,
:snippets_enabled,
:tag_list,
:visibility_level,
:wiki_enabled,
# EE-only
:approvals_before_merge,
:approver_ids,
:issues_template,
:merge_requests_ff_only_enabled,
:merge_requests_rebase_enabled,
:merge_requests_template,
:mirror,
:mirror_user_id,
:reset_approvals_on_push
)
end
def autocomplete_emojis
Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do
Emoji.emojis.map do |name, emoji|
{
name: name,
path: view_context.image_url("emoji/#{emoji["unicode"]}.png")
}
end
end
end
def render_go_import
return unless params["go-get"] == "1"
@namespace = params[:namespace_id]
@id = params[:project_id] || params[:id]
@id = @id.gsub(/\.git\Z/, "")
render "go_import", layout: false
end
def repo_exists?
project.repository_exists? && !project.empty_repo?
end
# Override get_id from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch.
def get_id
project.repository.root_ref
end
end