Commit 3e75b7fa authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-headless-chrome-support

parents 541a082c 36ba84cb
...@@ -474,7 +474,6 @@ db:rollback-mysql: ...@@ -474,7 +474,6 @@ db:rollback-mysql:
variables: variables:
SIZE: "1" SIZE: "1"
SETUP_DB: "false" SETUP_DB: "false"
RAILS_ENV: "development"
script: script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git - git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git /home/git/repositories/gitlab-org/gitlab-test.git
......
...@@ -251,6 +251,10 @@ Layout/Tab: ...@@ -251,6 +251,10 @@ Layout/Tab:
Layout/TrailingBlankLines: Layout/TrailingBlankLines:
Enabled: true Enabled: true
# Avoid trailing whitespace.
Layout/TrailingWhitespace:
Enabled: true
# Style ####################################################################### # Style #######################################################################
# Check the naming of accessor methods for get_/set_. # Check the naming of accessor methods for get_/set_.
...@@ -1174,29 +1178,33 @@ RSpec/VerifiedDoubles: ...@@ -1174,29 +1178,33 @@ RSpec/VerifiedDoubles:
GitlabSecurity/DeepMunge: GitlabSecurity/DeepMunge:
Enabled: true Enabled: true
Exclude: Exclude:
- 'spec/**/*'
- 'lib/**/*.rake' - 'lib/**/*.rake'
- 'spec/**/*'
GitlabSecurity/PublicSend: GitlabSecurity/PublicSend:
Enabled: true Enabled: true
Exclude: Exclude:
- 'spec/**/*' - 'config/**/*'
- 'db/**/*'
- 'features/**/*'
- 'lib/**/*.rake' - 'lib/**/*.rake'
- 'qa/**/*'
- 'spec/**/*'
GitlabSecurity/RedirectToParamsUpdate: GitlabSecurity/RedirectToParamsUpdate:
Enabled: true Enabled: true
Exclude: Exclude:
- 'spec/**/*'
- 'lib/**/*.rake' - 'lib/**/*.rake'
- 'spec/**/*'
GitlabSecurity/SqlInjection: GitlabSecurity/SqlInjection:
Enabled: true Enabled: true
Exclude: Exclude:
- 'spec/**/*'
- 'lib/**/*.rake' - 'lib/**/*.rake'
- 'spec/**/*'
GitlabSecurity/SystemCommandInjection: GitlabSecurity/SystemCommandInjection:
Enabled: true Enabled: true
Exclude: Exclude:
- 'spec/**/*'
- 'lib/**/*.rake' - 'lib/**/*.rake'
- 'spec/**/*'
...@@ -57,11 +57,6 @@ Layout/SpaceInsideParens: ...@@ -57,11 +57,6 @@ Layout/SpaceInsideParens:
Layout/SpaceInsidePercentLiteralDelimiters: Layout/SpaceInsidePercentLiteralDelimiters:
Enabled: false Enabled: false
# Offense count: 89
# Cop supports --auto-correct.
Layout/TrailingWhitespace:
Enabled: false
# Offense count: 272 # Offense count: 272
RSpec/EmptyLineAfterFinalLet: RSpec/EmptyLineAfterFinalLet:
Enabled: false Enabled: false
......
...@@ -232,7 +232,7 @@ gem 'ace-rails-ap', '~> 4.1.0' ...@@ -232,7 +232,7 @@ gem 'ace-rails-ap', '~> 4.1.0'
gem 'mousetrap-rails', '~> 1.4.6' gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3' gem 'charlock_holmes', '~> 0.7.5'
# Faster JSON # Faster JSON
gem 'oj', '~> 2.17.4' gem 'oj', '~> 2.17.4'
......
...@@ -117,7 +117,7 @@ GEM ...@@ -117,7 +117,7 @@ GEM
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
mime-types (>= 1.16) mime-types (>= 1.16)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.5)
childprocess (0.7.0) childprocess (0.7.0)
ffi (~> 1.0, >= 1.0.11) ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2) chronic (0.10.2)
...@@ -983,7 +983,7 @@ DEPENDENCIES ...@@ -983,7 +983,7 @@ DEPENDENCIES
capybara (~> 2.15) capybara (~> 2.15)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.1) carrierwave (~> 1.1)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
concurrent-ruby (~> 1.0.5) concurrent-ruby (~> 1.0.5)
......
...@@ -141,18 +141,22 @@ the stable branch are: ...@@ -141,18 +141,22 @@ the stable branch are:
* Fixes for security issues * Fixes for security issues
* New or updated translations (as long as they do not touch application code) * New or updated translations (as long as they do not touch application code)
Any merge requests cherry-picked into the stable branch for a previous release
will also be picked into the latest stable branch. These fixes will be shipped
in the next RC for that release if it is before the 22nd. If the fixes are are
completed on or after the 22nd, they will be shipped in a patch for that
release.
During the feature freeze all merge requests that are meant to go into the upcoming During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set, so that release managers can find and pick them. ~"Pick into Stable" set, so that release managers can find and pick them.
Merge requests without a milestone and this label will Merge requests without a milestone and this label will
not be merged into any stable branches. not be merged into any stable branches.
Fixes marked like this will be shipped in the next RC for that release. Once
the final RC has been prepared ready for release on the 22nd, further fixes
marked ~"Pick into Stable" will go into a patch for that release.
If a merge request is to be picked into more than one release it will also need
the ~"Pick into Backports" label set to remind the release manager to change
the milestone after cherry-picking. As before, it should still have the
~"Pick into Stable" label and the milestone of the highest release it will be
picked into.
### Asking for an exception ### Asking for an exception
If you think a merge request should go into an RC or patch even though it does not meet these requirements, If you think a merge request should go into an RC or patch even though it does not meet these requirements,
......
...@@ -17,7 +17,7 @@ window.CommitsList = (function() { ...@@ -17,7 +17,7 @@ window.CommitsList = (function() {
} }
}); });
Pager.init(limit, false, false, this.processCommits); Pager.init(parseInt(limit, 10), false, false, this.processCommits);
this.content = $("#commits-list"); this.content = $("#commits-list");
this.searchField = $("#commits-search"); this.searchField = $("#commits-search");
......
...@@ -42,6 +42,10 @@ $(() => { ...@@ -42,6 +42,10 @@ $(() => {
$components.each(function () { $components.each(function () {
const $this = $(this); const $this = $(this);
const noteId = $this.attr(':note-id'); const noteId = $this.attr(':note-id');
const discussionId = $this.attr(':discussion-id');
if ($this.is('comment-and-resolve-btn') && !discussionId) return;
const tmp = Vue.extend({ const tmp = Vue.extend({
template: $this.get(0).outerHTML template: $this.get(0).outerHTML
}); });
......
...@@ -644,7 +644,7 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -644,7 +644,7 @@ import initChangesDropdown from './init_changes_dropdown';
return Dispatcher; return Dispatcher;
})(); })();
$(function() { $(window).on('load', function() {
new Dispatcher(); new Dispatcher();
}); });
}).call(window); }).call(window);
...@@ -15,6 +15,10 @@ export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; }; ...@@ -15,6 +15,10 @@ export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x); export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
export const canShowActiveSubItems = (el) => { export const canShowActiveSubItems = (el) => {
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md'; const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
...@@ -74,7 +78,7 @@ export const moveSubItemsToPosition = (el, subItems) => { ...@@ -74,7 +78,7 @@ export const moveSubItemsToPosition = (el, subItems) => {
const isAbove = top < boundingRect.top; const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list'); subItems.classList.add('fly-out-list');
subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; // eslint-disable-line no-param-reassign subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
const subItemsRect = subItems.getBoundingClientRect(); const subItemsRect = subItems.getBoundingClientRect();
...@@ -153,6 +157,8 @@ export default () => { ...@@ -153,6 +157,8 @@ export default () => {
}, getHideSubItemsInterval()); }, getHideSubItemsInterval());
}); });
headerHeight = document.querySelector('.nav-sidebar').offsetTop;
items.forEach((el) => { items.forEach((el) => {
const subItems = el.querySelector('.sidebar-sub-level-items'); const subItems = el.querySelector('.sidebar-sub-level-items');
......
export const isSticky = (el, scrollY, stickyTop) => { export const isSticky = (el, scrollY, stickyTop) => {
const top = el.offsetTop - scrollY; const top = el.offsetTop - scrollY;
if (top === stickyTop) { if (top <= stickyTop) {
el.classList.add('is-stuck'); el.classList.add('is-stuck');
} else { } else {
el.classList.remove('is-stuck'); el.classList.remove('is-stuck');
......
...@@ -132,8 +132,9 @@ import './project_select'; ...@@ -132,8 +132,9 @@ import './project_select';
import './project_show'; import './project_show';
import './project_variables'; import './project_variables';
import './projects_list'; import './projects_list';
import './render_gfm'; import './syntax_highlight';
import './render_math'; import './render_math';
import './render_gfm';
import './right_sidebar'; import './right_sidebar';
import './search'; import './search';
import './search_autocomplete'; import './search_autocomplete';
...@@ -141,7 +142,6 @@ import './smart_interval'; ...@@ -141,7 +142,6 @@ import './smart_interval';
import './star'; import './star';
import './subscription'; import './subscription';
import './subscription_select'; import './subscription_select';
import './syntax_highlight';
import './dispatcher'; import './dispatcher';
......
...@@ -43,10 +43,12 @@ export default class NewNavSidebar { ...@@ -43,10 +43,12 @@ export default class NewNavSidebar {
} }
toggleCollapsedSidebar(collapsed) { toggleCollapsedSidebar(collapsed) {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed); const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) { if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
this.$page.toggleClass('page-with-new-sidebar', !collapsed); this.$page.toggleClass('page-with-new-sidebar', !collapsed);
this.$page.toggleClass('page-with-icon-sidebar', collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
NewNavSidebar.setCollapsedCookie(collapsed); NewNavSidebar.setCollapsedCookie(collapsed);
} }
......
...@@ -11,7 +11,5 @@ ...@@ -11,7 +11,5 @@
return this; return this;
}; };
$(document).on('ready load', function() { $(() => $('body').renderGFM());
return $('body').renderGFM();
});
}).call(window); }).call(window);
...@@ -43,15 +43,18 @@ export default { ...@@ -43,15 +43,18 @@ export default {
</script> </script>
<template> <template>
<div class="repository-view tree-content-holder"> <div class="repository-view">
<repo-sidebar/><div v-if="isMini" <div class="tree-content-holder" :class="{'tree-content-holder-mini' : isMini}">
class="panel-right" <repo-sidebar/>
:class="{'edit-mode': editMode}"> <div v-if="isMini"
<repo-tabs/> class="panel-right"
<component :class="{'edit-mode': editMode}">
:is="currentBlobView" <repo-tabs/>
class="blob-viewer-container"/> <component
<repo-file-buttons/> :is="currentBlobView"
class="blob-viewer-container"/>
<repo-file-buttons/>
</div>
</div> </div>
<repo-commit-section/> <repo-commit-section/>
<popup-dialog <popup-dialog
......
...@@ -71,7 +71,7 @@ export default { ...@@ -71,7 +71,7 @@ export default {
/> />
<div v-if="!isConfidential" class="no-value confidential-value"> <div v-if="!isConfidential" class="no-value confidential-value">
<i class="fa fa-eye is-not-confidential"></i> <i class="fa fa-eye is-not-confidential"></i>
This issue is not confidential Not confidential
</div> </div>
<div v-else class="value confidential-value hide-collapsed"> <div v-else class="value confidential-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i> <i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
......
...@@ -204,6 +204,16 @@ ...@@ -204,6 +204,16 @@
} }
} }
div.avatar {
display: inline-flex;
justify-content: center;
align-items: center;
.center {
line-height: 14px;
}
}
strong { strong {
color: $gl-text-color; color: $gl-text-color;
} }
......
...@@ -161,6 +161,8 @@ ...@@ -161,6 +161,8 @@
} }
.nav-controls { .nav-controls {
@include new-style-dropdown;
display: inline-block; display: inline-block;
float: right; float: right;
text-align: right; text-align: right;
......
...@@ -97,9 +97,9 @@ $new-sidebar-collapsed-width: 50px; ...@@ -97,9 +97,9 @@ $new-sidebar-collapsed-width: 50px;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow: auto;
background-color: $gray-normal; background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color; box-shadow: inset -2px 0 0 $border-color;
transform: translate3d(0, 0, 0);
&.sidebar-icons-only { &.sidebar-icons-only {
width: $new-sidebar-collapsed-width; width: $new-sidebar-collapsed-width;
...@@ -176,6 +176,12 @@ $new-sidebar-collapsed-width: 50px; ...@@ -176,6 +176,12 @@ $new-sidebar-collapsed-width: 50px;
} }
} }
.nav-sidebar-inner-scroll {
height: 100%;
width: 100%;
overflow: auto;
}
.with-performance-bar .nav-sidebar { .with-performance-bar .nav-sidebar {
top: $header-height + $performance-bar-height; top: $header-height + $performance-bar-height;
} }
......
...@@ -81,6 +81,7 @@ ...@@ -81,6 +81,7 @@
border: 1px solid $white-normal; border: 1px solid $white-normal;
padding: 5px; padding: 5px;
max-height: calc(100vh - 100px); max-height: calc(100vh - 100px);
max-width: 100%;
} }
.emoji-block { .emoji-block {
......
...@@ -47,14 +47,26 @@ ...@@ -47,14 +47,26 @@
margin: 20px; margin: 20px;
} }
.repository-view.tree-content-holder { .repository-view {
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: $border-radius-default; border-radius: $border-radius-default;
color: $almost-black; color: $almost-black;
.tree-content-holder {
display: flex;
max-height: 100vh;
min-height: 300px;
}
.tree-content-holder-mini {
height: 100vh;
}
.panel-right { .panel-right {
display: inline-block; display: flex;
flex-direction: column;
width: 80%; width: 80%;
height: 100%;
.monaco-editor.vs { .monaco-editor.vs {
.line-numbers { .line-numbers {
...@@ -85,16 +97,17 @@ ...@@ -85,16 +97,17 @@
} }
.blob-viewer-container { .blob-viewer-container {
height: calc(100vh - 62px); flex: 1;
overflow: auto; overflow: auto;
} }
#tabs { #tabs {
flex-shrink: 0;
display: flex;
width: 100%;
padding-left: 0; padding-left: 0;
margin-bottom: 0; margin-bottom: 0;
display: flex;
white-space: nowrap; white-space: nowrap;
width: 100%;
overflow-y: hidden; overflow-y: hidden;
overflow-x: auto; overflow-x: auto;
...@@ -222,14 +235,12 @@ ...@@ -222,14 +235,12 @@
} }
#sidebar { #sidebar {
flex: 1;
height: 100%;
&.sidebar-mini { &.sidebar-mini {
display: inline-block;
vertical-align: top;
width: 20%; width: 20%;
border-right: 1px solid $white-normal; border-right: 1px solid $white-normal;
min-height: 475px;
height: calc(100vh + 20px);
overflow: auto; overflow: auto;
} }
......
...@@ -10,7 +10,7 @@ module IssuableActions ...@@ -10,7 +10,7 @@ module IssuableActions
def destroy def destroy
issuable.destroy issuable.destroy
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
TodoService.new.public_send(destroy_method, issuable, current_user) TodoService.new.public_send(destroy_method, issuable, current_user) # rubocop:disable GitlabSecurity/PublicSend
name = issuable.human_class_name name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted." flash[:notice] = "The #{name} was successfully deleted."
......
...@@ -34,7 +34,7 @@ class Groups::ApplicationController < ApplicationController ...@@ -34,7 +34,7 @@ class Groups::ApplicationController < ApplicationController
def build_canonical_path(group) def build_canonical_path(group)
params[:group_id] = group.to_param params[:group_id] = group.to_param
url_for(params) url_for(params)
end end
end end
...@@ -64,7 +64,7 @@ class Import::GithubController < Import::BaseController ...@@ -64,7 +64,7 @@ class Import::GithubController < Import::BaseController
end end
def import_enabled? def import_enabled?
__send__("#{provider}_import_enabled?") __send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
end end
def new_import_url def new_import_url
......
...@@ -142,13 +142,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -142,13 +142,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth def oauth
@oauth ||= request.env['omniauth.auth'] @oauth ||= request.env['omniauth.auth']
end end
def fail_login def fail_login
error_message = @user.errors.full_messages.to_sentence error_message = @user.errors.full_messages.to_sentence
return redirect_to omniauth_error_path(oauth['provider'], error: error_message) return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end end
def fail_ldap_login def fail_ldap_login
flash[:alert] = 'Access denied for your LDAP account.' flash[:alert] = 'Access denied for your LDAP account.'
......
...@@ -2,7 +2,7 @@ module Projects ...@@ -2,7 +2,7 @@ module Projects
module CycleAnalytics module CycleAnalytics
class EventsController < Projects::ApplicationController class EventsController < Projects::ApplicationController
include CycleAnalyticsParams include CycleAnalyticsParams
before_action :authorize_read_cycle_analytics! before_action :authorize_read_cycle_analytics!
before_action :authorize_read_build!, only: [:test, :staging] before_action :authorize_read_build!, only: [:test, :staging]
before_action :authorize_read_issue!, only: [:issue, :production] before_action :authorize_read_issue!, only: [:issue, :production]
...@@ -11,33 +11,33 @@ module Projects ...@@ -11,33 +11,33 @@ module Projects
def issue def issue
render_events(cycle_analytics[:issue].events) render_events(cycle_analytics[:issue].events)
end end
def plan def plan
render_events(cycle_analytics[:plan].events) render_events(cycle_analytics[:plan].events)
end end
def code def code
render_events(cycle_analytics[:code].events) render_events(cycle_analytics[:code].events)
end end
def test def test
options(events_params)[:branch] = events_params[:branch_name] options(events_params)[:branch] = events_params[:branch_name]
render_events(cycle_analytics[:test].events) render_events(cycle_analytics[:test].events)
end end
def review def review
render_events(cycle_analytics[:review].events) render_events(cycle_analytics[:review].events)
end end
def staging def staging
render_events(cycle_analytics[:staging].events) render_events(cycle_analytics[:staging].events)
end end
def production def production
render_events(cycle_analytics[:production].events) render_events(cycle_analytics[:production].events)
end end
private private
def render_events(events) def render_events(events)
...@@ -46,14 +46,14 @@ module Projects ...@@ -46,14 +46,14 @@ module Projects
format.json { render json: { events: events } } format.json { render json: { events: events } }
end end
end end
def cycle_analytics def cycle_analytics
@cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params)) @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params))
end end
def events_params def events_params
return {} unless params[:events].present? return {} unless params[:events].present?
params[:events].permit(:start_date, :branch_name) params[:events].permit(:start_date, :branch_name)
end end
end end
......
...@@ -218,8 +218,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -218,8 +218,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
if can?(current_user, :read_environment, environment) && environment.has_metrics? if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_project_environment_deployment_path(environment.project, environment, deployment) metrics_project_environment_deployment_path(environment.project, environment, deployment)
end end
metrics_monitoring_url = metrics_monitoring_url =
if can?(current_user, :read_environment, environment) if can?(current_user, :read_environment, environment)
environment_metrics_path(environment) environment_metrics_path(environment)
end end
......
...@@ -89,7 +89,7 @@ class UploadsController < ApplicationController ...@@ -89,7 +89,7 @@ class UploadsController < ApplicationController
@uploader.retrieve_from_store!(params[:filename]) @uploader.retrieve_from_store!(params[:filename])
else else
@uploader = @model.send(upload_mount) @uploader = @model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
redirect_to @uploader.url unless @uploader.file_storage? redirect_to @uploader.url unless @uploader.file_storage?
end end
......
...@@ -128,10 +128,10 @@ module CommitsHelper ...@@ -128,10 +128,10 @@ module CommitsHelper
# avatar: true will prepend the avatar image # avatar: true will prepend the avatar image
# size: size of the avatar image in px # size: size of the avatar image in px
def commit_person_link(commit, options = {}) def commit_person_link(commit, options = {})
user = commit.send(options[:source]) user = commit.public_send(options[:source]) # rubocop:disable GitlabSecurity/PublicSend
source_name = clean(commit.send "#{options[:source]}_name".to_sym) source_name = clean(commit.public_send(:"#{options[:source]}_name")) # rubocop:disable GitlabSecurity/PublicSend
source_email = clean(commit.send "#{options[:source]}_email".to_sym) source_email = clean(commit.public_send(:"#{options[:source]}_email")) # rubocop:disable GitlabSecurity/PublicSend
person_name = user.try(:name) || source_name person_name = user.try(:name) || source_name
......
...@@ -5,7 +5,7 @@ module ImportHelper ...@@ -5,7 +5,7 @@ module ImportHelper
end end
def provider_project_link(provider, path_with_namespace) def provider_project_link(provider, path_with_namespace)
url = __send__("#{provider}_project_url", path_with_namespace) url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend
link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer' link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
end end
......
...@@ -174,7 +174,14 @@ module IssuablesHelper ...@@ -174,7 +174,14 @@ module IssuablesHelper
end end
def assigned_issuables_count(issuable_type) def assigned_issuables_count(issuable_type)
current_user.public_send("assigned_open_#{issuable_type}_count") case issuable_type
when :issues
current_user.assigned_open_issues_count
when :merge_requests
current_user.assigned_open_merge_requests_count
else
raise ArgumentError, "invalid issuable `#{issuable_type}`"
end
end end
def issuable_filter_params def issuable_filter_params
...@@ -298,10 +305,6 @@ module IssuablesHelper ...@@ -298,10 +305,6 @@ module IssuablesHelper
cookies[:collapsed_gutter] == 'true' cookies[:collapsed_gutter] == 'true'
end end
def base_issuable_scope(issuable)
issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
end
def issuable_state_scope(issuable) def issuable_state_scope(issuable)
if issuable.respond_to?(:merged?) && issuable.merged? if issuable.respond_to?(:merged?) && issuable.merged?
:merged :merged
......
...@@ -32,7 +32,18 @@ module MilestonesHelper ...@@ -32,7 +32,18 @@ module MilestonesHelper
end end
def milestone_issues_by_label_count(milestone, label, state:) def milestone_issues_by_label_count(milestone, label, state:)
milestone.issues.with_label(label.title).send(state).size issues = milestone.issues.with_label(label.title)
issues =
case state
when :opened
issues.opened
when :closed
issues.closed
else
raise ArgumentError, "invalid milestone state `#{state}`"
end
issues.size
end end
# Returns count of milestones for different states # Returns count of milestones for different states
......
module PipelineSchedulesHelper module PipelineSchedulesHelper
def timezone_data def timezone_data
ActiveSupport::TimeZone.all.map do |timezone| ActiveSupport::TimeZone.all.map do |timezone|
{ {
name: timezone.name, name: timezone.name,
offset: timezone.utc_offset, offset: timezone.utc_offset,
identifier: timezone.tzinfo.identifier identifier: timezone.tzinfo.identifier
} }
end end
end end
......
...@@ -149,15 +149,16 @@ module ProjectsHelper ...@@ -149,15 +149,16 @@ module ProjectsHelper
# Don't show option "everyone with access" if project is private # Don't show option "everyone with access" if project is private
options = project_feature_options options = project_feature_options
level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend
if @project.private? if @project.private?
level = @project.project_feature.send(field)
disabled_option = ProjectFeature::ENABLED disabled_option = ProjectFeature::ENABLED
highest_available_option = ProjectFeature::PRIVATE if level == disabled_option highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
end end
options = options_for_select( options = options_for_select(
options.invert, options.invert,
selected: highest_available_option || @project.project_feature.public_send(field), selected: highest_available_option || level,
disabled: disabled_option disabled: disabled_option
) )
...@@ -488,7 +489,7 @@ module ProjectsHelper ...@@ -488,7 +489,7 @@ module ProjectsHelper
end end
def filename_path(project, filename) def filename_path(project, filename)
if project && blob = project.repository.send(filename) if project && blob = project.repository.public_send(filename) # rubocop:disable GitlabSecurity/PublicSend
project_blob_path( project_blob_path(
project, project,
tree_join(project.default_branch, blob.name) tree_join(project.default_branch, blob.name)
......
...@@ -2,7 +2,7 @@ module VersionCheckHelper ...@@ -2,7 +2,7 @@ module VersionCheckHelper
def version_status_badge def version_status_badge
if Rails.env.production? && current_application_settings.version_check_enabled if Rails.env.production? && current_application_settings.version_check_enabled
image_url = VersionCheck.new.url image_url = VersionCheck.new.url
image_tag image_url, class: 'js-version-status-badge', lazy: false image_tag image_url, class: 'js-version-status-badge'
end end
end end
end end
...@@ -2,7 +2,7 @@ module BlobViewer ...@@ -2,7 +2,7 @@ module BlobViewer
class Notebook < Base class Notebook < Base
include Rich include Rich
include ClientSide include ClientSide
self.partial_name = 'notebook' self.partial_name = 'notebook'
self.extensions = %w(ipynb) self.extensions = %w(ipynb)
self.binary = false self.binary = false
......
...@@ -194,10 +194,7 @@ module Ci ...@@ -194,10 +194,7 @@ module Ci
# * Maximum length is 63 bytes # * Maximum length is 63 bytes
# * First/Last Character is not a hyphen # * First/Last Character is not a hyphen
def ref_slug def ref_slug
ref.to_s Gitlab::Utils.slugify(ref.to_s)
.downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
end end
# Variables whose value does not depend on environment # Variables whose value does not depend on environment
......
...@@ -200,7 +200,7 @@ class Commit ...@@ -200,7 +200,7 @@ class Commit
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
@raw.send(m, *args, &block) @raw.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end end
def respond_to_missing?(method, include_private = false) def respond_to_missing?(method, include_private = false)
...@@ -383,6 +383,6 @@ class Commit ...@@ -383,6 +383,6 @@ class Commit
end end
def gpg_commit def gpg_commit
@gpg_commit ||= Gitlab::Gpg::Commit.new(self) @gpg_commit ||= Gitlab::Gpg::Commit.for_commit(self)
end end
end end
...@@ -78,7 +78,7 @@ module CacheMarkdownField ...@@ -78,7 +78,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field) def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field) html_field = cached_markdown_fields.html_field(markdown_field)
cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
return false unless cached return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false markdown_changed = attribute_changed?(markdown_field) || false
...@@ -93,14 +93,14 @@ module CacheMarkdownField ...@@ -93,14 +93,14 @@ module CacheMarkdownField
end end
def attribute_invalidated?(attr) def attribute_invalidated?(attr)
__send__("#{attr}_invalidated?") __send__("#{attr}_invalidated?") # rubocop:disable GitlabSecurity/PublicSend
end end
def cached_html_for(markdown_field) def cached_html_for(markdown_field)
raise ArgumentError.new("Unknown field: #{field}") unless raise ArgumentError.new("Unknown field: #{field}") unless
cached_markdown_fields.markdown_fields.include?(markdown_field) cached_markdown_fields.markdown_fields.include?(markdown_field)
__send__(cached_markdown_fields.html_field(markdown_field)) __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end end
included do included do
......
...@@ -9,7 +9,7 @@ module InternalId ...@@ -9,7 +9,7 @@ module InternalId
def set_iid def set_iid
if iid.blank? if iid.blank?
parent = project || group parent = project || group
records = parent.send(self.class.name.tableize) records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
records = records.with_deleted if self.paranoid? records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid) max_iid = records.maximum(:iid)
......
...@@ -56,7 +56,7 @@ module Mentionable ...@@ -56,7 +56,7 @@ module Mentionable
end end
self.class.mentionable_attrs.each do |attr, options| self.class.mentionable_attrs.each do |attr, options|
text = __send__(attr) text = __send__(attr) # rubocop:disable GitlabSecurity/PublicSend
options = options.merge( options = options.merge(
cache_key: [self, attr], cache_key: [self, attr],
author: author, author: author,
...@@ -100,7 +100,7 @@ module Mentionable ...@@ -100,7 +100,7 @@ module Mentionable
end end
self.class.mentionable_attrs.any? do |attr, _| self.class.mentionable_attrs.any? do |attr, _|
__send__(attr) =~ reference_pattern __send__(attr) =~ reference_pattern # rubocop:disable GitlabSecurity/PublicSend
end end
end end
......
...@@ -82,7 +82,7 @@ module Participable ...@@ -82,7 +82,7 @@ module Participable
if attr.respond_to?(:call) if attr.respond_to?(:call)
source.instance_exec(current_user, ext, &attr) source.instance_exec(current_user, ext, &attr)
else else
process << source.__send__(attr) process << source.__send__(attr) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
when Enumerable, ActiveRecord::Relation when Enumerable, ActiveRecord::Relation
......
...@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility ...@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility
build_project_feature unless project_feature build_project_feature unless project_feature
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.send(:write_attribute, field, access_level) project_feature.__send__(:write_attribute, field, access_level) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
...@@ -12,7 +12,7 @@ class DeployKeysProject < ActiveRecord::Base ...@@ -12,7 +12,7 @@ class DeployKeysProject < ActiveRecord::Base
def destroy_orphaned_deploy_key def destroy_orphaned_deploy_key
return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned? return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned?
self.deploy_key.destroy self.deploy_key.destroy
end end
end end
...@@ -18,4 +18,8 @@ class GpgSignature < ActiveRecord::Base ...@@ -18,4 +18,8 @@ class GpgSignature < ActiveRecord::Base
def commit def commit
project.commit(commit_sha) project.commit(commit_sha)
end end
def gpg_commit
Gitlab::Gpg::Commit.new(project, commit_sha)
end
end end
...@@ -12,7 +12,7 @@ module Network ...@@ -12,7 +12,7 @@ module Network
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
@commit.send(m, *args, &block) @commit.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end end
def space def space
......
...@@ -920,14 +920,14 @@ class Project < ActiveRecord::Base ...@@ -920,14 +920,14 @@ class Project < ActiveRecord::Base
end end
def execute_hooks(data, hooks_scope = :push_hooks) def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook| hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, hooks_scope.to_s) hook.async_execute(data, hooks_scope.to_s)
end end
end end
def execute_services(data, hooks_scope = :push_hooks) def execute_services(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope # Call only service hooks that are active for this scope
services.send(hooks_scope).each do |service| services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
service.async_execute(data) service.async_execute(data)
end end
end end
...@@ -1282,12 +1282,16 @@ class Project < ActiveRecord::Base ...@@ -1282,12 +1282,16 @@ class Project < ActiveRecord::Base
status.zero? status.zero?
end end
def full_path_slug
Gitlab::Utils.slugify(full_path.to_s)
end
def predefined_variables def predefined_variables
[ [
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true }, { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true }, { key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: full_path, public: true }, { key: 'CI_PROJECT_PATH', value: full_path, public: true },
{ key: 'CI_PROJECT_PATH_SLUG', value: full_path.parameterize, public: true }, { key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true }, { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true } { key: 'CI_PROJECT_URL', value: web_url, public: true }
] ]
......
...@@ -115,7 +115,7 @@ class ChatNotificationService < Service ...@@ -115,7 +115,7 @@ class ChatNotificationService < Service
def get_channel_field(event) def get_channel_field(event)
field_name = event_channel_name(event) field_name = event_channel_name(event)
self.public_send(field_name) self.public_send(field_name) # rubocop:disable GitlabSecurity/PublicSend
end end
def build_event_channels def build_event_channels
......
...@@ -53,7 +53,7 @@ class HipchatService < Service ...@@ -53,7 +53,7 @@ class HipchatService < Service
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
message = create_message(data) message = create_message(data)
return unless message.present? return unless message.present?
gate[room].send('GitLab', message, message_options(data)) gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend
end end
def test(data) def test(data)
......
class ProtectableDropdown class ProtectableDropdown
REF_TYPES = %i[branches tags].freeze
def initialize(project, ref_type) def initialize(project, ref_type)
raise ArgumentError, "invalid ref type `#{ref_type}`" unless ref_type.in?(REF_TYPES)
@project = project @project = project
@ref_type = ref_type @ref_type = ref_type
end end
...@@ -16,7 +20,7 @@ class ProtectableDropdown ...@@ -16,7 +20,7 @@ class ProtectableDropdown
private private
def refs def refs
@project.repository.public_send(@ref_type) @project.repository.public_send(@ref_type) # rubocop:disable GitlabSecurity/PublicSend
end end
def ref_names def ref_names
...@@ -24,7 +28,7 @@ class ProtectableDropdown ...@@ -24,7 +28,7 @@ class ProtectableDropdown
end end
def protections def protections
@project.public_send("protected_#{@ref_type}") @project.public_send("protected_#{@ref_type}") # rubocop:disable GitlabSecurity/PublicSend
end end
def non_wildcard_protected_ref_names def non_wildcard_protected_ref_names
......
...@@ -14,7 +14,7 @@ class RedirectRoute < ActiveRecord::Base ...@@ -14,7 +14,7 @@ class RedirectRoute < ActiveRecord::Base
else else
'redirect_routes.path = ? OR redirect_routes.path LIKE ?' 'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
end end
where(wheres, path, "#{sanitize_sql_like(path)}/%") where(wheres, path, "#{sanitize_sql_like(path)}/%")
end end
end end
...@@ -48,7 +48,9 @@ class Repository ...@@ -48,7 +48,9 @@ class Repository
alias_method(original, name) alias_method(original, name)
define_method(name) do define_method(name) do
cache_method_output(name, fallback: fallback, memoize_only: memoize_only) { __send__(original) } cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
__send__(original) # rubocop:disable GitlabSecurity/PublicSend
end
end end
end end
...@@ -443,9 +445,9 @@ class Repository ...@@ -443,9 +445,9 @@ class Repository
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
if m == :lookup && !block_given? if m == :lookup && !block_given?
lookup_cache[m] ||= {} lookup_cache[m] ||= {}
lookup_cache[m][args.join(":")] ||= raw_repository.send(m, *args, &block) lookup_cache[m][args.join(":")] ||= raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
else else
raw_repository.send(m, *args, &block) raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
...@@ -776,7 +778,7 @@ class Repository ...@@ -776,7 +778,7 @@ class Repository
end end
actions.each do |options| actions.each do |options|
index.public_send(options.delete(:action), options) index.public_send(options.delete(:action), options) # rubocop:disable GitlabSecurity/PublicSend
end end
options = { options = {
......
...@@ -1070,7 +1070,7 @@ class User < ActiveRecord::Base ...@@ -1070,7 +1070,7 @@ class User < ActiveRecord::Base
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args) def send_devise_notification(notification, *args)
return true unless can?(:receive_notifications) return true unless can?(:receive_notifications)
devise_mailer.send(notification, self, *args).deliver_later devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
end end
# This works around a bug in Devise 4.2.0 that erroneously causes a user to # This works around a bug in Devise 4.2.0 that erroneously causes a user to
......
class ProjectEntity < Grape::Entity class ProjectEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
expose :id expose :id
expose :name expose :name
......
...@@ -58,7 +58,7 @@ class AkismetService ...@@ -58,7 +58,7 @@ class AkismetService
} }
begin begin
akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend
true true
rescue => e rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
......
...@@ -23,7 +23,7 @@ module Ci ...@@ -23,7 +23,7 @@ module Ci
end end
attributes = CLONE_ACCESSORS.map do |attribute| attributes = CLONE_ACCESSORS.map do |attribute|
[attribute, build.send(attribute)] [attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end end
attributes.push([:user, current_user]) attributes.push([:user, current_user])
......
...@@ -11,6 +11,7 @@ module Commits ...@@ -11,6 +11,7 @@ module Commits
def commit_change(action) def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action) raise NotImplementedError unless repository.respond_to?(action)
# rubocop:disable GitlabSecurity/PublicSend
repository.public_send( repository.public_send(
action, action,
current_user, current_user,
......
...@@ -90,8 +90,19 @@ class GitPushService < BaseService ...@@ -90,8 +90,19 @@ class GitPushService < BaseService
end end
def update_signatures def update_signatures
@push_commits.each do |commit| commit_shas = @push_commits.last(PROCESS_COMMIT_LIMIT).map(&:sha)
CreateGpgSignatureWorker.perform_async(commit.sha, @project.id)
return if commit_shas.empty?
shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
commit_shas -= shas_with_cached_signatures
return if commit_shas.empty?
commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas)
commit_shas.each do |sha|
CreateGpgSignatureWorker.perform_async(sha, project.id)
end end
end end
......
...@@ -338,7 +338,7 @@ class IssuableBaseService < BaseService ...@@ -338,7 +338,7 @@ class IssuableBaseService < BaseService
def invalidate_cache_counts(issuable, users: [], skip_project_cache: false) def invalidate_cache_counts(issuable, users: [], skip_project_cache: false)
users.each do |user| users.each do |user|
user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") # rubocop:disable GitlabSecurity/PublicSend
end end
unless skip_project_cache unless skip_project_cache
......
...@@ -31,7 +31,7 @@ module Members ...@@ -31,7 +31,7 @@ module Members
source.members.find_by(condition) || source.members.find_by(condition) ||
source.requesters.find_by!(condition) source.requesters.find_by!(condition)
else else
source.public_send(scope).find_by!(condition) source.public_send(scope).find_by!(condition) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
......
...@@ -25,7 +25,6 @@ module MergeRequests ...@@ -25,7 +25,6 @@ module MergeRequests
end end
def after_create(issuable) def after_create(issuable)
event_service.open_mr(issuable, current_user)
todo_service.new_merge_request(issuable, current_user) todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user) issuable.cache_merge_request_closes_issues!(current_user)
update_merge_requests_head_pipeline(issuable) update_merge_requests_head_pipeline(issuable)
......
# rubocop:disable GitlabSecurity/PublicSend
# NotificationService class # NotificationService class
# #
# Used for notifying users with emails about different events # Used for notifying users with emails about different events
......
...@@ -4,7 +4,7 @@ class SystemHooksService ...@@ -4,7 +4,7 @@ class SystemHooksService
end end
def execute_hooks(data, hooks_scope = :all) def execute_hooks(data, hooks_scope = :all)
SystemHook.public_send(hooks_scope).find_each do |hook| SystemHook.public_send(hooks_scope).find_each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, 'system_hooks') hook.async_execute(data, 'system_hooks')
end end
end end
......
...@@ -18,7 +18,7 @@ module TestHooks ...@@ -18,7 +18,7 @@ module TestHooks
end end
error_message = catch(:validation_error) do error_message = catch(:validation_error) do
sample_data = self.__send__(trigger_data_method) sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend
return hook.execute(sample_data, trigger) return hook.execute(sample_data, trigger)
end end
......
...@@ -189,7 +189,7 @@ ...@@ -189,7 +189,7 @@
= icon('chevron-down') = icon('chevron-down')
%ul.dropdown-menu %ul.dropdown-menu
%li %li
%a Sort by date = link_to 'Sort by date', '#'
= link_to 'New issue', '#', class: 'btn btn-new btn-inverted' = link_to 'New issue', '#', class: 'btn btn-new btn-inverted'
......
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header .nav-sidebar-inner-scroll
= link_to group_path(@group), title: @group.name do .context-header
.avatar-container.s40.group-avatar = link_to group_path(@group), title: @group.name do
= image_tag group_icon(@group), class: "avatar s40 avatar-tile" .avatar-container.s40.group-avatar
.group-title = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
= @group.name .group-title
%ul.sidebar-top-level-items = @group.name
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do %ul.sidebar-top-level-items
= link_to group_path(@group), title: 'Group overview' do = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
.nav-icon-container = link_to group_path(@group), title: 'Group overview' do
= custom_icon('project') .nav-icon-container
%span.nav-item-name = custom_icon('project')
Overview %span.nav-item-name
Overview
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Group details' do
%span
Details
= nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do
%span
Activity
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= link_to issues_group_path(@group), title: 'Issues' do
.nav-icon-container
= custom_icon('issues')
%span.nav-item-name
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
Issues
%span.badge.count= number_with_delimiter(issues.count)
%ul.sidebar-sub-level-items
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to issues_group_path(@group), title: 'List' do
%span
List
= nav_link(path: 'labels#index') do %ul.sidebar-sub-level-items
= link_to group_labels_path(@group), title: 'Labels' do = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
%span = link_to group_path(@group), title: 'Group details' do
Labels %span
Details
= nav_link(path: 'milestones#index') do = nav_link(path: 'groups#activity') do
= link_to group_milestones_path(@group), title: 'Milestones' do = link_to activity_group_path(@group), title: 'Activity' do
%span %span
Milestones Activity
= nav_link(path: 'groups#merge_requests') do = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do = link_to issues_group_path(@group), title: 'Issues' do
.nav-icon-container
= custom_icon('mr_bold')
%span.nav-item-name
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
Merge Requests
%span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group), title: 'Members' do
.nav-icon-container
= custom_icon('members')
%span.nav-item-name
Members
- if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= link_to edit_group_path(@group), title: 'Settings' do
.nav-icon-container .nav-icon-container
= custom_icon('settings') = custom_icon('issues')
%span.nav-item-name %span.nav-item-name
Settings - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
Issues
%span.badge.count= number_with_delimiter(issues.count)
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(path: 'groups#edit') do = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to edit_group_path(@group), title: 'General' do = link_to issues_group_path(@group), title: 'List' do
%span %span
General List
= nav_link(path: 'groups#projects') do = nav_link(path: 'labels#index') do
= link_to projects_group_path(@group), title: 'Projects' do = link_to group_labels_path(@group), title: 'Labels' do
%span %span
Projects Labels
= nav_link(controller: :ci_cd) do = nav_link(path: 'milestones#index') do
= link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do = link_to group_milestones_path(@group), title: 'Milestones' do
%span %span
CI / CD Milestones
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
.nav-icon-container
= custom_icon('mr_bold')
%span.nav-item-name
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
Merge Requests
%span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group), title: 'Members' do
.nav-icon-container
= custom_icon('members')
%span.nav-item-name
Members
- if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= link_to edit_group_path(@group), title: 'Settings' do
.nav-icon-container
= custom_icon('settings')
%span.nav-item-name
Settings
%ul.sidebar-sub-level-items
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), title: 'General' do
%span
General
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects' do
%span
Projects
= nav_link(controller: :ci_cd) do
= link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do
%span
CI / CD
= render 'shared/sidebar_toggle_button' = render 'shared/sidebar_toggle_button'
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
.context-header .nav-sidebar-inner-scroll
= link_to profile_path, title: 'Profile Settings' do .context-header
.avatar-container.s40.settings-avatar
= icon('user')
.project-title User Settings
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
.nav-icon-container .avatar-container.s40.settings-avatar
= custom_icon('profile') = icon('user')
%span.nav-item-name .project-title User Settings
Profile %ul.sidebar-top-level-items
= nav_link(controller: [:accounts, :two_factor_auths]) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_account_path, title: 'Account' do = link_to profile_path, title: 'Profile Settings' do
.nav-icon-container
= custom_icon('account')
%span.nav-item-name
Account
- if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do
= link_to applications_profile_path, title: 'Applications' do
.nav-icon-container .nav-icon-container
= custom_icon('applications') = custom_icon('profile')
%span.nav-item-name %span.nav-item-name
Applications Profile
= nav_link(controller: :chat_names) do = nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_chat_names_path, title: 'Chat' do = link_to profile_account_path, title: 'Account' do
.nav-icon-container
= custom_icon('chat')
%span.nav-item-name
Chat
= nav_link(controller: :personal_access_tokens) do
= link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
.nav-icon-container
= custom_icon('access_tokens')
%span.nav-item-name
Access Tokens
= nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do
.nav-icon-container
= custom_icon('emails')
%span.nav-item-name
Emails
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
.nav-icon-container .nav-icon-container
= custom_icon('lock') = custom_icon('account')
%span.nav-item-name %span.nav-item-name
Password Account
= nav_link(controller: :notifications) do - if current_application_settings.user_oauth_applications?
= link_to profile_notifications_path, title: 'Notifications' do = nav_link(controller: 'oauth/applications') do
.nav-icon-container = link_to applications_profile_path, title: 'Applications' do
= custom_icon('notifications') .nav-icon-container
%span.nav-item-name = custom_icon('applications')
Notifications %span.nav-item-name
Applications
= nav_link(controller: :chat_names) do
= link_to profile_chat_names_path, title: 'Chat' do
.nav-icon-container
= custom_icon('chat')
%span.nav-item-name
Chat
= nav_link(controller: :personal_access_tokens) do
= link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
.nav-icon-container
= custom_icon('access_tokens')
%span.nav-item-name
Access Tokens
= nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do
.nav-icon-container
= custom_icon('emails')
%span.nav-item-name
Emails
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
.nav-icon-container
= custom_icon('lock')
%span.nav-item-name
Password
= nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications' do
.nav-icon-container
= custom_icon('notifications')
%span.nav-item-name
Notifications
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys' do = link_to profile_keys_path, title: 'SSH Keys' do
.nav-icon-container .nav-icon-container
= custom_icon('key') = custom_icon('key')
%span.nav-item-name %span.nav-item-name
SSH Keys SSH Keys
= nav_link(controller: :gpg_keys) do = nav_link(controller: :gpg_keys) do
= link_to profile_gpg_keys_path, title: 'GPG Keys' do = link_to profile_gpg_keys_path, title: 'GPG Keys' do
.nav-icon-container .nav-icon-container
= custom_icon('key_2') = custom_icon('key_2')
%span.nav-item-name %span.nav-item-name
GPG Keys GPG Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
.nav-icon-container .nav-icon-container
= custom_icon('preferences') = custom_icon('preferences')
%span.nav-item-name %span.nav-item-name
Preferences Preferences
= nav_link(path: 'profiles#audit_log') do = nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Authentication log' do = link_to audit_log_profile_path, title: 'Authentication log' do
.nav-icon-container .nav-icon-container
= custom_icon('authentication_log') = custom_icon('authentication_log')
%span.nav-item-name %span.nav-item-name
Authentication log Authentication log
= render 'shared/sidebar_toggle_button' = render 'shared/sidebar_toggle_button'
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
= link_to 'Close merge request', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: { original_text: "Close merge request", alternative_text: "Comment & close merge request"} = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: { original_text: "Close merge request", alternative_text: "Comment & close merge request"}
- if @merge_request.reopenable? - if @merge_request.reopenable?
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-close js-note-target-reopen", title: "Reopen merge request", data: { original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-close js-note-target-reopen", title: "Reopen merge request", data: { original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
%comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } %comment-and-resolve-btn{ "inline-template" => true }
%button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } } %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
{{ buttonText }} {{ buttonText }}
#notes= render "shared/notes/notes_with_form", :autocomplete => true #notes= render "shared/notes/notes_with_form", :autocomplete => true
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
.col-sm-6.milestone-actions .col-sm-6.milestone-actions
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
- if milestone.is_group_milestone? - if milestone.is_group_milestone?
= link_to edit_group_milestone_path(@group, milestone.id), class: "btn btn-xs btn-grouped" do = link_to edit_group_milestone_path(@group, milestone), class: "btn btn-xs btn-grouped" do
Edit Edit
\ \
- if milestone.closed? - if milestone.closed?
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.pull-right .pull-right
- if can?(current_user, :admin_milestones, group) - if can?(current_user, :admin_milestones, group)
- if milestone.is_group_milestone? - if milestone.is_group_milestone?
= link_to edit_group_milestone_path(group, milestone.iid), class: "btn btn btn-grouped" do = link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do
Edit Edit
- if milestone.active? - if milestone.active?
= link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close" = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
......
...@@ -4,13 +4,9 @@ class CreateGpgSignatureWorker ...@@ -4,13 +4,9 @@ class CreateGpgSignatureWorker
def perform(commit_sha, project_id) def perform(commit_sha, project_id)
project = Project.find_by(id: project_id) project = Project.find_by(id: project_id)
return unless project return unless project
commit = project.commit(commit_sha) # This calculates and caches the signature in the database
Gitlab::Gpg::Commit.new(project, commit_sha).signature
return unless commit
commit.signature
end end
end end
...@@ -4,6 +4,6 @@ class GitlabShellWorker ...@@ -4,6 +4,6 @@ class GitlabShellWorker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
def perform(action, *arg) def perform(action, *arg)
gitlab_shell.send(action, *arg) gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
---
title: Added tests for commits API unauthenticated user and public/private project
merge_request: 13287
author: Jacopo Beschi @jacopo-beschi
---
title: Fix CI_PROJECT_PATH_SLUG slugify
merge_request: 13350
author: Ivan Chernov
---
title: Fix commit list not loading the correct page when scrolling
merge_request:
author:
type: fixed
...@@ -649,6 +649,9 @@ test: ...@@ -649,6 +649,9 @@ test:
default: default:
path: tmp/tests/repositories/ path: tmp/tests/repositories/
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
failure_count_threshold: 999999
failure_wait_time: 0
storage_timeout: 30
broken: broken:
path: tmp/tests/non-existent-repositories path: tmp/tests/non-existent-repositories
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
......
...@@ -5,5 +5,5 @@ ActsAsTaggableOn.strict_case_match = true ...@@ -5,5 +5,5 @@ ActsAsTaggableOn.strict_case_match = true
ActsAsTaggableOn.tags_counter = false ActsAsTaggableOn.tags_counter = false
# validate that counter cache is disabled # validate that counter cache is disabled
raise "Counter cache is not disabled" if raise "Counter cache is not disabled" if
ActsAsTaggableOn::Tagging.reflections["tag"].options[:counter_cache] ActsAsTaggableOn::Tagging.reflections["tag"].options[:counter_cache]
# rubocop:disable GitlabSecurity/PublicSend
require_dependency Rails.root.join('lib/gitlab') # Load Gitlab as soon as possible require_dependency Rails.root.join('lib/gitlab') # Load Gitlab as soon as possible
class Settings < Settingslogic class Settings < Settingslogic
......
...@@ -37,12 +37,12 @@ def validate_storages_config ...@@ -37,12 +37,12 @@ def validate_storages_config
storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example") storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
end end
%w(failure_count_threshold failure_wait_time failure_reset_time storage_timeout).each do |setting| %w(failure_count_threshold failure_reset_time storage_timeout).each do |setting|
# Falling back to the defaults is fine! # Falling back to the defaults is fine!
next if repository_storage[setting].nil? next if repository_storage[setting].nil?
unless repository_storage[setting].to_f > 0 unless repository_storage[setting].to_f > 0
storage_validation_error("#{setting}, for storage `#{name}` needs to be greater than 0") storage_validation_error("`#{setting}` for storage `#{name}` needs to be greater than 0")
end end
end end
end end
......
module ActiveRecord
class PredicateBuilder
class ArrayHandler
module TypeCasting
def call(attribute, value)
# This is necessary because by default ActiveRecord does not respect
# custom type definitions (like our `ShaAttribute`) when providing an
# array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`.
model = attribute.relation&.engine
type = model.user_provided_columns[attribute.name] if model
value = value.map { |value| type.type_cast_for_database(value) } if type
super(attribute, value)
end
end
prepend TypeCasting
end
end
end
app = Rails.application app = Rails.application
if app.config.serve_static_files if app.config.serve_static_files
# The `ActionDispatch::Static` middleware intercepts requests for static files # The `ActionDispatch::Static` middleware intercepts requests for static files
# by checking if they exist in the `/public` directory. # by checking if they exist in the `/public` directory.
# We're replacing it with our `Gitlab::Middleware::Static` that does the same, # We're replacing it with our `Gitlab::Middleware::Static` that does the same,
# except ignoring `/uploads`, letting those go through to the GitLab Rails app. # except ignoring `/uploads`, letting those go through to the GitLab Rails app.
app.config.middleware.swap( app.config.middleware.swap(
ActionDispatch::Static, ActionDispatch::Static,
Gitlab::Middleware::Static, Gitlab::Middleware::Static,
app.paths["public"].first, app.paths["public"].first,
app.config.static_cache_control app.config.static_cache_control
) )
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# as the ActionDispatch::Request object. This is necessary for libraries # as the ActionDispatch::Request object. This is necessary for libraries
# like rack_attack where they don't use ActionDispatch, and we want them # like rack_attack where they don't use ActionDispatch, and we want them
# to block/throttle requests on private networks. # to block/throttle requests on private networks.
# Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145 # Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145
module Rack module Rack
class Request class Request
def trusted_proxy?(ip) def trusted_proxy?(ip)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
resource :repository, only: [:create] do resource :repository, only: [:create] do
member do member do
get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive' get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
# deprecated since GitLab 9.5 # deprecated since GitLab 9.5
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative' get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
end end
......
class DefaultRequestAccessProjects < ActiveRecord::Migration class DefaultRequestAccessProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
DOWNTIME = false DOWNTIME = false
def up def up
change_column_default :projects, :request_access_enabled, false change_column_default :projects, :request_access_enabled, false
end end
......
...@@ -21,7 +21,7 @@ class UpdateRetriedForCiBuild < ActiveRecord::Migration ...@@ -21,7 +21,7 @@ class UpdateRetriedForCiBuild < ActiveRecord::Migration
private private
def up_mysql def up_mysql
# This is a trick to overcome MySQL limitation: # This is a trick to overcome MySQL limitation:
# Mysql2::Error: Table 'ci_builds' is specified twice, both as a target for 'UPDATE' and as a separate source for data # Mysql2::Error: Table 'ci_builds' is specified twice, both as a target for 'UPDATE' and as a separate source for data
# However, this leads to create a temporary table from `max(ci_builds.id)` which is slow and do full database update # However, this leads to create a temporary table from `max(ci_builds.id)` which is slow and do full database update
execute <<-SQL.strip_heredoc execute <<-SQL.strip_heredoc
......
...@@ -7,7 +7,7 @@ class MigrateOldArtifacts < ActiveRecord::Migration ...@@ -7,7 +7,7 @@ class MigrateOldArtifacts < ActiveRecord::Migration
# This uses special heuristic to find potential candidates for data migration # This uses special heuristic to find potential candidates for data migration
# Read more about this here: https://gitlab.com/gitlab-org/gitlab-ce/issues/32036#note_30422345 # Read more about this here: https://gitlab.com/gitlab-org/gitlab-ce/issues/32036#note_30422345
def up def up
builds_with_artifacts.find_each do |build| builds_with_artifacts.find_each do |build|
build.migrate_artifacts! build.migrate_artifacts!
...@@ -51,14 +51,14 @@ class MigrateOldArtifacts < ActiveRecord::Migration ...@@ -51,14 +51,14 @@ class MigrateOldArtifacts < ActiveRecord::Migration
private private
def source_artifacts_path def source_artifacts_path
@source_artifacts_path ||= @source_artifacts_path ||=
File.join(Gitlab.config.artifacts.path, File.join(Gitlab.config.artifacts.path,
created_at.utc.strftime('%Y_%m'), created_at.utc.strftime('%Y_%m'),
ci_id.to_s, id.to_s) ci_id.to_s, id.to_s)
end end
def target_artifacts_path def target_artifacts_path
@target_artifacts_path ||= @target_artifacts_path ||=
File.join(Gitlab.config.artifacts.path, File.join(Gitlab.config.artifacts.path,
created_at.utc.strftime('%Y_%m'), created_at.utc.strftime('%Y_%m'),
project_id.to_s, id.to_s) project_id.to_s, id.to_s)
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveDuplicateMrEvents < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
class Event < ActiveRecord::Base
self.table_name = 'events'
end
def up
base_condition = "action = 1 AND target_type = 'MergeRequest' AND created_at > '2017-08-13'"
Event.select('target_id, count(*)')
.where(base_condition)
.group('target_id').having('count(*) > 1').each do |event|
duplicates = Event.where("#{base_condition} AND target_id = #{event.target_id}").pluck(:id)
duplicates.shift
Event.where(id: duplicates).delete_all
end
end
def down
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170809161910) do ActiveRecord::Schema.define(version: 20170815060945) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
## Databases ## Databases
- [Merge Request Checklist](database_merge_request_checklist.md)
- [What requires downtime?](what_requires_downtime.md) - [What requires downtime?](what_requires_downtime.md)
- [Adding database indexes](adding_database_indexes.md) - [Adding database indexes](adding_database_indexes.md)
- [Post Deployment Migrations](post_deployment_migrations.md) - [Post Deployment Migrations](post_deployment_migrations.md)
...@@ -56,6 +57,9 @@ ...@@ -56,6 +57,9 @@
- [Background Migrations](background_migrations.md) - [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md) - [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
- [Iterating Tables In Batches](iterating_tables_in_batches.md) - [Iterating Tables In Batches](iterating_tables_in_batches.md)
- [Ordering Table Columns](ordering_table_columns.md)
- [Verifying Database Capabilities](verifying_database_capabilities.md)
- [Hash Indexes](hash_indexes.md)
## i18n ## i18n
......
# Merge Request Checklist
When creating a merge request that performs database related changes (schema
changes, adjusting queries to optimise performance, etc) you should use the
merge request template called "Database Changes". This template contains a
checklist of steps to follow to make sure the changes are up to snuff.
To use the checklist, create a new merge request and click on the "Choose a
template" dropdown, then click "Database Changes".
An example of this checklist can be found at
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12463.
The source code of the checklist can be found in at
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20Changes.md
# Hash Indexes
Both PostgreSQL and MySQL support hash indexes besides the regular btree
indexes. Hash indexes however are to be avoided at all costs. While they may
_sometimes_ provide better performance the cost of rehashing can be very high.
More importantly: at least until PostgreSQL 10.0 hash indexes are not
WAL-logged, meaning they are not replicated to any replicas. From the PostgreSQL
documentation:
> Hash index operations are not presently WAL-logged, so hash indexes might need
> to be rebuilt with REINDEX after a database crash if there were unwritten
> changes. Also, changes to hash indexes are not replicated over streaming or
> file-based replication after the initial base backup, so they give wrong
> answers to queries that subsequently use them. For these reasons, hash index
> use is presently discouraged.
RuboCop is configured to register an offence when it detects the use of a hash
index.
Instead of using hash indexes you should use regular btree indexes.
# Ordering Table Columns
Similar to C structures the space of a table is influenced by the order of
columns. This is because the size of columns is aligned depending on the type of
the column. Take the following column order for example:
* id (integer, 4 bytes)
* name (text, variable)
* user_id (integer, 4 bytes)
Integers are aligned to the word size. This means that on a 64 bit platform the
actual size of each column would be: 8 bytes, variable, 8 bytes. This means that
each row will require at least 16 bytes for the two integers, and a variable
amount for the text field. If a table has a few rows this is not an issue, but
once you start storing millions of rows you can save space by using a different
order. For the above example a more ideal column order would be the following:
* id (integer, 4 bytes)
* user_id (integer, 4 bytes)
* name (text, variable)
In this setup the `id` and `user_id` columns can be packed together, which means
we only need 8 bytes to store _both_ of them. This in turn each row will require
8 bytes less of space.
For GitLab we require that columns of new tables are ordered based to use the
least amount of space. An easy way of doing this is to order them based on the
type size in descending order with variable sizes (string and text columns for
example) at the end.
## Type Sizes
While the PostgreSQL docuemntation
(https://www.postgresql.org/docs/current/static/datatype.html) contains plenty
of information we will list the sizes of common types here so it's easier to
look them up. Here "word" refers to the word size, which is 4 bytes for a 32
bits platform and 8 bytes for a 64 bits platform.
| Type | Size | Aligned To |
|:-----------------|:-------------------------------------|:-----------|
| smallint | 2 bytes | 1 word |
| integer | 4 bytes | 1 word |
| bigint | 8 bytes | 8 bytes |
| real | 4 bytes | 1 word |
| double precision | 8 bytes | 8 bytes |
| boolean | 1 byte | not needed |
| text / string | variable, 1 byte plus the data | 1 word |
| bytea | variable, 1 or 4 bytes plus the data | 1 word |
| timestamp | 8 bytes | 8 bytes |
| timestamptz | 8 bytes | 8 bytes |
| date | 4 bytes | 1 word |
A "variable" size means the actual size depends on the value being stored. If
PostgreSQL determines this can be embedded directly into a row it may do so, but
for very large values it will store the data externally and store a pointer (of
1 word in size) in the column. Because of this variable sized columns should
always be at the end of a table.
## Real Example
Let's use the "events" table as an example, which currently has the following
layout:
| Column | Type | Size |
|:------------|:----------------------------|:---------|
| id | integer | 4 bytes |
| target_type | character varying | variable |
| target_id | integer | 4 bytes |
| title | character varying | variable |
| data | text | variable |
| project_id | integer | 4 bytes |
| created_at | timestamp without time zone | 8 bytes |
| updated_at | timestamp without time zone | 8 bytes |
| action | integer | 4 bytes |
| author_id | integer | 4 bytes |
After adding padding to align the columns this would translate to columns being
divided into fixed size chunks as follows:
| Chunk Size | Columns |
|:-----------|:------------------|
| 8 bytes | id |
| variable | target_type |
| 8 bytes | target_id |
| variable | title |
| variable | data |
| 8 bytes | project_id |
| 8 bytes | created_at |
| 8 bytes | updated_at |
| 8 bytes | action, author_id |
This means that excluding the variable sized data we need at least 48 bytes per
row.
We can optimise this by using the following column order instead:
| Column | Type | Size |
|:------------|:----------------------------|:---------|
| created_at | timestamp without time zone | 8 bytes |
| updated_at | timestamp without time zone | 8 bytes |
| id | integer | 4 bytes |
| target_id | integer | 4 bytes |
| project_id | integer | 4 bytes |
| action | integer | 4 bytes |
| author_id | integer | 4 bytes |
| target_type | character varying | variable |
| title | character varying | variable |
| data | text | variable |
This would produce the following chunks:
| Chunk Size | Columns |
|:-----------|:-------------------|
| 8 bytes | created_at |
| 8 bytes | updated_at |
| 8 bytes | id, target_id |
| 8 bytes | project_id, action |
| 8 bytes | author_id |
| variable | target_type |
| variable | title |
| variable | data |
Here we only need 40 bytes per row excluding the variable sized data. 8 bytes
being saved may not sound like much, but for tables as large as the "events"
table it does begin to matter. For example, when storing 80 000 000 rows this
translates to a space saving of at least 610 MB: all by just changing the order
of a few columns.
# Serializing Data # Serializing Data
**Summary:** don't store serialized data in the database, use separate columns **Summary:** don't store serialized data in the database, use separate columns
and/or tables instead. and/or tables instead. This includes storing of comma separated values as a
string.
Rails makes it possible to store serialized data in JSON, YAML or other formats. Rails makes it possible to store serialized data in JSON, YAML or other formats.
Such a field can be defined as follows: Such a field can be defined as follows:
......
...@@ -216,4 +216,30 @@ exact same results. This also means there's no need to add an index on ...@@ -216,4 +216,30 @@ exact same results. This also means there's no need to add an index on
`created_at` to ensure consistent performance as `id` is already indexed by `created_at` to ensure consistent performance as `id` is already indexed by
default. default.
## Use WHERE EXISTS instead of WHERE IN
While `WHERE IN` and `WHERE EXISTS` can be used to produce the same data it is
recommended to use `WHERE EXISTS` whenever possible. While in many cases
PostgreSQL can optimise `WHERE IN` quite well there are also many cases where
`WHERE EXISTS` will perform (much) better.
In Rails you have to use this by creating SQL fragments:
```ruby
Project.where('EXISTS (?)', User.select(1).where('projects.creator_id = users.id AND users.foo = X'))
```
This would then produce a query along the lines of the following:
```sql
SELECT *
FROM projects
WHERE EXISTS (
SELECT 1
FROM users
WHERE projects.creator_id = users.id
AND users.foo = X
)
```
[gin-index]: http://www.postgresql.org/docs/current/static/gin.html [gin-index]: http://www.postgresql.org/docs/current/static/gin.html
# Verifying Database Capabilities
Sometimes certain bits of code may only work on a certain database and/or
version. While we try to avoid such code as much as possible sometimes it is
necessary to add database (version) specific behaviour.
To facilitate this we have the following methods that you can use:
* `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
* `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
* `Gitlab::Database.version`: returns the PostgreSQL version number as a string
in the format `X.Y.Z`. This method does not work for MySQL
This allows you to write code such as:
```ruby
if Gitlab::Database.postgresql?
if Gitlab::Database.version.to_f >= 9.6
run_really_fast_query
else
run_fast_query
end
else
run_query
end
```
# How to create a project in GitLab # How to create a project in GitLab
>**Notes:**
- For a list of words that are not allowed to be used as project names see the
[reserved names][reserved].
1. In your dashboard, click the green **New project** button or use the plus 1. In your dashboard, click the green **New project** button or use the plus
icon in the upper right corner of the navigation bar. icon in the upper right corner of the navigation bar.
...@@ -26,3 +30,4 @@ ...@@ -26,3 +30,4 @@
1. Click **Create project**. 1. Click **Create project**.
[import it]: ../workflow/importing/README.md [import it]: ../workflow/importing/README.md
[reserved]: ../user/reserved_names.md
...@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed ...@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed
# Install Git # Install Git
sudo apt-get install -y git-core sudo apt-get install -y git-core
# Make sure Git is version 2.8.4 or higher # Make sure Git is version 2.13.0 or higher
git --version git --version
Is the system packaged Git too old? Remove it and compile from source. Is the system packaged Git too old? Remove it and compile from source.
......
...@@ -57,6 +57,10 @@ By doing so: ...@@ -57,6 +57,10 @@ By doing so:
## Create a new group ## Create a new group
> **Notes:**
- For a list of words that are not allowed to be used as group names see the
[reserved names][reserved].
You can create a group in GitLab from: You can create a group in GitLab from:
1. The Groups page: expand the left menu, click **Groups**, and click the green button **New group**: 1. The Groups page: expand the left menu, click **Groups**, and click the green button **New group**:
...@@ -213,4 +217,5 @@ for the group (GitLab admins only, available in [GitLab Enterprise Edition Start ...@@ -213,4 +217,5 @@ for the group (GitLab admins only, available in [GitLab Enterprise Edition Start
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group - **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
[permissions]: ../permissions.md#permissions [permissions]: ../permissions.md#permissions
[ee]: https://about.gitlab.com/products/ [ee]: https://about.gitlab.com/products/
\ No newline at end of file [reserved]: ../reserved_names.md
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment