Commit 8a26f836 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/project-import_url

parents e937f312 383ead5d
...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased) v 8.6.0 (unreleased)
- Add ability to move issue to another project - Add ability to move issue to another project
- Prevent tokens in the import URL to be showed by the UI
- Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu) - Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
- Make HTTP(s) label consistent on clone bar (Stan Hu) - Make HTTP(s) label consistent on clone bar (Stan Hu)
- Add confidential issues - Add confidential issues
...@@ -60,6 +61,7 @@ v 8.6.0 (unreleased) ...@@ -60,6 +61,7 @@ v 8.6.0 (unreleased)
- User deletion is now done in the background so the request can not time out - User deletion is now done in the background so the request can not time out
- Canceled builds are now ignored in compound build status if marked as `allowed to fail` - Canceled builds are now ignored in compound build status if marked as `allowed to fail`
- Trigger a todo for mentions on commits page - Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
v 8.5.8 v 8.5.8
- Bump Git version requirement to 2.7.4 - Bump Git version requirement to 2.7.4
......
...@@ -1064,6 +1064,3 @@ DEPENDENCIES ...@@ -1064,6 +1064,3 @@ DEPENDENCIES
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH
1.11.2
...@@ -139,7 +139,7 @@ $ -> ...@@ -139,7 +139,7 @@ $ ->
# Initialize tooltips # Initialize tooltips
$('body').tooltip( $('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"]' selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) -> placement: (_, el) ->
$el = $(el) $el = $(el)
$el.data('placement') || 'bottom' $el.data('placement') || 'bottom'
......
...@@ -122,7 +122,7 @@ class @AwardsHandler ...@@ -122,7 +122,7 @@ class @AwardsHandler
nodes = [] nodes = []
nodes.push( nodes.push(
"<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>", "<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>", "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>", "<span class='award-control-text js-counter'>1</span>",
"</button>" "</button>"
......
...@@ -107,10 +107,28 @@ ...@@ -107,10 +107,28 @@
margin: 0; margin: 0;
font-size: 23px; font-size: 23px;
font-weight: normal; font-weight: normal;
margin: 16px 0 5px 0; margin: 16px 0 5px;
color: #4c4e54; color: #4c4e54;
font-size: 23px; font-size: 23px;
line-height: 1.1; line-height: 1.1;
h1 {
color: #313236;
margin-bottom: 6px;
font-size: 23px;
}
.visibility-icon {
display: inline-block;
margin-left: 5px;
font-size: 18px;
color: $gray;
}
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
} }
.cover-desc { .cover-desc {
......
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
width: 240px; width: 240px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
padding: 10px 10px; padding: 10px;
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
background-color: $dropdown-bg; background-color: $dropdown-bg;
......
...@@ -16,7 +16,7 @@ body { ...@@ -16,7 +16,7 @@ body {
} }
.container .content { .container .content {
margin: 0 0; margin: 0;
} }
.navless-container { .navless-container {
......
/** /**
* Generic mixins * Generic mixins
*/ */
@mixin box-shadow($shadow) { @mixin box-shadow($shadow) {
-webkit-box-shadow: $shadow; -webkit-box-shadow: $shadow;
-moz-box-shadow: $shadow; -moz-box-shadow: $shadow;
-ms-box-shadow: $shadow; -ms-box-shadow: $shadow;
......
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
h1 { h1 {
font-size: 1.3em; font-size: 1.3em;
font-weight: 600; font-weight: 600;
margin: 24px 0 12px 0; margin: 24px 0 12px;
padding: 0 0 10px 0; padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed; border-bottom: 1px solid #e7e9ed;
color: #313236; color: #313236;
} }
...@@ -48,27 +48,27 @@ ...@@ -48,27 +48,27 @@
h2 { h2 {
font-size: 1.2em; font-size: 1.2em;
font-weight: 600; font-weight: 600;
margin: 24px 0 12px 0; margin: 24px 0 12px;
color: #313236; color: #313236;
} }
h3 { h3 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 1.1em; font-size: 1.1em;
} }
h4 { h4 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 0.98em; font-size: 0.98em;
} }
h5 { h5 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 0.95em; font-size: 0.95em;
} }
h6 { h6 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 0.90em; font-size: 0.90em;
} }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
color: #7f8fa4; color: #7f8fa4;
font-size: inherit; font-size: inherit;
padding: 8px 21px; padding: 8px 21px;
margin: 12px 0 12px; margin: 12px 0;
border-left: 3px solid #e7e9ed; border-left: 3px solid #e7e9ed;
} }
...@@ -88,13 +88,13 @@ ...@@ -88,13 +88,13 @@
p { p {
color: #5c5d5e; color: #5c5d5e;
margin: 6px 0 0 0; margin: 6px 0 0;
} }
table { table {
@extend .table; @extend .table;
@extend .table-bordered; @extend .table-bordered;
margin: 12px 0 12px 0; margin: 12px 0;
color: #5c5d5e; color: #5c5d5e;
th { th {
background: #f8fafc; background: #f8fafc;
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
} }
pre { pre {
margin: 12px 0 12px 0; margin: 12px 0;
font-size: 13px; font-size: 13px;
line-height: 1.6em; line-height: 1.6em;
overflow-x: auto; overflow-x: auto;
...@@ -191,7 +191,7 @@ body { ...@@ -191,7 +191,7 @@ body {
line-height: 1.3; line-height: 1.3;
font-size: 1.25em; font-size: 1.25em;
font-weight: 600; font-weight: 600;
margin: 12px 7px 12px 7px; margin: 12px 7px;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
......
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
} }
.image-info { .image-info {
font-size: 12px; font-size: 12px;
margin: 5px 0 0 0; margin: 5px 0 0;
color: grey; color: grey;
} }
......
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
.block { .block {
width: $sidebar_collapsed_width - 1px; width: $sidebar_collapsed_width - 1px;
margin-left: -19px; margin-left: -19px;
padding: 15px 0 0 0; padding: 15px 0 0;
border-bottom: none; border-bottom: none;
overflow: hidden; overflow: hidden;
} }
...@@ -273,12 +273,12 @@ ...@@ -273,12 +273,12 @@
} }
.participants-list { .participants-list {
margin: -5px -5px; margin: -5px;
} }
.participants-author { .participants-author {
display: inline-block; display: inline-block;
padding: 5px 5px; padding: 5px;
.author_link { .author_link {
display: block; display: block;
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
.login-heading h3 { .login-heading h3 {
font-weight: 300; font-weight: 300;
line-height: 1.5; line-height: 1.5;
margin: 0 0 10px 0; margin: 0 0 10px;
} }
.login-footer { .login-footer {
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
} }
.account-well { .account-well {
padding: 10px 10px; padding: 10px;
background-color: $help-well-bg; background-color: $help-well-bg;
border: 1px solid $help-well-border; border: 1px solid $help-well-border;
border-radius: $border-radius-base; border-radius: $border-radius-base;
......
...@@ -68,28 +68,6 @@ ...@@ -68,28 +68,6 @@
} }
} }
.project-home-desc {
h1 {
color: #313236;
margin: 0;
margin-bottom: 6px;
font-size: 23px;
font-weight: normal;
}
.visibility-icon {
display: inline-block;
margin-left: 5px;
font-size: 18px;
color: $gray;
}
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
}
.project-repo-buttons { .project-repo-buttons {
margin-top: 20px; margin-top: 20px;
margin-bottom: 0; margin-bottom: 0;
...@@ -333,7 +311,7 @@ pre.light-well { ...@@ -333,7 +311,7 @@ pre.light-well {
} }
.git-empty { .git-empty {
margin: 0 7px 0 7px; margin: 0 7px;
h5 { h5 {
color: #5c5d5e; color: #5c5d5e;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
#contributors { #contributors {
.contributors-list { .contributors-list {
margin: 0 0 10px 0; margin: 0 0 10px;
list-style: none; list-style: none;
padding: 0; padding: 0;
} }
......
...@@ -61,6 +61,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -61,6 +61,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:session_expire_delay, :session_expire_delay,
:default_project_visibility, :default_project_visibility,
:default_snippet_visibility, :default_snippet_visibility,
:default_group_visibility,
:restricted_signup_domains_raw, :restricted_signup_domains_raw,
:version_check_enabled, :version_check_enabled,
:admin_notification_email, :admin_notification_email,
......
...@@ -59,6 +59,6 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -59,6 +59,6 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar) params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level)
end end
end end
class Admin::ProjectsController < Admin::ApplicationController class Admin::ProjectsController < Admin::ApplicationController
before_action :project, only: [:show, :transfer] before_action :project, only: [:show, :transfer]
before_action :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
before_action :repository, only: [:show, :transfer]
def index def index
@projects = Project.all @projects = Project.all
......
...@@ -23,7 +23,6 @@ class ApplicationController < ActionController::Base ...@@ -23,7 +23,6 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?, :current_application_settings helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
helper_method :repository, :can_collaborate_with_project?
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -116,47 +115,6 @@ class ApplicationController < ActionController::Base ...@@ -116,47 +115,6 @@ class ApplicationController < ActionController::Base
abilities.allowed?(object, action, subject) abilities.allowed?(object, action, subject)
end end
def project
unless @project
namespace = params[:namespace_id]
id = params[:project_id] || params[:id]
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\/?\Z/, '') and return
end
project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
if @project and can?(current_user, :read_project, @project)
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
end
@project
elsif current_user.nil?
@project = nil
authenticate_user!
else
@project = nil
render_404 and return
end
end
@project
end
def repository
@repository ||= project.repository
end
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
def access_denied! def access_denied!
render "errors/access_denied", layout: "errors", status: 404 render "errors/access_denied", layout: "errors", status: 404
end end
...@@ -165,14 +123,6 @@ class ApplicationController < ActionController::Base ...@@ -165,14 +123,6 @@ class ApplicationController < ActionController::Base
render "errors/git_not_found.html", layout: "errors", status: 404 render "errors/git_not_found.html", layout: "errors", status: 404
end end
def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
authorize_project!($1.to_sym)
else
super
end
end
def render_403 def render_403
head :forbidden head :forbidden
end end
...@@ -181,10 +131,6 @@ class ApplicationController < ActionController::Base ...@@ -181,10 +131,6 @@ class ApplicationController < ActionController::Base
render file: Rails.root.join("public", "404"), layout: false, status: "404" render file: Rails.root.join("public", "404"), layout: false, status: "404"
end end
def require_non_empty_project
redirect_to @project if @project.empty_repo?
end
def no_cache_headers def no_cache_headers
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache" response.headers["Pragma"] = "no-cache"
...@@ -410,13 +356,6 @@ class ApplicationController < ActionController::Base ...@@ -410,13 +356,6 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path current_user.nil? && root_path == request.path
end end
def can_collaborate_with_project?(project = nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end
private private
def set_default_sort def set_default_sort
......
module IssuableActions
extend ActiveSupport::Concern
included do
before_action :authorize_destroy_issuable!, only: :destroy
end
def destroy
issuable.destroy
name = issuable.class.name.titleize.downcase
flash[:notice] = "The #{name} was successfully deleted."
redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
end
private
def authorize_destroy_issuable!
unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
end
end
end
class Explore::GroupsController < Explore::ApplicationController class Explore::GroupsController < Explore::ApplicationController
def index def index
@groups = Group.order_id_desc @groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present? @groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort]) @groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]) @groups = @groups.page(params[:page])
......
class Groups::ApplicationController < ApplicationController class Groups::ApplicationController < ApplicationController
layout 'group' layout 'group'
skip_before_action :authenticate_user!
before_action :group before_action :group
private private
def group def group
@group ||= Group.find_by(path: params[:group_id]) unless @group
end id = params[:group_id] || params[:id]
@group = Group.find_by(path: id)
unless @group && can?(current_user, :read_group, @group)
@group = nil
def authorize_read_group!
unless @group and can?(current_user, :read_group, @group)
if current_user.nil? if current_user.nil?
return authenticate_user! authenticate_user!
else else
return render_404 render_404
end end
end end
end end
@group
end
def group_projects
@projects ||= GroupProjectsFinder.new(group).execute(current_user)
end
def authorize_admin_group! def authorize_admin_group!
unless can?(current_user, :admin_group, group) unless can?(current_user, :admin_group, group)
return render_404 return render_404
......
class Groups::AvatarsController < Groups::ApplicationController class Groups::AvatarsController < Groups::ApplicationController
before_action :authorize_admin_group!
def destroy def destroy
@group.remove_avatar! @group.remove_avatar!
@group.save @group.save
......
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
skip_before_action :authenticate_user!, only: [:index]
# Authorize # Authorize
before_action :authorize_read_group!
before_action :authorize_admin_group_member!, except: [:index, :leave] before_action :authorize_admin_group_member!, except: [:index, :leave]
def index def index
......
class Groups::MilestonesController < Groups::ApplicationController class Groups::MilestonesController < Groups::ApplicationController
include GlobalMilestones include GlobalMilestones
before_action :projects before_action :group_projects
before_action :milestones, only: [:index] before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update] before_action :milestone, only: [:show, :update]
before_action :authorize_group_milestone!, only: [:create, :update] before_action :authorize_admin_milestones!, only: [:new, :create, :update]
def index def index
end end
...@@ -17,7 +17,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -17,7 +17,7 @@ class Groups::MilestonesController < Groups::ApplicationController
project_ids = params[:milestone][:project_ids] project_ids = params[:milestone][:project_ids]
title = milestone_params[:title] title = milestone_params[:title]
@group.projects.where(id: project_ids).each do |project| @projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute Milestones::CreateService.new(project, current_user, milestone_params).execute
end end
...@@ -37,7 +37,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -37,7 +37,7 @@ class Groups::MilestonesController < Groups::ApplicationController
private private
def authorize_group_milestone! def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group) return render_404 unless can?(current_user, :admin_milestones, group)
end end
...@@ -48,8 +48,4 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -48,8 +48,4 @@ class Groups::MilestonesController < Groups::ApplicationController
def milestone_path(title) def milestone_path(title)
group_milestone_path(@group, title.to_slug.to_s, title: title) group_milestone_path(@group, title.to_slug.to_s, title: title)
end end
def projects
@projects ||= @group.projects
end
end end
...@@ -5,16 +5,15 @@ class GroupsController < Groups::ApplicationController ...@@ -5,16 +5,15 @@ class GroupsController < Groups::ApplicationController
respond_to :html respond_to :html
skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests] before_action :authenticate_user!, only: [:new, :create]
before_action :group, except: [:index, :new, :create] before_action :group, except: [:index, :new, :create]
# Authorize # Authorize
before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects] before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_action :authorize_create_group!, only: [:new, :create] before_action :authorize_create_group!, only: [:new, :create]
# Load group projects # Load group projects
before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete] before_action :group_projects, only: [:show, :projects, :activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity] before_action :event_filter, only: [:activity]
layout :determine_layout layout :determine_layout
...@@ -28,11 +27,9 @@ class GroupsController < Groups::ApplicationController ...@@ -28,11 +27,9 @@ class GroupsController < Groups::ApplicationController
end end
def create def create
@group = Group.new(group_params) @group = Groups::CreateService.new(current_user, group_params).execute
@group.name = @group.path.dup unless @group.name
if @group.save if @group.persisted?
@group.add_owner(current_user)
redirect_to @group, notice: "Group '#{@group.name}' was successfully created." redirect_to @group, notice: "Group '#{@group.name}' was successfully created."
else else
render action: "new" render action: "new"
...@@ -41,12 +38,13 @@ class GroupsController < Groups::ApplicationController ...@@ -41,12 +38,13 @@ class GroupsController < Groups::ApplicationController
def show def show
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank? @projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@shared_projects = @group.shared_projects @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -83,7 +81,7 @@ class GroupsController < Groups::ApplicationController ...@@ -83,7 +81,7 @@ class GroupsController < Groups::ApplicationController
end end
def update def update
if @group.update_attributes(group_params) if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated." redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else else
render action: "edit" render action: "edit"
...@@ -98,26 +96,6 @@ class GroupsController < Groups::ApplicationController ...@@ -98,26 +96,6 @@ class GroupsController < Groups::ApplicationController
protected protected
def group
@group ||= Group.find_by(path: params[:id])
@group || render_404
end
def load_projects
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
end
# Dont allow unauthorized access to group
def authorize_read_group!
unless @group and (@projects.present? or can?(current_user, :read_group, @group))
if current_user.nil?
return authenticate_user!
else
return render_404
end
end
end
def authorize_create_group! def authorize_create_group!
unless can?(current_user, :create_group, nil) unless can?(current_user, :create_group, nil)
return render_404 return render_404
...@@ -135,7 +113,7 @@ class GroupsController < Groups::ApplicationController ...@@ -135,7 +113,7 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :public, :share_with_group_lock) params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock)
end end
def load_events def load_events
......
...@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController ...@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user if user
redirect_to user_path(user) redirect_to user_path(user)
elsif group elsif group && can?(current_user, :read_group, namespace)
redirect_to group_path(group) redirect_to group_path(group)
elsif current_user.nil? elsif current_user.nil?
authenticate_user! authenticate_user!
......
class Projects::ApplicationController < ApplicationController class Projects::ApplicationController < ApplicationController
skip_before_action :authenticate_user!
before_action :project before_action :project
before_action :repository before_action :repository
layout 'project' layout 'project'
def authenticate_user! helper_method :repository, :can_collaborate_with_project?
# Restrict access to Projects area only
# for non-signed users private
if !current_user
def project
unless @project
namespace = params[:namespace_id]
id = params[:project_id] || params[:id] id = params[:project_id] || params[:id]
project_with_namespace = "#{params[:namespace_id]}/#{id}"
@project = Project.find_with_namespace(project_with_namespace)
return if @project && @project.public? # Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\/?\Z/, '')
return
end
project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
if @project && can?(current_user, :read_project, @project)
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace)
end
else
@project = nil
if current_user.nil?
authenticate_user!
else
render_404
end
end
end
@project
end end
def repository
@repository ||= project.repository
end
def can_collaborate_with_project?(project = nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
authorize_project!($1.to_sym)
else
super super
end end
end
def require_non_empty_project
redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo?
end
def require_branch_head def require_branch_head
unless @repository.branch_names.include?(@ref) unless @repository.branch_names.include?(@ref)
...@@ -26,8 +80,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -26,8 +80,6 @@ class Projects::ApplicationController < ApplicationController
end end
end end
private
def apply_diff_view_cookie! def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view] view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view cookies.permanent[:diff_view] = params[:view] = view if view
......
class Projects::AvatarsController < Projects::ApplicationController class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper include BlobHelper
before_action :project before_action :authorize_admin_project!, only: [:destroy]
def show def show
@blob = @repository.blob_at_branch('master', @project.avatar_in_git) @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
......
class Projects::IssuesController < Projects::ApplicationController class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions
before_action :module_enabled before_action :module_enabled
before_action :issue, only: [:edit, :update, :show] before_action :issue, only: [:edit, :update, :show]
...@@ -133,6 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -133,6 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
end end
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
def authorize_read_issue! def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
......
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include DiffHelper include DiffHelper
include IssuableActions
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
...@@ -255,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -255,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end end
alias_method :subscribable_resource, :merge_request alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
def closes_issues def closes_issues
@closes_issues ||= @merge_request.closes_issues @closes_issues ||= @merge_request.closes_issues
......
class Projects::UploadsController < Projects::ApplicationController class Projects::UploadsController < Projects::ApplicationController
skip_before_action :authenticate_user!, :reject_blocked!, :project, skip_before_action :reject_blocked!, :project,
:repository, if: -> { action_name == 'show' && image? } :repository, if: -> { action_name == 'show' && image? }
before_action :authorize_upload_file!, only: [:create]
def create def create
link_to_file = ::Projects::UploadService.new(project, params[:file]). link_to_file = ::Projects::UploadService.new(project, params[:file]).
execute execute
...@@ -26,6 +28,8 @@ class Projects::UploadsController < Projects::ApplicationController ...@@ -26,6 +28,8 @@ class Projects::UploadsController < Projects::ApplicationController
send_file uploader.file.path, disposition: disposition send_file uploader.file.path, disposition: disposition
end end
private
def uploader def uploader
return @uploader if defined?(@uploader) return @uploader if defined?(@uploader)
......
class ProjectsController < ApplicationController class ProjectsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
skip_before_action :authenticate_user!, only: [:show, :activity] before_action :authenticate_user!, except: [:show, :activity]
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
......
...@@ -108,7 +108,7 @@ class UsersController < ApplicationController ...@@ -108,7 +108,7 @@ class UsersController < ApplicationController
end end
def load_groups def load_groups
@groups = @user.groups.order_id_desc @groups = JoinedGroupsFinder.new(@user).execute(current_user)
end end
def projects_for_current_user def projects_for_current_user
......
class ContributedProjectsFinder class ContributedProjectsFinder < UnionFinder
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -11,27 +11,19 @@ class ContributedProjectsFinder ...@@ -11,27 +11,19 @@ class ContributedProjectsFinder
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def execute(current_user = nil) def execute(current_user = nil)
if current_user segments = all_projects(current_user)
relation = projects_visible_to_user(current_user)
else
relation = public_projects
end
relation.includes(:namespace).order_id_desc find_union(segments, Project).includes(:namespace).order_id_desc
end end
private private
def projects_visible_to_user(current_user) def all_projects(current_user)
authorized = @user.contributed_projects.visible_to_user(current_user) projects = []
union = Gitlab::SQL::Union.
new([authorized.select(:id), public_projects.select(:id)])
Project.where("projects.id IN (#{union.to_sql})") projects << @user.contributed_projects.visible_to_user(current_user) if current_user
end projects << @user.contributed_projects.public_to_user(current_user)
def public_projects projects
@user.contributed_projects.public_only
end end
end end
class GroupProjectsFinder < UnionFinder
def initialize(group, options = {})
@group = group
@options = options
end
def execute(current_user = nil)
segments = group_projects(current_user)
find_union(segments, Project)
end
private
def group_projects(current_user)
only_owned = @options.fetch(:only_owned, false)
only_shared = @options.fetch(:only_shared, false)
projects = []
if current_user
if @group.users.include?(current_user)
projects << @group.projects unless only_shared
projects << @group.shared_projects unless only_owned
else
unless only_shared
projects << @group.projects.visible_to_user(current_user)
projects << @group.projects.public_to_user(current_user)
end
unless only_owned
projects << @group.shared_projects.visible_to_user(current_user)
projects << @group.shared_projects.public_to_user(current_user)
end
end
else
projects << @group.projects.public_only unless only_shared
projects << @group.shared_projects.public_only unless only_owned
end
projects
end
end
class GroupsFinder < UnionFinder
def execute(current_user = nil)
segments = all_groups(current_user)
find_union(segments, Group).order_id_desc
end
private
def all_groups(current_user)
groups = []
groups << current_user.authorized_groups if current_user
groups << Group.unscoped.public_to_user(current_user)
groups
end
end
...@@ -80,9 +80,10 @@ class IssuableFinder ...@@ -80,9 +80,10 @@ class IssuableFinder
@projects = project @projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related? elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects.reorder(nil) @projects = current_user.authorized_projects.reorder(nil)
elsif group
@projects = GroupProjectsFinder.new(group).execute(current_user).reorder(nil)
else else
@projects = ProjectsFinder.new.execute(current_user, group: group). @projects = ProjectsFinder.new.execute(current_user).reorder(nil)
reorder(nil)
end end
end end
...@@ -171,14 +172,12 @@ class IssuableFinder ...@@ -171,14 +172,12 @@ class IssuableFinder
def by_scope(items) def by_scope(items)
case params[:scope] case params[:scope]
when 'created-by-me', 'authored' then when 'created-by-me', 'authored'
items.where(author_id: current_user.id) items.where(author_id: current_user.id)
when 'all' then when 'assigned-to-me'
items
when 'assigned-to-me' then
items.where(assignee_id: current_user.id) items.where(assignee_id: current_user.id)
else else
raise 'You must specify default scope' items
end end
end end
...@@ -198,8 +197,7 @@ class IssuableFinder ...@@ -198,8 +197,7 @@ class IssuableFinder
end end
def by_group(items) def by_group(items)
items = items.of_group(group) if group # Selection by group is already covered by `by_project` and `projects`
items items
end end
......
class JoinedGroupsFinder < UnionFinder
def initialize(user)
@user = user
end
# Finds the groups of the source user, optionally limited to those visible to
# the current user.
def execute(current_user = nil)
segments = all_groups(current_user)
find_union(segments, Group).order_id_desc
end
private
def all_groups(current_user)
groups = []
groups << @user.authorized_groups.visible_to_user(current_user) if current_user
groups << @user.authorized_groups.public_to_user(current_user)
groups
end
end
class PersonalProjectsFinder class PersonalProjectsFinder < UnionFinder
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -11,31 +11,19 @@ class PersonalProjectsFinder ...@@ -11,31 +11,19 @@ class PersonalProjectsFinder
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def execute(current_user = nil) def execute(current_user = nil)
if current_user segments = all_projects(current_user)
relation = projects_visible_to_user(current_user)
else
relation = public_projects
end
relation.includes(:namespace).order_id_desc find_union(segments, Project).includes(:namespace).order_id_desc
end end
private private
def projects_visible_to_user(current_user) def all_projects(current_user)
authorized = @user.personal_projects.visible_to_user(current_user) projects = []
union = Gitlab::SQL::Union.
new([authorized.select(:id), public_and_internal_projects.select(:id)])
Project.where("projects.id IN (#{union.to_sql})") projects << @user.personal_projects.visible_to_user(current_user) if current_user
end projects << @user.personal_projects.public_to_user(current_user)
def public_projects
@user.personal_projects.public_only
end
def public_and_internal_projects projects
@user.personal_projects.public_and_internal_only
end end
end end
class ProjectsFinder class ProjectsFinder < UnionFinder
# Returns all projects, optionally including group projects a user has access
# to.
#
# ## Examples
#
# Retrieving all public projects:
#
# ProjectsFinder.new.execute
#
# Retrieving all public/internal projects and those the given user has access
# to:
#
# ProjectsFinder.new.execute(some_user)
#
# Retrieving all public/internal projects as well as the group's projects the
# user has access to:
#
# ProjectsFinder.new.execute(some_user, group: some_group)
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil, options = {}) def execute(current_user = nil, options = {})
group = options[:group]
if group
segments = group_projects(current_user, group)
else
segments = all_projects(current_user) segments = all_projects(current_user)
end
if segments.length > 1 find_union(segments, Project)
union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
Project.where("projects.id IN (#{union.to_sql})")
else
segments.first
end
end end
private private
def group_projects(current_user, group)
return [group.projects.public_only] unless current_user
user_group_projects = [
group_projects_for_user(current_user, group),
group.shared_projects.visible_to_user(current_user)
]
if current_user.external?
user_group_projects << group.projects.public_only
else
user_group_projects << group.projects.public_and_internal_only
end
end
def all_projects(current_user) def all_projects(current_user)
return [public_projects] unless current_user projects = []
if current_user.external? projects << current_user.authorized_projects if current_user
[current_user.authorized_projects, public_projects] projects << Project.unscoped.public_to_user(current_user)
else
[current_user.authorized_projects, public_and_internal_projects]
end
end
def group_projects_for_user(current_user, group)
if group.users.include?(current_user)
group.projects
else
group.projects.visible_to_user(current_user)
end
end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects projects
Project.unscoped.public_and_internal_only
end end
end end
class UnionFinder
def find_union(segments, klass)
if segments.length > 1
union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
klass.where("#{klass.table_name}.id IN (#{union.to_sql})")
else
segments.first
end
end
end
...@@ -27,7 +27,7 @@ module BlobHelper ...@@ -27,7 +27,7 @@ module BlobHelper
link_opts) link_opts)
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn' link_to "Edit", edit_path, class: 'btn'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
...@@ -50,9 +50,9 @@ module BlobHelper ...@@ -50,9 +50,9 @@ module BlobHelper
return unless blob return unless blob
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer? elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_edit_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
......
...@@ -24,7 +24,7 @@ module ButtonHelper ...@@ -24,7 +24,7 @@ module ButtonHelper
def http_clone_button(project) def http_clone_button(project)
klass = 'http-selector' klass = 'http-selector'
klass << ' has_tooltip' if current_user.try(:require_password?) klass << ' has-tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase protocol = gitlab_config.protocol.upcase
...@@ -41,7 +41,7 @@ module ButtonHelper ...@@ -41,7 +41,7 @@ module ButtonHelper
def ssh_clone_button(project) def ssh_clone_button(project)
klass = 'ssh-selector' klass = 'ssh-selector'
klass << ' has_tooltip' if current_user.try(:require_ssh_key?) klass << ' has-tooltip' if current_user.try(:require_ssh_key?)
content_tag :a, 'SSH', content_tag :a, 'SSH',
class: klass, class: klass,
......
...@@ -182,7 +182,7 @@ module CommitsHelper ...@@ -182,7 +182,7 @@ module CommitsHelper
end end
options = { options = {
class: "commit-#{options[:source]}-link has_tooltip", class: "commit-#{options[:source]}-link has-tooltip",
data: { 'original-title'.to_sym => sanitize(source_email) } data: { 'original-title'.to_sym => sanitize(source_email) }
} }
......
...@@ -19,6 +19,10 @@ module GroupsHelper ...@@ -19,6 +19,10 @@ module GroupsHelper
end end
end end
def can_change_group_visibility_level?(group)
can?(current_user, :change_visibility_level, group)
end
def group_icon(group) def group_icon(group)
if group.is_a?(String) if group.is_a?(String)
group = Group.find_by(path: group) group = Group.find_by(path: group)
......
...@@ -56,7 +56,7 @@ module LabelsHelper ...@@ -56,7 +56,7 @@ module LabelsHelper
# Intentionally not using content_tag here so that this method can be called # Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter # by LabelReferenceFilter
span = %(<span class="label color-label #{"has_tooltip" if tooltip}" ) + span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
%(style="background-color: #{label_color}; color: #{text_color}" ) + %(style="background-color: #{label_color}; color: #{text_color}" ) +
%(title="#{escape_once(label.description)}" data-container="body">) + %(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>) %(#{escape_once(label.name)}#{label_suffix}</span>)
......
...@@ -52,7 +52,7 @@ module ProjectsHelper ...@@ -52,7 +52,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else else
title = opts[:title].sub(":name", sanitize(author.name)) title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
end end
end end
......
...@@ -19,6 +19,8 @@ module VisibilityLevelHelper ...@@ -19,6 +19,8 @@ module VisibilityLevelHelper
case form_model case form_model
when Project when Project
project_visibility_level_description(level) project_visibility_level_description(level)
when Group
group_visibility_level_description(level)
when Snippet when Snippet
snippet_visibility_level_description(level, form_model) snippet_visibility_level_description(level, form_model)
end end
...@@ -35,6 +37,17 @@ module VisibilityLevelHelper ...@@ -35,6 +37,17 @@ module VisibilityLevelHelper
end end
end end
def group_visibility_level_description(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
"The group and its projects can only be viewed by members."
when Gitlab::VisibilityLevel::INTERNAL
"The group and any internal projects can be viewed by any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
"The group and any public projects can be viewed without any authentication."
end
end
def snippet_visibility_level_description(level, snippet = nil) def snippet_visibility_level_description(level, snippet = nil)
case level case level
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
...@@ -50,6 +63,23 @@ module VisibilityLevelHelper ...@@ -50,6 +63,23 @@ module VisibilityLevelHelper
end end
end end
def visibility_icon_description(form_model)
case form_model
when Project
project_visibility_icon_description(form_model.visibility_level)
when Group
group_visibility_icon_description(form_model.visibility_level)
end
end
def group_visibility_icon_description(level)
"#{visibility_level_label(level)} - #{group_visibility_level_description(level)}"
end
def project_visibility_icon_description(level)
"#{visibility_level_label(level)} - #{project_visibility_level_description(level)}"
end
def visibility_level_label(level) def visibility_level_label(level)
Project.visibility_levels.key(level) Project.visibility_levels.key(level)
end end
...@@ -67,8 +97,11 @@ module VisibilityLevelHelper ...@@ -67,8 +97,11 @@ module VisibilityLevelHelper
current_application_settings.default_snippet_visibility current_application_settings.default_snippet_visibility
end end
def default_group_visibility
current_application_settings.default_group_visibility
end
def skip_level?(form_model, level) def skip_level?(form_model, level)
form_model.is_a?(Project) && form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
!form_model.visibility_level_allowed?(level)
end end
end end
...@@ -85,7 +85,7 @@ class Ability ...@@ -85,7 +85,7 @@ class Ability
subject.group subject.group
end end
if group && group.projects.public_only.any? if group && group.public?
[:read_group] [:read_group]
else else
[] []
...@@ -114,6 +114,13 @@ class Ability ...@@ -114,6 +114,13 @@ class Ability
# Push abilities on the users team role # Push abilities on the users team role
rules.push(*project_team_rules(project.team, user)) rules.push(*project_team_rules(project.team, user))
if project.owner == user ||
(project.group && project.group.has_owner?(user)) ||
user.admin?
rules.push(*project_owner_rules)
end
if project.public? || (project.internal? && !user.external?) if project.public? || (project.internal? && !user.external?)
rules.push(*public_project_rules) rules.push(*public_project_rules)
...@@ -121,14 +128,6 @@ class Ability ...@@ -121,14 +128,6 @@ class Ability
rules << :read_build if project.public_builds? rules << :read_build if project.public_builds?
end end
if project.owner == user || user.admin?
rules.push(*project_admin_rules)
end
if project.group && project.group.has_owner?(user)
rules.push(*project_admin_rules)
end
if project.archived? if project.archived?
rules -= project_archived_rules rules -= project_archived_rules
end end
...@@ -171,7 +170,8 @@ class Ability ...@@ -171,7 +170,8 @@ class Ability
:read_note, :read_note,
:create_project, :create_project,
:create_issue, :create_issue,
:create_note :create_note,
:upload_file
] ]
end end
...@@ -228,14 +228,16 @@ class Ability ...@@ -228,14 +228,16 @@ class Ability
] ]
end end
def project_admin_rules def project_owner_rules
@project_admin_rules ||= project_master_rules + [ @project_owner_rules ||= project_master_rules + [
:change_namespace, :change_namespace,
:change_visibility_level, :change_visibility_level,
:rename_project, :rename_project,
:remove_project, :remove_project,
:archive_project, :archive_project,
:remove_fork_project :remove_fork_project,
:destroy_merge_request,
:destroy_issue
] ]
end end
...@@ -273,11 +275,9 @@ class Ability ...@@ -273,11 +275,9 @@ class Ability
def group_abilities(user, group) def group_abilities(user, group)
rules = [] rules = []
if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? rules << :read_group if can_read_group?(user, group)
rules << :read_group
end
# Only group masters and group owners can create new projects in group # Only group masters and group owners can create new projects
if group.has_master?(user) || group.has_owner?(user) || user.admin? if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules += [ rules += [
:create_projects, :create_projects,
...@@ -290,13 +290,23 @@ class Ability ...@@ -290,13 +290,23 @@ class Ability
rules += [ rules += [
:admin_group, :admin_group,
:admin_namespace, :admin_namespace,
:admin_group_member :admin_group_member,
:change_visibility_level
] ]
end end
rules.flatten rules.flatten
end end
def can_read_group?(user, group)
return true if user.admin?
return true if group.public?
return true if group.internal? && !user.external?
return true if group.users.include?(user)
GroupProjectsFinder.new(group).execute(user).any?
end
def namespace_abilities(user, namespace) def namespace_abilities(user, namespace)
rules = [] rules = []
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# max_attachment_size :integer default(10), not null # max_attachment_size :integer default(10), not null
# default_project_visibility :integer # default_project_visibility :integer
# default_snippet_visibility :integer # default_snippet_visibility :integer
# default_group_visibility :integer
# restricted_signup_domains :text # restricted_signup_domains :text
# user_oauth_applications :boolean default(TRUE) # user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255) # after_sign_out_path :string(255)
......
...@@ -7,7 +7,10 @@ module InternalId ...@@ -7,7 +7,10 @@ module InternalId
end end
def set_iid def set_iid
max_iid = project.send(self.class.name.tableize).maximum(:iid) records = project.send(self.class.name.tableize)
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid)
self.iid = max_iid.to_i + 1 self.iid = max_iid.to_i + 1
end end
......
...@@ -58,6 +58,8 @@ module Issuable ...@@ -58,6 +58,8 @@ module Issuable
attr_mentionable :description, cache: true attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations participant :author, :assignee, :notes_with_associations
strip_attributes :title strip_attributes :title
acts_as_paranoid
end end
module ClassMethods module ClassMethods
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
# name :string(255) not null # name :string(255) not null
# path :string(255) not null # path :string(255) not null
# owner_id :integer # owner_id :integer
# visibility_level :integer default(20), not null
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# type :string(255) # type :string(255)
...@@ -18,6 +19,7 @@ require 'file_size_validator' ...@@ -18,6 +19,7 @@ require 'file_size_validator'
class Group < Namespace class Group < Namespace
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Gitlab::VisibilityLevel
include Referable include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
...@@ -27,6 +29,8 @@ class Group < Namespace ...@@ -27,6 +29,8 @@ class Group < Namespace
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
...@@ -74,6 +78,21 @@ class Group < Namespace ...@@ -74,6 +78,21 @@ class Group < Namespace
name name
end end
def visibility_level_field
visibility_level
end
def visibility_level_allowed_by_projects
allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none?
unless allowed_by_projects
level_name = Gitlab::VisibilityLevel.level_name(visibility_level).downcase
self.errors.add(:visibility_level, "#{level_name} is not allowed since there are projects with higher visibility.")
end
allowed_by_projects
end
def avatar_url(size = nil) def avatar_url(size = nil)
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
......
...@@ -36,9 +36,6 @@ class Issue < ActiveRecord::Base ...@@ -36,9 +36,6 @@ class Issue < ActiveRecord::Base
validates :project, presence: true validates :project, presence: true
scope :of_group,
->(group) { where(project_id: group.projects.select(:id).reorder(nil)) }
scope :cared, ->(user) { where(assignee_id: user) } scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) } scope :open_for, ->(user) { opened.assigned_to(user) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids) } scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
......
...@@ -131,7 +131,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -131,7 +131,6 @@ class MergeRequest < ActiveRecord::Base
validate :validate_branches validate :validate_branches
validate :validate_fork validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.projects.select(:id).reorder(nil)) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
......
...@@ -73,7 +73,7 @@ class Project < ActiveRecord::Base ...@@ -73,7 +73,7 @@ class Project < ActiveRecord::Base
update_column(:last_activity_at, self.created_at) update_column(:last_activity_at, self.created_at)
end end
# update visibility_levet of forks # update visibility_level of forks
after_update :update_forks_visibility_level after_update :update_forks_visibility_level
def update_forks_visibility_level def update_forks_visibility_level
return unless visibility_level < visibility_level_was return unless visibility_level < visibility_level_was
...@@ -197,6 +197,8 @@ class Project < ActiveRecord::Base ...@@ -197,6 +197,8 @@ class Project < ActiveRecord::Base
validate :avatar_type, validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? } if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
validate :visibility_level_allowed_by_group
validate :visibility_level_allowed_as_fork
add_authentication_token_field :runners_token add_authentication_token_field :runners_token
before_save :ensure_runners_token before_save :ensure_runners_token
...@@ -215,8 +217,6 @@ class Project < ActiveRecord::Base ...@@ -215,8 +217,6 @@ class Project < ActiveRecord::Base
scope :in_group_namespace, -> { joins(:group) } scope :in_group_namespace, -> { joins(:group) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
...@@ -246,10 +246,6 @@ class Project < ActiveRecord::Base ...@@ -246,10 +246,6 @@ class Project < ActiveRecord::Base
end end
class << self class << self
def public_and_internal_levels
[Project::PUBLIC, Project::INTERNAL]
end
def abandoned def abandoned
where('projects.last_activity_at < ?', 6.months.ago) where('projects.last_activity_at < ?', 6.months.ago)
end end
...@@ -464,10 +460,25 @@ class Project < ActiveRecord::Base ...@@ -464,10 +460,25 @@ class Project < ActiveRecord::Base
def check_limit def check_limit
unless creator.can_create_project? or namespace.kind == 'group' unless creator.can_create_project? or namespace.kind == 'group'
errors[:limit_reached] << ("Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it") self.errors.add(:limit_reached, "Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it")
end end
rescue rescue
errors[:base] << ("Can't check your ability to create project") self.errors.add(:base, "Can't check your ability to create project")
end
def visibility_level_allowed_by_group
return if visibility_level_allowed_by_group?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
self.errors.add(:visibility_level, "#{level_name} is not allowed in a #{group_level_name} group.")
end
def visibility_level_allowed_as_fork
return if visibility_level_allowed_as_fork?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
end end
def to_param def to_param
...@@ -983,9 +994,25 @@ class Project < ActiveRecord::Base ...@@ -983,9 +994,25 @@ class Project < ActiveRecord::Base
issues.opened.count issues.opened.count
end end
def visibility_level_allowed?(level) def visibility_level_allowed_as_fork?(level = self.visibility_level)
return true unless forked? return true unless forked?
Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
# self.forked_from_project will be nil before the project is saved, so
# we need to go through the relation
original_project = forked_project_link.forked_from_project
return true unless original_project
level <= original_project.visibility_level
end
def visibility_level_allowed_by_group?(level = self.visibility_level)
return true unless group
level <= group.visibility_level
end
def visibility_level_allowed?(level = self.visibility_level)
visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
end end
def runners_token def runners_token
......
...@@ -43,12 +43,9 @@ class BaseService ...@@ -43,12 +43,9 @@ class BaseService
def deny_visibility_level(model, denied_visibility_level = nil) def deny_visibility_level(model, denied_visibility_level = nil)
denied_visibility_level ||= model.visibility_level denied_visibility_level ||= model.visibility_level
level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level) level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level).downcase
model.errors.add( model.errors.add(:visibility_level, "#{level_name} has been restricted by your GitLab administrator")
:visibility_level,
"#{level_name} visibility has been restricted by your GitLab administrator"
)
end end
private private
......
...@@ -6,8 +6,7 @@ class CreateSnippetService < BaseService ...@@ -6,8 +6,7 @@ class CreateSnippetService < BaseService
snippet = project.snippets.build(params) snippet = project.snippets.build(params)
end end
unless Gitlab::VisibilityLevel.allowed_for?(current_user, unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
params[:visibility_level])
deny_visibility_level(snippet) deny_visibility_level(snippet)
return snippet return snippet
end end
......
module Groups
class BaseService < ::BaseService
attr_accessor :group, :current_user, :params
def initialize(group, user, params = {})
@group, @current_user, @params = group, user, params.dup
end
end
end
module Groups
class CreateService < Groups::BaseService
def initialize(user, params = {})
@current_user, @params = user, params.dup
end
def execute
@group = Group.new(params)
unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
deny_visibility_level(@group)
return @group
end
@group.name ||= @group.path.dup
@group.save
@group.add_owner(current_user)
@group
end
end
end
module Groups
class UpdateService < Groups::BaseService
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != group.visibility_level
unless can?(current_user, :change_visibility_level, group) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(group, new_visibility)
return group
end
end
group.assign_attributes(params)
group.save
end
end
end
...@@ -9,10 +9,8 @@ module Projects ...@@ -9,10 +9,8 @@ module Projects
@project = Project.new(params) @project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility # Make sure that the user is allowed to use the specified visibility level
# level unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
unless Gitlab::VisibilityLevel.allowed_for?(current_user,
params[:visibility_level])
deny_visibility_level(@project) deny_visibility_level(@project)
return @project return @project
end end
...@@ -55,9 +53,7 @@ module Projects ...@@ -55,9 +53,7 @@ module Projects
@project.save @project.save
if @project.persisted? && !@project.import? if @project.persisted? && !@project.import?
unless @project.create_repository raise 'Failed to create repository' unless @project.create_repository
raise 'Failed to create repository'
end
end end
end end
......
...@@ -3,18 +3,15 @@ module Projects ...@@ -3,18 +3,15 @@ module Projects
def execute def execute
# check that user is allowed to set specified visibility_level # check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level] new_visibility = params[:visibility_level]
if new_visibility if new_visibility && new_visibility.to_i != project.visibility_level
if new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) && unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(project, new_visibility) deny_visibility_level(project, new_visibility)
return project return project
end end
end end
return false unless visibility_level_allowed?(new_visibility)
end
new_branch = params[:default_branch] new_branch = params[:default_branch]
if project.repository.exists? && new_branch && new_branch != project.default_branch if project.repository.exists? && new_branch && new_branch != project.default_branch
...@@ -27,19 +24,5 @@ module Projects ...@@ -27,19 +24,5 @@ module Projects
end end
end end
end end
private
def visibility_level_allowed?(level)
return true if project.visibility_level_allowed?(level)
level_name = Gitlab::VisibilityLevel.level_name(level)
project.errors.add(
:visibility_level,
"#{level_name} could not be set as visibility level of this project - parent project settings are more restrictive"
)
false
end
end end
end end
...@@ -9,7 +9,6 @@ class UpdateSnippetService < BaseService ...@@ -9,7 +9,6 @@ class UpdateSnippetService < BaseService
def execute def execute
# check that user is allowed to set specified visibility_level # check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level] new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != snippet.visibility_level if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility) deny_visibility_level(snippet, new_visibility)
......
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
= f.label :default_snippet_visibility, class: 'control-label col-sm-2' = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new) = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
.form-group.group-visibility-level-holder
= f.label :default_group_visibility, class: 'control-label col-sm-2'
.col-sm-10
= render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
.form-group .form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2' = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
.col-sm-10 .col-sm-10
= render 'shared/choose_group_avatar_button', f: f = render 'shared/choose_group_avatar_button', f: f
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
- if @group.new_record? - if @group.new_record?
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
...@@ -46,6 +46,9 @@ ...@@ -46,6 +46,9 @@
%h4 %h4
= link_to [:admin, group] do = link_to [:admin, group] do
%span{ class: visibility_level_color(group.visibility_level) }
= visibility_level_icon(group.visibility_level)
%i.fa.fa-folder %i.fa.fa-folder
= group.name = group.name
......
...@@ -27,6 +27,11 @@ ...@@ -27,6 +27,11 @@
%strong %strong
= @group.description = @group.description
%li
%span.light Visibility level:
%strong
= visibility_level_label(@group.visibility_level)
%li %li
%span.light Created on: %span.light Created on:
%strong %strong
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
%li %li
%span.light fs: %span.light fs:
%strong %strong
= @repository.path_to_repo = @project.repository.path_to_repo
%li %li
%span.light Size %span.light Size
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= event_action_name(event) = event_action_name(event)
- if event.target - if event.target
%strong= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target] %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target]
= event_preposition(event) = event_preposition(event)
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
%hr %hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
.form-group .form-group
%hr %hr
= f.label :share_with_group_lock, class: 'control-label' do = f.label :share_with_group_lock, class: 'control-label' do
...@@ -32,6 +34,7 @@ ...@@ -32,6 +34,7 @@
= f.check_box :share_with_group_lock = f.check_box :share_with_group_lock
%span.descr Prevent sharing a project with another group within this group %span.descr Prevent sharing a project with another group within this group
.form-actions .form-actions
= f.submit 'Save group', class: "btn btn-save" = f.submit 'Save group', class: "btn btn-save"
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
.col-sm-10 .col-sm-10
= render 'shared/choose_group_avatar_button', f: f = render 'shared/choose_group_avatar_button', f: f
= render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= render 'shared/group_tips' = render 'shared/group_tips'
......
- @no_container = true - @no_container = true
- unless can?(current_user, :read_group, @group)
- @disable_search_panel = true
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
...@@ -18,7 +15,10 @@ ...@@ -18,7 +15,10 @@
= link_to group_icon(@group), target: '_blank' do = link_to group_icon(@group), target: '_blank' do
= image_tag group_icon(@group), class: "avatar group-avatar s90" = image_tag group_icon(@group), class: "avatar group-avatar s90"
.cover-title .cover-title
%h1
= @group.name = @group.name
%span.visibility-icon.has_tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false)
.cover-desc.username .cover-desc.username
@#{@group.path} @#{@group.path}
...@@ -27,8 +27,7 @@ ...@@ -27,8 +27,7 @@
.cover-desc.description .cover-desc.description
= markdown(@group.description, pipeline: :description) = markdown(@group.description, pipeline: :description)
- if can?(current_user, :read_group, @group) %div{ class: container_class }
%div{ class: container_class }
.top-area .top-area
%ul.nav-links %ul.nav-links
%li.active %li.active
...@@ -54,7 +53,3 @@ ...@@ -54,7 +53,3 @@
- if @shared_projects.present? - if @shared_projects.present?
.tab-pane#shared .tab-pane#shared
= render "shared_projects", projects: @shared_projects = render "shared_projects", projects: @shared_projects
- else
%p.nav-links.no-top
No projects to show
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
.navbar-collapse.collapse .navbar-collapse.collapse
%ul.nav.navbar-nav.pull-right %ul.nav.navbar-nav.pull-right
- unless @disable_search_panel
%li.hidden-sm.hidden-xs %li.hidden-sm.hidden-xs
= render 'layouts/search' = render 'layouts/search'
%li.visible-sm.visible-xs %li.visible-sm.visible-xs
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
= link_to dashboard_projects_path, title: 'Projects' do = link_to dashboard_projects_path, title: 'Projects' do
= icon('home fw') = icon('bookmark fw')
%span %span
Projects Projects
= nav_link(controller: :todos) do = nav_link(controller: :todos) do
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do = link_to explore_root_path, title: 'Projects' do
= icon('home fw') = icon('bookmark fw')
%span %span
Projects Projects
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
......
...@@ -12,13 +12,11 @@ ...@@ -12,13 +12,11 @@
= icon('group fw') = icon('group fw')
%span %span
Group Group
- if can?(current_user, :read_group, @group)
= nav_link(path: 'groups#activity') do = nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do = link_to activity_group_path(@group), title: 'Activity' do
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Activity Activity
- if current_user
= nav_link(controller: [:group, :milestones]) do = nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do = link_to group_milestones_path(@group), title: 'Milestones' do
= icon('clock-o fw') = icon('clock-o fw')
...@@ -29,15 +27,15 @@ ...@@ -29,15 +27,15 @@
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
%span %span
Issues Issues
- if current_user - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.count= number_with_delimiter(Issue.opened.of_group(@group).count) %span.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do = nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
- if current_user - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.count= number_with_delimiter(MergeRequest.opened.of_group(@group).count) %span.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do = nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw') = icon('users fw')
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= key.fingerprint = key.fingerprint
.pull-right .pull-right
%span.key-created-at %span.key-created-at
created #{time_ago_with_tooltip(key.created_at)} ago created #{time_ago_with_tooltip(key.created_at)}
= link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do = link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
%span.sr-only Remove %span.sr-only Remove
= icon('trash') = icon('trash')
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)} .project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder .project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90') = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc .cover-title.project-home-desc
%h1 %h1
= @project.name = @project.name
%span.visibility-icon.has_tooltip{data: { container: 'body' }, %span.visibility-icon.has_tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
title: "#{visibility_level_label(@project.visibility_level)} - #{project_visibility_level_description(@project.visibility_level)}"}
= visibility_level_icon(@project.visibility_level, fw: false) = visibility_level_icon(@project.visibility_level, fw: false)
- if @project.description.present? - if @project.description.present?
.cover-desc.project-home-desc
= markdown(@project.description, pipeline: :description) = markdown(@project.description, pipeline: :description)
- if forked_from_project = @project.forked_from_project - if forked_from_project = @project.forked_from_project
%p .cover-desc
Forked from Forked from
= link_to project_path(forked_from_project) do = link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name) = forked_from_project.namespace.try(:name)
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name - elsif @repository.merged_to_root_ref? branch.name
%span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}") %span.label.label-info.has-tooltip(title="Merged into #{@repository.root_ref}")
merged merged
- if @project.protected_branch? branch.name - if @project.protected_branch? branch.name
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
Compare Compare
- if can_remove_branch?(@project, branch.name) - if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o") = icon("trash-o")
- if branch.name != @repository.root_ref - if branch.name != @repository.root_ref
......
- unless @project.empty_repo? - unless @project.empty_repo?
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= icon('download') = icon('download')
- unless @project.empty_repo? - unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project) - if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
%div.count-with-arrow %div.count-with-arrow
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%span.count %span.count
= @project.forks_count = @project.forks_count
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
%div.count-with-arrow %div.count-with-arrow
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
= notification_list_item(level, @membership) = notification_list_item(level, @membership)
- when GroupMember - when GroupMember
.btn.disabled.notifications-btn.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."} .btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
= icon('bell') = icon('bell')
= notification_label(@membership) = notification_label(@membership)
= icon('angle-down') = icon('angle-down')
- if current_user - if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: "Star project" do
- if current_user.starred?(@project) - if current_user.starred?(@project)
= icon('star fw') = icon('star fw')
%span.starred Unstar %span.starred Unstar
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= @project.star_count = @project.star_count
- else - else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw') = icon('star fw')
Star Star
%div.count-with-arrow %div.count-with-arrow
......
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix .clearfix
- if params[:to] && params[:from] - if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
.form-group .form-group
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon from %span.input-group-addon from
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.file-actions.hidden-xs .file-actions.hidden-xs
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has_tooltip', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do
= icon('comments') = icon('comments')
\ \
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.col-md-2.col-sm-3 .col-md-2.col-sm-3
- if fork = namespace.find_fork_of(@project) - if fork = namespace.find_fork_of(@project)
.fork-thumbnail .fork-thumbnail
= link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do = link_to project_path(fork), title: "Visit project fork", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100) = image_tag namespace_icon(namespace, 100)
.caption .caption
%strong %strong
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
- else - else
.fork-thumbnail .fork-thumbnail
= link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100) = image_tag namespace_icon(namespace, 100)
.caption .caption
%strong %strong
......
...@@ -45,7 +45,6 @@ ...@@ -45,7 +45,6 @@
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
- if merge_request.open? && merge_request.broken? - if merge_request.open? && merge_request.broken?
%li %li
= link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle') = icon('exclamation-triangle')
- if merge_request.assignee - if merge_request.assignee
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
- if @merge_request.open? - if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request' = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
%i.fa.fa-pencil-square-o = icon('pencil-square-o')
Edit Edit
- if @merge_request.closed? - if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment