Merge branch 'dm-archived-read-only' into 'master'

Make archived projects completely read-only

Closes #44788

See merge request gitlab-org/gitlab-ce!18136
This commit is contained in:
Robert Speicher 2018-04-12 17:04:08 +00:00
commit f5a3d8f75d
91 changed files with 935 additions and 297 deletions

View File

@ -317,10 +317,10 @@ Please check your network connection and try again.`;
<note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget
issuable-type="issue"
v-else-if="!canCreateNote"
v-else-if="isLocked(getNoteableData) && !canCreateNote"
/>
<ul
v-else
v-else-if="canCreateNote"
class="notes notes-form timeline">
<li class="timeline-entry">
<div class="timeline-entry-inner">

View File

@ -40,6 +40,10 @@ export default {
type: Boolean,
required: true,
},
canAwardEmoji: {
type: Boolean,
required: true,
},
canDelete: {
type: Boolean,
required: true,
@ -74,9 +78,6 @@ export default {
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId;
},
@ -149,7 +150,7 @@ export default {
</button>
</div>
<div
v-if="canAddAwardEmoji"
v-if="canAwardEmoji"
class="note-actions-item">
<a
v-tooltip

View File

@ -28,6 +28,10 @@ export default {
type: Number,
required: true,
},
canAwardEmoji: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters(['getUserData']),
@ -67,9 +71,6 @@ export default {
isAuthoredByMe() {
return this.noteAuthorId === this.getUserData.id;
},
isLoggedIn() {
return this.getUserData.id;
},
},
created() {
this.emojiSmiling = emojiSmiling;
@ -156,7 +157,7 @@ export default {
return title;
},
handleAward(awardName) {
if (!this.isLoggedIn) {
if (!this.canAwardEmoji) {
return;
}
@ -208,7 +209,7 @@ export default {
</span>
</button>
<div
v-if="isLoggedIn"
v-if="canAwardEmoji"
class="award-menu-holder">
<button
v-tooltip

View File

@ -112,6 +112,7 @@ export default {
:note-author-id="note.author.id"
:awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path"
:can-award-emoji="note.current_user.can_award_emoji"
/>
<note-attachment
v-if="note.attachment"

View File

@ -177,6 +177,7 @@ export default {
:note-id="note.id"
:access-level="note.human_access"
:can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
:report-abuse-path="note.report_abuse_path"

View File

@ -0,0 +1,21 @@
module ChecksCollaboration
def can_collaborate_with_project?(project, ref: nil)
return true if can?(current_user, :push_code, project)
can_create_merge_request =
can?(current_user, :create_merge_request_in, project) &&
current_user.already_forked?(project)
can_create_merge_request ||
user_access(project).can_push_to_branch?(ref)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# enabling this so we can easily cache the user access value as it might be
# used across multiple calls in the view
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end

View File

@ -1,5 +1,6 @@
class Projects::ApplicationController < ApplicationController
include RoutableActions
include ChecksCollaboration
skip_before_action :authenticate_user!
before_action :project
@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
@repository ||= project.repository
end
def can_collaborate_with_project?(project = nil, ref: nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project)) ||
user_access(project).can_push_to_branch?(ref)
end
def authorize_action!(action)
unless can?(current_user, action, project)
return access_denied!
@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
end

View File

@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
respond_to :html

View File

@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request!
before_action :authorize_create_merge_request_from!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]

View File

@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
if @source_project.fork_network
@source_project.fork_network.projects
.public_or_visible_to_user(current_user)
.non_archived
.with_feature_available_for_user(:merge_requests, current_user)
else
Project.where(id: source_project)

View File

@ -59,7 +59,7 @@ module BlobHelper
button_tag label, class: "#{common_classes} 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_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
end
end
@ -280,7 +280,7 @@ module BlobHelper
options << link_to("submit an issue", new_project_issue_path(project))
end
merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
merge_project = merge_request_source_project_for_project(@project)
if merge_project
options << link_to("create a merge request", project_new_merge_request_path(project))
end
@ -334,7 +334,7 @@ module BlobHelper
# Web IDE (Beta) requires the user to have this feature enabled
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
edit_link_tag(text, edit_path, common_classes)
elsif current_user && can?(current_user, :fork_project, project)
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
end
end

View File

@ -163,7 +163,7 @@ module CommitsHelper
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
if can_collaborate_with_project?
if can_collaborate_with_project?(@project)
link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project)
continue_params = {

View File

@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
can?(current_user, :create_merge_request, project) &&
can?(current_user, :create_merge_request_from, project) &&
project.repository.branch_exists?(from) &&
project.repository.branch_exists?(to)
end

View File

@ -82,8 +82,8 @@ module IssuesHelper
names.to_sentence
end
def award_state_class(awards, current_user)
if !current_user
def award_state_class(awardable, awards, current_user)
if !can?(current_user, :award_emoji, awardable)
"disabled"
elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active"
@ -126,6 +126,17 @@ module IssuesHelper
link_to link_text, path
end
def show_new_issue_link?(project)
return false unless project
return false if project.archived?
# We want to show the link to users that are not signed in, that way they
# get directed to the sign-in/sign-up flow and afterwards to the new issue page.
return true unless current_user
can?(current_user, :create_issue, project)
end
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
module_function :url_for_internal_issue

View File

@ -138,6 +138,18 @@ module MergeRequestsHelper
end
end
def merge_request_source_project_for_project(project = @project)
unless can?(current_user, :create_merge_request_in, project)
return nil
end
if can?(current_user, :create_merge_request_from, project)
project
else
current_user.fork_of(project)
end
end
def merge_params_ee(merge_request)
{}
end

View File

@ -6,10 +6,6 @@ module NotesHelper
end
end
def note_editable?(note)
Ability.can_edit_note?(current_user, note)
end
def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note)
end

View File

@ -46,10 +46,6 @@ class Ability
end
end
def can_edit_note?(user, note)
allowed?(user, :edit_note, note)
end
def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash)
opts, subject = subject, :global

View File

@ -79,11 +79,7 @@ module Awardable
end
def user_can_award?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
end
def user_authored?(current_user)
@ -119,4 +115,12 @@ module Awardable
def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name)
end
def awardable_by_user?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
end
end

View File

@ -11,7 +11,7 @@ module Ci
end
condition(:owner_of_job) do
can?(:developer_access) && @subject.triggered_by?(@user)
@subject.triggered_by?(@user)
end
rule { protected_ref }.policy do
@ -19,6 +19,6 @@ module Ci
prevent :erase_build
end
rule { can?(:master_access) | owner_of_job }.enable :erase_build
rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
end
end

View File

@ -7,23 +7,17 @@ module Ci
end
condition(:owner_of_schedule) do
can?(:developer_access) && pipeline_schedule.owned_by?(@user)
pipeline_schedule.owned_by?(@user)
end
condition(:non_owner_of_schedule) do
!pipeline_schedule.owned_by?(@user)
end
rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
rule { can?(:developer_access) }.policy do
enable :play_pipeline_schedule
end
rule { can?(:master_access) | owner_of_schedule }.policy do
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
enable :update_pipeline_schedule
enable :admin_pipeline_schedule
end
rule { can?(:master_access) & non_owner_of_schedule }.policy do
rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
enable :take_ownership_pipeline_schedule
end

View File

@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy
rule { locked & ~is_project_member }.policy do
prevent :create_note
prevent :update_note
prevent :admin_note
prevent :resolve_note
prevent :edit_note
end
end

View File

@ -1,26 +1,21 @@
class NotePolicy < BasePolicy
delegate { @subject.project }
delegate { @subject.noteable if @subject.noteable.lockable? }
delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) }
condition(:is_author) { @user && @subject.author == @user }
condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
condition(:editable, scope: :subject) { @subject.editable? }
rule { ~editable | anonymous }.prevent :edit_note
rule { is_author | admin }.enable :edit_note
rule { can?(:master_access) }.enable :edit_note
rule { ~editable }.prevent :admin_note
rule { is_author }.policy do
enable :read_note
enable :update_note
enable :admin_note
enable :resolve_note
end
rule { for_merge_request & is_noteable_author }.policy do
rule { is_noteable_author }.policy do
enable :resolve_note
end
end

View File

@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
end
rule { anonymous }.prevent :comment_personal_snippet
rule { can?(:comment_personal_snippet) }.enable :award_emoji
end

View File

@ -1,12 +1,26 @@
class ProjectPolicy < BasePolicy
def self.create_read_update_admin(name)
[
:"create_#{name}",
:"read_#{name}",
:"update_#{name}",
:"admin_#{name}"
]
end
extend ClassMethods
READONLY_FEATURES_WHEN_ARCHIVED = %i[
issue
list
merge_request
label
milestone
project_snippet
wiki
note
pipeline
pipeline_schedule
build
trigger
environment
deployment
commit_status
container_image
pages
cluster
].freeze
desc "User is a project owner"
condition :owner do
@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
end
desc "Project has public builds enabled"
condition(:public_builds, scope: :subject) { project.public_builds? }
condition(:public_builds, scope: :subject, score: 0) { project.public_builds? }
# For guest access we use #team_member? so we can use
# project.members, which gets cached in subject scope.
@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
condition(:master) { team_access_level >= Gitlab::Access::MASTER }
desc "Project is public"
condition(:public_project, scope: :subject) { project.public? }
condition(:public_project, scope: :subject, score: 0) { project.public? }
desc "Project is visible to internal users"
condition(:internal_access) do
@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
condition(:group_member, scope: :subject) { project_group_member? }
desc "Project is archived"
condition(:archived, scope: :subject) { project.archived? }
condition(:archived, scope: :subject, score: 0) { project.archived? }
condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy
end
desc "Project has an external wiki"
condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
condition(:has_external_wiki, scope: :subject, score: 0) { project.has_external_wiki? }
desc "Project has request access enabled"
condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
desc "Has merge requests allowing pushes to user"
condition(:has_merge_requests_allowing_pushes, scope: :subject) do
@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:guest_access) }.policy do
enable :read_project
enable :create_merge_request_in
enable :read_board
enable :read_list
enable :read_wiki
@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy
enable :create_note
enable :upload_file
enable :read_cycle_analytics
enable :award_emoji
end
# These abilities are not allowed to admins that are not members of the project,
@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
enable :create_merge_request
enable :create_merge_request_from
enable :create_wiki
enable :push_code
enable :resolve_note
@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:master_access) }.policy do
enable :delete_protected_branch
enable :push_to_delete_protected_branch
enable :update_project_snippet
enable :update_environment
enable :update_deployment
@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy
end
rule { archived }.policy do
prevent :create_merge_request
prevent :push_code
prevent :delete_protected_branch
prevent :update_merge_request
prevent :admin_merge_request
prevent :push_to_delete_protected_branch
prevent :request_access
prevent :upload_file
prevent :resolve_note
prevent :create_merge_request_from
prevent :create_merge_request_in
prevent :award_emoji
READONLY_FEATURES_WHEN_ARCHIVED.each do |feature|
prevent(*create_update_admin_destroy(feature))
end
end
rule { issues_disabled }.policy do
prevent(*create_read_update_admin_destroy(:issue))
end
rule { merge_requests_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:merge_request))
prevent :create_merge_request_in
prevent :create_merge_request_from
prevent(*create_read_update_admin_destroy(:merge_request))
end
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin(:label))
prevent(*create_read_update_admin(:milestone))
prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin_destroy(:milestone))
end
rule { snippets_disabled }.policy do
prevent(*create_read_update_admin(:project_snippet))
prevent(*create_read_update_admin_destroy(:project_snippet))
end
rule { wiki_disabled & ~has_external_wiki }.policy do
prevent(*create_read_update_admin(:wiki))
prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code)
end
rule { builds_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:build))
prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
prevent(*create_read_update_admin(:pipeline_schedule))
prevent(*create_read_update_admin(:environment))
prevent(*create_read_update_admin(:deployment))
prevent(*create_update_admin_destroy(:pipeline))
prevent(*create_read_update_admin_destroy(:build))
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
prevent(*create_read_update_admin_destroy(:environment))
prevent(*create_read_update_admin_destroy(:deployment))
end
rule { repository_disabled }.policy do
@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy
end
rule { container_registry_disabled }.policy do
prevent(*create_read_update_admin(:container_image))
prevent(*create_read_update_admin_destroy(:container_image))
end
rule { anonymous & ~public_project }.prevent_all
@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy
enable :read_pipeline_schedule
end
rule { issues_disabled }.policy do
prevent :create_issue
prevent :update_issue
prevent :admin_issue
prevent :read_issue
end
# These rules are included to allow maintainers of projects to push to certain
# to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do

View File

@ -0,0 +1,19 @@
class ProjectPolicy
module ClassMethods
def create_read_update_admin_destroy(name)
[
:"read_#{name}",
*create_update_admin_destroy(name)
]
end
def create_update_admin_destroy(name)
[
:"create_#{name}",
:"update_#{name}",
:"admin_#{name}",
:"destroy_#{name}"
]
end
end
end

View File

@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper
include MarkupHelper
include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
presents :merge_request
@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def can_revert_on_current_merge_request?
user_can_collaborate_with_project? && cached_can_be_reverted?
can_collaborate_with_project?(project) && cached_can_be_reverted?
end
def can_cherry_pick_on_current_merge_request?
user_can_collaborate_with_project? && can_be_cherry_picked?
can_collaborate_with_project?(project) && can_be_cherry_picked?
end
def can_push_to_source_branch?
@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end.sort.to_sentence
end
def user_can_collaborate_with_project?
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project)) ||
can_push_to_source_branch?
end
def user_can_fork_project?
can?(current_user, :fork_project, project)
end

View File

@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
expose :can_update do |issue|
can?(request.current_user, :update_issue, issue)
end
expose :can_award_emoji do |issue|
can?(request.current_user, :award_emoji, issue)
end
end
expose :create_note_path do |issue|

View File

@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note
expose :current_user do
expose :can_edit do |note|
Ability.can_edit_note?(request.current_user, note)
Ability.allowed?(request.current_user, :admin_note, note)
end
expose :can_award_emoji do |note|
Ability.allowed?(request.current_user, :award_emoji, note)
end
end

View File

@ -71,8 +71,8 @@ module MergeRequests
params.delete(:source_project_id)
params.delete(:target_project_id)
unless can?(current_user, :read_project, @source_project) &&
can?(current_user, :read_project, @project)
unless can?(current_user, :create_merge_request_from, @source_project) &&
can?(current_user, :create_merge_request_in, @project)
raise Gitlab::Access::AccessDeniedError
end

View File

@ -101,7 +101,7 @@
- if @project.archived?
%li
%span.light archived:
%strong repository is read-only
%strong project is read-only
%li
%span.light access:

View File

@ -3,13 +3,13 @@
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
- if current_user
- if can?(current_user, :award_emoji, awardable)
.award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': 'Add reaction',

View File

@ -19,8 +19,8 @@
%li.dropdown-bold-header GitLab
- if @project&.persisted?
- create_project_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- create_project_issue = show_new_issue_link?(@project)
- merge_project = merge_request_source_project_for_project(@project)
- create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if create_project_issue || merge_project || create_project_snippet
%li.dropdown-bold-header This project

View File

@ -13,6 +13,7 @@
#{time_ago_with_tooltip(event.created_at)}
.flex-right
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
#{ _('Create merge request') }
- if can?(current_user, :create_merge_request_in, event.project.default_merge_request_target)
.flex-right
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
#{ _('Create merge request') }

View File

@ -4,7 +4,7 @@
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind]
- number_commits_ahead = diverging_commit_counts[:ahead]
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- merge_project = merge_request_source_project_for_project(@project)
%li{ class: "branch-item js-branch-#{branch.name}" }
.branch-info
.branch-title
@ -61,7 +61,7 @@
title: s_('Branches|The default branch cannot be deleted') }
= icon("trash-o")
- elsif protected_branch?(@project, branch)
- if can?(current_user, :delete_protected_branch, @project)
- if can?(current_user, :push_to_delete_protected_branch, @project)
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: s_('Branches|Delete protected branch'),
data: { toggle: "modal",

View File

@ -1,13 +1,17 @@
- if current_user
- can_create_issue = show_new_issue_link?(@project)
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
- can_push_code = can?(current_user, :push_code, @project)
- create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
- merge_project = merge_request_source_project_for_project(@project)
- show_menu = can_create_issue || can_create_project_snippet || can_push_code || create_mr_from_new_fork || merge_project
- if show_menu
.project-action-button.dropdown.inline
%a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
= icon('plus')
= icon("caret-down")
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
- can_create_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if can_create_issue || merge_project || can_create_project_snippet
%li.dropdown-header= _('This project')
@ -20,17 +24,17 @@
- if can_create_project_snippet
%li= link_to _('New snippet'), new_project_snippet_path(@project)
- if can?(current_user, :push_code, @project)
- if can_push_code
%li.dropdown-header= _('This repository')
- if can?(current_user, :push_code, @project)
- if can_push_code
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo?
%li= link_to _('New branch'), new_project_branch_path(@project)
%li= link_to _('New tag'), new_project_tag_path(@project)
- elsif current_user && current_user.already_forked?(@project)
- elsif can_collaborate_with_project?(@project)
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- elsif can?(current_user, :fork_project, @project)
- elsif create_mr_from_new_fork
- continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }

View File

@ -7,5 +7,6 @@
- link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
.text-center
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
- if can?(current_user, :create_cluster, @project)
.text-center
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'

View File

@ -1,3 +1,5 @@
- can_collaborate = can_collaborate_with_project?(@project)
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
.header-main-content
= render partial: 'signature', object: @commit.signature
@ -32,12 +34,13 @@
%li.visible-xs-block.visible-sm-block
= link_to project_tree_path(@project, @commit) do
#{ _('Browse Files') }
- unless @commit.has_been_reverted?(current_user)
- if can_collaborate && !@commit.has_been_reverted?(current_user)
%li.clearfix
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
%li.clearfix
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can_collaborate_with_project?
- if can_collaborate
%li.clearfix
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can?(current_user, :push_code, @project)
%li.clearfix
= link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
%li.divider

View File

@ -17,6 +17,6 @@
.limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true
- if can_collaborate_with_project?
- if can_collaborate_with_project?(@project)
- %w(revert cherry-pick).each do |type|
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title

View File

@ -114,17 +114,18 @@
Archive project
- if @project.archived?
%p
Unarchiving the project will mark its repository as active. The project can be committed to.
Unarchiving the project will restore people's ability to make changes to it.
The repository can be committed to, and issues, comments and other entities can be created.
%strong Once active this project shows up in the search and on the dashboard.
= link_to 'Unarchive project', unarchive_project_path(@project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
data: { confirm: "Are you sure that you want to unarchive this project?" },
method: :post, class: "btn btn-success"
- else
%p
Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
%strong Archived projects cannot be committed to!
Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
%strong The repository cannot be committed to, and no issues, comments or other entities can be created.
= link_to 'Archive project', archive_project_path(@project),
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
data: { confirm: "Are you sure that you want to archive this project?" },
method: :post, class: "btn btn-warning"
.sub-section.rename-respository
%h4.warning-title

View File

@ -2,9 +2,10 @@
= icon('rss')
- if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
= link_to "New issue", new_project_issue_path(@project,
issue: { assignee_id: finder.assignee.try(:id),
milestone_id: finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New issue",
id: "new_issue_link"
- if show_new_issue_link?(@project)
= link_to "New issue", new_project_issue_path(@project,
issue: { assignee_id: finder.assignee.try(:id),
milestone_id: finder.milestones.first.try(:id) }),
class: "btn btn-new",
title: "New issue",
id: "new_issue_link"

View File

@ -1,8 +1,8 @@
- can_create_merge_request = can?(current_user, :create_merge_request, @project)
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
- if can?(current_user, :push_code, @project)
- can_create_merge_request = can?(current_user, :create_merge_request_in, @project)
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
- can_create_path = can_create_branch_project_issue_path(@project, @issue)
- create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)

View File

@ -7,6 +7,7 @@
- can_update_issue = can?(current_user, :update_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
.detail-page-header
.detail-page-header-body
@ -42,16 +43,18 @@
%li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- if can_report_spam
%li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can_update_issue || can_report_spam
%li.divider
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_create_issue
- if can_update_issue || can_report_spam
%li.divider
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
- if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- if can_create_issue
= link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
.issue-details.issuable-details
.detail-page-description.content-block

View File

@ -1,6 +1,6 @@
- @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- merge_project = merge_request_source_project_for_project(@project)
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests"

View File

@ -36,7 +36,7 @@
%template{ 'v-else' => '' }
= render 'shared/icons/icon_resolve_discussion.svg'
- if current_user
- if can?(current_user, :award_emoji, note)
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
.note-actions-item

View File

@ -24,7 +24,7 @@
.text-warning.center.prepend-top-20
%p
= icon("exclamation-triangle fw")
#{ _('Archived project! Repository is read-only') }
#{ _('Archived project! Repository and other project resources are read-only') }
- view_path = @project.default_view

View File

@ -31,6 +31,6 @@
= link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
= link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
= icon("trash-o")
- if can?(current_user, :admin_project, @project)
= link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
= icon("trash-o")

View File

@ -28,7 +28,7 @@
= icon('history')
.btn-container.controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
- if can?(current_user, :push_code, @project) && can?(current_user, :admin_project, @project)
.btn-container.controls-item-full
= link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do
%i.fa.fa-trash-o

View File

@ -1,3 +1,6 @@
- can_collaborate = can_collaborate_with_project?(@project)
- can_create_mr_from_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
.tree-ref-container
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
@ -15,7 +18,7 @@
%li
= link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
- if current_user
- if can_collaborate || can_create_mr_from_fork
%li
%a.btn.add-to-tree{ addtotree_toggle_attributes }
= sprite_icon('plus', size: 16, css_class: 'pull-left')
@ -35,7 +38,7 @@
%li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
#{ _('New directory') }
- elsif can?(current_user, :fork_project, @project)
- elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
%li
- continue_params = { to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice,
@ -61,23 +64,25 @@
= link_to fork_path, method: :post do
#{ _('New directory') }
%li.divider
%li.dropdown-header
#{ _('This repository') }
%li
= link_to new_project_branch_path(@project) do
#{ _('New branch') }
%li
= link_to new_project_tag_path(@project) do
#{ _('New tag') }
- if can?(current_user, :push_code, @project)
%li.divider
%li.dropdown-header
#{ _('This repository') }
%li
= link_to new_project_branch_path(@project) do
#{ _('New branch') }
%li
= link_to new_project_tag_path(@project) do
#{ _('New tag') }
.tree-controls
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link'
= succeed " " do
= link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do
= _('Web IDE')
- if can_collaborate
= succeed " " do
= link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do
= _('Web IDE')
= render 'projects/buttons/download', project: @project, ref: @ref

View File

@ -47,20 +47,20 @@
class: 'text-danger'
.pull-right.hidden-xs.hidden-sm
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
%button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'),
disabled: true,
type: 'button',
data: { url: promote_project_label_path(label.project, label),
label_title: label.title,
label_color: label.color,
label_text_color: label.text_color,
group_name: label.project.group.name,
target: '#promote-label-modal',
container: 'body',
toggle: 'modal' } }
= sprite_icon('level-up')
- if can?(current_user, :admin_label, label)
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
%button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'),
disabled: true,
type: 'button',
data: { url: promote_project_label_path(label.project, label),
label_title: label.title,
label_color: label.color,
label_text_color: label.text_color,
group_name: label.project.group.name,
target: '#promote-label-modal',
container: 'body',
toggle: 'modal' } }
= sprite_icon('level-up')
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%span.sr-only Edit
= sprite_icon('pencil')

View File

@ -72,7 +72,7 @@
.title.hide-collapsed
Issues
%span.badge= milestone.issues_visible_to_user(current_user).count
- if project && can?(current_user, :create_issue, project)
- if show_new_issue_link?(project)
= link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
New issue
.value.hide-collapsed.bold

View File

@ -2,7 +2,7 @@
- return if note.cross_reference_not_visible_for?(current_user)
- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
- note_editable = note_editable?(note)
- note_editable = can?(current_user, :admin_note, note)
- note_counter = local_assigns.fetch(:note_counter, 0)
%li.timeline-entry{ id: dom_id(note),

View File

@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do
# Limit the number of merge requests per project to avoid long seeds
MAX_NUM_MERGE_REQUESTS = 10
Project.all.reject(&:empty_repo?).each do |project|
Project.non_archived.with_merge_requests_enabled.reject(&:empty_repo?).each do |project|
branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2)
branches.each do |branch_name|
@ -21,7 +21,11 @@ Gitlab::Seeder.quiet do
assignee: project.team.users.sample
}
MergeRequests::CreateService.new(project, project.team.users.sample, params).execute
# Only create MRs with users that are allowed to create MRs
developer = project.team.developers.sample
break unless developer
MergeRequests::CreateService.new(project, developer, params).execute
print '.'
end
end

View File

@ -57,15 +57,20 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project.
NOTE: **Note:**
Only project Owners and Admin users have the [permissions] to archive a project.
An archived project will be hidden by default in the project listings.
Archiving a project makes it read-only for all users and indicates that it is
no longer actively maintained. Projects that have been archived can also be
unarchived.
When a project is archived, the repository, issues, merge requests and all
other features are read-only. Archived projects are also hidden
in project listings.
To archive a project:
1. Navigate to your project's **Settings > General > Advanced settings**.
1. Under "Archive project", hit the **Archive project** button.
1. In the Archive project section, click the **Archive project** button.
1. Confirm the action when asked to.
An archived project can be fully restored and will therefore retain its
repository and all associated resources whilst in an archived state.
#### Renaming a repository
NOTE: **Note:**

View File

@ -189,7 +189,7 @@ module API
post ":id/merge_requests" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316')
authorize! :create_merge_request, user_project
authorize! :create_merge_request_from, user_project
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)

View File

@ -93,7 +93,7 @@ module API
post ":id/merge_requests" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126')
authorize! :create_merge_request, user_project
authorize! :create_merge_request_from, user_project
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?

View File

@ -23,7 +23,8 @@ module Gitlab
def execute
raise ProjectNotFound unless project
validate_permission!(:create_merge_request)
validate_permission!(:create_merge_request_in)
validate_permission!(:create_merge_request_from)
verify_record!(
record: create_merge_request,

View File

@ -51,7 +51,7 @@ module Gitlab
return false unless can_access_git?
if protected?(ProtectedBranch, project, ref)
user.can?(:delete_protected_branch, project)
user.can?(:push_to_delete_protected_branch, project)
else
user.can?(:push_code, project)
end

View File

@ -0,0 +1,55 @@
require 'spec_helper'
describe ChecksCollaboration do
include ProjectForksHelper
let(:helper) do
fake_class = Class.new(ApplicationController) do
include ChecksCollaboration
end
fake_class.new
end
describe '#can_collaborate_with_project?' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?) do |user, ability, subject|
Ability.allowed?(user, ability, subject)
end
end
it 'is true if the user can push to the project' do
project.add_developer(user)
expect(helper.can_collaborate_with_project?(project)).to be_truthy
end
it 'is true when the user can push to a branch of the project' do
fake_access = double('Gitlab::UserAccess')
expect(fake_access).to receive(:can_push_to_branch?).with('a-branch').and_return(true)
expect(Gitlab::UserAccess).to receive(:new).with(user, project: project).and_return(fake_access)
expect(helper.can_collaborate_with_project?(project, ref: 'a-branch')).to be_truthy
end
context 'when the user has forked the project' do
before do
fork_project(project, user, namespace: user.namespace)
end
it 'is true' do
expect(helper.can_collaborate_with_project?(project)).to be_truthy
end
it 'is false when the project is archived' do
project.archived = true
expect(helper.can_collaborate_with_project?(project)).to be_falsy
end
end
end
end

View File

@ -938,7 +938,7 @@ describe Projects::IssuesController do
end
describe 'POST create_merge_request' do
let(:project) { create(:project, :repository) }
let(:project) { create(:project, :repository, :public) }
before do
project.add_developer(user)
@ -955,6 +955,22 @@ describe Projects::IssuesController do
expect(response).to match_response_schema('merge_request')
end
it 'is not available when the project is archived' do
project.update!(archived: true)
create_merge_request
expect(response).to have_gitlab_http_status(404)
end
it 'is not available for users who cannot create merge requests' do
sign_in(create(:user))
create_merge_request
expect(response).to have_gitlab_http_status(404)
end
def create_merge_request
post :create_merge_request, namespace_id: project.namespace.to_param,
project_id: project.to_param,

View File

@ -4,6 +4,10 @@ FactoryBot.define do
user
awardable factory: :issue
after(:create) do |award, evaluator|
award.awardable.project.add_guest(evaluator.user)
end
trait :upvote
trait :downvote do
name "thumbsdown"

View File

@ -45,7 +45,7 @@ feature 'Admin Broadcast Messages' do
page.within('.broadcast-message-preview') do
expect(page).to have_selector('strong', text: 'Markdown')
expect(page).to have_selector('gl-emoji[data-name="tada"]')
expect(page).to have_emoji('tada')
end
end
end

View File

@ -98,7 +98,7 @@ feature 'Group show page' do
it 'shows the project info' do
expect(page).to have_content(project.title)
expect(page).to have_selector('gl-emoji[data-name="smile"]')
expect(page).to have_emoji('smile')
end
end
end

View File

@ -35,6 +35,14 @@ describe 'Merge request > User awards emoji', :js do
expect(page).to have_selector('.emoji-menu', count: 1)
end
describe 'the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
it 'does not see award menu button' do
expect(page).not_to have_selector('.js-award-holder')
end
end
end
describe 'logged out' do

View File

@ -40,6 +40,14 @@ describe 'Merge request > User cherry-picks', :js do
expect(page).to have_link 'Cherry-pick'
end
it 'hides the cherry pick button for an archived project' do
project.update!(archived: true)
visit project_merge_request_path(project, merge_request)
expect(page).not_to have_link 'Cherry-pick'
end
end
end
end

View File

@ -99,6 +99,74 @@ describe 'User interacts with awards in an issue', :js do
click_button('Comment')
end
expect(page).to have_selector('gl-emoji[data-name="smile"]')
expect(page).to have_emoji('smile')
end
context 'when a project is archived' do
let(:project) { create(:project, :archived) }
it 'hides the add award button' do
page.within('.awards') do
expect(page).not_to have_css('.js-add-award')
end
end
end
context 'awards on a note' do
let!(:note) { create(:note, noteable: issue, project: issue.project) }
let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') }
it 'shows the award on the note' do
page.within('.note-awards') do
expect(page).to have_emoji('100')
end
end
it 'allows adding a vote to an award' do
page.within('.note-awards') do
find('gl-emoji[data-name="100"]').click
end
wait_for_requests
expect(note.reload.award_emoji.size).to eq(2)
end
it 'allows adding a new emoji' do
page.within('.note-actions') do
find('a.js-add-award').click
end
page.within('.emoji-menu-content') do
find('gl-emoji[data-name="8ball"]').click
end
wait_for_requests
page.within('.note-awards') do
expect(page).to have_emoji('8ball')
end
expect(note.reload.award_emoji.size).to eq(2)
end
context 'when the project is archived' do
let(:project) { create(:project, :archived) }
it 'hides the buttons for adding new emoji' do
page.within('.note-awards') do
expect(page).not_to have_css('.award-menu-holder')
end
page.within('.note-actions') do
expect(page).not_to have_css('a.js-add-award')
end
end
it 'does not allow toggling existing emoji' do
page.within('.note-awards') do
find('gl-emoji[data-name="100"]').click
end
wait_for_requests
expect(note.reload.award_emoji.size).to eq(1)
end
end
end
end

View File

@ -195,6 +195,26 @@ describe 'Branches' do
expect(page).to have_content("Protected branches can be managed in project settings")
end
end
it 'shows the merge request button' do
visit project_branches_path(project)
page.within first('.all-branches li') do
expect(page).to have_content 'Merge request'
end
end
context 'when the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
it 'does not show the merge request button when the project is archived' do
visit project_branches_path(project)
page.within first('.all-branches li') do
expect(page).not_to have_content 'Merge request'
end
end
end
end
context 'logged out' do
@ -204,7 +224,7 @@ describe 'Branches' do
it 'does not show merge request button' do
page.within first('.all-branches li') do
expect(page).not_to have_content 'Merge Request'
expect(page).not_to have_content 'Merge request'
end
end
end

View File

@ -89,4 +89,15 @@ describe 'Cherry-pick Commits' do
expect(page).to have_content('The commit has been successfully cherry-picked.')
end
end
context 'when the project is archived' do
let(:project) { create(:project, :repository, :archived, namespace: group) }
it 'does not show the cherry-pick link' do
find('.header-action-buttons a.dropdown-toggle').click
expect(page).not_to have_text("Cherry-pick")
expect(page).not_to have_css("a[href='#modal-cherry-pick-commit']")
end
end
end

View File

@ -10,13 +10,16 @@ describe 'User reverts a commit', :js do
sign_in(user)
visit(project_commit_path(project, sample_commit.id))
end
def click_revert
find('.header-action-buttons .dropdown').click
find('a[href="#modal-revert-commit"]').click
end
context 'without creating a new merge request' do
before do
click_revert
page.within('#modal-revert-commit') do
uncheck('create_merge_request')
click_button('Revert')
@ -44,6 +47,10 @@ describe 'User reverts a commit', :js do
end
context 'with creating a new merge request' do
before do
click_revert
end
it 'reverts a commit' do
page.within('#modal-revert-commit') do
click_button('Revert')
@ -53,4 +60,14 @@ describe 'User reverts a commit', :js do
expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master")
end
end
context 'when the project is archived' do
let(:project) { create(:project, :repository, :archived, namespace: user.namespace) }
it 'does not show the revert link' do
find('.header-action-buttons .dropdown').click
expect(page).not_to have_link('Revert')
end
end
end

View File

@ -12,6 +12,23 @@ describe 'Projects > Files > User edits files' do
sign_in(user)
end
shared_examples 'unavailable for an archived project' do
it 'does not show the edit link for an archived project', :js do
project.update!(archived: true)
visit project_tree_path(project, project.repository.root_ref)
click_link('.gitignore')
aggregate_failures 'available edit buttons' do
expect(page).not_to have_text('Edit')
expect(page).not_to have_text('Web IDE')
expect(page).not_to have_text('Replace')
expect(page).not_to have_text('Delete')
end
end
end
context 'when an user has write access' do
before do
project.add_master(user)
@ -85,6 +102,8 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_css('.line_holder.new')
end
it_behaves_like 'unavailable for an archived project'
end
context 'when an user does not have write access' do
@ -168,6 +187,10 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_content("From #{forked_project.full_path}")
expect(page).to have_content("into #{project2.full_path}")
end
it_behaves_like 'unavailable for an archived project' do
let(:project) { project2 }
end
end
end
end

View File

@ -6,11 +6,27 @@ describe "User views issue" do
set(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
before do
project.add_guest(user)
project.add_developer(user)
sign_in(user)
visit(project_issue_path(project, issue))
end
it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
it 'shows the merge request and issue actions', :aggregate_failures do
expect(page).to have_link('New issue')
expect(page).to have_button('Create merge request')
expect(page).to have_link('Close issue')
end
context 'when the project is archived' do
let(:project) { create(:project, :public, :archived) }
it 'hides the merge request and issue actions', :aggregate_failures do
expect(page).not_to have_link('New issue')
expect(page).not_to have_button('Create merge request')
expect(page).not_to have_link('Close issue')
end
end
end

View File

@ -45,6 +45,18 @@ feature 'Merge Request button' do
end
end
end
context 'when the project is archived' do
it 'hides the link' do
project.update!(archived: true)
visit url
within("#content-body") do
expect(page).not_to have_link(label)
end
end
end
end
context 'logged in as non-member' do

View File

@ -56,4 +56,12 @@ describe 'User reverts a merge request', :js do
expect(page).to have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
end
it 'cannot revert a merge requests for an archived project' do
project.update!(archived: true)
visit(merge_request_path(merge_request))
expect(page).not_to have_link('Revert')
end
end

View File

@ -94,6 +94,18 @@ describe 'User views open merge requests' do
end
include_examples 'shows merge requests'
it 'shows the new merge request button' do
expect(page).to have_link('New merge request')
end
context 'when the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
it 'hides the new merge request button' do
expect(page).not_to have_link('New merge request')
end
end
end
end

View File

@ -0,0 +1,87 @@
require 'spec_helper'
describe 'Projects > Show > Collaboration links' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
project.add_developer(user)
sign_in(user)
end
it 'shows all the expected links' do
visit project_path(project)
# The navigation bar
page.within('.header-new') do
aggregate_failures 'dropdown links in the navigation bar' do
expect(page).to have_link('New issue')
expect(page).to have_link('New merge request')
expect(page).to have_link('New snippet', href: new_project_snippet_path(project))
end
end
# The project header
page.within('.project-home-panel') do
aggregate_failures 'dropdown links in the project home panel' do
expect(page).to have_link('New issue')
expect(page).to have_link('New merge request')
expect(page).to have_link('New snippet')
expect(page).to have_link('New file')
expect(page).to have_link('New branch')
expect(page).to have_link('New tag')
end
end
# The dropdown above the tree
page.within('.repo-breadcrumb') do
aggregate_failures 'dropdown links above the repo tree' do
expect(page).to have_link('New file')
expect(page).to have_link('Upload file')
expect(page).to have_link('New directory')
expect(page).to have_link('New branch')
expect(page).to have_link('New tag')
end
end
# The Web IDE
expect(page).to have_link('Web IDE')
end
it 'hides the links when the project is archived' do
project.update!(archived: true)
visit project_path(project)
page.within('.header-new') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New issue')
expect(page).not_to have_link('New merge request')
expect(page).not_to have_link('New snippet', href: new_project_snippet_path(project))
end
end
page.within('.project-home-panel') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New issue')
expect(page).not_to have_link('New merge request')
expect(page).not_to have_link('New snippet')
expect(page).not_to have_link('New file')
expect(page).not_to have_link('New branch')
expect(page).not_to have_link('New tag')
end
end
page.within('.repo-breadcrumb') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New file')
expect(page).not_to have_link('Upload file')
expect(page).not_to have_link('New directory')
expect(page).not_to have_link('New branch')
expect(page).not_to have_link('New tag')
end
end
expect(page).not_to have_link('Web IDE')
end
end

View File

@ -19,6 +19,12 @@ describe MergeRequestTargetProjectFinder do
expect(finder.execute).to contain_exactly(forked_project)
end
it 'does not contain archived projects' do
base_project.update!(archived: true)
expect(finder.execute).to contain_exactly(other_fork, forked_project)
end
end
context 'public projects' do

View File

@ -96,13 +96,32 @@ describe IssuesHelper do
describe '#award_state_class' do
let!(:upvote) { create(:award_emoji) }
let(:awardable) { upvote.awardable }
let(:user) { upvote.user }
before do
allow(helper).to receive(:can?) do |*args|
Ability.allowed?(*args)
end
end
it "returns disabled string for unauthenticated user" do
expect(award_state_class(AwardEmoji.all, nil)).to eq("disabled")
expect(helper.award_state_class(awardable, AwardEmoji.all, nil)).to eq("disabled")
end
it "returns disabled for a user that does not have access to the awardable" do
expect(helper.award_state_class(awardable, AwardEmoji.all, build(:user))).to eq("disabled")
end
it "returns active string for author" do
expect(award_state_class(AwardEmoji.all, upvote.user)).to eq("active")
expect(helper.award_state_class(awardable, AwardEmoji.all, upvote.user)).to eq("active")
end
it "is blank for a user that has access to the awardable" do
user = build(:user)
expect(helper).to receive(:can?).with(user, :award_emoji, awardable).and_return(true)
expect(helper.award_state_class(awardable, AwardEmoji.all, user)).to be_blank
end
end
@ -144,4 +163,26 @@ describe IssuesHelper do
end
end
end
describe '#show_new_issue_link?' do
before do
allow(helper).to receive(:current_user)
end
it 'is false when no project there is no project' do
expect(helper.show_new_issue_link?(nil)).to be_falsey
end
it 'is true when there is a project and no logged in user' do
expect(helper.show_new_issue_link?(build(:project))).to be_truthy
end
it 'is true when the current user does not have access to the project' do
project = build(:project)
allow(helper).to receive(:current_user).and_return(project.owner)
expect(helper).to receive(:can?).with(project.owner, :create_issue, project).and_return(true)
expect(helper.show_new_issue_link?(project)).to be_truthy
end
end
end

View File

@ -3,7 +3,7 @@ import store from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data';
describe('issse_note_actions component', () => {
describe('issue_note_actions component', () => {
let vm;
let Component;
@ -24,6 +24,7 @@ describe('issse_note_actions component', () => {
authorId: 26,
canDelete: true,
canEdit: true,
canAwardEmoji: true,
canReportAsAbuse: true,
noteId: 539,
reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
@ -70,6 +71,7 @@ describe('issse_note_actions component', () => {
authorId: 26,
canDelete: false,
canEdit: false,
canAwardEmoji: false,
canReportAsAbuse: false,
noteId: 539,
reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',

View File

@ -29,6 +29,7 @@ describe('note_awards_list component', () => {
awards: awardsMock,
noteAuthorId: 2,
noteId: 545,
canAwardEmoji: true,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
},
}).$mount();
@ -43,14 +44,45 @@ describe('note_awards_list component', () => {
expect(vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]')).toBeDefined();
});
it('should be possible to remove awareded emoji', () => {
it('should be possible to remove awarded emoji', () => {
spyOn(vm, 'handleAward').and.callThrough();
spyOn(vm, 'toggleAwardRequest').and.callThrough();
vm.$el.querySelector('.js-awards-block button').click();
expect(vm.handleAward).toHaveBeenCalledWith('flag_tz');
expect(vm.toggleAwardRequest).toHaveBeenCalled();
});
it('should be possible to add new emoji', () => {
expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
});
describe('when the user cannot award emoji', () => {
beforeEach(() => {
const Component = Vue.extend(awardsNote);
vm = new Component({
store,
propsData: {
awards: awardsMock,
noteAuthorId: 2,
noteId: 545,
canAwardEmoji: false,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
},
}).$mount();
});
it('should not be possible to remove awarded emoji', () => {
spyOn(vm, 'toggleAwardRequest').and.callThrough();
vm.$el.querySelector('.js-awards-block button').click();
expect(vm.toggleAwardRequest).not.toHaveBeenCalled();
});
it('should not be possible to add new emoji', () => {
expect(vm.$el.querySelector('.js-add-award')).toBeNull();
});
});
});

View File

@ -18,6 +18,7 @@ describe('issue_note_body component', () => {
propsData: {
note,
canEdit: true,
canAwardEmoji: true,
},
}).$mount();
});

View File

@ -9,6 +9,7 @@ export const notesDataMock = {
totalNotes: 1,
closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close',
reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen',
canAwardEmoji: true,
};
export const userDataMock = {
@ -30,6 +31,7 @@ export const noteableDataMock = {
current_user: {
can_create_note: true,
can_update: true,
can_award_emoji: true,
},
description: '',
due_date: null,
@ -86,7 +88,10 @@ export const individualNote = {
human_access: 'Owner',
note: 'sdfdsaf',
note_html: "<p dir='auto'>sdfdsaf</p>",
current_user: { can_edit: true },
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
emoji_awardable: true,
award_emoji: [
@ -129,6 +134,7 @@ export const note = {
note_html: '<p dir="auto">Vel id placeat reprehenderit sit numquam.</p>',
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: 'd3842a451b7f3d9a5dfce329515127b2d29a4cd0',
emoji_awardable: true,
@ -187,6 +193,7 @@ export const discussionMock = {
note_html: "<p dir='auto'>THIS IS A DICUSSSION!</p>",
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
@ -231,6 +238,7 @@ export const discussionMock = {
},
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
@ -275,6 +283,7 @@ export const discussionMock = {
},
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
@ -365,6 +374,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003esdfdsaf\u003c/p\u003e',
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
emoji_awardable: true,
@ -425,6 +435,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003eNew note!\u003c/p\u003e',
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
emoji_awardable: true,
@ -478,6 +489,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
},
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true,
@ -527,6 +539,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true,

View File

@ -7,62 +7,6 @@ describe Ability do
end
end
describe '.can_edit_note?' do
let(:project) { create(:project) }
let(:note) { create(:note_on_issue, project: project) }
context 'using an anonymous user' do
it 'returns false' do
expect(described_class.can_edit_note?(nil, note)).to be_falsy
end
end
context 'using a system note' do
it 'returns false' do
system_note = create(:note, system: true)
user = create(:user)
expect(described_class.can_edit_note?(user, system_note)).to be_falsy
end
end
context 'using users with different access levels' do
let(:user) { create(:user) }
it 'returns true for the author' do
expect(described_class.can_edit_note?(note.author, note)).to be_truthy
end
it 'returns false for a guest user' do
project.add_guest(user)
expect(described_class.can_edit_note?(user, note)).to be_falsy
end
it 'returns false for a developer' do
project.add_developer(user)
expect(described_class.can_edit_note?(user, note)).to be_falsy
end
it 'returns true for a master' do
project.add_master(user)
expect(described_class.can_edit_note?(user, note)).to be_truthy
end
it 'returns true for a group owner' do
group = create(:group)
project.project_group_links.create(
group: group,
group_access: Gitlab::Access::MASTER)
group.add_owner(user)
expect(described_class.can_edit_note?(user, note)).to be_truthy
end
end
end
describe '.users_that_can_read_project' do
context 'using a public project' do
it 'returns all the users' do

View File

@ -46,6 +46,31 @@ describe Awardable do
end
end
describe '#user_can_award?' do
let(:user) { create(:user) }
before do
issue.project.add_guest(user)
end
it 'does not allow upvoting or downvoting your own issue' do
issue.update!(author: user)
expect(issue.user_can_award?(user, AwardEmoji::DOWNVOTE_NAME)).to be_falsy
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
end
it 'is truthy when the user is allowed to award emoji' do
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_truthy
end
it 'is falsy when the project is archived' do
issue.project.update!(archived: true)
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
end
end
describe "#toggle_award_emoji" do
it "adds an emoji if it isn't awarded yet" do
expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1)

View File

@ -18,7 +18,6 @@ describe NotePolicy, mdoels: true do
context 'when the project is public' do
context 'when the note author is not a project member' do
it 'can edit a note' do
expect(policies).to be_allowed(:update_note)
expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note)
@ -29,7 +28,6 @@ describe NotePolicy, mdoels: true do
it 'can edit note' do
policies = policies(create(:project_snippet, project: project))
expect(policies).to be_allowed(:update_note)
expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note)
@ -47,7 +45,6 @@ describe NotePolicy, mdoels: true do
end
it 'can edit a note' do
expect(policies).to be_allowed(:update_note)
expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note)
@ -56,7 +53,6 @@ describe NotePolicy, mdoels: true do
context 'when the note author is not a project member' do
it 'can not edit a note' do
expect(policies).to be_disallowed(:update_note)
expect(policies).to be_disallowed(:admin_note)
expect(policies).to be_disallowed(:resolve_note)
end

View File

@ -27,6 +27,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -37,6 +38,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -47,6 +49,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
end
@ -61,6 +64,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -71,6 +75,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -81,6 +86,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -91,6 +97,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
end
@ -105,6 +112,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -115,6 +123,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -125,6 +134,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@ -135,6 +145,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
end

View File

@ -14,7 +14,8 @@ describe ProjectPolicy do
read_project read_board read_list read_wiki read_issue
read_project_for_iids read_issue_iid read_merge_request_iid read_label
read_milestone read_project_snippet read_project_member read_note
create_project create_issue create_note upload_file
create_project create_issue create_note upload_file create_merge_request_in
award_emoji
]
end
@ -35,7 +36,7 @@ describe ProjectPolicy do
%i[
admin_milestone admin_merge_request update_merge_request create_commit_status
update_commit_status create_build update_build create_pipeline
update_pipeline create_merge_request create_wiki push_code
update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image
create_environment create_deployment
]
@ -43,7 +44,7 @@ describe ProjectPolicy do
let(:base_master_permissions) do
%i[
delete_protected_branch update_project_snippet update_environment
push_to_delete_protected_branch update_project_snippet update_environment
update_deployment admin_project_snippet
admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
@ -136,13 +137,66 @@ describe ProjectPolicy do
end
end
context 'merge requests feature' do
subject { described_class.new(owner, project) }
it 'disallows all permissions when the feature is disabled' do
project.project_feature.update(merge_requests_access_level: ProjectFeature::DISABLED)
mr_permissions = [:create_merge_request_from, :read_merge_request,
:update_merge_request, :admin_merge_request,
:create_merge_request_in]
expect_disallowed(*mr_permissions)
end
end
shared_examples 'archived project policies' do
let(:feature_write_abilities) do
described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
described_class.create_update_admin_destroy(feature)
end
end
let(:other_write_abilities) do
%i[
create_merge_request_in
create_merge_request_from
push_to_delete_protected_branch
push_code
request_access
upload_file
resolve_note
award_emoji
]
end
context 'when the project is archived' do
before do
project.archived = true
end
it 'disables write actions on all relevant project features' do
expect_disallowed(*feature_write_abilities)
end
it 'disables some other important write actions' do
expect_disallowed(*other_write_abilities)
end
it 'does not disable other other abilities' do
expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
end
end
end
shared_examples 'project policies as anonymous' do
context 'abilities for public projects' do
context 'when a project has pending invites' do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
let(:anonymous_permissions) { guest_permissions - user_permissions }
let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
let(:anonymous_permissions) { guest_permissions - user_permissions }
subject { described_class.new(nil, project) }
@ -154,6 +208,10 @@ describe ProjectPolicy do
expect_allowed(*anonymous_permissions)
expect_disallowed(*user_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { anonymous_permissions }
end
end
end
@ -184,6 +242,10 @@ describe ProjectPolicy do
expect_disallowed(*owner_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { guest_permissions }
end
context 'public builds enabled' do
it do
expect_allowed(*guest_permissions)
@ -224,12 +286,15 @@ describe ProjectPolicy do
it do
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*team_member_reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
expect_disallowed(*owner_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { reporter_permissions }
end
end
end
@ -247,6 +312,10 @@ describe ProjectPolicy do
expect_disallowed(*master_permissions)
expect_disallowed(*owner_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { developer_permissions }
end
end
end
@ -264,6 +333,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions)
expect_disallowed(*owner_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { master_permissions }
end
end
end
@ -281,6 +354,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions)
expect_allowed(*owner_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { owner_permissions }
end
end
end
@ -298,6 +375,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions)
expect_allowed(*owner_permissions)
end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { owner_permissions }
end
end
end

View File

@ -861,7 +861,7 @@ describe API::MergeRequests do
expect(json_response['title']).to eq('Test merge_request')
end
it 'returns 422 when target project has disabled merge requests' do
it 'returns 403 when target project has disabled merge requests' do
project.project_feature.update(merge_requests_access_level: 0)
post api("/projects/#{forked_project.id}/merge_requests", user2),
@ -871,7 +871,7 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
expect(response).to have_gitlab_http_status(422)
expect(response).to have_gitlab_http_status(403)
end
it "returns 400 when source_branch is missing" do

View File

@ -340,7 +340,7 @@ describe API::MergeRequests do
expect(json_response['title']).to eq('Test merge_request')
end
it "returns 422 when target project has disabled merge requests" do
it "returns 403 when target project has disabled merge requests" do
project.project_feature.update(merge_requests_access_level: 0)
post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
@ -350,7 +350,7 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
expect(response).to have_gitlab_http_status(422)
expect(response).to have_gitlab_http_status(403)
end
it "returns 400 when source_branch is missing" do

View File

@ -1,6 +1,8 @@
require 'spec_helper'
describe MergeRequests::CreateService do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:assignee) { create(:user) }
@ -300,7 +302,7 @@ describe MergeRequests::CreateService do
end
context 'when source and target projects are different' do
let(:target_project) { create(:project) }
let(:target_project) { fork_project(project, nil, repository: true) }
let(:opts) do
{
@ -334,6 +336,26 @@ describe MergeRequests::CreateService do
.to raise_error Gitlab::Access::AccessDeniedError
end
end
context 'when the user has access to both projects' do
before do
target_project.add_developer(user)
project.add_developer(user)
end
it 'creates the merge request' do
merge_request = described_class.new(project, user, opts).execute
expect(merge_request).to be_persisted
end
it 'does not create the merge request when the target project is archived' do
target_project.update!(archived: true)
expect { described_class.new(project, user, opts).execute }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
end
context 'when user sets source project id' do

View File

@ -0,0 +1,5 @@
RSpec::Matchers.define :have_emoji do |emoji_name|
match do |actual|
expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
end
end

View File

@ -8,7 +8,8 @@ describe 'projects/buttons/_dropdown' do
assign(:project, project)
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:can?).with(user, :push_code, project).and_return(true)
allow(view).to receive(:can_collaborate_with_project?).and_return(true)
end
context 'empty repository' do

View File

@ -7,6 +7,7 @@ describe 'projects/commit/_commit_box.html.haml' do
before do
assign(:project, project)
assign(:commit, project.commit)
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can_collaborate_with_project?).and_return(false)
end
@ -47,7 +48,8 @@ describe 'projects/commit/_commit_box.html.haml' do
context 'viewing a commit' do
context 'as a developer' do
before do
expect(view).to receive(:can_collaborate_with_project?).and_return(true)
project.add_developer(user)
allow(view).to receive(:can_collaborate_with_project?).and_return(true)
end
it 'has a link to create a new tag' do
@ -58,10 +60,6 @@ describe 'projects/commit/_commit_box.html.haml' do
end
context 'as a non-developer' do
before do
expect(view).to receive(:can_collaborate_with_project?).and_return(false)
end
it 'does not have a link to create a new tag' do
render