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:
commit
f5a3d8f75d
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { anonymous }.prevent :comment_personal_snippet
|
||||
|
||||
rule { can?(:comment_personal_snippet) }.enable :award_emoji
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') }
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ describe('issue_note_body component', () => {
|
|||
propsData: {
|
||||
note,
|
||||
canEdit: true,
|
||||
canAwardEmoji: true,
|
||||
},
|
||||
}).$mount();
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue