New upstream version 11.8.2
This commit is contained in:
parent
0329642ba5
commit
5595a2eca7
129 changed files with 2040 additions and 687 deletions
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -2,6 +2,48 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 11.8.2 (2019-03-13)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Fixed ability to see private groups by users not belonging to given group.
|
||||
|
||||
### Fixed (5 changes)
|
||||
|
||||
- Fix import_jid error on project import. !25239
|
||||
- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
|
||||
- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
|
||||
- Fix method to mark a project repository as writable. !25546
|
||||
- Allow project members to see private group if the project is in the group namespace.
|
||||
|
||||
|
||||
## 11.8.1 (2019-02-28)
|
||||
|
||||
### Security (21 changes)
|
||||
|
||||
- Stop linking to unrecognized package sources. !55518
|
||||
- Don't allow non-members to see private related MRs.
|
||||
- Do not display impersonated sessions under active sessions and remove ability to revoke session.
|
||||
- Display only information visible to current user on the Milestone page.
|
||||
- Show only merge requests visible to user on milestone detail page.
|
||||
- Disable issue boards API when issues are disabled.
|
||||
- Don't show new issue link after move when a user does not have permissions.
|
||||
- Fix git clone revealing private repo's presence.
|
||||
- Fix blind SSRF in Prometheus integration by checking URL before querying.
|
||||
- Check snippet attached file to be moved is within designated directory.
|
||||
- Check if desired milestone for an issue is available.
|
||||
- Fix arbitrary file read via diffs during import.
|
||||
- Display the correct number of MRs a user has access to.
|
||||
- Forbid creating discussions for users with restricted access.
|
||||
- Do not disclose milestone titles for unauthorized users.
|
||||
- Validate session key when authorizing with GCP to create a cluster.
|
||||
- Block local URLs for Kubernetes integration.
|
||||
- Limit mermaid rendering to 5K characters.
|
||||
- Remove the possibility to share a project with a group that a user is not a member of.
|
||||
- Fix leaking private repository information in API.
|
||||
- Prevent releases links API to leak tag existance.
|
||||
|
||||
|
||||
## 11.8.0 (2019-02-22)
|
||||
|
||||
### Security (7 changes, 1 of them is from the community)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
11.8.0
|
||||
11.8.2
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import flash from '~/flash';
|
||||
import { sprintf, __ } from '../../locale';
|
||||
|
||||
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
||||
// `js-render-mermaid` class.
|
||||
|
@ -14,6 +15,9 @@ import flash from '~/flash';
|
|||
// </pre>
|
||||
//
|
||||
|
||||
// This is an arbitary number; Can be iterated upon when suitable.
|
||||
const MAX_CHAR_LIMIT = 5000;
|
||||
|
||||
export default function renderMermaid($els) {
|
||||
if (!$els.length) return;
|
||||
|
||||
|
@ -34,6 +38,21 @@ export default function renderMermaid($els) {
|
|||
$els.each((i, el) => {
|
||||
const source = el.textContent;
|
||||
|
||||
/**
|
||||
* Restrict the rendering to a certain amount of character to
|
||||
* prevent mermaidjs from hanging up the entire thread and
|
||||
* causing a DoS.
|
||||
*/
|
||||
if (source && source.length > MAX_CHAR_LIMIT) {
|
||||
el.textContent = sprintf(
|
||||
__(
|
||||
'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
|
||||
),
|
||||
{ charLimit: MAX_CHAR_LIMIT },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any extra spans added by the backend syntax highlighting.
|
||||
Object.assign(el, { textContent: source });
|
||||
|
||||
|
|
|
@ -315,7 +315,7 @@ export default {
|
|||
:endpoint="mr.testResultsPath"
|
||||
/>
|
||||
|
||||
<div class="mr-widget-section p-0">
|
||||
<div class="mr-widget-section">
|
||||
<component :is="componentName" :mr="mr" :service="service" />
|
||||
|
||||
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
|
||||
|
|
|
@ -82,7 +82,6 @@
|
|||
}
|
||||
|
||||
.mr-widget-body,
|
||||
.mr-widget-section,
|
||||
.mr-widget-content,
|
||||
.mr-widget-footer {
|
||||
padding: $gl-padding;
|
||||
|
|
|
@ -8,7 +8,7 @@ module MilestoneActions
|
|||
format.html { redirect_to milestone_redirect_path }
|
||||
format.json do
|
||||
render json: tabs_json("shared/milestones/_merge_requests_tab", {
|
||||
merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
show_project_name: true
|
||||
})
|
||||
end
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
module GoogleApi
|
||||
class AuthorizationsController < ApplicationController
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
before_action :validate_session_key!
|
||||
|
||||
def callback
|
||||
token, expires_at = GoogleApi::CloudPlatform::Client
|
||||
.new(nil, callback_google_api_auth_url)
|
||||
|
@ -11,21 +15,27 @@ module GoogleApi
|
|||
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
|
||||
expires_at.to_s
|
||||
|
||||
state_redirect_uri = redirect_uri_from_session_key(params[:state])
|
||||
|
||||
if state_redirect_uri
|
||||
redirect_to state_redirect_uri
|
||||
else
|
||||
redirect_to root_path
|
||||
end
|
||||
redirect_to redirect_uri_from_session
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirect_uri_from_session_key(state)
|
||||
key = GoogleApi::CloudPlatform::Client
|
||||
.session_key_for_redirect_uri(params[:state])
|
||||
session[key] if key
|
||||
def validate_session_key!
|
||||
access_denied! unless redirect_uri_from_session.present?
|
||||
end
|
||||
|
||||
def redirect_uri_from_session
|
||||
strong_memoize(:redirect_uri_from_session) do
|
||||
if params[:state].present?
|
||||
session[session_key_for_redirect_uri(params[:state])]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def session_key_for_redirect_uri(state)
|
||||
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,15 +2,6 @@
|
|||
|
||||
class Profiles::ActiveSessionsController < Profiles::ApplicationController
|
||||
def index
|
||||
@sessions = ActiveSession.list(current_user)
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveSession.destroy(current_user, params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to profile_active_sessions_url, status: :found }
|
||||
format.js { head :ok }
|
||||
end
|
||||
@sessions = ActiveSession.list(current_user).reject(&:is_impersonated)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::AutocompleteSourcesController < Projects::ApplicationController
|
||||
before_action :authorize_read_milestone!, only: :milestones
|
||||
|
||||
def members
|
||||
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
|
||||
end
|
||||
|
|
|
@ -65,7 +65,11 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def merge_requests
|
||||
@merge_requests = @commit.merge_requests.map do |mr|
|
||||
@merge_requests = MergeRequestsFinder.new(
|
||||
current_user,
|
||||
project_id: @project.id,
|
||||
commit_sha: @commit.sha
|
||||
).execute.map do |mr|
|
||||
{ iid: mr.iid, path: merge_request_path(mr), title: mr.title }
|
||||
end
|
||||
|
||||
|
|
|
@ -13,9 +13,10 @@ class Projects::GroupLinksController < Projects::ApplicationController
|
|||
group = Group.find(params[:link_group_id]) if params[:link_group_id].present?
|
||||
|
||||
if group
|
||||
return render_404 unless can?(current_user, :read_group, group)
|
||||
result = Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
|
||||
return render_404 if result[:http_status] == 404
|
||||
|
||||
Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
|
||||
flash[:alert] = result[:message] if result[:http_status] == 409
|
||||
else
|
||||
flash[:alert] = 'Please select a group.'
|
||||
end
|
||||
|
|
|
@ -37,13 +37,20 @@ class MergeRequestsFinder < IssuableFinder
|
|||
end
|
||||
|
||||
def filter_items(_items)
|
||||
items = by_source_branch(super)
|
||||
items = by_commit(super)
|
||||
items = by_source_branch(items)
|
||||
items = by_wip(items)
|
||||
by_target_branch(items)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_commit(items)
|
||||
return items unless params[:commit_sha].presence
|
||||
|
||||
items.by_commit_sha(params[:commit_sha])
|
||||
end
|
||||
|
||||
def source_branch
|
||||
@source_branch ||= params[:source_branch].presence
|
||||
end
|
||||
|
|
|
@ -16,7 +16,6 @@ module Types
|
|||
|
||||
field :description, GraphQL::STRING_TYPE, null: true
|
||||
|
||||
field :default_branch, GraphQL::STRING_TYPE, null: true
|
||||
field :tag_list, GraphQL::STRING_TYPE, null: true
|
||||
|
||||
field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true
|
||||
|
@ -59,7 +58,6 @@ module Types
|
|||
end
|
||||
|
||||
field :import_status, GraphQL::STRING_TYPE, null: true
|
||||
field :ci_config_path, GraphQL::STRING_TYPE, null: true
|
||||
|
||||
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true
|
||||
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
|
||||
|
|
|
@ -74,6 +74,7 @@ module Emails
|
|||
|
||||
@new_issue = new_issue
|
||||
@new_project = new_issue.project
|
||||
@can_access_project = recipient.can?(:read_project, @new_project)
|
||||
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ class ActiveSession
|
|||
|
||||
attr_accessor :created_at, :updated_at,
|
||||
:session_id, :ip_address,
|
||||
:browser, :os, :device_name, :device_type
|
||||
:browser, :os, :device_name, :device_type,
|
||||
:is_impersonated
|
||||
|
||||
def current?(session)
|
||||
return false if session_id.nil? || session.id.nil?
|
||||
|
@ -31,7 +32,8 @@ class ActiveSession
|
|||
device_type: client.device_type,
|
||||
created_at: user.current_sign_in_at || timestamp,
|
||||
updated_at: timestamp,
|
||||
session_id: session_id
|
||||
session_id: session_id,
|
||||
is_impersonated: request.session[:impersonator_id].present?
|
||||
)
|
||||
|
||||
redis.pipelined do
|
||||
|
|
|
@ -41,7 +41,7 @@ module Clusters
|
|||
validate :no_namespace, unless: :allow_user_defined_namespace?
|
||||
|
||||
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
|
||||
validates :api_url, url: true, presence: true
|
||||
validates :api_url, public_url: true, presence: true
|
||||
validates :token, presence: true
|
||||
|
||||
validate :prevent_modification, on: :update
|
||||
|
|
|
@ -74,6 +74,7 @@ module Issuable
|
|||
|
||||
validates :author, presence: true
|
||||
validates :title, presence: true, length: { maximum: 255 }
|
||||
validate :milestone_is_valid
|
||||
|
||||
scope :authored, ->(user) { where(author_id: user) }
|
||||
scope :recent, -> { reorder(id: :desc) }
|
||||
|
@ -117,6 +118,16 @@ module Issuable
|
|||
def has_multiple_assignees?
|
||||
assignees.count > 1
|
||||
end
|
||||
|
||||
def milestone_available?
|
||||
project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def milestone_is_valid
|
||||
errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available?
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
|
|
@ -46,12 +46,31 @@ module Milestoneish
|
|||
end
|
||||
end
|
||||
|
||||
def issue_participants_visible_by_user(user)
|
||||
User.joins(:issue_assignees)
|
||||
.where('issue_assignees.issue_id' => issues_visible_to_user(user).select(:id))
|
||||
.distinct
|
||||
end
|
||||
|
||||
def issue_labels_visible_by_user(user)
|
||||
Label.joins(:label_links)
|
||||
.where('label_links.target_id' => issues_visible_to_user(user).select(:id), 'label_links.target_type' => 'Issue')
|
||||
.distinct
|
||||
end
|
||||
|
||||
def sorted_issues(user)
|
||||
issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
|
||||
end
|
||||
|
||||
def sorted_merge_requests
|
||||
merge_requests.sort_by_attribute('label_priority')
|
||||
def sorted_merge_requests(user)
|
||||
merge_requests_visible_to_user(user).sort_by_attribute('label_priority')
|
||||
end
|
||||
|
||||
def merge_requests_visible_to_user(user)
|
||||
memoize_per_user(user, :merge_requests_visible_to_user) do
|
||||
MergeRequestsFinder.new(user, issues_finder_params)
|
||||
.execute.where(milestone_id: milestoneish_id)
|
||||
end
|
||||
end
|
||||
|
||||
def upcoming?
|
||||
|
|
|
@ -71,7 +71,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
|
||||
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
after_create :ensure_merge_request_diff, unless: :importing?
|
||||
after_create :ensure_merge_request_diff
|
||||
after_update :clear_memoized_shas
|
||||
after_update :reload_diff_if_branch_changed
|
||||
after_save :ensure_metrics
|
||||
|
@ -189,6 +189,9 @@ class MergeRequest < ActiveRecord::Base
|
|||
|
||||
after_save :keep_around_commit
|
||||
|
||||
alias_attribute :project, :target_project
|
||||
alias_attribute :project_id, :target_project_id
|
||||
|
||||
def self.reference_prefix
|
||||
'!'
|
||||
end
|
||||
|
@ -837,10 +840,6 @@ class MergeRequest < ActiveRecord::Base
|
|||
target_project != source_project
|
||||
end
|
||||
|
||||
def project
|
||||
target_project
|
||||
end
|
||||
|
||||
# If the merge request closes any issues, save this information in the
|
||||
# `MergeRequestsClosingIssues` model. This is a performance optimization.
|
||||
# Calculating this information for a number of merge requests requires
|
||||
|
|
|
@ -25,6 +25,8 @@ class MergeRequestDiff < ActiveRecord::Base
|
|||
|
||||
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
|
||||
|
||||
validates :base_commit_sha, :head_commit_sha, :start_commit_sha, sha: true
|
||||
|
||||
state_machine :state, initial: :empty do
|
||||
event :clean do
|
||||
transition any => :without_files
|
||||
|
|
|
@ -1830,7 +1830,7 @@ class Project < ActiveRecord::Base
|
|||
# Set repository as writable again
|
||||
def set_repository_writable!
|
||||
with_lock do
|
||||
update_column(repository_read_only, false)
|
||||
update_column(:repository_read_only, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class ProjectFeature < ActiveRecord::Base
|
|||
# This feature might not be behind a feature flag at all, so default to true
|
||||
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
|
||||
|
||||
get_permission(user, access_level(feature))
|
||||
get_permission(user, feature)
|
||||
end
|
||||
|
||||
def access_level(feature)
|
||||
|
@ -134,12 +134,12 @@ class ProjectFeature < ActiveRecord::Base
|
|||
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
|
||||
end
|
||||
|
||||
def get_permission(user, level)
|
||||
case level
|
||||
def get_permission(user, feature)
|
||||
case access_level(feature)
|
||||
when DISABLED
|
||||
false
|
||||
when PRIVATE
|
||||
user && (project.team.member?(user) || user.full_private_access?)
|
||||
team_access?(user, feature)
|
||||
when ENABLED
|
||||
true
|
||||
when PUBLIC
|
||||
|
@ -148,4 +148,11 @@ class ProjectFeature < ActiveRecord::Base
|
|||
true
|
||||
end
|
||||
end
|
||||
|
||||
def team_access?(user, feature)
|
||||
return unless user
|
||||
return true if user.full_private_access?
|
||||
|
||||
project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
|
|||
end
|
||||
|
||||
def prometheus_client
|
||||
RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active?
|
||||
RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client?
|
||||
end
|
||||
|
||||
def prometheus_available?
|
||||
|
@ -83,6 +83,10 @@ class PrometheusService < MonitoringService
|
|||
|
||||
private
|
||||
|
||||
def should_return_client?
|
||||
api_url && manual_configuration? && active? && valid?
|
||||
end
|
||||
|
||||
def synchronize_service_state
|
||||
self.active = prometheus_available? || manual_configuration?
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class GroupPolicy < BasePolicy
|
|||
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
|
||||
|
||||
condition(:has_projects) do
|
||||
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any?
|
||||
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
|
||||
end
|
||||
|
||||
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
|
||||
|
@ -53,8 +53,9 @@ class GroupPolicy < BasePolicy
|
|||
rule { admin }.enable :read_group
|
||||
|
||||
rule { has_projects }.policy do
|
||||
enable :read_group
|
||||
enable :read_list
|
||||
enable :read_label
|
||||
enable :read_group
|
||||
end
|
||||
|
||||
rule { has_access }.enable :read_namespace
|
||||
|
|
|
@ -299,6 +299,8 @@ class ProjectPolicy < BasePolicy
|
|||
|
||||
rule { issues_disabled }.policy do
|
||||
prevent(*create_read_update_admin_destroy(:issue))
|
||||
prevent(*create_read_update_admin_destroy(:board))
|
||||
prevent(*create_read_update_admin_destroy(:list))
|
||||
end
|
||||
|
||||
rule { merge_requests_disabled | repository_disabled }.policy do
|
||||
|
@ -462,7 +464,7 @@ class ProjectPolicy < BasePolicy
|
|||
when ProjectFeature::DISABLED
|
||||
false
|
||||
when ProjectFeature::PRIVATE
|
||||
guest? || admin?
|
||||
admin? || team_access_level >= ProjectFeature.required_minimum_access_level(feature)
|
||||
else
|
||||
true
|
||||
end
|
||||
|
|
|
@ -387,4 +387,10 @@ class IssuableBaseService < BaseService
|
|||
def parent
|
||||
project
|
||||
end
|
||||
|
||||
# we need to check this because milestone from milestone_id param is displayed on "new" page
|
||||
# where private project milestone could leak without this check
|
||||
def ensure_milestone_available(issuable)
|
||||
issuable.milestone_id = nil unless issuable.milestone_available?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,9 @@ module Issues
|
|||
|
||||
def execute
|
||||
filter_resolve_discussion_params
|
||||
@issue = project.issues.new(issue_params)
|
||||
@issue = project.issues.new(issue_params).tap do |issue|
|
||||
ensure_milestone_available(issue)
|
||||
end
|
||||
end
|
||||
|
||||
def issue_params_with_info_from_discussions
|
||||
|
|
|
@ -19,6 +19,7 @@ module MergeRequests
|
|||
merge_request.target_project = find_target_project
|
||||
merge_request.target_branch = find_target_branch
|
||||
merge_request.can_be_created = projects_and_branches_valid?
|
||||
ensure_milestone_available(merge_request)
|
||||
|
||||
# compare branches only if branches are valid, otherwise
|
||||
# compare_branches may raise an error
|
||||
|
|
|
@ -4,13 +4,19 @@ module Projects
|
|||
module GroupLinks
|
||||
class CreateService < BaseService
|
||||
def execute(group)
|
||||
return false unless group
|
||||
return error('Not Found', 404) unless group && can?(current_user, :read_namespace, group)
|
||||
|
||||
project.project_group_links.create(
|
||||
link = project.project_group_links.new(
|
||||
group: group,
|
||||
group_access: params[:link_group_access],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
|
||||
if link.save
|
||||
success(link: link)
|
||||
else
|
||||
error(link.errors.full_messages.to_sentence, 409)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,8 @@ class FileMover
|
|||
end
|
||||
|
||||
def execute
|
||||
return unless valid?
|
||||
|
||||
move
|
||||
|
||||
if update_markdown
|
||||
|
@ -21,6 +23,12 @@ class FileMover
|
|||
|
||||
private
|
||||
|
||||
def valid?
|
||||
Pathname.new(temp_file_path).realpath.to_path.start_with?(
|
||||
(Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
|
||||
)
|
||||
end
|
||||
|
||||
def move
|
||||
FileUtils.mkdir_p(File.dirname(file_path))
|
||||
FileUtils.move(temp_file_path, file_path)
|
||||
|
|
9
app/validators/sha_validator.rb
Normal file
9
app/validators/sha_validator.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ShaValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
return if value.blank? || value.match(/\A\h{40}\z/)
|
||||
|
||||
record.errors.add(attribute, 'is not a valid SHA')
|
||||
end
|
||||
end
|
|
@ -1,6 +1,9 @@
|
|||
%p
|
||||
Issue was moved to another project.
|
||||
%p
|
||||
- if @can_access_project
|
||||
%p
|
||||
New issue:
|
||||
= link_to project_issue_url(@new_project, @new_issue) do
|
||||
= @new_issue.title
|
||||
- else
|
||||
You don't have access to the project.
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
Issue was moved to another project.
|
||||
|
||||
<% if @can_access_project %>
|
||||
New issue location:
|
||||
<%= project_issue_url(@new_project, @new_issue) %>
|
||||
<% else %>
|
||||
You don't have access to the project.
|
||||
<% end %>
|
||||
|
|
|
@ -23,9 +23,3 @@
|
|||
%strong Signed in
|
||||
on
|
||||
= l(active_session.created_at, format: :short)
|
||||
|
||||
- unless is_current_session
|
||||
.float-right
|
||||
= link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
|
||||
%span.sr-only Revoke
|
||||
Revoke
|
||||
|
|
|
@ -3,9 +3,4 @@
|
|||
This project manages its dependencies using
|
||||
%strong= viewer.manager_name
|
||||
|
||||
- if viewer.package_name
|
||||
and defines a #{viewer.package_type} named
|
||||
%strong<
|
||||
= link_to_if viewer.package_url.present?, viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
= link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer'
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
= milestone_progress_bar(milestone)
|
||||
= link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path
|
||||
·
|
||||
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
|
||||
= link_to pluralize(milestone.merge_requests_visible_to_user(current_user).size, 'Merge Request'), merge_requests_path
|
||||
.float-lg-right.light #{milestone.percent_complete(current_user)}% complete
|
||||
.col-sm-2
|
||||
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
%li.nav-item
|
||||
= link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
|
||||
Merge Requests
|
||||
%span.badge.badge-pill= milestone.merge_requests.size
|
||||
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
|
||||
- else
|
||||
%li.nav-item
|
||||
= link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
|
||||
|
@ -21,11 +21,11 @@
|
|||
%li.nav-item
|
||||
= link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
|
||||
Participants
|
||||
%span.badge.badge-pill= milestone.participants.count
|
||||
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
|
||||
%li.nav-item
|
||||
= link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
|
||||
Labels
|
||||
%span.badge.badge-pill= milestone.labels.count
|
||||
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
|
||||
|
||||
- issues = milestone.sorted_issues(current_user)
|
||||
- show_project_name = local_assigns.fetch(:show_project_name, false)
|
||||
|
|
|
@ -40,7 +40,7 @@ scope(path: '*namespace_id/:project_id',
|
|||
# /info/refs?service=git-receive-pack, but nothing else.
|
||||
#
|
||||
git_http_handshake = lambda do |request|
|
||||
::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
|
||||
::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) &&
|
||||
(request.query_string.blank? ||
|
||||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
|
||||
end
|
||||
|
|
|
@ -48,7 +48,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
|
||||
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
|
||||
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
|
||||
- [Merge request diffs](merge_request_diffs.md): Configure the diffs shown on merge requests
|
||||
- [Merge request diffs storage](merge_request_diffs.md): Configure merge requests diffs external storage.
|
||||
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
|
||||
|
||||
#### Customizing GitLab's appearance
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Merge request diffs administration
|
||||
# Merge request diffs storage **[CORE ONLY]**
|
||||
|
||||
> **Notes:**
|
||||
> - External merge request diffs introduced in GitLab 11.8
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52568) in GitLab 11.8.
|
||||
|
||||
Merge request diffs are size-limited copies of diffs associated with merge
|
||||
requests. When viewing a merge request, diffs are sourced from these copies
|
||||
|
@ -16,9 +15,7 @@ large, in which case, switching to external storage is recommended.
|
|||
Merge request diffs can be stored on disk, or in object storage. In general, it
|
||||
is better to store the diffs in the database than on disk.
|
||||
|
||||
To enable external storage of merge request diffs:
|
||||
|
||||
---
|
||||
To enable external storage of merge request diffs, follow the instructions below.
|
||||
|
||||
**In Omnibus installations:**
|
||||
|
||||
|
@ -30,16 +27,14 @@ To enable external storage of merge request diffs:
|
|||
|
||||
1. _The external diffs will be stored in in
|
||||
`/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path,
|
||||
for example to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
|
||||
for example, to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
|
||||
and add the following line:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs"
|
||||
```
|
||||
|
||||
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
|
||||
|
||||
---
|
||||
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||
|
||||
**In installations from source:**
|
||||
|
||||
|
@ -52,7 +47,7 @@ To enable external storage of merge request diffs:
|
|||
```
|
||||
|
||||
1. _The external diffs will be stored in
|
||||
`/home/git/gitlab/shared/external-diffs`._ To change the path, for example
|
||||
`/home/git/gitlab/shared/external-diffs`._ To change the path, for example,
|
||||
to `/mnt/storage/external-diffs`, edit `/home/git/gitlab/config/gitlab.yml`
|
||||
and add or amend the following lines:
|
||||
|
||||
|
@ -62,18 +57,18 @@ To enable external storage of merge request diffs:
|
|||
storage_path: /mnt/storage/external-diffs
|
||||
```
|
||||
|
||||
1. Save the file and [restart GitLab][] for the changes to take effect.
|
||||
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
|
||||
|
||||
### Using object storage
|
||||
|
||||
Instead of storing the external diffs on disk, we recommended you use an object
|
||||
Instead of storing the external diffs on disk, we recommended the use of an object
|
||||
store like AWS S3 instead. This configuration relies on valid AWS credentials to
|
||||
be configured already.
|
||||
|
||||
### Object Storage Settings
|
||||
|
||||
For source installations, these settings are nested under `external_diffs:` and
|
||||
then `object_store:`. On omnibus installs, they are prefixed by
|
||||
then `object_store:`. On Omnibus installations, they are prefixed by
|
||||
`external_diffs_object_store_`.
|
||||
|
||||
| Setting | Description | Default |
|
||||
|
@ -118,7 +113,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
|
|||
}
|
||||
```
|
||||
|
||||
NOTE: if you are using AWS IAM profiles, be sure to omit the
|
||||
Note that, if you are using AWS IAM profiles, be sure to omit the
|
||||
AWS access key and secret access key/value pairs. For example:
|
||||
|
||||
```ruby
|
||||
|
@ -129,9 +124,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
|
|||
}
|
||||
```
|
||||
|
||||
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
|
||||
|
||||
---
|
||||
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||
|
||||
**In installations from source:**
|
||||
|
||||
|
@ -151,4 +144,4 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
|
|||
region: eu-central-1
|
||||
```
|
||||
|
||||
1. Save the file and [restart GitLab][] for the changes to take effect.
|
||||
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
|
||||
|
|
|
@ -33,7 +33,7 @@ Please consider using a virtual machine to run GitLab.
|
|||
|
||||
## Ruby versions
|
||||
|
||||
GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13.
|
||||
GitLab requires Ruby (MRI) 2.5. Support for Ruby versions below 2.5 (2.3, 2.4) will stop with GitLab 11.6.
|
||||
|
||||
You will have to use the standard MRI implementation of Ruby.
|
||||
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
> in GitLab 10.8.
|
||||
|
||||
GitLab lists all devices that have logged into your account. This allows you to
|
||||
review the sessions and revoke any of it that you don't recognize.
|
||||
review the sessions.
|
||||
|
||||
## Listing all active sessions
|
||||
|
||||
|
@ -12,9 +12,3 @@ review the sessions and revoke any of it that you don't recognize.
|
|||
1. Navigate to the **Active Sessions** tab.
|
||||
|
||||
![Active sessions list](img/active_sessions_list.png)
|
||||
|
||||
## Revoking a session
|
||||
|
||||
1. Navigate to your [profile's](#profile-settings) **Settings > Active Sessions**.
|
||||
1. Click on **Revoke** besides a session. The current session cannot be
|
||||
revoked, as this would sign you out of GitLab.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 19 KiB |
|
@ -318,10 +318,18 @@ module API
|
|||
use :pagination
|
||||
end
|
||||
get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
|
||||
authorize! :read_merge_request, user_project
|
||||
|
||||
commit = user_project.commit(params[:sha])
|
||||
not_found! 'Commit' unless commit
|
||||
|
||||
present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
|
||||
commit_merge_requests = MergeRequestsFinder.new(
|
||||
current_user,
|
||||
project_id: user_project.id,
|
||||
commit_sha: commit.sha
|
||||
).execute
|
||||
|
||||
present paginate(commit_merge_requests), with: Entities::MergeRequestBasic
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -156,7 +156,7 @@ module API
|
|||
class BasicProjectDetails < ProjectIdentity
|
||||
include ::API::ProjectsRelationBuilder
|
||||
|
||||
expose :default_branch
|
||||
expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
||||
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
|
||||
expose :tag_list do |project|
|
||||
# project.tags.order(:name).pluck(:name) is the most suitable option
|
||||
|
@ -261,7 +261,7 @@ module API
|
|||
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
|
||||
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
|
||||
expose :public_builds, as: :public_jobs
|
||||
expose :ci_config_path
|
||||
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
||||
expose :shared_with_groups do |project, options|
|
||||
SharedGroup.represent(project.project_group_links, options)
|
||||
end
|
||||
|
@ -270,8 +270,9 @@ module API
|
|||
expose :only_allow_merge_if_all_discussions_are_resolved
|
||||
expose :printing_merge_request_link_enabled
|
||||
expose :merge_method
|
||||
|
||||
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
|
||||
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
|
||||
options[:statistics] && Ability.allowed?(options[:current_user], :download_code, project)
|
||||
}
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def self.preload_relation(projects_relation, options = {})
|
||||
|
|
|
@ -22,7 +22,7 @@ module API
|
|||
get ':id/environments' do
|
||||
authorize! :read_environment, user_project
|
||||
|
||||
present paginate(user_project.environments), with: Entities::Environment
|
||||
present paginate(user_project.environments), with: Entities::Environment, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Creates a new environment' do
|
||||
|
@ -40,7 +40,7 @@ module API
|
|||
environment = user_project.environments.create(declared_params)
|
||||
|
||||
if environment.persisted?
|
||||
present environment, with: Entities::Environment
|
||||
present environment, with: Entities::Environment, current_user: current_user
|
||||
else
|
||||
render_validation_error!(environment)
|
||||
end
|
||||
|
@ -63,7 +63,7 @@ module API
|
|||
|
||||
update_params = declared_params(include_missing: false).extract!(:name, :external_url)
|
||||
if environment.update(update_params)
|
||||
present environment, with: Entities::Environment
|
||||
present environment, with: Entities::Environment, current_user: current_user
|
||||
else
|
||||
render_validation_error!(environment)
|
||||
end
|
||||
|
@ -99,7 +99,7 @@ module API
|
|||
environment.stop_with_action!(current_user)
|
||||
|
||||
status 200
|
||||
present environment, with: Entities::Environment
|
||||
present environment, with: Entities::Environment, current_user: current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,14 +70,7 @@ module API
|
|||
def find_noteable(parent, noteables_str, noteable_id)
|
||||
noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
readable =
|
||||
if noteable.is_a?(Commit)
|
||||
# for commits there is not :read_commit policy, check if user
|
||||
# has :read_note permission on the commit's project
|
||||
can?(current_user, :read_note, user_project)
|
||||
else
|
||||
can?(current_user, noteable_read_ability_name(noteable), noteable)
|
||||
end
|
||||
readable = can?(current_user, noteable_read_ability_name(noteable), noteable)
|
||||
|
||||
return not_found!(noteables_str) unless readable
|
||||
|
||||
|
@ -89,12 +82,11 @@ module API
|
|||
end
|
||||
|
||||
def create_note(noteable, opts)
|
||||
policy_object = noteable.is_a?(Commit) ? user_project : noteable
|
||||
authorize!(:create_note, policy_object)
|
||||
authorize!(:create_note, noteable)
|
||||
|
||||
parent = noteable_parent(noteable)
|
||||
|
||||
opts.delete(:created_at) unless current_user.can?(:set_note_created_at, policy_object)
|
||||
opts.delete(:created_at) unless current_user.can?(:set_note_created_at, noteable)
|
||||
|
||||
opts[:updated_at] = opts[:created_at] if opts[:created_at]
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module API
|
|||
end
|
||||
|
||||
def get_runner_ip
|
||||
{ ip_address: request.env["HTTP_X_FORWARDED_FOR"] || request.ip }
|
||||
{ ip_address: env["action_dispatch.remote_ip"].to_s || request.ip }
|
||||
end
|
||||
|
||||
def current_runner
|
||||
|
|
|
@ -184,7 +184,8 @@ module API
|
|||
|
||||
if project.saved?
|
||||
present project, with: Entities::Project,
|
||||
user_can_admin_project: can?(current_user, :admin_project, project)
|
||||
user_can_admin_project: can?(current_user, :admin_project, project),
|
||||
current_user: current_user
|
||||
else
|
||||
if project.errors[:limit_reached].present?
|
||||
error!(project.errors[:limit_reached], 403)
|
||||
|
@ -217,7 +218,8 @@ module API
|
|||
|
||||
if project.saved?
|
||||
present project, with: Entities::Project,
|
||||
user_can_admin_project: can?(current_user, :admin_project, project)
|
||||
user_can_admin_project: can?(current_user, :admin_project, project),
|
||||
current_user: current_user
|
||||
else
|
||||
render_validation_error!(project)
|
||||
end
|
||||
|
@ -279,7 +281,8 @@ module API
|
|||
conflict!(forked_project.errors.messages)
|
||||
else
|
||||
present forked_project, with: Entities::Project,
|
||||
user_can_admin_project: can?(current_user, :admin_project, forked_project)
|
||||
user_can_admin_project: can?(current_user, :admin_project, forked_project),
|
||||
current_user: current_user
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -328,7 +331,8 @@ module API
|
|||
|
||||
if result[:status] == :success
|
||||
present user_project, with: Entities::Project,
|
||||
user_can_admin_project: can?(current_user, :admin_project, user_project)
|
||||
user_can_admin_project: can?(current_user, :admin_project, user_project),
|
||||
current_user: current_user
|
||||
else
|
||||
render_validation_error!(user_project)
|
||||
end
|
||||
|
@ -342,7 +346,7 @@ module API
|
|||
|
||||
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
|
||||
|
||||
present user_project, with: Entities::Project
|
||||
present user_project, with: Entities::Project, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Unarchive a project' do
|
||||
|
@ -353,7 +357,7 @@ module API
|
|||
|
||||
::Projects::UpdateService.new(@project, current_user, archived: false).execute
|
||||
|
||||
present user_project, with: Entities::Project
|
||||
present user_project, with: Entities::Project, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Star a project' do
|
||||
|
@ -366,7 +370,7 @@ module API
|
|||
current_user.toggle_star(user_project)
|
||||
user_project.reload
|
||||
|
||||
present user_project, with: Entities::Project
|
||||
present user_project, with: Entities::Project, current_user: current_user
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -378,7 +382,7 @@ module API
|
|||
current_user.toggle_star(user_project)
|
||||
user_project.reload
|
||||
|
||||
present user_project, with: Entities::Project
|
||||
present user_project, with: Entities::Project, current_user: current_user
|
||||
else
|
||||
not_modified!
|
||||
end
|
||||
|
@ -414,7 +418,7 @@ module API
|
|||
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
|
||||
|
||||
if result
|
||||
present user_project.reload, with: Entities::Project
|
||||
present user_project.reload, with: Entities::Project, current_user: current_user
|
||||
else
|
||||
render_api_error!("Project already forked", 409) if user_project.forked?
|
||||
end
|
||||
|
@ -436,27 +440,24 @@ module API
|
|||
end
|
||||
params do
|
||||
requires :group_id, type: Integer, desc: 'The ID of a group'
|
||||
requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level'
|
||||
requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level'
|
||||
optional :expires_at, type: Date, desc: 'Share expiration date'
|
||||
end
|
||||
post ":id/share" do
|
||||
authorize! :admin_project, user_project
|
||||
group = Group.find_by_id(params[:group_id])
|
||||
|
||||
unless group && can?(current_user, :read_group, group)
|
||||
not_found!('Group')
|
||||
end
|
||||
|
||||
unless user_project.allowed_to_share_with_group?
|
||||
break render_api_error!("The project sharing with group is disabled", 400)
|
||||
end
|
||||
|
||||
link = user_project.project_group_links.new(declared_params(include_missing: false))
|
||||
result = ::Projects::GroupLinks::CreateService.new(user_project, current_user, declared_params(include_missing: false))
|
||||
.execute(group)
|
||||
|
||||
if link.save
|
||||
present link, with: Entities::ProjectGroupLink
|
||||
if result[:status] == :success
|
||||
present result[:link], with: Entities::ProjectGroupLink
|
||||
else
|
||||
render_api_error!(link.errors.full_messages.first, 409)
|
||||
render_api_error!(result[:message], result[:http_status])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -520,7 +521,7 @@ module API
|
|||
result = ::Projects::TransferService.new(user_project, current_user).execute(namespace)
|
||||
|
||||
if result
|
||||
present user_project, with: Entities::Project
|
||||
present user_project, with: Entities::Project, current_user: current_user
|
||||
else
|
||||
render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400)
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ module API
|
|||
RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
|
||||
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
|
||||
|
||||
before { authorize! :read_release, user_project }
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project'
|
||||
end
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
module Constraints
|
||||
class ProjectUrlConstrainer
|
||||
def matches?(request)
|
||||
def matches?(request, existence_check: true)
|
||||
namespace_path = request.params[:namespace_id]
|
||||
project_path = request.params[:project_id] || request.params[:id]
|
||||
full_path = [namespace_path, project_path].join('/')
|
||||
|
||||
return false unless ProjectPathValidator.valid_path?(full_path)
|
||||
return true unless existence_check
|
||||
|
||||
# We intentionally allow SELECT(*) here so result of this query can be used
|
||||
# as cache for further Project.find_by_full_path calls within request
|
||||
|
|
|
@ -4,6 +4,7 @@ module Gitlab
|
|||
module DependencyLinker
|
||||
class BaseLinker
|
||||
URL_REGEX = %r{https?://[^'" ]+}.freeze
|
||||
GIT_INVALID_URL_REGEX = /^git\+#{URL_REGEX}/.freeze
|
||||
REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze
|
||||
|
||||
class_attribute :file_type
|
||||
|
@ -29,8 +30,25 @@ module Gitlab
|
|||
highlighted_lines.join.html_safe
|
||||
end
|
||||
|
||||
def external_url(name, external_ref)
|
||||
return if external_ref =~ GIT_INVALID_URL_REGEX
|
||||
|
||||
case external_ref
|
||||
when /\A#{URL_REGEX}\z/
|
||||
external_ref
|
||||
when /\A#{REPO_REGEX}\z/
|
||||
github_url(external_ref)
|
||||
else
|
||||
package_url(name)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def package_url(_name)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def link_dependencies
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
@ -8,8 +8,8 @@ module Gitlab
|
|||
private
|
||||
|
||||
def link_packages
|
||||
link_packages_at_key("require", &method(:package_url))
|
||||
link_packages_at_key("require-dev", &method(:package_url))
|
||||
link_packages_at_key("require")
|
||||
link_packages_at_key("require-dev")
|
||||
end
|
||||
|
||||
def package_url(name)
|
||||
|
|
|
@ -3,8 +3,14 @@
|
|||
module Gitlab
|
||||
module DependencyLinker
|
||||
class GemfileLinker < MethodLinker
|
||||
class_attribute :package_keyword
|
||||
|
||||
self.package_keyword = :gem
|
||||
self.file_type = :gemfile
|
||||
|
||||
GITHUB_REGEX = /(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/.freeze
|
||||
GIT_REGEX = /(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/.freeze
|
||||
|
||||
private
|
||||
|
||||
def link_dependencies
|
||||
|
@ -14,20 +20,34 @@ module Gitlab
|
|||
|
||||
def link_urls
|
||||
# Link `github: "user/repo"` to https://github.com/user/repo
|
||||
link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
|
||||
link_regex(GITHUB_REGEX, &method(:github_url))
|
||||
|
||||
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
|
||||
link_regex(/(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/, &:itself)
|
||||
link_regex(GIT_REGEX, &:itself)
|
||||
|
||||
# Link `source "https://rubygems.org"` to https://rubygems.org
|
||||
link_method_call('source', URL_REGEX, &:itself)
|
||||
end
|
||||
|
||||
def link_packages
|
||||
# Link `gem "package_name"` to https://rubygems.org/gems/package_name
|
||||
link_method_call('gem') do |name|
|
||||
packages = parse_packages
|
||||
|
||||
return if packages.blank?
|
||||
|
||||
packages.each do |package|
|
||||
link_method_call('gem', package.name) do
|
||||
external_url(package.name, package.external_ref)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def package_url(name)
|
||||
"https://rubygems.org/gems/#{name}"
|
||||
end
|
||||
|
||||
def parse_packages
|
||||
parser = Gitlab::DependencyLinker::Parser::Gemfile.new(plain_text)
|
||||
parser.parse(keyword: self.class.package_keyword)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
link_method_call('homepage', URL_REGEX, &:itself)
|
||||
link_method_call('license', &method(:license_url))
|
||||
|
||||
link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency]) do |name|
|
||||
link_method_call(%w[add_dependency add_runtime_dependency add_development_dependency]) do |name|
|
||||
"https://rubygems.org/gems/#{name}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,18 +23,22 @@ module Gitlab
|
|||
# link_method_call('name')
|
||||
# # Will link `package` in `self.name = "package"`
|
||||
def link_method_call(method_name, value = nil, &url_proc)
|
||||
regex = method_call_regex(method_name, value)
|
||||
|
||||
link_regex(regex, &url_proc)
|
||||
end
|
||||
|
||||
def method_call_regex(method_name, value = nil)
|
||||
method_name = regexp_for_value(method_name)
|
||||
value = regexp_for_value(value)
|
||||
|
||||
regex = %r{
|
||||
%r{
|
||||
#{method_name} # Method name
|
||||
\s* # Whitespace
|
||||
[(=]? # Opening brace or equals sign
|
||||
\s* # Whitespace
|
||||
['"](?<name>#{value})['"] # Package name in quotes
|
||||
}x
|
||||
|
||||
link_regex(regex, &url_proc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
19
lib/gitlab/dependency_linker/package.rb
Normal file
19
lib/gitlab/dependency_linker/package.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module DependencyLinker
|
||||
class Package
|
||||
attr_reader :name, :git_ref, :github_ref
|
||||
|
||||
def initialize(name, git_ref, github_ref)
|
||||
@name = name
|
||||
@git_ref = git_ref
|
||||
@github_ref = github_ref
|
||||
end
|
||||
|
||||
def external_ref
|
||||
@git_ref || @github_ref
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,7 +8,6 @@ module Gitlab
|
|||
private
|
||||
|
||||
def link_dependencies
|
||||
link_json('name', json["name"], &method(:package_url))
|
||||
link_json('license', &method(:license_url))
|
||||
link_json(%w[homepage url], URL_REGEX, &:itself)
|
||||
|
||||
|
@ -16,25 +15,19 @@ module Gitlab
|
|||
end
|
||||
|
||||
def link_packages
|
||||
link_packages_at_key("dependencies", &method(:package_url))
|
||||
link_packages_at_key("devDependencies", &method(:package_url))
|
||||
link_packages_at_key("dependencies")
|
||||
link_packages_at_key("devDependencies")
|
||||
end
|
||||
|
||||
def link_packages_at_key(key, &url_proc)
|
||||
def link_packages_at_key(key)
|
||||
dependencies = json[key]
|
||||
return unless dependencies
|
||||
|
||||
dependencies.each do |name, version|
|
||||
link_json(name, version, link: :key, &url_proc)
|
||||
external_url = external_url(name, version)
|
||||
|
||||
link_json(name) do |value|
|
||||
case value
|
||||
when /\A#{URL_REGEX}\z/
|
||||
value
|
||||
when /\A#{REPO_REGEX}\z/
|
||||
github_url(value)
|
||||
end
|
||||
end
|
||||
link_json(name, version, link: :key) { external_url }
|
||||
link_json(name) { external_url }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
40
lib/gitlab/dependency_linker/parser/gemfile.rb
Normal file
40
lib/gitlab/dependency_linker/parser/gemfile.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module DependencyLinker
|
||||
module Parser
|
||||
class Gemfile < MethodLinker
|
||||
GIT_REGEX = Gitlab::DependencyLinker::GemfileLinker::GIT_REGEX
|
||||
GITHUB_REGEX = Gitlab::DependencyLinker::GemfileLinker::GITHUB_REGEX
|
||||
|
||||
def initialize(plain_text)
|
||||
@plain_text = plain_text
|
||||
end
|
||||
|
||||
# Returns a list of Gitlab::DependencyLinker::Package
|
||||
#
|
||||
# keyword - The package definition keyword, e.g. `:gem` for
|
||||
# Gemfile parsing, `:pod` for Podfile.
|
||||
def parse(keyword:)
|
||||
plain_lines.each_with_object([]) do |line, packages|
|
||||
name = fetch(line, method_call_regex(keyword))
|
||||
|
||||
next unless name
|
||||
|
||||
git_ref = fetch(line, GIT_REGEX)
|
||||
github_ref = fetch(line, GITHUB_REGEX)
|
||||
|
||||
packages << Gitlab::DependencyLinker::Package.new(name, git_ref, github_ref)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch(line, regex, group: :name)
|
||||
match = line.match(regex)
|
||||
match[group] if match
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,12 +5,21 @@ module Gitlab
|
|||
class PodfileLinker < GemfileLinker
|
||||
include Cocoapods
|
||||
|
||||
self.package_keyword = :pod
|
||||
self.file_type = :podfile
|
||||
|
||||
private
|
||||
|
||||
def link_packages
|
||||
link_method_call('pod', &method(:package_url))
|
||||
packages = parse_packages
|
||||
|
||||
return unless packages
|
||||
|
||||
packages.each do |package|
|
||||
link_method_call('pod', package.name) do
|
||||
external_url(package.name, package.external_ref)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ module Gitlab
|
|||
link_method_call('license', &method(:license_url))
|
||||
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
|
||||
|
||||
link_method_call(%w[name dependency], &method(:package_url))
|
||||
link_method_call('dependency', &method(:package_url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,17 @@ module Gitlab
|
|||
create_target_branch unless branch_exists?(@merge_request.target_branch)
|
||||
end
|
||||
|
||||
# The merge_request_diff associated with the current @merge_request might
|
||||
# be invalid. Than means, when the @merge_request object is saved, the
|
||||
# @merge_request.merge_request_diff won't. This can leave the merge request
|
||||
# in an invalid state, because a merge request must have an associated
|
||||
# merge request diff.
|
||||
# In this change, if the associated merge request diff is invalid, we set
|
||||
# it to nil. This change, in association with the after callback
|
||||
# :ensure_merge_request_diff in the MergeRequest class, makes that
|
||||
# when the merge request is going to be created and it doesn't have
|
||||
# one, a default one will be generated.
|
||||
@merge_request.merge_request_diff = nil unless @merge_request.merge_request_diff&.valid?
|
||||
@merge_request
|
||||
end
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ module Gitlab
|
|||
def log_base_data
|
||||
{
|
||||
importer: 'Import/Export',
|
||||
import_jid: @project&.import_state&.import_jid,
|
||||
import_jid: @project&.import_state&.jid,
|
||||
project_id: @project&.id,
|
||||
project_path: @project&.full_path
|
||||
}
|
||||
|
|
|
@ -82,6 +82,8 @@ module Gitlab
|
|||
def initialize(api_prefix, **kubeclient_options)
|
||||
@api_prefix = api_prefix
|
||||
@kubeclient_options = kubeclient_options.merge(http_max_redirects: 0)
|
||||
|
||||
validate_url!
|
||||
end
|
||||
|
||||
def create_or_update_cluster_role_binding(resource)
|
||||
|
@ -118,6 +120,12 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def validate_url!
|
||||
return if Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
|
||||
|
||||
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
|
||||
end
|
||||
|
||||
def cluster_role_binding_exists?(resource)
|
||||
get_cluster_role_binding(resource.metadata.name)
|
||||
rescue ::Kubeclient::ResourceNotFoundError
|
||||
|
|
|
@ -1293,6 +1293,9 @@ msgstr ""
|
|||
msgid "Cannot modify managed Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
|
||||
msgstr ""
|
||||
|
||||
msgid "Certificate"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ gem 'rspec', '~> 3.7'
|
|||
gem 'selenium-webdriver', '~> 3.12'
|
||||
gem 'airborne', '~> 0.2.13'
|
||||
gem 'nokogiri', '~> 1.10.1'
|
||||
gem 'rspec-retry', '~> 0.6.1'
|
||||
|
|
|
@ -76,6 +76,8 @@ GEM
|
|||
rspec-mocks (3.7.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-retry (0.6.1)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.7.0)
|
||||
rubyzip (1.2.2)
|
||||
selenium-webdriver (3.141.0)
|
||||
|
@ -101,6 +103,7 @@ DEPENDENCIES
|
|||
pry-byebug (~> 3.5.1)
|
||||
rake (~> 12.3.0)
|
||||
rspec (~> 3.7)
|
||||
rspec-retry (~> 0.6.1)
|
||||
selenium-webdriver (~> 3.12)
|
||||
|
||||
BUNDLED WITH
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
context 'Create' do
|
||||
# https://gitlab.com/gitlab-org/quality/staging/issues/40
|
||||
context 'Create', :quarantine do
|
||||
describe 'Push mirror a repository over HTTP' do
|
||||
it 'configures and syncs a (push) mirrored repository' do
|
||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require_relative '../qa'
|
||||
require 'rspec/retry'
|
||||
|
||||
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
|
||||
|
||||
|
@ -43,6 +44,17 @@ RSpec.configure do |config|
|
|||
config.profile_examples = 10
|
||||
config.order = :random
|
||||
Kernel.srand config.seed
|
||||
|
||||
# show retry status in spec process
|
||||
config.verbose_retry = true
|
||||
|
||||
# show exception that triggers a retry if verbose_retry is set to true
|
||||
config.display_try_failure_messages = true
|
||||
|
||||
config.around do |example|
|
||||
retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 3
|
||||
example.run_with_retry retry: retry_times
|
||||
end
|
||||
end
|
||||
|
||||
# Skip tests in quarantine unless we explicitly focus on them.
|
||||
|
|
|
@ -28,6 +28,22 @@ describe 'rspec config tests' do
|
|||
end
|
||||
end
|
||||
|
||||
let(:group_2) do
|
||||
RSpec.describe do
|
||||
before(:all) do
|
||||
@expectations = [1, 2, 3]
|
||||
end
|
||||
|
||||
example 'not in quarantine' do
|
||||
expect(@expectations.shift).to be(3)
|
||||
end
|
||||
|
||||
example 'in quarantine', :quarantine do
|
||||
expect(@expectations.shift).to be(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no tags focussed' do
|
||||
before do
|
||||
group.run
|
||||
|
@ -301,4 +317,39 @@ describe 'rspec config tests' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'rspec retry' do
|
||||
context 'in an untagged context' do
|
||||
before do
|
||||
group_2.run
|
||||
end
|
||||
|
||||
it 'should run example :retry times' do
|
||||
examples = group_2.descendant_filtered_examples
|
||||
ex = examples.find { |e| e.description == 'not in quarantine' }
|
||||
expect(ex.execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :quarantine focussed' do
|
||||
before do
|
||||
RSpec.configure do |config|
|
||||
config.inclusion_filter = :quarantine
|
||||
end
|
||||
group_2.run
|
||||
end
|
||||
|
||||
after do
|
||||
RSpec.configure do |config|
|
||||
config.inclusion_filter.clear
|
||||
end
|
||||
end
|
||||
|
||||
it 'should run example once only' do
|
||||
examples = group_2.descendant_filtered_examples
|
||||
ex = examples.find { |e| e.description == 'in quarantine' }
|
||||
expect(ex.execution_result.status).to eq(:failed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ describe Dashboard::MilestonesController do
|
|||
)
|
||||
end
|
||||
let(:issue) { create(:issue, project: project, milestone: project_milestone) }
|
||||
let(:group_issue) { create(:issue, milestone: group_milestone) }
|
||||
let(:group_issue) { create(:issue, milestone: group_milestone, project: create(:project, group: group)) }
|
||||
|
||||
let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) }
|
||||
let!(:group_label) { create(:group_label, group: group, title: 'Group Issue Label', issues: [group_issue]) }
|
||||
|
|
|
@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do
|
|||
let(:token) { 'token' }
|
||||
let(:expires_at) { 1.hour.since.strftime('%s') }
|
||||
|
||||
subject { get :callback, params: { code: 'xxx', state: @state } }
|
||||
subject { get :callback, params: { code: 'xxx', state: state } }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
@ -15,6 +15,26 @@ describe GoogleApi::AuthorizationsController do
|
|||
.to receive(:get_token).and_return([token, expires_at])
|
||||
end
|
||||
|
||||
shared_examples_for 'access denied' do
|
||||
it 'returns a 404' do
|
||||
subject
|
||||
|
||||
expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'session key is present' do
|
||||
let(:session_key) { 'session-key' }
|
||||
let(:redirect_uri) { 'example.com' }
|
||||
|
||||
before do
|
||||
session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri
|
||||
end
|
||||
|
||||
context 'session key matches state param' do
|
||||
let(:state) { session_key }
|
||||
|
||||
it 'sets token and expires_at in session' do
|
||||
subject
|
||||
|
||||
|
@ -24,26 +44,28 @@ describe GoogleApi::AuthorizationsController do
|
|||
.to eq(expires_at)
|
||||
end
|
||||
|
||||
context 'when redirect uri key is stored in state' do
|
||||
set(:project) { create(:project) }
|
||||
let(:redirect_uri) { project_clusters_url(project).to_s }
|
||||
|
||||
before do
|
||||
@state = GoogleApi::CloudPlatform::Client
|
||||
.new_session_key_for_redirect_uri do |key|
|
||||
session[key] = redirect_uri
|
||||
end
|
||||
end
|
||||
|
||||
it 'redirects to the URL stored in state param' do
|
||||
expect(subject).to redirect_to(redirect_uri)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when redirection url is not stored in state' do
|
||||
it 'redirects to root_path' do
|
||||
expect(subject).to redirect_to(root_path)
|
||||
context 'session key does not match state param' do
|
||||
let(:state) { 'bad-key' }
|
||||
|
||||
it_behaves_like 'access denied'
|
||||
end
|
||||
|
||||
context 'state param is blank' do
|
||||
let(:state) { '' }
|
||||
|
||||
it_behaves_like 'access denied'
|
||||
end
|
||||
end
|
||||
|
||||
context 'state param is present, but session key is blank' do
|
||||
let(:state) { 'session-key' }
|
||||
|
||||
it_behaves_like 'access denied'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ describe Groups::SharedProjectsController do
|
|||
end
|
||||
|
||||
def share_project(project)
|
||||
group.add_developer(user)
|
||||
|
||||
Projects::GroupLinks::CreateService.new(
|
||||
project,
|
||||
user,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::AutocompleteSourcesController do
|
||||
describe 'GET milestones' do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group, :public) }
|
||||
let(:project) { create(:project, :public, namespace: group) }
|
||||
let!(:project_milestone) { create(:milestone, project: project) }
|
||||
let!(:group_milestone) { create(:milestone, group: group) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'lists milestones' do
|
||||
group.add_owner(user)
|
||||
|
||||
get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path }
|
||||
|
||||
milestone_titles = json_response.map { |milestone| milestone["title"] }
|
||||
expect(milestone_titles).to match_array([project_milestone.title, group_milestone.title])
|
||||
end
|
||||
|
||||
context 'when user cannot read project issues and merge requests' do
|
||||
it 'renders 404' do
|
||||
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||
|
||||
get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path }
|
||||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -65,8 +65,24 @@ describe Projects::GroupLinksController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when user does not have access to the public group' do
|
||||
let(:group) { create(:group, :public) }
|
||||
|
||||
include_context 'link project to group'
|
||||
|
||||
it 'renders 404' do
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
|
||||
it 'does not share project with that group' do
|
||||
expect(group.shared_projects).not_to include project
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project group id equal link group id' do
|
||||
before do
|
||||
group2.add_developer(user)
|
||||
|
||||
post(:create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
|
@ -102,5 +118,26 @@ describe Projects::GroupLinksController do
|
|||
expect(flash[:alert]).to eq('Please select a group.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when link is not persisted in the database' do
|
||||
before do
|
||||
allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute)
|
||||
.and_return({ status: :error, http_status: 409, message: 'error' })
|
||||
|
||||
post(:create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
link_group_id: group.id,
|
||||
link_group_access: ProjectGroupLink.default_access
|
||||
})
|
||||
end
|
||||
|
||||
it 'redirects to project group links page' do
|
||||
expect(response).to redirect_to(
|
||||
project_project_members_path(project)
|
||||
)
|
||||
expect(flash[:alert]).to eq('error')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -205,6 +205,8 @@ describe SnippetsController do
|
|||
end
|
||||
|
||||
context 'when the snippet description contains a file' do
|
||||
include FileMoverHelpers
|
||||
|
||||
let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
|
||||
let(:text_file) { '/-/system/temp/secret78/text.txt' }
|
||||
let(:description) do
|
||||
|
@ -215,6 +217,8 @@ describe SnippetsController do
|
|||
before do
|
||||
allow(FileUtils).to receive(:mkdir_p)
|
||||
allow(FileUtils).to receive(:move)
|
||||
stub_file_mover(text_file)
|
||||
stub_file_mover(picture_file)
|
||||
end
|
||||
|
||||
subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
|
||||
|
|
|
@ -233,8 +233,8 @@ describe 'Issues' do
|
|||
created_at: Time.now - (index * 60))
|
||||
end
|
||||
end
|
||||
let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
|
||||
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
|
||||
let(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') }
|
||||
let(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') }
|
||||
|
||||
it 'sorts by newest' do
|
||||
visit project_issues_path(project, sort: sort_value_created_date)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'Merge request > User sees versions', :js do
|
||||
let(:merge_request) { create(:merge_request, importing: true) }
|
||||
let(:merge_request) do
|
||||
create(:merge_request).tap do |mr|
|
||||
mr.merge_request_diff.destroy
|
||||
end
|
||||
end
|
||||
let(:project) { merge_request.source_project }
|
||||
let(:user) { project.creator }
|
||||
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
|
||||
|
|
|
@ -13,7 +13,7 @@ describe 'Merge requests > User lists merge requests' do
|
|||
source_project: project,
|
||||
source_branch: 'fix',
|
||||
assignee: user,
|
||||
milestone: create(:milestone, due_date: '2013-12-11'),
|
||||
milestone: create(:milestone, project: project, due_date: '2013-12-11'),
|
||||
created_at: 1.minute.ago,
|
||||
updated_at: 1.minute.ago)
|
||||
create(:merge_request,
|
||||
|
@ -21,7 +21,7 @@ describe 'Merge requests > User lists merge requests' do
|
|||
source_project: project,
|
||||
source_branch: 'markdown',
|
||||
assignee: user,
|
||||
milestone: create(:milestone, due_date: '2013-12-12'),
|
||||
milestone: create(:milestone, project: project, due_date: '2013-12-12'),
|
||||
created_at: 2.minutes.ago,
|
||||
updated_at: 2.minutes.ago)
|
||||
create(:merge_request,
|
||||
|
|
|
@ -7,6 +7,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
let(:admin) { create(:admin) }
|
||||
|
||||
around do |example|
|
||||
Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
|
||||
example.run
|
||||
|
@ -16,6 +18,7 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
|||
it 'User sees their active sessions' do
|
||||
Capybara::Session.new(:session1)
|
||||
Capybara::Session.new(:session2)
|
||||
Capybara::Session.new(:session3)
|
||||
|
||||
# note: headers can only be set on the non-js (aka. rack-test) driver
|
||||
using_session :session1 do
|
||||
|
@ -37,9 +40,27 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
|||
gitlab_sign_in(user)
|
||||
end
|
||||
|
||||
# set an admin session impersonating the user
|
||||
using_session :session3 do
|
||||
Capybara.page.driver.header(
|
||||
'User-Agent',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
|
||||
)
|
||||
|
||||
gitlab_sign_in(admin)
|
||||
|
||||
visit admin_user_path(user)
|
||||
|
||||
click_link 'Impersonate'
|
||||
end
|
||||
|
||||
using_session :session1 do
|
||||
visit profile_active_sessions_path
|
||||
|
||||
expect(page).to(
|
||||
have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
|
||||
count: 2 }))
|
||||
|
||||
expect(page).to have_content(
|
||||
'127.0.0.1 ' \
|
||||
'This is your current session ' \
|
||||
|
@ -57,33 +78,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
|||
)
|
||||
|
||||
expect(page).to have_selector '[title="Smartphone"]', count: 1
|
||||
end
|
||||
end
|
||||
|
||||
it 'User can revoke a session', :js, :redis_session_store do
|
||||
Capybara::Session.new(:session1)
|
||||
Capybara::Session.new(:session2)
|
||||
|
||||
# set an additional session in another browser
|
||||
using_session :session2 do
|
||||
gitlab_sign_in(user)
|
||||
end
|
||||
|
||||
using_session :session1 do
|
||||
gitlab_sign_in(user)
|
||||
visit profile_active_sessions_path
|
||||
|
||||
expect(page).to have_link('Revoke', count: 1)
|
||||
|
||||
accept_confirm { click_on 'Revoke' }
|
||||
|
||||
expect(page).not_to have_link('Revoke')
|
||||
end
|
||||
|
||||
using_session :session2 do
|
||||
visit profile_active_sessions_path
|
||||
|
||||
expect(page).to have_content('You need to sign in or sign up before continuing.')
|
||||
expect(page).not_to have_content('Chrome on Windows')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -548,10 +548,7 @@ describe 'File blob', :js do
|
|||
it 'displays an auxiliary viewer' do
|
||||
aggregate_failures do
|
||||
# shows names of dependency manager and package
|
||||
expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.')
|
||||
|
||||
# shows a link to the gem
|
||||
expect(page).to have_link('activerecord', href: 'https://rubygems.org/gems/activerecord')
|
||||
expect(page).to have_content('This project manages its dependencies using RubyGems.')
|
||||
|
||||
# shows a learn more link
|
||||
expect(page).to have_link('Learn more', href: 'https://rubygems.org/')
|
||||
|
|
|
@ -27,6 +27,7 @@ describe 'Project > Members > Invite group', :js do
|
|||
|
||||
before do
|
||||
project.add_maintainer(maintainer)
|
||||
group_to_share_with.add_guest(maintainer)
|
||||
sign_in(maintainer)
|
||||
end
|
||||
|
||||
|
@ -112,6 +113,7 @@ describe 'Project > Members > Invite group', :js do
|
|||
|
||||
before do
|
||||
project.add_maintainer(maintainer)
|
||||
group.add_guest(maintainer)
|
||||
sign_in(maintainer)
|
||||
|
||||
visit project_settings_members_path(project)
|
||||
|
|
|
@ -10,6 +10,7 @@ describe 'Projects > Settings > User manages group links' do
|
|||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
group_market.add_guest(user)
|
||||
sign_in(user)
|
||||
|
||||
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
|
||||
|
|
|
@ -93,4 +93,29 @@ describe 'Private Group access' do
|
|||
it { is_expected.to be_denied_for(:visitor) }
|
||||
it { is_expected.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
describe 'GET /groups/:path for shared projects' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
before do
|
||||
Projects::GroupLinks::CreateService.new(
|
||||
project,
|
||||
create(:user),
|
||||
link_group_access: ProjectGroupLink::DEVELOPER
|
||||
).execute(group)
|
||||
end
|
||||
|
||||
subject { group_path(group) }
|
||||
|
||||
it { is_expected.to be_allowed_for(:admin) }
|
||||
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||
it { is_expected.to be_allowed_for(:reporter).of(group) }
|
||||
it { is_expected.to be_allowed_for(:guest).of(group) }
|
||||
it { is_expected.to be_denied_for(project_guest) }
|
||||
it { is_expected.to be_denied_for(:user) }
|
||||
it { is_expected.to be_denied_for(:external) }
|
||||
it { is_expected.to be_denied_for(:visitor) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ describe MergeRequestsFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context "multiple projects with merge requests" do
|
||||
let(:user) { create :user }
|
||||
let(:user2) { create :user }
|
||||
|
||||
|
@ -31,7 +32,7 @@ describe MergeRequestsFinder do
|
|||
p
|
||||
end
|
||||
end
|
||||
let(:project4) { create_project_without_n_plus_1(group: subgroup) }
|
||||
let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) }
|
||||
let(:project5) { create_project_without_n_plus_1(group: subgroup) }
|
||||
let(:project6) { create_project_without_n_plus_1(group: subgroup) }
|
||||
|
||||
|
@ -55,7 +56,7 @@ describe MergeRequestsFinder do
|
|||
project6.add_developer(user)
|
||||
end
|
||||
|
||||
describe "#execute" do
|
||||
describe '#execute' do
|
||||
it 'filters by scope' do
|
||||
params = { scope: 'authored', state: 'opened' }
|
||||
merge_requests = described_class.new(user, params).execute
|
||||
|
@ -68,6 +69,15 @@ describe MergeRequestsFinder do
|
|||
expect(merge_requests.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'filters by commit sha' do
|
||||
merge_requests = described_class.new(
|
||||
user,
|
||||
commit_sha: merge_request5.merge_request_diff.last_commit_sha
|
||||
).execute
|
||||
|
||||
expect(merge_requests).to contain_exactly(merge_request5)
|
||||
end
|
||||
|
||||
context 'filtering by group' do
|
||||
it 'includes all merge requests when user has access' do
|
||||
params = { group_id: group.id }
|
||||
|
@ -284,4 +294,128 @@ describe MergeRequestsFinder do
|
|||
expect(finder.row_count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when projects require different access levels for merge requests' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let(:public_project) { create(:project, :public) }
|
||||
let(:internal) { create(:project, :internal) }
|
||||
let(:private_project) { create(:project, :private) }
|
||||
let(:public_with_private_repo) { create(:project, :public, :repository, :repository_private) }
|
||||
let(:internal_with_private_repo) { create(:project, :internal, :repository, :repository_private) }
|
||||
|
||||
let(:merge_requests) { described_class.new(user, {}).execute }
|
||||
|
||||
let!(:mr_public) { create(:merge_request, source_project: public_project) }
|
||||
let!(:mr_private) { create(:merge_request, source_project: private_project) }
|
||||
let!(:mr_internal) { create(:merge_request, source_project: internal) }
|
||||
let!(:mr_private_repo_access) { create(:merge_request, source_project: public_with_private_repo) }
|
||||
let!(:mr_internal_private_repo_access) { create(:merge_request, source_project: internal_with_private_repo) }
|
||||
|
||||
context 'with admin user' do
|
||||
let(:user) { create(:user, :admin) }
|
||||
|
||||
it 'returns all merge requests' do
|
||||
expect(merge_requests).to eq(
|
||||
[mr_internal_private_repo_access, mr_private_repo_access, mr_internal, mr_private, mr_public]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project restricts merge requests' do
|
||||
let(:non_member) { create(:user) }
|
||||
let(:project) { create(:project, :repository, :public, :merge_requests_private) }
|
||||
let!(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
it "returns nothing to to non members" do
|
||||
merge_requests = described_class.new(
|
||||
non_member,
|
||||
project_id: project.id
|
||||
).execute
|
||||
|
||||
expect(merge_requests).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with external user' do
|
||||
let(:user) { create(:user, :external) }
|
||||
|
||||
it 'returns only public merge requests' do
|
||||
expect(merge_requests).to eq([mr_public])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with authenticated user' do
|
||||
it 'returns public and internal merge requests' do
|
||||
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||
end
|
||||
|
||||
context 'being added to the private project' do
|
||||
context 'as a guest' do
|
||||
before do
|
||||
private_project.add_guest(user)
|
||||
end
|
||||
|
||||
it 'does not return merge requests from the private project' do
|
||||
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a developer' do
|
||||
before do
|
||||
private_project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns merge requests from the private project' do
|
||||
expect(merge_requests).to eq([mr_internal, mr_private, mr_public])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'being added to the public project with private repo access' do
|
||||
context 'as a guest' do
|
||||
before do
|
||||
public_with_private_repo.add_guest(user)
|
||||
end
|
||||
|
||||
it 'returns merge requests from the project' do
|
||||
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a reporter' do
|
||||
before do
|
||||
public_with_private_repo.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'returns merge requests from the project' do
|
||||
expect(merge_requests).to eq([mr_private_repo_access, mr_internal, mr_public])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'being added to the internal project with private repo access' do
|
||||
context 'as a guest' do
|
||||
before do
|
||||
internal_with_private_repo.add_guest(user)
|
||||
end
|
||||
|
||||
it 'returns merge requests from the project' do
|
||||
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a reporter' do
|
||||
before do
|
||||
internal_with_private_repo.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'returns merge requests from the project' do
|
||||
expect(merge_requests).to eq([mr_internal_private_repo_access, mr_internal, mr_public])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,6 +16,10 @@ describe Constraints::ProjectUrlConstrainer do
|
|||
let(:request) { build_request('foo', 'bar') }
|
||||
|
||||
it { expect(subject.matches?(request)).to be_falsey }
|
||||
|
||||
context 'existence_check is false' do
|
||||
it { expect(subject.matches?(request, existence_check: false)).to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
context "project id ending with .git" do
|
||||
|
|
|
@ -50,8 +50,8 @@ describe Gitlab::DependencyLinker::ComposerJsonLinker do
|
|||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||
end
|
||||
|
||||
it 'links the module name' do
|
||||
expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
|
||||
it 'does not link the module name' do
|
||||
expect(subject).not_to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
|
||||
end
|
||||
|
||||
it 'links the homepage' do
|
||||
|
|
|
@ -41,13 +41,16 @@ describe Gitlab::DependencyLinker::GemfileLinker do
|
|||
end
|
||||
|
||||
it 'links dependencies' do
|
||||
expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails'))
|
||||
expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer'))
|
||||
expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders'))
|
||||
expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets'))
|
||||
expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for'))
|
||||
end
|
||||
|
||||
it 'links to external dependencies' do
|
||||
expect(subject).to include(link('rails', 'https://github.com/rails/rails'))
|
||||
expect(subject).to include(link('responders', 'https://github.com/rails/responders'))
|
||||
expect(subject).to include(link('sprockets', 'https://gitlab.example.com/gems/sprockets'))
|
||||
end
|
||||
|
||||
it 'links GitHub repos' do
|
||||
expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails'))
|
||||
expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders'))
|
||||
|
|
|
@ -43,8 +43,8 @@ describe Gitlab::DependencyLinker::GemspecLinker do
|
|||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||
end
|
||||
|
||||
it 'links the gem name' do
|
||||
expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
|
||||
it 'does not link the gem name' do
|
||||
expect(subject).not_to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
|
||||
end
|
||||
|
||||
it 'links the license' do
|
||||
|
|
|
@ -33,7 +33,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
|
|||
"express": "4.2.x",
|
||||
"bigpipe": "bigpipe/pagelet",
|
||||
"plates": "https://github.com/flatiron/plates/tarball/master",
|
||||
"karma": "^1.4.1"
|
||||
"karma": "^1.4.1",
|
||||
"random": "git+https://EdOverflow@github.com/example/example.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vows": "^0.7.0",
|
||||
|
@ -51,8 +52,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
|
|||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||
end
|
||||
|
||||
it 'links the module name' do
|
||||
expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name'))
|
||||
it 'does not link the module name' do
|
||||
expect(subject).not_to include(link('module-name', 'https://npmjs.com/package/module-name'))
|
||||
end
|
||||
|
||||
it 'links the homepage' do
|
||||
|
@ -71,14 +72,21 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
|
|||
expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
|
||||
expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
|
||||
expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
|
||||
expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe'))
|
||||
expect(subject).to include(link('plates', 'https://npmjs.com/package/plates'))
|
||||
expect(subject).to include(link('karma', 'https://npmjs.com/package/karma'))
|
||||
expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
|
||||
expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
|
||||
expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
|
||||
end
|
||||
|
||||
it 'links dependencies to URL detected on value' do
|
||||
expect(subject).to include(link('bigpipe', 'https://github.com/bigpipe/pagelet'))
|
||||
expect(subject).to include(link('plates', 'https://github.com/flatiron/plates/tarball/master'))
|
||||
end
|
||||
|
||||
it 'does not link to NPM when invalid git URL' do
|
||||
expect(subject).not_to include(link('random', 'https://npmjs.com/package/random'))
|
||||
end
|
||||
|
||||
it 'links GitHub repos' do
|
||||
expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
|
||||
end
|
||||
|
|
42
spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
Normal file
42
spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Gitlab::DependencyLinker::Parser::Gemfile do
|
||||
describe '#parse' do
|
||||
let(:file_content) do
|
||||
<<-CONTENT.strip_heredoc
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem "rails", '4.2.6', github: "rails/rails"
|
||||
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||
gem 'responders', '~> 2.0', :github => 'rails/responders'
|
||||
gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets'
|
||||
gem 'default_value_for', '~> 3.0.0'
|
||||
CONTENT
|
||||
end
|
||||
|
||||
subject { described_class.new(file_content).parse(keyword: 'gem') }
|
||||
|
||||
def fetch_package(name)
|
||||
subject.find { |package| package.name == name }
|
||||
end
|
||||
|
||||
it 'returns parsed packages' do
|
||||
expect(subject.size).to eq(5)
|
||||
expect(subject).to all(be_a(Gitlab::DependencyLinker::Package))
|
||||
end
|
||||
|
||||
it 'packages respond to name and external_ref accordingly' do
|
||||
expect(fetch_package('rails')).to have_attributes(name: 'rails',
|
||||
github_ref: 'rails/rails',
|
||||
git_ref: nil)
|
||||
|
||||
expect(fetch_package('sprockets')).to have_attributes(name: 'sprockets',
|
||||
github_ref: nil,
|
||||
git_ref: 'https://gitlab.example.com/gems/sprockets')
|
||||
|
||||
expect(fetch_package('default_value_for')).to have_attributes(name: 'default_value_for',
|
||||
github_ref: nil,
|
||||
git_ref: nil)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -43,7 +43,10 @@ describe Gitlab::DependencyLinker::PodfileLinker do
|
|||
|
||||
it 'links packages' do
|
||||
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
|
||||
expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar'))
|
||||
end
|
||||
|
||||
it 'links external packages' do
|
||||
expect(subject).to include(link('Interstellar/Core', 'https://github.com/ashfurrow/Interstellar.git'))
|
||||
end
|
||||
|
||||
it 'links Git repos' do
|
||||
|
|
|
@ -42,8 +42,8 @@ describe Gitlab::DependencyLinker::PodspecLinker do
|
|||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||
end
|
||||
|
||||
it 'links the gem name' do
|
||||
expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
|
||||
it 'does not link the pod name' do
|
||||
expect(subject).not_to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
|
||||
end
|
||||
|
||||
it 'links the license' do
|
||||
|
|
|
@ -41,4 +41,20 @@ describe Gitlab::ImportExport::MergeRequestParser do
|
|||
|
||||
expect(parsed_merge_request).to eq(merge_request)
|
||||
end
|
||||
|
||||
context 'when the merge request has diffs' do
|
||||
let(:merge_request) do
|
||||
build(:merge_request, source_project: forked_project, target_project: project)
|
||||
end
|
||||
|
||||
context 'when the diff is invalid' do
|
||||
let(:merge_request_diff) { build(:merge_request_diff, merge_request: merge_request, base_commit_sha: 'foobar') }
|
||||
|
||||
it 'sets the diff to nil' do
|
||||
expect(merge_request_diff).to be_invalid
|
||||
expect(merge_request_diff.merge_request).to eq merge_request
|
||||
expect(parsed_merge_request.merge_request_diff).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,16 @@ describe Gitlab::ImportExport::Shared do
|
|||
expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]'])
|
||||
end
|
||||
|
||||
it 'updates the import JID' do
|
||||
import_state = create(:import_state, project: project, jid: 'jid-test')
|
||||
|
||||
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
|
||||
expect(logger).to receive(:error).with(hash_including(import_jid: import_state.jid))
|
||||
end
|
||||
|
||||
subject.error(error)
|
||||
end
|
||||
|
||||
it 'calls the error logger with the full message' do
|
||||
expect(subject).to receive(:log_error).with(hash_including(message: error.message))
|
||||
|
||||
|
|
|
@ -50,6 +50,36 @@ describe Gitlab::Kubernetes::KubeClient do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
shared_examples 'local address' do
|
||||
it 'blocks local addresses' do
|
||||
expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
|
||||
end
|
||||
|
||||
context 'when local requests are allowed' do
|
||||
before do
|
||||
stub_application_setting(allow_local_requests_from_hooks_and_services: true)
|
||||
end
|
||||
|
||||
it 'allows local addresses' do
|
||||
expect { client }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'localhost address' do
|
||||
let(:api_url) { 'http://localhost:22' }
|
||||
|
||||
it_behaves_like 'local address'
|
||||
end
|
||||
|
||||
context 'private network address' do
|
||||
let(:api_url) { 'http://192.168.1.2:3003' }
|
||||
|
||||
it_behaves_like 'local address'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#core_client' do
|
||||
subject { client.core_client }
|
||||
|
||||
|
|
|
@ -194,6 +194,11 @@ describe Notify do
|
|||
let(:new_issue) { create(:issue) }
|
||||
subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) }
|
||||
|
||||
context 'when a user has permissions to access the new issue' do
|
||||
before do
|
||||
new_issue.project.add_developer(recipient)
|
||||
end
|
||||
|
||||
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
|
||||
let(:model) { issue }
|
||||
end
|
||||
|
@ -213,6 +218,31 @@ describe Notify do
|
|||
is_expected.to have_body_text(project_issue_path(project, issue))
|
||||
end
|
||||
end
|
||||
|
||||
it 'contains the issue title' do
|
||||
is_expected.to have_body_text new_issue.title
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user does not permissions to access the new issue' do
|
||||
it 'has the correct subject and body' do
|
||||
new_issue_url = project_issue_path(new_issue.project, new_issue)
|
||||
|
||||
aggregate_failures do
|
||||
is_expected.to have_referable_subject(issue, reply: true)
|
||||
is_expected.not_to have_body_text(new_issue_url)
|
||||
is_expected.to have_body_text(project_issue_path(project, issue))
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not contain the issue title' do
|
||||
is_expected.not_to have_body_text new_issue.title
|
||||
end
|
||||
|
||||
it 'contains information about missing permissions' do
|
||||
is_expected.to have_body_text "You don't have access to the project."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,10 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
let(:session) { double(:session, id: '6919a6f1bb119dd7396fadc38fd18d0d') }
|
||||
let(:session) do
|
||||
double(:session, { id: '6919a6f1bb119dd7396fadc38fd18d0d',
|
||||
'[]': {} })
|
||||
end
|
||||
|
||||
let(:request) do
|
||||
double(:request, {
|
||||
|
|
|
@ -98,6 +98,22 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
|
|||
|
||||
it { expect(kubernetes.save).to be_truthy }
|
||||
end
|
||||
|
||||
context 'when api_url is localhost' do
|
||||
let(:api_url) { 'http://localhost:22' }
|
||||
|
||||
it { expect(kubernetes.save).to be_falsey }
|
||||
|
||||
context 'Application settings allows local requests' do
|
||||
before do
|
||||
allow(ApplicationSetting)
|
||||
.to receive(:current)
|
||||
.and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
|
||||
end
|
||||
|
||||
it { expect(kubernetes.save).to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates token' do
|
||||
|
|
|
@ -32,6 +32,7 @@ describe Issuable do
|
|||
end
|
||||
|
||||
describe "Validation" do
|
||||
context 'general validations' do
|
||||
subject { build(:issue) }
|
||||
|
||||
before do
|
||||
|
@ -45,6 +46,44 @@ describe Issuable do
|
|||
it { is_expected.to validate_length_of(:title).is_at_most(255) }
|
||||
end
|
||||
|
||||
describe 'milestone' do
|
||||
let(:project) { create(:project) }
|
||||
let(:milestone_id) { create(:milestone, project: project).id }
|
||||
let(:params) do
|
||||
{
|
||||
title: 'something',
|
||||
project: project,
|
||||
author: build(:user),
|
||||
milestone_id: milestone_id
|
||||
}
|
||||
end
|
||||
|
||||
subject { issuable_class.new(params) }
|
||||
|
||||
context 'with correct params' do
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'with empty string milestone' do
|
||||
let(:milestone_id) { '' }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'with nil milestone id' do
|
||||
let(:milestone_id) { nil }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'with a milestone id from another project' do
|
||||
let(:milestone_id) { create(:milestone).id }
|
||||
|
||||
it { is_expected.to be_invalid }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Scope" do
|
||||
subject { build(:issue) }
|
||||
|
||||
|
@ -66,6 +105,48 @@ describe Issuable do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#milestone_available?' do
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
def build_issuable(milestone_id)
|
||||
issuable_class.new(project: project, milestone_id: milestone_id)
|
||||
end
|
||||
|
||||
it 'returns true with a milestone from the issue project' do
|
||||
milestone = create(:milestone, project: project)
|
||||
|
||||
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true with a milestone from the issue project group' do
|
||||
milestone = create(:milestone, group: group)
|
||||
|
||||
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do
|
||||
parent = create(:group)
|
||||
group.update(parent: parent)
|
||||
milestone = create(:milestone, group: parent)
|
||||
|
||||
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false with a milestone from another project' do
|
||||
milestone = create(:milestone)
|
||||
|
||||
expect(build_issuable(milestone.id).milestone_available?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false with a milestone from another group' do
|
||||
milestone = create(:milestone, group: create(:group))
|
||||
|
||||
expect(build_issuable(milestone.id).milestone_available?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe ".search" do
|
||||
let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
|
||||
let!(:searchable_issue2) { create(:issue, title: 'Aw') }
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue