New upstream version 8.13.6+dfsg1

This commit is contained in:
Praveen Arimbrathodiyil 2016-11-24 13:41:30 +05:30
parent dbe389e2f0
commit 819c14ecb5
153 changed files with 1837 additions and 563 deletions

View file

@ -1,5 +1,43 @@
Please view this file on the master branch, on stable branches it's out of date.
## 8.13.6 (2016-11-17)
- Omniauth auto link LDAP user falls back to find by DN when user cannot be found by UID. !7002
- Fix no "Register" tab if ldap auth is enabled (#24038). !7274 (Luc Didry)
- Fix cache for commit status in commits list to respect branches. !7372
- Fix issue causing Labels not to appear in sidebar on MR page. !7416 (Alex Sanford)
- Limit labels returned for a specific project as an administrator. !7496
- Clicking "force remove source branch" label now toggles the checkbox again.
- Allow commit note to be visible if repo is visible.
- Fix project Visibility Level selector not using default values.
## 8.13.5 (2016-11-08)
- Restore unauthenticated access to public container registries
## 8.13.4 (2016-11-07)
- Fix showing pipeline status for a given commit from correct branch. !7034
- Only skip group when it's actually a group in the "Share with group" select. !7262
- Introduce round-robin project creation to spread load over multiple shards. !7266
- Ensure merge request's "remove branch" accessors return booleans. !7267
- Ensure external users are not able to clone disabled repositories.
- Fix XSS issue in Markdown autolinker.
- Respect event visibility in Gitlab::ContributionsCalendar.
- Honour issue and merge request visibility in their respective finders.
- Disable reference Markdown for unavailable features.
- Fix lightweight tags not processed correctly by GitTagPushService. !6532
- Allow owners to fetch source code in CI builds. !6943
- Return conflict error in label API when title is taken by group label. !7014
- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project. !7123
- Fix builds tab visibility. !7178
- Fix project features default values. !7181
## 8.13.3 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
## 8.13.2 (2016-10-31)
- Fix encoding issues on pipeline commits. !6832

View file

@ -51,7 +51,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.6.8'
gem 'gitlab_git', '~> 10.7.0'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes

View file

@ -283,7 +283,7 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-markup (1.5.0)
gitlab_git (10.6.8)
gitlab_git (10.7.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@ -867,7 +867,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.0)
gitlab_git (~> 10.6.8)
gitlab_git (~> 10.7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
@ -994,4 +994,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.13.2
1.13.5

View file

@ -1 +1 @@
8.13.3
8.13.6

View file

@ -127,19 +127,30 @@
return $(document).off('scroll');
};
window.shiftWindow = function() {
return scrollBy(0, -100);
};
document.addEventListener("page:fetch", unbindEvents);
window.addEventListener("hashchange", shiftWindow);
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
window.adjustScroll = function() {
var navbar = document.querySelector('.navbar-gitlab');
var subnav = document.querySelector('.layout-nav');
var fixedTabs = document.querySelector('.js-tabs-affix');
window.onload = function() {
adjustment = 0;
if (navbar) adjustment -= navbar.offsetHeight;
if (subnav) adjustment -= subnav.offsetHeight;
if (fixedTabs) adjustment -= fixedTabs.offsetHeight;
return scrollBy(0, adjustment);
};
window.addEventListener("hashchange", adjustScroll);
window.onload = function () {
// Scroll the window to avoid the topnav bar
// https://github.com/twitter/bootstrap/issues/1768
if (location.hash) {
return setTimeout(shiftWindow, 100);
return setTimeout(adjustScroll, 100);
}
};

View file

@ -129,7 +129,7 @@
MergeRequestTabs.prototype.scrollToElement = function(container) {
var $el, navBarHeight;
if (window.location.hash) {
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + document.querySelector('.js-tabs-affix').offsetHeight;
$el = $(container + " " + window.location.hash + ":not(.match)");
if ($el.length) {
return $.scrollTo(container + " " + window.location.hash + ":not(.match)", {

View file

@ -116,8 +116,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_packet_size,
:send_user_confirmation_email,
:container_registry_token_expire_delay,
:repository_storage,
:enabled_git_access_protocol,
repository_storages: [],
restricted_visibility_levels: [],
import_sources: [],
disabled_oauth_sign_in_sources: []

View file

@ -192,9 +192,10 @@ class ApplicationController < ActionController::Base
end
# JSON for infinite scroll via Pager object
def pager_json(partial, count)
def pager_json(partial, count, locals = {})
html = render_to_string(
partial,
locals: locals,
layout: false,
formats: [:html]
)

View file

@ -12,7 +12,7 @@ class JwtController < ApplicationController
return head :not_found unless service
result = service.new(@authentication_result.project, @authentication_result.actor, auth_params).
execute(authentication_abilities: @authentication_result.authentication_abilities || [])
execute(authentication_abilities: @authentication_result.authentication_abilities)
render json: result, status: result[:http_status]
end
@ -20,7 +20,7 @@ class JwtController < ApplicationController
private
def authenticate_project_or_user
@authentication_result = Gitlab::Auth::Result.new
@authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_authentication_abilities)
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)

View file

@ -26,8 +26,15 @@ class Projects::CommitsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false }
format.json do
pager_json(
'projects/commits/_commits',
@commits.size,
project: @project,
ref: @ref)
end
end
end
end

View file

@ -21,10 +21,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController
def authenticate_user
@authentication_result = Gitlab::Auth::Result.new
if project && project.public? && download_request?
return # Allow access
end
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
@ -41,6 +37,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_final_spnego_response
return # Allow access
end
elsif project && download_request? && Guest.can?(:download_code, project)
@authentication_result = Gitlab::Auth::Result.new(nil, project, :none, [:download_code])
return # Allow access
end
send_challenges

View file

@ -78,11 +78,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def upload_pack_allowed?
return false unless Gitlab.config.gitlab_shell.upload_pack
if user
access_check.allowed?
else
ci? || project.public?
end
access_check.allowed? || ci?
end
def access

View file

@ -7,7 +7,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
@group_links = project.project_group_links.all
@skip_groups = @group_links.pluck(:group_id)
@skip_groups << project.group.try(:id)
@skip_groups << project.namespace_id unless project.personal?
end
def create

View file

@ -126,7 +126,7 @@ class Projects::LabelsController < Projects::ApplicationController
alias_method :subscribable_resource, :label
def find_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities)
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end
def authorize_admin_labels!

View file

@ -31,10 +31,6 @@ class Projects::LfsApiController < Projects::GitHttpClientController
private
def objects
@objects ||= (params[:objects] || []).to_a
end
def existing_oids
@existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)

View file

@ -352,13 +352,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def branch_from
# This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
if params[:ref].present?
@ref = params[:ref]
@commit = @repository.commit(@ref)
end
render layout: false
end
def branch_to
@target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
if params[:ref].present?
@ref = params[:ref]
@commit = @target_project.commit(@ref)
end
render layout: false
end
@ -497,6 +507,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.close
end
labels
define_pipelines_vars
end

View file

@ -23,7 +23,7 @@ class Projects::TagsController < Projects::ApplicationController
return render_404 unless @tag
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target)
@commit = @repository.commit(@tag.dereferenced_target)
end
def create

View file

@ -287,7 +287,8 @@ class ProjectsController < Projects::ApplicationController
render 'projects/empty' if @project.empty_repo?
else
if @project.wiki_enabled?
@wiki_home = @project.wiki.find_page('home', params[:version_id])
@project_wiki = @project.wiki
@wiki_home = @project_wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
@issues = issues_collection
@issues = @issues.page(params[:page])

View file

@ -104,8 +104,7 @@ class UsersController < ApplicationController
end
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.
new(contributed_projects, user)
@contributions_calendar ||= Gitlab::ContributionsCalendar.new(user, current_user)
end
def load_events

View file

@ -61,31 +61,26 @@ class IssuableFinder
def project
return @project if defined?(@project)
if project?
@project = Project.find(params[:project_id])
project = Project.find(params[:project_id])
project = nil unless Ability.allowed?(current_user, :"read_#{klass.to_ability_name}", project)
unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
@project = nil
end
@project
@project = project
end
def projects
return @projects if defined?(@projects)
return @projects = project if project?
if project?
@projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects.reorder(nil)
projects =
if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects
elsif group
@projects = GroupProjectsFinder.new(group).execute(current_user).reorder(nil)
GroupProjectsFinder.new(group).execute(current_user)
else
@projects = ProjectsFinder.new.execute(current_user).reorder(nil)
ProjectsFinder.new.execute(current_user)
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
end
def search
@ -126,7 +121,7 @@ class IssuableFinder
@labels =
if labels? && !filter_by_no_label?
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true)
else
Label.none
end
@ -273,7 +268,7 @@ class IssuableFinder
items = items.with_label(label_names, params[:sort])
if projects
label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id)
label_ids = LabelsFinder.new(current_user, project_ids: projects).execute(skip_authorization: true).select(:id)
items = items.where(labels: { id: label_ids })
end
end

View file

@ -6,7 +6,7 @@ class LabelsFinder < UnionFinder
def execute(skip_authorization: false)
@skip_authorization = skip_authorization
items = find_union(label_ids, Label)
items = find_union(label_ids, Label) || Label.none
items = with_title(items)
sort(items)
end
@ -18,9 +18,11 @@ class LabelsFinder < UnionFinder
def label_ids
label_ids = []
if project?
if project
label_ids << project.group.labels if project.group.present?
label_ids << project.labels
end
else
label_ids << Label.where(group_id: projects.group_ids)
label_ids << Label.where(project_id: projects.select(:id))
@ -40,16 +42,16 @@ class LabelsFinder < UnionFinder
items.where(title: title)
end
def group_id
params[:group_id].presence
def group?
params[:group_id].present?
end
def project_id
params[:project_id].presence
def project?
params[:project_id].present?
end
def projects_ids
params[:project_ids]
def projects?
params[:project_ids].present?
end
def title
@ -59,8 +61,9 @@ class LabelsFinder < UnionFinder
def project
return @project if defined?(@project)
if project_id
@project = find_project
if project?
@project = Project.find(params[:project_id])
@project = nil unless authorized_to_read_labels?(@project)
else
@project = nil
end
@ -68,26 +71,20 @@ class LabelsFinder < UnionFinder
@project
end
def find_project
if skip_authorization
Project.find_by(id: project_id)
else
available_projects.find_by(id: project_id)
end
end
def projects
return @projects if defined?(@projects)
@projects = skip_authorization ? Project.all : available_projects
@projects = @projects.in_namespace(group_id) if group_id
@projects = @projects.where(id: projects_ids) if projects_ids
@projects = skip_authorization ? Project.all : ProjectsFinder.new.execute(current_user)
@projects = @projects.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil)
@projects
end
def available_projects
@available_projects ||= ProjectsFinder.new.execute(current_user)
def authorized_to_read_labels?(project)
return true if skip_authorization
Ability.allowed?(current_user, :read_label, project)
end
end

View file

@ -93,11 +93,11 @@ module ApplicationSettingsHelper
end
end
def repository_storage_options_for_select
def repository_storages_options_for_select
options = Gitlab.config.repositories.storages.map do |name, path|
["#{name} - #{path}", name]
end
options_for_select(options, @application_setting.repository_storage)
options_for_select(options, @application_setting.repository_storages)
end
end

View file

@ -54,10 +54,18 @@ module CiStatusHelper
custom_icon(icon_name)
end
def render_commit_status(commit, tooltip_placement: 'auto left')
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
path = pipelines_namespace_project_commit_path(project.namespace, project, commit)
render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement)
path = pipelines_namespace_project_commit_path(
project.namespace,
project,
commit)
render_status_with_link(
'commit',
commit.status(ref),
path,
tooltip_placement: tooltip_placement)
end
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')

View file

@ -25,9 +25,11 @@ module CommitsHelper
end
end
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
render "projects/commits/#{template}", commit: commit, project: project unless commit.nil?
def commit_to_html(commit, ref, project)
render 'projects/commits/commit',
commit: commit,
ref: ref,
project: project
end
# Breadcrumb links for a Project and, if applicable, a tree path

View file

@ -80,7 +80,7 @@ module EventsHelper
elsif event.merge_request?
namespace_project_merge_request_url(event.project.namespace,
event.project, event.merge_request)
elsif event.note? && event.commit_note?
elsif event.commit_note?
namespace_project_commit_url(event.project.namespace, event.project,
event.note_target)
elsif event.note?
@ -121,7 +121,7 @@ module EventsHelper
end
def event_note_target_path(event)
if event.note? && event.commit_note?
if event.commit_note?
namespace_project_commit_path(event.project.namespace,
event.project,
event.note_target,

View file

@ -27,7 +27,11 @@ module LfsHelper
def lfs_download_access?
return false unless project.lfs_enabled?
project.public? || ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code?
end
def objects
@objects ||= (params[:objects] || []).to_a
end
def user_can_download_code?

View file

@ -174,10 +174,14 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if can?(current_user, :read_build, project)
if can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
end
if can?(current_user, :read_build, project)
nav_tabs << :builds
end
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
nav_tabs << :container_registry
end

View file

@ -18,6 +18,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :disabled_oauth_sign_in_sources, Array
serialize :domain_whitelist, Array
serialize :domain_blacklist, Array
serialize :repository_storages
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
@ -74,9 +75,8 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :repository_storages, presence: true
validate :check_repository_storages
validates :enabled_git_access_protocol,
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
@ -166,7 +166,7 @@ class ApplicationSetting < ActiveRecord::Base
disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false,
container_registry_token_expire_delay: 5,
repository_storage: 'default',
repository_storages: ['default'],
user_default_external: false,
)
end
@ -201,6 +201,25 @@ class ApplicationSetting < ActiveRecord::Base
self.domain_blacklist_raw = file.read
end
def repository_storages
Array(read_attribute(:repository_storages))
end
# repository_storage is still required in the API. Remove in 9.0
def repository_storage
repository_storages.first
end
def repository_storage=(value)
self.repository_storages = [value]
end
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
repository_storages.sample
end
def runners_registration_token
ensure_runners_registration_token!
end
@ -208,4 +227,12 @@ class ApplicationSetting < ActiveRecord::Base
def health_check_access_token
ensure_health_check_access_token!
end
private
def check_repository_storages
invalid = repository_storages - Gitlab.config.repositories.storages.keys
errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
invalid.empty?
end
end

View file

@ -226,12 +226,19 @@ class Commit
end
def pipelines
@pipeline ||= project.pipelines.where(sha: sha)
project.pipelines.where(sha: sha)
end
def status
return @status if defined?(@status)
@status ||= pipelines.status
def status(ref = nil)
@statuses ||= {}
if @statuses.key?(ref)
@statuses[ref]
elsif ref
@statuses[ref] = pipelines.where(ref: ref).status
else
@statuses[ref] = pipelines.status
end
end
def revert_branch_name

View file

@ -182,6 +182,10 @@ module Issuable
grouping_columns
end
def to_ability_name
model_name.singular
end
end
def today?
@ -243,7 +247,7 @@ module Issuable
# issuable.class # => MergeRequest
# issuable.to_ability_name # => "merge_request"
def to_ability_name
self.class.to_s.underscore
self.class.to_ability_name
end
# Returns a Hash of attributes to be used for Twitter card metadata

View file

@ -31,7 +31,7 @@ module ProjectFeaturesCompatibility
def write_feature_attribute(field, value)
build_project_feature unless project_feature
access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.update_attribute(field, access_level)
end
end

View file

@ -48,6 +48,7 @@ class Event < ActiveRecord::Base
update_all(updated_at: Time.now)
end
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
where("action = ? OR (target_type in (?) AND action in (?))",
Event::PUSHED, ["MergeRequest", "Issue"],
@ -60,8 +61,8 @@ class Event < ActiveRecord::Base
end
def visible_to_user?(user = nil)
if push?
true
if push? || commit_note?
Ability.allowed?(user, :download_code, project)
elsif membership_changed?
true
elsif created_project?
@ -275,7 +276,7 @@ class Event < ActiveRecord::Base
end
def commit_note?
target.for_commit?
note? && target && target.for_commit?
end
def issue_note?
@ -287,7 +288,7 @@ class Event < ActiveRecord::Base
end
def project_snippet_note?
target.for_snippet?
note? && target && target.for_snippet?
end
def note_target

View file

@ -5,6 +5,10 @@ class GroupLabel < Label
alias_attribute :subject, :group
def subject_foreign_key
'group_id'
end
def to_reference(source_project = nil, target_project = nil, format: :id)
super(source_project, target_project, format: format)
end

7
app/models/guest.rb Normal file
View file

@ -0,0 +1,7 @@
class Guest
class << self
def can?(action, subject)
Ability.allowed?(nil, action, subject)
end
end
end

View file

@ -245,31 +245,11 @@ class Issue < ActiveRecord::Base
# Returns `true` if the current issue can be viewed by either a logged in User
# or an anonymous user.
def visible_to_user?(user = nil)
return false unless project.feature_available?(:issues, user)
user ? readable_by?(user) : publicly_visible?
end
# Returns `true` if the given User can read the current Issue.
def readable_by?(user)
if user.admin?
true
elsif project.owner == user
true
elsif confidential?
author == user ||
assignee == user ||
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
project.internal? && !user.external? ||
project.team.member?(user)
end
end
# Returns `true` if this Issue is visible to everybody.
def publicly_visible?
project.public? && !confidential?
end
def overdue?
due_date.try(:past?) || false
end
@ -290,4 +270,32 @@ class Issue < ActiveRecord::Base
end
end
end
private
# Returns `true` if the given User can read the current Issue.
#
# This method duplicates the same check of issue_policy.rb
# for performance reasons, check commit: 002ad215818450d2cbbc5fa065850a953dc7ada8
# Make sure to sync this method with issue_policy.rb
def readable_by?(user)
if user.admin?
true
elsif project.owner == user
true
elsif confidential?
author == user ||
assignee == user ||
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
project.internal? && !user.external? ||
project.team.member?(user)
end
end
# Returns `true` if this Issue is visible to everybody.
def publicly_visible?
project.public? && !confidential?
end
end

View file

@ -92,16 +92,23 @@ class Label < ActiveRecord::Base
nil
end
def open_issues_count(user = nil, project = nil)
issues_count(user, project_id: project.try(:id) || project_id, state: 'opened')
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
def closed_issues_count(user = nil, project = nil)
issues_count(user, project_id: project.try(:id) || project_id, state: 'closed')
def closed_issues_count(user = nil)
issues_count(user, state: 'closed')
end
def open_merge_requests_count(user = nil, project = nil)
merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened')
def open_merge_requests_count(user = nil)
params = {
subject_foreign_key => subject.id,
label_name: title,
scope: 'all',
state: 'opened'
}
MergeRequestsFinder.new(user, params.with_indifferent_access).execute.count
end
def prioritize!(project, value)
@ -167,15 +174,8 @@ class Label < ActiveRecord::Base
end
def issues_count(user, params = {})
IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
.execute
.count
end
def merge_requests_count(user, params = {})
MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
.execute
.count
params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all')
IssuesFinder.new(user, params.with_indifferent_access).execute.count
end
def label_format_reference(format = :id)

View file

@ -442,11 +442,11 @@ class MergeRequest < ActiveRecord::Base
end
def should_remove_source_branch?
merge_params['should_remove_source_branch'].present?
Gitlab::Utils.to_boolean(merge_params['should_remove_source_branch'])
end
def force_remove_source_branch?
merge_params['force_remove_source_branch'].present?
Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end
def remove_source_branch?

View file

@ -28,8 +28,13 @@ class Project < ActiveRecord::Base
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
after_create :ensure_dir_exist
after_create :create_project_feature, unless: :project_feature
@ -202,8 +207,38 @@ class Project < ActiveRecord::Base
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :with_builds_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') }
scope :with_issues_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.issues_access_level IS NULL or project_features.issues_access_level > 0') }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] })
}
# Picks a feature where the level is exactly that given.
scope :with_feature_access_level, ->(feature, level) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
with_project_feature.where(project_features: { access_level_attribute => level })
}
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
# project features may be "disabled", "internal" or "enabled". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user.
def self.with_feature_available_for_user(feature, user)
return with_feature_enabled(feature) if user.try(:admin?)
unconditional = with_feature_access_level(feature, [nil, ProjectFeature::ENABLED])
return unconditional if user.nil?
conditional = with_feature_access_level(feature, ProjectFeature::PRIVATE)
authorized = user.authorized_projects.merge(conditional.reorder(nil))
union = Gitlab::SQL::Union.new([unconditional.select(:id), authorized.select(:id)])
where(arel_table[:id].in(Arel::Nodes::SqlLiteral.new(union.to_sql)))
end
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
@ -390,7 +425,7 @@ class Project < ActiveRecord::Base
end
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id)
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
end
@ -1062,10 +1097,6 @@ class Project < ActiveRecord::Base
forks.count
end
def find_label(name)
labels.find_by(name: name)
end
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end

View file

@ -20,6 +20,15 @@ class ProjectFeature < ActiveRecord::Base
FEATURES = %i(issues merge_requests wiki snippets builds repository)
class << self
def access_level_attribute(feature)
feature = feature.model_name.plural.to_sym if feature.respond_to?(:model_name)
raise ArgumentError, "invalid project feature: #{feature}" unless FEATURES.include?(feature)
"#{feature}_access_level".to_sym
end
end
# Default scopes force us to unscope here since a service may need to check
# permissions for a project in pending_delete
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
@ -35,9 +44,8 @@ class ProjectFeature < ActiveRecord::Base
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
get_permission(user, public_send("#{feature}_access_level"))
access_level = public_send(ProjectFeature.access_level_attribute(feature))
get_permission(user, access_level)
end
def builds_enabled?

View file

@ -12,6 +12,10 @@ class ProjectLabel < Label
alias_attribute :subject, :project
def subject_foreign_key
'project_id'
end
def to_reference(target_project = nil, format: :id)
super(project, target_project, format: format)
end

View file

@ -178,7 +178,7 @@ class Repository
before_remove_branch
branch = find_branch(branch_name)
oldrev = branch.try(:target).try(:id)
oldrev = branch.try(:dereferenced_target).try(:id)
newrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
@ -294,10 +294,10 @@ class Repository
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
number_commits_behind = raw_repository.
count_commits_between(branch.target.sha, root_ref_hash)
count_commits_between(branch.dereferenced_target.sha, root_ref_hash)
number_commits_ahead = raw_repository.
count_commits_between(root_ref_hash, branch.target.sha)
count_commits_between(root_ref_hash, branch.dereferenced_target.sha)
{ behind: number_commits_behind, ahead: number_commits_ahead }
end
@ -679,11 +679,11 @@ class Repository
branches.sort_by(&:name)
when 'updated_desc'
branches.sort do |a, b|
commit(b.target).committed_date <=> commit(a.target).committed_date
commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date
end
when 'updated_asc'
branches.sort do |a, b|
commit(a.target).committed_date <=> commit(b.target).committed_date
commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date
end
else
branches
@ -858,7 +858,7 @@ class Repository
branch = find_branch(ref)
if branch
last_commit = branch.target
last_commit = branch.dereferenced_target
index.read_tree(last_commit.raw_commit.tree)
parents = [last_commit.sha]
end
@ -945,7 +945,7 @@ class Repository
end
def revert(user, commit, base_branch, revert_tree_id = nil)
source_sha = find_branch(base_branch).target.sha
source_sha = find_branch(base_branch).dereferenced_target.sha
revert_tree_id ||= check_revert_content(commit, base_branch)
return false unless revert_tree_id
@ -962,7 +962,7 @@ class Repository
end
def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
source_sha = find_branch(base_branch).target.sha
source_sha = find_branch(base_branch).dereferenced_target.sha
cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
return false unless cherry_pick_tree_id
@ -991,7 +991,7 @@ class Repository
end
def check_revert_content(commit, base_branch)
source_sha = find_branch(base_branch).target.sha
source_sha = find_branch(base_branch).dereferenced_target.sha
args = [commit.id, source_sha]
args << { mainline: 1 } if commit.merge_commit?
@ -1005,7 +1005,7 @@ class Repository
end
def check_cherry_pick_content(commit, base_branch)
source_sha = find_branch(base_branch).target.sha
source_sha = find_branch(base_branch).dereferenced_target.sha
args = [commit.id, source_sha]
args << 1 if commit.merge_commit?
@ -1078,7 +1078,7 @@ class Repository
if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
oldrev = Gitlab::Git::BLANK_SHA
else
oldrev = rugged.merge_base(newrev, target_branch.target.sha)
oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha)
end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
@ -1138,7 +1138,7 @@ class Repository
end
def tags_sorted_by_committed_date
tags.sort_by { |tag| tag.target.committed_date }
tags.sort_by { |tag| tag.dereferenced_target.committed_date }
end
def keep_around_ref_name(sha)

View file

@ -1,4 +1,8 @@
class IssuePolicy < IssuablePolicy
# This class duplicates the same check of Issue#readable_by? for performance reasons
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
def issue
@subject
end

View file

@ -2,11 +2,11 @@ class ProjectPolicy < BasePolicy
def rules
team_access!(user)
owner = user.admin? ||
project.owner == user ||
owner = project.owner == user ||
(project.group && project.group.has_owner?(user))
owner_access! if owner
owner_access! if user.admin? || owner
team_member_owner_access! if owner
if project.public? || (project.internal? && !user.external?)
guest_access!
@ -16,7 +16,7 @@ class ProjectPolicy < BasePolicy
can! :read_build if project.public_builds?
if project.request_access_enabled &&
!(owner || project.team.member?(user) || project_group_member?(user))
!(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access
end
end
@ -135,6 +135,10 @@ class ProjectPolicy < BasePolicy
can! :destroy_issue
end
def team_member_owner_access!
team_member_reporter_access!
end
# Push abilities on the users team role
def team_access!(user)
access = project.team.max_member_access(user.id)

View file

@ -9,8 +9,8 @@ module Auth
return error('UNAVAILABLE', status: 404, message: 'registry not enabled') unless registry.enabled
unless current_user || project
return error('DENIED', status: 403, message: 'access forbidden') unless scope
unless scope || current_user || project
return error('DENIED', status: 403, message: 'access forbidden')
end
{ token: authorized_token(scope).encoded }
@ -76,7 +76,7 @@ module Auth
case requested_action
when 'pull'
requested_project.public? || build_can_pull?(requested_project) || user_can_pull?(requested_project)
build_can_pull?(requested_project) || user_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
else
@ -92,23 +92,23 @@ module Auth
# Build can:
# 1. pull from its own project (for ex. a build)
# 2. read images from dependent projects if creator of build is a team member
@authentication_abilities.include?(:build_read_container_image) &&
has_authentication_ability?(:build_read_container_image) &&
(requested_project == project || can?(current_user, :build_read_container_image, requested_project))
end
def user_can_pull?(requested_project)
@authentication_abilities.include?(:read_container_image) &&
has_authentication_ability?(:read_container_image) &&
can?(current_user, :read_container_image, requested_project)
end
def build_can_push?(requested_project)
# Build can push only to the project from which it originates
@authentication_abilities.include?(:build_create_container_image) &&
has_authentication_ability?(:build_create_container_image) &&
requested_project == project
end
def user_can_push?(requested_project)
@authentication_abilities.include?(:create_container_image) &&
has_authentication_ability?(:create_container_image) &&
can?(current_user, :create_container_image, requested_project)
end
@ -118,5 +118,9 @@ module Auth
http_status: status
}
end
def has_authentication_ability?(capability)
(@authentication_abilities || []).include?(capability)
end
end
end

View file

@ -42,7 +42,7 @@ class DeleteBranchService < BaseService
Gitlab::DataBuilder::Push.build(
project,
current_user,
branch.target.sha,
branch.dereferenced_target.sha,
Gitlab::Git::BLANK_SHA,
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}",
[])

View file

@ -36,7 +36,7 @@ class DeleteTagService < BaseService
Gitlab::DataBuilder::Push.build(
project,
current_user,
tag.target.sha,
tag.dereferenced_target.sha,
Gitlab::Git::BLANK_SHA,
"#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
[])

View file

@ -27,8 +27,8 @@ class GitTagPushService < BaseService
tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name)
if tag && tag.object_sha == params[:newrev]
commit = project.commit(tag.target)
if tag && tag.target == params[:newrev]
commit = project.commit(tag.dereferenced_target)
commits = [commit].compact
message = tag.message
end

View file

@ -353,9 +353,9 @@
%fieldset
%legend Repository Storage
.form-group
= f.label :repository_storage, 'Storage path for new projects', class: 'control-label col-sm-2'
= f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
.col-sm-10
= f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control'
= f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control'
.help-block
Manage repository storage paths. Learn more in the
= succeed "." do

View file

@ -8,3 +8,6 @@
- if signin_enabled?
%li
= link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
- if signin_enabled? && signup_enabled?
%li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab'

View file

@ -13,7 +13,7 @@
.other-labels
- if @labels.present?
%ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', collection: @labels, as: :label
= render partial: 'shared/label', subject: @group, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- else
.nothing-here-block

View file

@ -1,7 +1,9 @@
- if commit.status
= link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
= ci_icon_for_status(commit.status)
= ci_label_for_status(commit.status)
- ref = local_assigns.fetch(:ref)
- status = commit.status(ref)
- if status
= link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{status}" do
= ci_icon_for_status(status)
= ci_label_for_status(status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"

View file

@ -20,7 +20,7 @@
%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
= render blob_commit, project: @project, ref: @ref
%div#blob-content-holder.blob-content-holder
%article.file-holder

View file

@ -1,4 +1,4 @@
- commit = @repository.commit(branch.target)
- commit = @repository.commit(branch.dereferenced_target)
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind]

View file

@ -1,3 +1,4 @@
- ref = local_assigns.fetch(:ref)
- if @note_counts
- note_count = @note_counts.fetch(commit.id, 0)
- else
@ -5,7 +6,7 @@
- note_count = notes.user.count
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(commit.status) if commit.status
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
@ -18,15 +19,15 @@
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
- if commit.status
- if commit.status(ref)
.visible-xs-inline
= render_commit_status(commit)
= render_commit_status(commit, ref: ref)
- if commit.description?
%a.text-expander.hidden-xs.js-toggle-button ...
.commit-actions.hidden-xs
- if commit.status
= render_commit_status(commit)
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit)

View file

@ -11,4 +11,4 @@
%li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
%ul.content-list= render commits, project: @project
%ul.content-list= render commits, project: @project, ref: @ref

View file

@ -1,13 +1,11 @@
- unless defined?(project)
- project = @project
- ref = local_assigns.fetch(:ref)
- commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}"
%li.commits-row
%ul.list-unstyled.commit-list
= render commits, project: project
= render commits, project: project, ref: ref
- if hidden > 0
%li.alert.alert-warning

View file

@ -35,7 +35,7 @@
%div{id: dom_id(@project)}
%ol#commits-list.list-unstyled.content_list
= render "commits", project: @project
= render 'commits', project: @project, ref: @ref
= spinner
:javascript

View file

@ -4,7 +4,7 @@
%ul.unstyled-list.related-merge-requests
- @related_branches.each do |branch|
%li
- target = @project.repository.find_branch(branch).target
- target = @project.repository.find_branch(branch).dereferenced_target
- pipeline = @project.pipeline_for(branch, target.sha) if target
- if pipeline
%span.related-branch-ci-status

View file

@ -22,14 +22,14 @@
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
- if @prioritized_labels.present?
= render partial: 'shared/label', collection: @prioritized_labels, as: :label
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
.other-labels
- if can?(current_user, :admin_label, @project)
%h5{ class: ('hide' if hide) } Other Labels
%ul.content-list.manage-labels-list.js-other-labels
- if @labels.present?
= render partial: 'shared/label', collection: @labels, as: :label
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- if @labels.blank?
.nothing-here-block

View file

@ -1 +1,2 @@
= commit_to_html(@commit, @source_project, false)
- if @commit
= commit_to_html(@commit, @ref, @source_project)

View file

@ -1 +1,2 @@
= commit_to_html(@commit, @target_project, false)
- if @commit
= commit_to_html(@commit, @ref, @target_project)

View file

@ -3,4 +3,4 @@
Most recent commits displayed first
%ol#commits-list.list-unstyled
= render "projects/commits/commits", project: @merge_request.project
= render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch

View file

@ -90,7 +90,8 @@
= f.label :visibility_level, class: 'label-light' do
Visibility Level
= link_to "(?)", help_page_path("public_access/public_access")
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
= render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'

View file

@ -79,7 +79,7 @@
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project
= render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
%div{ class: container_class }
- if @project.archived?

View file

@ -1,4 +1,4 @@
- commit = @repository.commit(tag.target)
- commit = @repository.commit(tag.dereferenced_target)
- release = @releases.find { |release| release.tag == tag.name }
%li
%div

View file

@ -1 +1 @@
= render 'projects/commits/commit', project: @project, commit: commit
= render 'projects/commits/commit', project: @project, commit: commit, ref: nil

View file

@ -1,6 +1,7 @@
- label_css_id = dom_id(label)
- open_issues_count = label.open_issues_count(current_user, @project)
- open_merge_requests_count = label.open_merge_requests_count(current_user, @project)
- open_issues_count = label.open_issues_count(current_user)
- open_merge_requests_count = label.open_merge_requests_count(current_user)
- subject = local_assigns[:subject]
%li{id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label
@ -12,10 +13,10 @@
.dropdown-menu.dropdown-menu-align-right
%ul
%li
= link_to_label(label, subject: @project, type: :merge_request) do
= link_to_label(label, subject: subject, type: :merge_request) do
= pluralize open_merge_requests_count, 'merge request'
%li
= link_to_label(label, subject: @project) do
= link_to_label(label, subject: subject) do
= pluralize open_issues_count, 'open issue'
- if current_user
%li.label-subscription{ data: toggle_subscription_data(label) }
@ -28,9 +29,9 @@
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'}
.pull-right.hidden-xs.hidden-sm.hidden-md
= link_to_label(label, subject: @project, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
= pluralize open_merge_requests_count, 'merge request'
= link_to_label(label, subject: @project, css_class: 'btn btn-transparent btn-action') do
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do
= pluralize open_issues_count, 'open issue'
- if current_user

View file

@ -142,6 +142,7 @@
.col-sm-10.col-sm-offset-2
.checkbox
= label_tag 'merge_request[force_remove_source_branch]' do
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
Remove source branch when merge request is accepted.

View file

@ -1,10 +1,10 @@
- project = @target_project || @project
- extra_class = extra_class || ''
- show_menu_above = show_menu_above || false
- selected_text = selected.try(:title)
- selected_text = selected.try(:title) || params[:milestone_title]
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone")
- if selected.present?
= hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id)
= hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if project

View file

@ -0,0 +1,29 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RenameRepositoryStorageColumn < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = true
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
DOWNTIME_REASON = 'Renaming the application_settings.repository_storage column'
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
rename_column :application_settings, :repository_storage, :repository_storages
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161024042317) do
ActiveRecord::Schema.define(version: 20161103171205) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -88,7 +88,7 @@ ActiveRecord::Schema.define(version: 20161024042317) do
t.integer "container_registry_token_expire_delay", default: 5
t.text "after_sign_up_text"
t.boolean "user_default_external", default: false, null: false
t.string "repository_storage", default: "default"
t.string "repository_storages", default: "default"
t.string "enabled_git_access_protocol"
t.boolean "domain_blacklist_enabled", default: false
t.text "domain_blacklist"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -91,6 +91,9 @@ be stored via the **Application Settings** in the Admin area.
![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png)
Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
randomly placed on one of the selected paths.
[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
[restart gitlab]: restart_gitlab.md#installations-from-source
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure

View file

@ -42,6 +42,7 @@ Example response:
"sign_in_text" : null,
"container_registry_token_expire_delay": 5,
"repository_storage": "default",
"repository_storages": ["default"],
"koding_enabled": false,
"koding_url": null
}
@ -73,7 +74,8 @@ PUT /application/settings
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
| `after_sign_out_path` | string | no | Where to redirect users after logout |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
| `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml |
| `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
| `repository_storage` | string | no | The first entry in `repository_storages`. Deprecated, but retained for compatibility reasons |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |

View file

@ -66,6 +66,12 @@ producing errors whenever it tries to use the `dummy` column.
As a result of the above downtime _is_ required when removing a column, even
when using PostgreSQL.
## Renaming Columns
Renaming columns requires downtime as running GitLab instances will continue
using the old column name until a new version is deployed. This can result
in the instance producing errors, which in turn can impact the user experience.
## Changing Column Constraints
Generally changing column constraints requires checking all rows in the table to

View file

@ -138,7 +138,7 @@ module API
expose :name
expose :commit do |repo_branch, options|
options[:project].repository.commit(repo_branch.target)
options[:project].repository.commit(repo_branch.dereferenced_target)
end
expose :protected do |repo_branch, options|
@ -510,6 +510,7 @@ module API
expose :after_sign_out_path
expose :container_registry_token_expire_delay
expose :repository_storage
expose :repository_storages
expose :koding_enabled
expose :koding_url
end
@ -523,7 +524,7 @@ module API
expose :name, :message
expose :commit do |repo_tag, options|
options[:project].repository.commit(repo_tag.target)
options[:project].repository.commit(repo_tag.dereferenced_target)
end
expose :release, using: Entities::Release do |repo_tag, options|

View file

@ -1,18 +1,12 @@
module API
module Helpers
include Gitlab::Utils
PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
PRIVATE_TOKEN_PARAM = :private_token
SUDO_HEADER = "HTTP_SUDO"
SUDO_PARAM = :sudo
def to_boolean(value)
return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
return false if value =~ /^(false|f|no|n|0|off)$/i
nil
end
def private_token
params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
end

View file

@ -29,8 +29,8 @@ module API
required_attributes! [:name, :color]
attrs = attributes_for_keys [:name, :color, :description]
label = user_project.find_label(attrs[:name])
label = available_labels.find_by(title: attrs[:name])
conflict!('Label already exists') if label
label = user_project.labels.create(attrs)
@ -54,7 +54,7 @@ module API
authorize! :admin_label, user_project
required_attributes! [:name]
label = user_project.find_label(params[:name])
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
label.destroy
@ -75,7 +75,7 @@ module API
authorize! :admin_label, user_project
required_attributes! [:name]
label = user_project.find_label(params[:name])
label = user_project.labels.find_by(title: params[:name])
not_found!('Label not found') unless label
attrs = attributes_for_keys [:new_name, :color, :description]

View file

@ -17,12 +17,12 @@ module API
present current_settings, with: Entities::ApplicationSetting
end
# Modify applicaiton settings
# Modify application settings
#
# Example Request:
# PUT /application/settings
put "application/settings" do
attributes = current_settings.attributes.keys - ["id"]
attributes = ["repository_storage"] + current_settings.attributes.keys - ["id"]
attrs = attributes_for_keys(attributes)
if current_settings.update_attributes(attrs)

View file

@ -71,6 +71,14 @@ module Banzai
@doc = parse_html(rinku)
end
# Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
def contains_unsafe?(scheme)
return false unless scheme
scheme = scheme.strip.downcase
Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
end
# Autolinks any text matching LINK_PATTERN that Rinku didn't already
# replace
def text_parse
@ -89,8 +97,15 @@ module Banzai
doc
end
def autolink_filter(text)
text.gsub(LINK_PATTERN) do |match|
def autolink_match(match)
# start by stripping out dangerous links
begin
uri = Addressable::URI.parse(match)
return match if contains_unsafe?(uri.scheme)
rescue Addressable::URI::InvalidURIError
return match
end
# Remove any trailing HTML entities and store them for appending
# outside the link element. The entity must be marked HTML safe in
# order to be output literally rather than escaped.
@ -100,6 +115,9 @@ module Banzai
options = link_options.merge(href: match)
content_tag(:a, match, options) + dropped
end
def autolink_filter(text)
text.gsub(LINK_PATTERN) { |match| autolink_match(match) }
end
def link_options

View file

@ -63,12 +63,7 @@ module Banzai
nodes.select do |node|
if node.has_attribute?(project_attr)
node_id = node.attr(project_attr).to_i
if project && project.id == node_id
true
else
can?(user, :read_project, projects[node_id])
end
can_read_reference?(user, projects[node_id])
else
true
end
@ -226,6 +221,15 @@ module Banzai
attr_reader :current_user, :project
# When a feature is disabled or visible only for
# team members we should not allow team members
# see reference comments.
# Override this method on subclasses
# to check if user can read resource
def can_read_reference?(user, ref_project)
raise NotImplementedError
end
def lazy(&block)
Gitlab::Lazy.new(&block)
end

View file

@ -29,6 +29,12 @@ module Banzai
commits
end
private
def can_read_reference?(user, ref_project)
can?(user, :download_code, ref_project)
end
end
end
end

View file

@ -33,6 +33,12 @@ module Banzai
range.valid_commits? ? range : nil
end
private
def can_read_reference?(user, ref_project)
can?(user, :download_code, ref_project)
end
end
end
end

View file

@ -20,6 +20,12 @@ module Banzai
def issue_ids_per_project(nodes)
gather_attributes_per_project(nodes, self.class.data_attribute)
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_issue, ref_project)
end
end
end
end

View file

@ -6,6 +6,12 @@ module Banzai
def references_relation
Label
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_label, ref_project)
end
end
end
end

View file

@ -6,6 +6,12 @@ module Banzai
def references_relation
MergeRequest.includes(:author, :assignee, :target_project)
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_merge_request, ref_project)
end
end
end
end

View file

@ -6,6 +6,12 @@ module Banzai
def references_relation
Milestone
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_milestone, ref_project)
end
end
end
end

View file

@ -6,6 +6,12 @@ module Banzai
def references_relation
Snippet
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_project_snippet, ref_project)
end
end
end
end

View file

@ -30,22 +30,36 @@ module Banzai
nodes.each do |node|
if node.has_attribute?(group_attr)
node_group = groups[node.attr(group_attr).to_i]
if node_group &&
can?(user, :read_group, node_group)
next unless can_read_group_reference?(node, user, groups)
visible << node
elsif can_read_project_reference?(node)
visible << node
end
# Remaining nodes will be processed by the parent class'
# implementation of this method.
else
remaining << node
end
end
# If project does not belong to a group
# and does not have the same project id as the current project
# base class will check if user can read the project that contains
# the user reference.
visible + super(current_user, remaining)
end
# Check if project belongs to a group which
# user can read.
def can_read_group_reference?(node, user, groups)
node_group = groups[node.attr('data-group').to_i]
node_group && can?(user, :read_group, node_group)
end
def can_read_project_reference?(node)
node_id = node.attr('data-project').to_i
project && project.id == node_id
end
def nodes_user_can_reference(current_user, nodes)
project_attr = 'data-project'
author_attr = 'data-author'
@ -88,6 +102,10 @@ module Banzai
collection_objects_for_ids(Project, ids).
flat_map { |p| p.team.members.to_a }
end
def can_read_reference?(user, ref_project)
can?(user, :read_project, ref_project)
end
end
end
end

View file

@ -46,7 +46,7 @@ module Banzai
return html if html.present?
html = cacheless_render_field(object, field)
object.update_column(html_field, html) unless object.new_record? || object.destroyed?
update_object(object, html_field, html) unless object.new_record? || object.destroyed?
html
end
@ -166,5 +166,9 @@ module Banzai
return unless cache_key
Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
end
def update_object(object, html_field, html)
object.update_column(html_field, html)
end
end
end

View file

@ -1,45 +1,44 @@
module Gitlab
class ContributionsCalendar
attr_reader :activity_dates, :projects, :user
attr_reader :contributor
attr_reader :current_user
attr_reader :projects
def initialize(projects, user)
@projects = projects
@user = user
def initialize(contributor, current_user = nil)
@contributor = contributor
@current_user = current_user
@projects = ContributedProjectsFinder.new(contributor).execute(current_user)
end
def activity_dates
return @activity_dates if @activity_dates.present?
@activity_dates = {}
# Can't use Event.contributions here because we need to check 3 different
# project_features for the (currently) 3 different contribution types
date_from = 1.year.ago
repo_events = event_counts(date_from, :repository).
having(action: Event::PUSHED)
issue_events = event_counts(date_from, :issues).
having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
mr_events = event_counts(date_from, :merge_requests).
having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
events = Event.reorder(nil).contributions.where(author_id: user.id).
where("created_at > ?", date_from).where(project_id: projects).
group('date(created_at)').
select('date(created_at) as date, count(id) as total_amount').
map(&:attributes)
union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes)
activity_dates = (1.year.ago.to_date..Date.today).to_a
activity_dates.each do |date|
day_events = events.find { |day_events| day_events["date"] == date }
if day_events
@activity_dates[date] = day_events["total_amount"]
@activity_events = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
activities[event["date"]] += event["total_amount"]
end
end
@activity_dates
end
def events_by_date(date)
events = Event.contributions.where(author_id: user.id).
where("created_at > ? AND created_at < ?", date.beginning_of_day, date.end_of_day).
events = Event.contributions.where(author_id: contributor.id).
where(created_at: date.beginning_of_day..date.end_of_day).
where(project_id: projects)
events.select do |event|
event.push? || event.issue? || event.merge_request?
end
# Use visible_to_user? instead of the complicated logic in activity_dates
# because we're only viewing the events for a single day.
events.select {|event| event.visible_to_user?(current_user) }
end
def starting_year
@ -49,5 +48,30 @@ module Gitlab
def starting_month
Date.today.month
end
private
def event_counts(date_from, feature)
t = Event.arel_table
# re-running the contributed projects query in each union is expensive, so
# use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short
@contributed_project_ids ||= projects.uniq.pluck(:id)
authed_projects = Project.where(id: @contributed_project_ids).
with_feature_available_for_user(feature, current_user).
reorder(nil).
select(:id)
conditions = t[:created_at].gteq(date_from.beginning_of_day).
and(t[:created_at].lteq(Date.today.end_of_day)).
and(t[:author_id].eq(contributor.id))
Event.reorder(nil).
select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount').
group(t[:project_id], t[:target_type], t[:action], 'date(created_at)').
where(conditions).
having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
end
end
end

View file

@ -83,7 +83,7 @@ module Gitlab
tag = repository.find_tag(tag_name)
if tag
commit = repository.commit(tag.target)
commit = repository.commit(tag.dereferenced_target)
commit.try(:sha)
end
else

View file

@ -2,8 +2,18 @@
# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
UnauthorizedError = Class.new(StandardError)
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
deploy_key: 'Deploy keys are not allowed to push code.',
no_repo: 'A repository for this project does not exist yet.'
}
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
PUSH_COMMANDS = %w{ git-receive-pack }
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
@ -16,56 +26,43 @@ module Gitlab
end
def check(cmd, changes)
return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
unless actor
return build_status_object(false, "No user or key was provided.")
end
if user && !user_access.allowed?
return build_status_object(false, "Your account has been blocked.")
end
unless project && (user_access.can_read_project? || deploy_key_can_read_project?)
return build_status_object(false, 'The project you were looking for could not be found.')
end
check_protocol!
check_active_user!
check_project_accessibility!
check_command_existence!(cmd)
case cmd
when *DOWNLOAD_COMMANDS
download_access_check
when *PUSH_COMMANDS
push_access_check(changes)
else
build_status_object(false, "The command you're trying to execute is not allowed.")
end
build_status_object(true)
rescue UnauthorizedError => ex
build_status_object(false, ex.message)
end
def download_access_check
if user
user_download_access_check
elsif deploy_key
build_status_object(true)
else
raise 'Wrong actor'
elsif deploy_key.nil? && !Guest.can?(:download_code, project)
raise UnauthorizedError, ERROR_MESSAGES[:download]
end
end
def push_access_check(changes)
if user
user_push_access_check(changes)
elsif deploy_key
build_status_object(false, "Deploy keys are not allowed to push code.")
else
raise 'Wrong actor'
raise UnauthorizedError, ERROR_MESSAGES[deploy_key ? :deploy_key : :upload]
end
end
def user_download_access_check
unless user_can_download_code? || build_can_download_code?
return build_status_object(false, "You are not allowed to download code from this project.")
raise UnauthorizedError, ERROR_MESSAGES[:download]
end
build_status_object(true)
end
def user_can_download_code?
@ -78,15 +75,15 @@ module Gitlab
def user_push_access_check(changes)
unless authentication_abilities.include?(:push_code)
return build_status_object(false, "You are not allowed to upload code for this project.")
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
if changes.blank?
return build_status_object(true)
return # Allow access.
end
unless project.repository.exists?
return build_status_object(false, "A repository for this project does not exist yet.")
raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
end
changes_list = Gitlab::ChangesList.new(changes)
@ -96,11 +93,9 @@ module Gitlab
status = change_access_check(change)
unless status.allowed?
# If user does not have access to make at least one change - cancel all push
return status
raise UnauthorizedError, status.message
end
end
build_status_object(true)
end
def change_access_check(change)
@ -113,6 +108,30 @@ module Gitlab
private
def check_protocol!
unless protocol_allowed?
raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
end
end
def check_active_user!
if user && !user_access.allowed?
raise UnauthorizedError, "Your account has been blocked."
end
end
def check_project_accessibility!
if project.blank? || !can_read_project?
raise UnauthorizedError, 'The project you were looking for could not be found.'
end
end
def check_command_existence!(cmd)
unless ALL_COMMANDS.include?(cmd)
raise UnauthorizedError, "The command you're trying to execute is not allowed."
end
end
def matching_merge_request?(newrev, branch_name)
Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
end
@ -130,6 +149,16 @@ module Gitlab
end
end
def can_read_project?
if user
user_access.can_read_project?
elsif deploy_key
deploy_key_can_read_project?
else
Guest.can?(:read_project, project)
end
end
protected
def user

View file

@ -102,6 +102,8 @@ module Gitlab
Gitlab::LDAP::Config.providers.each do |provider|
adapter = Gitlab::LDAP::Adapter.new(provider)
@ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
# The `uid` might actually be a DN. Try it next.
@ldap_person ||= Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
break if @ldap_person
end
@ldap_person

View file

@ -13,5 +13,13 @@ module Gitlab
def force_utf8(str)
str.force_encoding(Encoding::UTF_8)
end
def to_boolean(value)
return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
return false if value =~ /^(false|f|no|n|0|off)$/i
nil
end
end
end

View file

@ -39,6 +39,17 @@ describe Projects::MergeRequestsController do
end
end
shared_examples "loads labels" do |action|
it "loads labels into the @labels variable" do
get action,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: 'html'
expect(assigns(:labels)).not_to be_nil
end
end
describe "GET show" do
shared_examples "export merge as" do |format|
it "does generally work" do
@ -51,6 +62,8 @@ describe Projects::MergeRequestsController do
expect(response).to be_success
end
it_behaves_like "loads labels", :show
it "generates it" do
expect_any_instance_of(MergeRequest).to receive(:"to_#{format}")
@ -340,6 +353,8 @@ describe Projects::MergeRequestsController do
get :diffs, params.merge(extra_params)
end
it_behaves_like "loads labels", :diffs
context 'with default params' do
context 'as html' do
before { go(format: 'html') }
@ -546,6 +561,8 @@ describe Projects::MergeRequestsController do
format: format
end
it_behaves_like "loads labels", :commits
context 'as html' do
it 'renders the show template' do
go
@ -564,6 +581,14 @@ describe Projects::MergeRequestsController do
end
end
describe 'GET builds' do
it_behaves_like "loads labels", :builds
end
describe 'GET pipelines' do
it_behaves_like "loads labels", :pipelines
end
describe 'GET conflicts' do
let(:json_response) { JSON.parse(response.body) }

View file

@ -49,13 +49,17 @@ FactoryGirl.define do
end
after(:create) do |project, evaluator|
# Builds and MRs can't have higher visibility level than repository access level.
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
project.project_feature.
update_attributes(
update_attributes!(
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: evaluator.builds_access_level,
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: evaluator.merge_requests_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level
)
end

View file

@ -12,11 +12,15 @@ describe 'Commits' do
end
let!(:pipeline) do
FactoryGirl.create :ci_pipeline, project: project, sha: project.commit.sha
create(:ci_pipeline,
project: project,
ref: project.default_branch,
sha: project.commit.sha,
status: :success)
end
context 'commit status is Generic Commit Status' do
let!(:status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
let!(:status) { create(:generic_commit_status, pipeline: pipeline) }
before do
project.team << [@user, :reporter]
@ -39,7 +43,7 @@ describe 'Commits' do
end
context 'commit status is Ci Build' do
let!(:build) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build) { create(:ci_build, pipeline: pipeline) }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do
@ -48,13 +52,22 @@ describe 'Commits' do
end
describe 'Project commits' do
let!(:pipeline_from_other_branch) do
create(:ci_pipeline,
project: project,
ref: 'fix',
sha: project.commit.sha,
status: :failed)
end
before do
visit namespace_project_commits_path(project.namespace, project, :master)
end
it 'shows build status' do
it 'shows correct build status from default branch' do
page.within("//li[@id='commit-#{pipeline.short_sha}']") do
expect(page).to have_css(".ci-status-link")
expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
end
end
end

View file

@ -0,0 +1,8 @@
require 'spec_helper'
feature 'Group issues page', feature: true do
let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', Issue
end

View file

@ -0,0 +1,8 @@
require 'spec_helper'
feature 'Group merge requests page', feature: true do
let(:path) { merge_requests_group_path(group) }
let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', MergeRequest
end

Some files were not shown because too many files have changed in this diff Show more