diff options
189 files changed, 1846 insertions, 1232 deletions
@@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0' gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem 'mysql2', '~> 0.3.16', group: :mysql +gem 'mysql2', '~> 0.4.5', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres gem 'rugged', '~> 0.25.1.1' diff --git a/Gemfile.lock b/Gemfile.lock index c27c06e9c34..41ef1e9d456 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -470,7 +470,7 @@ GEM tool (~> 0.2) mustermann-grape (0.4.0) mustermann (= 0.4.0) - mysql2 (0.3.20) + mysql2 (0.4.5) net-ldap (0.12.1) netrc (0.11.0) nokogiri (1.6.8.1) @@ -1011,7 +1011,7 @@ DEPENDENCIES method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) - mysql2 (~> 0.3.16) + mysql2 (~> 0.4.5) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.4) octokit (~> 4.6.2) diff --git a/README.md b/README.md index 59de828e1ac..9309922ae39 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Test coverage - [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby -- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript +- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript ## Canonical source diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index 2c38440a2af..687f09882a7 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -18,13 +18,26 @@ window.gl.CommitPipelinesTable = CommitPipelinesTable; document.addEventListener('DOMContentLoaded', () => { const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); - if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { - const table = new CommitPipelinesTable({ - propsData: { - endpoint: pipelineTableViewEl.dataset.endpoint, - helpPagePath: pipelineTableViewEl.dataset.helpPagePath, - }, - }).$mount(); - pipelineTableViewEl.appendChild(table.$el); + if (pipelineTableViewEl) { + // Update MR and Commits tabs + pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => { + if (event.detail.pipelines && + event.detail.pipelines.count && + event.detail.pipelines.count.all) { + const badge = document.querySelector('.js-pipelines-mr-count'); + + badge.textContent = event.detail.pipelines.count.all; + } + }); + + if (pipelineTableViewEl.dataset.disableInitialization === undefined) { + const table = new CommitPipelinesTable({ + propsData: { + endpoint: pipelineTableViewEl.dataset.endpoint, + helpPagePath: pipelineTableViewEl.dataset.helpPagePath, + }, + }).$mount(); + pipelineTableViewEl.appendChild(table.$el); + } } }); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 6d31b78b36d..dd751ec97a8 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -55,6 +55,17 @@ // depending of the endpoint the response can either bring a `pipelines` key or not. const pipelines = response.pipelines || response; this.setCommonData(pipelines); + + const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { + detail: { + pipelines: response, + }, + }); + + // notifiy to update the count in tabs + if (this.$el.parentElement) { + this.$el.parentElement.dispatchEvent(updatePipelinesEvent); + } }); }, }, diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 892b3fab1c6..26c67fb721c 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -149,7 +149,6 @@ import './star'; import './subscription'; import './subscription_select'; import './syntax_highlight'; -import './user'; // eslint-disable-next-line global-require, import/no-commonjs if (process.env.NODE_ENV !== 'production') require('./test_utils/'); diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 49d980212d6..e3daa8cf949 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,6 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* global Mousetrap */ -/* global findFileURL */ import Cookies from 'js-cookie'; import findAndFollowLink from './shortcuts_dashboard_navigation'; @@ -20,6 +19,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; const $globalDropdownMenu = $('.global-dropdown-menu'); const $globalDropdownToggle = $('.global-dropdown-toggle'); + const findFileURL = document.body.dataset.findFile; $('.global-dropdown').on('hide.bs.dropdown', () => { $globalDropdownMenu.removeClass('shortcuts'); diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js new file mode 100644 index 00000000000..b7f50cfd083 --- /dev/null +++ b/app/assets/javascripts/users/activity_calendar.js @@ -0,0 +1,227 @@ +/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */ + +import d3 from 'd3'; + +export default class ActivityCalendar { + constructor(timestamps, calendar_activities_path) { + this.calendar_activities_path = calendar_activities_path; + this.clickDay = this.clickDay.bind(this); + this.currentSelectedDate = ''; + this.daySpace = 1; + this.daySize = 15; + this.daySizeWithSpace = this.daySize + (this.daySpace * 2); + this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + this.months = []; + // Loop through the timestamps to create a group of objects + // The group of objects will be grouped based on the day of the week they are + this.timestampsTmp = []; + var group = 0; + + var today = new Date(); + today.setHours(0, 0, 0, 0, 0); + + var oneYearAgo = new Date(today); + oneYearAgo.setFullYear(today.getFullYear() - 1); + + var days = gl.utils.getDayDifference(oneYearAgo, today); + + for (var i = 0; i <= days; i += 1) { + var date = new Date(oneYearAgo); + date.setDate(date.getDate() + i); + + var day = date.getDay(); + var count = timestamps[date.format('yyyy-mm-dd')]; + + // Create a new group array if this is the first day of the week + // or if is first object + if ((day === 0 && i !== 0) || i === 0) { + this.timestampsTmp.push([]); + group += 1; + } + + var innerArray = this.timestampsTmp[group - 1]; + // Push to the inner array the values that will be used to render map + innerArray.push({ + count: count || 0, + date: date, + day: day + }); + } + + // Init color functions + this.colorKey = this.initColorKey(); + this.color = this.initColor(); + // Init the svg element + this.renderSvg(group); + this.renderDays(); + this.renderMonths(); + this.renderDayTitles(); + this.renderKey(); + this.initTooltips(); + } + + // Add extra padding for the last month label if it is also the last column + getExtraWidthPadding(group) { + var extraWidthPadding = 0; + var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); + var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); + + if (lastColMonth != secondLastColMonth) { + extraWidthPadding = 3; + } + + return extraWidthPadding; + } + + renderSvg(group) { + var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); + return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); + } + + renderDays() { + return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) { + return function(group, i) { + _.each(group, function(stamp, a) { + var lastMonth, lastMonthX, month, x; + if (a === 0 && stamp.day === 0) { + month = stamp.date.getMonth(); + x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace; + lastMonth = _.last(_this.months); + if (lastMonth != null) { + lastMonthX = lastMonth.x; + } + if (lastMonth == null) { + return _this.months.push({ + month: month, + x: x + }); + } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) { + return _this.months.push({ + month: month, + x: x + }); + } + } + }); + return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)"; + }; + })(this)).selectAll('rect').data(function(stamp) { + return stamp; + }).enter().append('rect').attr('x', '0').attr('y', (function(_this) { + return function(stamp, i) { + return _this.daySizeWithSpace * stamp.day; + }; + })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) { + return function(stamp) { + var contribText, date, dateText; + date = new Date(stamp.date); + contribText = 'No contributions'; + if (stamp.count > 0) { + contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); + } + dateText = date.format('mmm d, yyyy'); + return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText; + }; + })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) { + return function(stamp) { + if (stamp.count !== 0) { + return _this.color(Math.min(stamp.count, 40)); + } else { + return '#ededed'; + } + }; + })(this)).attr('data-container', 'body').on('click', this.clickDay); + } + + renderDayTitles() { + var days; + days = [ + { + text: 'M', + y: 29 + (this.daySizeWithSpace * 1) + }, { + text: 'W', + y: 29 + (this.daySizeWithSpace * 3) + }, { + text: 'F', + y: 29 + (this.daySizeWithSpace * 5) + } + ]; + return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) { + return day.y; + }).text(function(day) { + return day.text; + }).attr('class', 'user-contrib-text'); + } + + renderMonths() { + return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { + return date.x; + }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { + return function(date) { + return _this.monthNames[date.month]; + }; + })(this)); + } + + renderKey() { + const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions']; + const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; + + this.svg.append('g') + .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) + .selectAll('rect') + .data(keyColors) + .enter() + .append('rect') + .attr('width', this.daySize) + .attr('height', this.daySize) + .attr('x', (color, i) => this.daySizeWithSpace * i) + .attr('y', 0) + .attr('fill', color => color) + .attr('class', 'js-tooltip') + .attr('title', (color, i) => keyValues[i]) + .attr('data-container', 'body'); + } + + initColor() { + var colorRange; + colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; + return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange); + } + + initColorKey() { + return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); + } + + clickDay(stamp) { + var formatted_date; + if (this.currentSelectedDate !== stamp.date) { + this.currentSelectedDate = stamp.date; + formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate(); + return $.ajax({ + url: this.calendar_activities_path, + data: { + date: formatted_date + }, + cache: false, + dataType: 'html', + beforeSend: function() { + return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'); + }, + success: function(data) { + return $('.user-calendar-activities').html(data); + } + }); + } else { + this.currentSelectedDate = ''; + return $('.user-calendar-activities').html(''); + } + } + + initTooltips() { + return $('.js-contrib-calendar .js-tooltip').tooltip({ + html: true + }); + } +} diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js deleted file mode 100644 index b11f691e424..00000000000 --- a/app/assets/javascripts/users/calendar.js +++ /dev/null @@ -1,231 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */ - -import d3 from 'd3'; - -(function() { - this.Calendar = (function() { - function Calendar(timestamps, calendar_activities_path) { - this.calendar_activities_path = calendar_activities_path; - this.clickDay = this.clickDay.bind(this); - this.currentSelectedDate = ''; - this.daySpace = 1; - this.daySize = 15; - this.daySizeWithSpace = this.daySize + (this.daySpace * 2); - this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - this.months = []; - // Loop through the timestamps to create a group of objects - // The group of objects will be grouped based on the day of the week they are - this.timestampsTmp = []; - var group = 0; - - var today = new Date(); - today.setHours(0, 0, 0, 0, 0); - - var oneYearAgo = new Date(today); - oneYearAgo.setFullYear(today.getFullYear() - 1); - - var days = gl.utils.getDayDifference(oneYearAgo, today); - - for (var i = 0; i <= days; i += 1) { - var date = new Date(oneYearAgo); - date.setDate(date.getDate() + i); - - var day = date.getDay(); - var count = timestamps[date.format('yyyy-mm-dd')]; - - // Create a new group array if this is the first day of the week - // or if is first object - if ((day === 0 && i !== 0) || i === 0) { - this.timestampsTmp.push([]); - group += 1; - } - - var innerArray = this.timestampsTmp[group - 1]; - // Push to the inner array the values that will be used to render map - innerArray.push({ - count: count || 0, - date: date, - day: day - }); - } - - // Init color functions - this.colorKey = this.initColorKey(); - this.color = this.initColor(); - // Init the svg element - this.renderSvg(group); - this.renderDays(); - this.renderMonths(); - this.renderDayTitles(); - this.renderKey(); - this.initTooltips(); - } - - // Add extra padding for the last month label if it is also the last column - Calendar.prototype.getExtraWidthPadding = function(group) { - var extraWidthPadding = 0; - var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); - var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); - - if (lastColMonth != secondLastColMonth) { - extraWidthPadding = 3; - } - - return extraWidthPadding; - }; - - Calendar.prototype.renderSvg = function(group) { - var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); - return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); - }; - - Calendar.prototype.renderDays = function() { - return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) { - return function(group, i) { - _.each(group, function(stamp, a) { - var lastMonth, lastMonthX, month, x; - if (a === 0 && stamp.day === 0) { - month = stamp.date.getMonth(); - x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace; - lastMonth = _.last(_this.months); - if (lastMonth != null) { - lastMonthX = lastMonth.x; - } - if (lastMonth == null) { - return _this.months.push({ - month: month, - x: x - }); - } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) { - return _this.months.push({ - month: month, - x: x - }); - } - } - }); - return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)"; - }; - })(this)).selectAll('rect').data(function(stamp) { - return stamp; - }).enter().append('rect').attr('x', '0').attr('y', (function(_this) { - return function(stamp, i) { - return _this.daySizeWithSpace * stamp.day; - }; - })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) { - return function(stamp) { - var contribText, date, dateText; - date = new Date(stamp.date); - contribText = 'No contributions'; - if (stamp.count > 0) { - contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); - } - dateText = date.format('mmm d, yyyy'); - return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText; - }; - })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) { - return function(stamp) { - if (stamp.count !== 0) { - return _this.color(Math.min(stamp.count, 40)); - } else { - return '#ededed'; - } - }; - })(this)).attr('data-container', 'body').on('click', this.clickDay); - }; - - Calendar.prototype.renderDayTitles = function() { - var days; - days = [ - { - text: 'M', - y: 29 + (this.daySizeWithSpace * 1) - }, { - text: 'W', - y: 29 + (this.daySizeWithSpace * 3) - }, { - text: 'F', - y: 29 + (this.daySizeWithSpace * 5) - } - ]; - return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) { - return day.y; - }).text(function(day) { - return day.text; - }).attr('class', 'user-contrib-text'); - }; - - Calendar.prototype.renderMonths = function() { - return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { - return date.x; - }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { - return function(date) { - return _this.monthNames[date.month]; - }; - })(this)); - }; - - Calendar.prototype.renderKey = function() { - const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions']; - const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; - - this.svg.append('g') - .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) - .selectAll('rect') - .data(keyColors) - .enter() - .append('rect') - .attr('width', this.daySize) - .attr('height', this.daySize) - .attr('x', (color, i) => this.daySizeWithSpace * i) - .attr('y', 0) - .attr('fill', color => color) - .attr('class', 'js-tooltip') - .attr('title', (color, i) => keyValues[i]) - .attr('data-container', 'body'); - }; - - Calendar.prototype.initColor = function() { - var colorRange; - colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; - return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange); - }; - - Calendar.prototype.initColorKey = function() { - return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); - }; - - Calendar.prototype.clickDay = function(stamp) { - var formatted_date; - if (this.currentSelectedDate !== stamp.date) { - this.currentSelectedDate = stamp.date; - formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate(); - return $.ajax({ - url: this.calendar_activities_path, - data: { - date: formatted_date - }, - cache: false, - dataType: 'html', - beforeSend: function() { - return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'); - }, - success: function(data) { - return $('.user-calendar-activities').html(data); - } - }); - } else { - this.currentSelectedDate = ''; - return $('.user-calendar-activities').html(''); - } - }; - - Calendar.prototype.initTooltips = function() { - return $('.js-contrib-calendar .js-tooltip').tooltip({ - html: true - }); - }; - - return Calendar; - })(); -}).call(window); diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/users/index.js new file mode 100644 index 00000000000..ecd8e09161e --- /dev/null +++ b/app/assets/javascripts/users/index.js @@ -0,0 +1,7 @@ +import ActivityCalendar from './activity_calendar'; +import User from './user'; + +// use legacy exports until embedded javascript is refactored +window.Calendar = ActivityCalendar; +window.gl = window.gl || {}; +window.gl.User = User; diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/users/user.js index 9ef94ac7616..0b0a3e1afb4 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/users/user.js @@ -1,9 +1,9 @@ -/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */ +/* eslint-disable class-methods-use-this */ import Cookies from 'js-cookie'; import UserTabs from './user_tabs'; -class User { +export default class User { constructor({ action }) { this.action = action; this.placeProfileAvatarsToTop(); @@ -13,25 +13,22 @@ class User { placeProfileAvatarsToTop() { $('.profile-groups-avatars').tooltip({ - placement: 'top' + placement: 'top', }); } initTabs() { return new UserTabs({ parentEl: '.user-profile', - action: this.action + action: this.action, }); } hideProjectLimitMessage() { - $('.hide-project-limit-message').on('click', e => { + $('.hide-project-limit-message').on('click', (e) => { e.preventDefault(); Cookies.set('hide_project_limit_message', 'false'); $(this).parents('.project-limit-message').remove(); }); } } - -window.gl = window.gl || {}; -window.gl.User = User; diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/users/user_tabs.js index f8e23c8624d..f8e23c8624d 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/users/user_tabs.js diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js deleted file mode 100644 index a38ce4eb25e..00000000000 --- a/app/assets/javascripts/users/users_bundle.js +++ /dev/null @@ -1 +0,0 @@ -import './calendar'; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3405f142428..8bd69faf84c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -110,10 +110,10 @@ $well-light-text-color: #5b6169; * Text */ $gl-font-size: 14px; -$gl-text-color: rgba(0, 0, 0, .85); -$gl-text-color-light: rgba(0, 0, 0, .7); -$gl-text-color-secondary: rgba(0, 0, 0, .55); -$gl-text-color-disabled: rgba(0, 0, 0, .35); +$gl-text-color: #2e2e2e; +$gl-text-color-secondary: #707070; +$gl-text-color-tertiary: #949494; +$gl-text-color-quaternary: #d6d6d6; $gl-text-color-inverted: rgba(255, 255, 255, 1.0); $gl-text-color-secondary-inverted: rgba(255, 255, 255, .85); $gl-text-green: $green-600; @@ -127,7 +127,7 @@ $gl-gray-dark: #313236; $gl-gray-light: #5c5c5c; $gl-header-color: #4c4e54; $gl-header-nav-hover-color: #434343; -$placeholder-text-color: rgba(0, 0, 0, .42); +$placeholder-text-color: $gl-text-color-tertiary; /* * Lists @@ -135,7 +135,7 @@ $placeholder-text-color: rgba(0, 0, 0, .42); $list-font-size: $gl-font-size; $list-title-color: $gl-text-color; $list-text-color: $gl-text-color; -$list-text-disabled-color: $gl-text-color-disabled; +$list-text-disabled-color: $gl-text-color-tertiary; $list-border-light: #eee; $list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 73cb3a7cf4c..a8ddd00eca0 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -284,7 +284,7 @@ header.navbar-gitlab-new { position: relative; top: -1px; padding: 0 5px; - color: rgba($black, .65); + color: $gl-text-color-secondary; font-size: 10px; line-height: 1; background: none; @@ -310,10 +310,10 @@ header.navbar-gitlab-new { .breadcrumbs-links { flex: 1; align-self: center; - color: $black-transparent; + color: $gl-text-color-quaternary; a { - color: rgba($black, .65); + color: $gl-text-color-secondary; &:not(:first-child), &.group-path { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 0393820dee6..b6fc628c02b 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -244,6 +244,10 @@ } } + .block-last { + padding: 16px 0; + } + .trigger-build-variable { color: $code-color; } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 1cc060e4de8..c1bc4c0d675 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -113,6 +113,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :html_emails_enabled, :koding_enabled, :koding_url, + :password_authentication_enabled, :plantuml_enabled, :plantuml_url, :max_artifacts_size, @@ -135,7 +136,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :require_two_factor_authentication, :session_expire_delay, :sign_in_text, - :signin_enabled, :signup_enabled, :sentry_dsn, :sentry_enabled, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index db7edbd619b..43462b13903 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -170,7 +170,7 @@ class ApplicationController < ActionController::Base end def check_password_expiration - if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? + if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication? return redirect_to new_profile_password_path end end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index fe331a883c1..3120916c5bb 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -5,10 +5,10 @@ class AutocompleteController < ApplicationController def users @users ||= User.none - @users = @users.search(params[:search]) if params[:search].present? - @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? @users = @users.active @users = @users.reorder(:name) + @users = @users.search(params[:search]) if params[:search].present? + @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? @users = @users.page(params[:page]).per(params[:per_page]) if params[:todo_filter].present? && current_user diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index a8575e037e4..aa8cf630032 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,6 +1,8 @@ class PasswordsController < Devise::PasswordsController + include Gitlab::CurrentSettings + before_action :resource_from_email, only: [:create] - before_action :prevent_ldap_reset, only: [:create] + before_action :check_password_authentication_available, only: [:create] before_action :throttle_reset, only: [:create] def edit @@ -25,7 +27,7 @@ class PasswordsController < Devise::PasswordsController def update super do |resource| - if resource.valid? && resource.require_password? + if resource.valid? && resource.require_password_creation? resource.update_attribute(:password_automatically_set, false) end end @@ -38,11 +40,11 @@ class PasswordsController < Devise::PasswordsController self.resource = resource_class.find_by_email(email) end - def prevent_ldap_reset - return unless resource && resource.ldap_user? + def check_password_authentication_available + return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?) redirect_to after_sending_reset_password_instructions_path_for(resource_name), - alert: "Cannot reset password for LDAP user." + alert: "Password authentication is unavailable." end def throttle_reset diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 10145bae0d3..c423761ab24 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController end def authorize_change_password! - return render_404 if @user.ldap_user? + render_404 unless @user.allow_password_authentication? end def user_params diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 14a1e11a6ea..6de125e7e80 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -38,9 +38,14 @@ class Projects::CommitController < Projects::ApplicationController format.json do Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: PipelineSerializer - .new(project: @project, current_user: @current_user) - .represent(@pipelines) + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines), + count: { + all: @pipelines.count + } + } end end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c9e636fb65e..13f03e7e63e 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by - @noteable = @issue ||= @project.issues.find_by!(iid: params[:id]) + @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! return render_404 unless can?(current_user, :read_issue, @issue) diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index da058da795e..f35d53896ba 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -107,7 +107,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap @target_project = @merge_request.target_project @source_project = @merge_request.source_project - @commits = @merge_request.compare_commits.reverse + @commits = @merge_request.commits @commit = @merge_request.diff_head_commit @note_counts = Note.where(commit_id: @commits.map(&:id)) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a573b392591..70c41da4de5 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -112,9 +112,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: PipelineSerializer - .new(project: @project, current_user: @current_user) - .represent(@pipelines) + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines), + count: { + all: @pipelines.count + } + } end def edit diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index a5b17fa65ea..e04145dd0b3 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -69,8 +69,7 @@ class Projects::TriggersController < Projects::ApplicationController def trigger_params params.require(:trigger).permit( - :description, - trigger_schedule_attributes: [:id, :active, :cron, :cron_timezone, :ref] + :description ) end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 87a69e8e6f9..c769693255c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -50,10 +50,13 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| if result[:status] == :success flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name } + format.html do redirect_to(edit_project_path(@project)) end else + flash[:alert] = result[:message] + format.html { render 'edit' } end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index f39441a281e..0e8a57f8e03 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -48,7 +48,7 @@ class SessionsController < Devise::SessionsController private def login_counter - @login_counter ||= Gitlab::Metrics.counter(:user_session_logins, 'User sign in count') + @login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count') end # Handle an "initial setup" state, where there's only one user, it's an admin, @@ -58,7 +58,7 @@ class SessionsController < Devise::SessionsController user = User.admins.last - return unless user && user.require_password? + return unless user && user.require_password_creation? Users::UpdateService.new(user).execute do |user| @token = user.generate_reset_token diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 45b10f61f33..29b88c60dab 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -1,7 +1,7 @@ module ApplicationSettingsHelper delegate :gravatar_enabled?, :signup_enabled?, - :signin_enabled?, + :password_authentication_enabled?, :akismet_enabled?, :koding_enabled?, to: :current_application_settings diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index ba84dbe4a7a..bf9ad95b7c2 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -50,12 +50,12 @@ module ButtonHelper def http_clone_button(project, placement = 'right', append_link: true) klass = 'http-selector' - klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?) + klass << ' has-tooltip' if current_user.try(:require_password_creation?) || current_user.try(:require_personal_access_token_creation_for_git_auth?) protocol = gitlab_config.protocol.upcase tooltip_title = - if current_user.try(:require_password?) + if current_user.try(:require_password_creation?) _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol } else _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol } diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 25969adb649..26d11f9ab46 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -214,11 +214,11 @@ module ProjectsHelper def show_no_password_message? cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && - ( current_user.require_password? || current_user.require_personal_access_token? ) + ( current_user.require_password_creation? || current_user.require_personal_access_token_creation_for_git_auth? ) end def link_to_set_password - if current_user.require_password? + if current_user.require_password_creation? link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path else link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path @@ -518,4 +518,12 @@ module ProjectsHelper current_application_settings.restricted_visibility_levels || [] end + + def find_file_path + return unless @project && !@project.empty_repo? + + ref = @ref || @project.repository.root_ref + + project_find_file_path(@project, ref) + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 98e3906a932..898ce45f60e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -237,6 +237,7 @@ class ApplicationSetting < ActiveRecord::Base koding_url: nil, max_artifacts_size: Settings.artifacts['max_size'], max_attachment_size: Settings.gitlab['max_attachment_size'], + password_authentication_enabled: Settings.gitlab['password_authentication_enabled'], performance_bar_allowed_group_id: nil, plantuml_enabled: false, plantuml_url: nil, @@ -251,7 +252,6 @@ class ApplicationSetting < ActiveRecord::Base shared_runners_text: nil, sidekiq_throttling_enabled: false, sign_in_text: nil, - signin_enabled: Settings.gitlab['signin_enabled'], signup_enabled: Settings.gitlab['signup_enabled'], terminal_max_session_time: 0, two_factor_grace_period: 48, diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index fdacfa5a194..a155a064032 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -5,25 +5,6 @@ module Sortable extend ActiveSupport::Concern - module DropDefaultScopeOnFinders - # Override these methods to drop the `ORDER BY id DESC` default scope. - # See http://dba.stackexchange.com/a/110919 for why we do this. - %i[find find_by find_by!].each do |meth| - define_method meth do |*args, &block| - return super(*args, &block) if block - - unordered_relation = unscope(:order) - - # We cannot simply call `meth` on `unscope(:order)`, since that is also - # an instance of the same relation class this module is included into, - # which means we'd get infinite recursion. - # We explicitly use the original implementation to prevent this. - original_impl = method(__method__).super_method.unbind - original_impl.bind(unordered_relation).call(*args) - end - end - end - included do # By default all models should be ordered # by created_at field starting from newest @@ -37,10 +18,6 @@ module Sortable scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_desc, -> { reorder(name: :desc) } - - # All queries (relations) on this model are instances of this `relation_klass`. - relation_klass = relation_delegate_class(ActiveRecord::Relation) - relation_klass.prepend DropDefaultScopeOnFinders end module ClassMethods diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 815c5b43406..e4e7999d0f2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -32,9 +32,6 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed - delegate :commits, :real_size, :commit_shas, :commits_count, - to: :merge_request_diff, prefix: nil - # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests attr_accessor :allow_broken @@ -224,6 +221,36 @@ class MergeRequest < ActiveRecord::Base "#{project.to_reference(from, full: full)}#{reference}" end + def commits + if persisted? + merge_request_diff.commits + elsif compare_commits + compare_commits.reverse + else + [] + end + end + + def commits_count + if persisted? + merge_request_diff.commits_count + elsif compare_commits + compare_commits.size + else + 0 + end + end + + def commit_shas + if persisted? + merge_request_diff.commit_shas + elsif compare_commits + compare_commits.reverse.map(&:sha) + else + [] + end + end + def first_commit merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end @@ -246,9 +273,7 @@ class MergeRequest < ActiveRecord::Base def diff_size # Calling `merge_request_diff.diffs.real_size` will also perform # highlighting, which we don't need here. - return real_size if merge_request_diff - - diffs.real_size + merge_request_diff&.real_size || diffs.real_size end def diff_base_commit diff --git a/app/models/project.rb b/app/models/project.rb index e50818e3dff..0b357d5d003 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -486,7 +486,9 @@ class Project < ActiveRecord::Base end def has_container_registry_tags? - container_repositories.to_a.any?(&:has_tags?) || + return @images if defined?(@images) + + @images = container_repositories.to_a.any?(&:has_tags?) || has_root_container_repository_tags? end @@ -845,7 +847,7 @@ class Project < ActiveRecord::Base end def ci_service - @ci_service ||= ci_services.find_by(active: true) + @ci_service ||= ci_services.reorder(nil).find_by(active: true) end def deployment_services @@ -853,7 +855,7 @@ class Project < ActiveRecord::Base end def deployment_service - @deployment_service ||= deployment_services.find_by(active: true) + @deployment_service ||= deployment_services.reorder(nil).find_by(active: true) end def monitoring_services @@ -861,7 +863,7 @@ class Project < ActiveRecord::Base end def monitoring_service - @monitoring_service ||= monitoring_services.find_by(active: true) + @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) end def jira_tracker? @@ -977,8 +979,6 @@ class Project < ActiveRecord::Base Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}" - expire_caches_before_rename(old_path_with_namespace) - if has_container_registry_tags? Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!" @@ -986,6 +986,8 @@ class Project < ActiveRecord::Base raise StandardError.new('Project cannot be renamed, because images are present in its container registry') end + expire_caches_before_rename(old_path_with_namespace) + if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) # If repository moved successfully we need to send update instructions to users. # However we cannot allow rollback since we moved repository diff --git a/app/models/repository.rb b/app/models/repository.rb index 10b429c707e..8663cf5e602 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -123,6 +123,7 @@ class Repository commits end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384 def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) unless exists? && has_visible_content? && query.present? return [] @@ -610,6 +611,7 @@ class Repository commit(sha) end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/383 def last_commit_id_for_path(sha, path) key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}" diff --git a/app/models/user.rb b/app/models/user.rb index 2d39b1c1c34..8f40af24e20 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -314,7 +314,7 @@ class User < ActiveRecord::Base table[:name].matches(pattern) .or(table[:email].matches(pattern)) .or(table[:username].matches(pattern)) - ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc) + ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) end # searches user by given pattern @@ -580,16 +580,20 @@ class User < ActiveRecord::Base keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh') end - def require_password? - password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled? + def require_password_creation? + password_automatically_set? && allow_password_authentication? end - def require_personal_access_token? - return false if current_application_settings.signin_enabled? || ldap_user? + def require_personal_access_token_creation_for_git_auth? + return false if allow_password_authentication? || ldap_user? PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? end + def allow_password_authentication? + !ldap_user? && current_application_settings.password_authentication_enabled? + end + def can_change_username? gitlab_config.username_changing_enabled end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 43636fde0be..32925e9c1f2 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -129,6 +129,7 @@ class GitOperationService end end + # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites. def update_ref(ref, newrev, oldrev) # We use 'git update-ref' because libgit2/rugged currently does not # offer 'compare and swap' ref updates. Without compare-and-swap we can diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 55d9cb13ae4..30ca95eef7a 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -1,22 +1,16 @@ module Projects class UpdateService < BaseService def execute - # check that user is allowed to set specified visibility_level - new_visibility = params[:visibility_level] - - if new_visibility && new_visibility.to_i != project.visibility_level - unless can?(current_user, :change_visibility_level, project) && - Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) - - deny_visibility_level(project, new_visibility) - return error('Visibility level unallowed') - end + unless visibility_level_allowed? + return error('New visibility level not allowed!') end - new_branch = params[:default_branch] + if project.has_container_registry_tags? + return error('Cannot rename project because it contains container registry tags!') + end - if project.repository.exists? && new_branch && new_branch != project.default_branch - project.change_head(new_branch) + if changing_default_branch? + project.change_head(params[:default_branch]) end if project.update_attributes(params.except(:default_branch)) @@ -28,8 +22,33 @@ module Projects success else - error('Project could not be updated') + error('Project could not be updated!') end end + + private + + def visibility_level_allowed? + # check that user is allowed to set specified visibility_level + new_visibility = params[:visibility_level] + + if new_visibility && new_visibility.to_i != project.visibility_level + unless can?(current_user, :change_visibility_level, project) && + Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) + + deny_visibility_level(project, new_visibility) + return false + end + end + + true + end + + def changing_default_branch? + new_branch = params[:default_branch] + + project.repository.exists? && + new_branch && new_branch != project.default_branch + end end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 7f1e13c7989..26f7c1a473a 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -145,9 +145,9 @@ .form-group .col-sm-offset-2.col-sm-10 .checkbox - = f.label :signin_enabled do - = f.check_box :signin_enabled - Sign-in enabled + = f.label :password_authentication_enabled do + = f.check_box :password_authentication_enabled + Password authentication enabled - if omniauth_enabled? && button_based_providers.any? .form-group = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index af87129e49e..dd61dcf2a7b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -6,15 +6,15 @@ - else = render 'devise/shared/tabs_normal' .tab-content - - if signin_enabled? || ldap_enabled? || crowd_enabled? + - if password_authentication_enabled? || ldap_enabled? || crowd_enabled? = render 'devise/shared/signin_box' -# Signup only makes sense if you can also sign-in - - if signin_enabled? && signup_enabled? + - if password_authentication_enabled? && signup_enabled? = render 'devise/shared/signup_box' -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + - if !password_authentication_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) %div No authentication methods configured. diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index da4769e214e..3b06008febe 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -7,12 +7,12 @@ .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) } .login-body = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? + - if password_authentication_enabled? .login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' -- elsif signin_enabled? +- elsif password_authentication_enabled? .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index dd34600490e..6d0243a325d 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -5,9 +5,9 @@ - @ldap_servers.each_with_index do |server, i| %li{ class: active_when(i.zero? && !crowd_enabled?) } = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? + - if password_authentication_enabled? %li = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' - - if signin_enabled? && signup_enabled? + - if password_authentication_enabled? && signup_enabled? %li = link_to 'Register', '#register-pane', 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index c225d800a98..212856c0676 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,6 +1,6 @@ %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in - - if signin_enabled? && signup_enabled? + - if password_authentication_enabled? && signup_enabled? %li{ role: 'presentation' } %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml index bcd2f03e83c..e2dbdcbb939 100644 --- a/app/views/layouts/_broadcast.html.haml +++ b/app/views/layouts/_broadcast.html.haml @@ -1,2 +1,2 @@ -- BroadcastMessage.current.each do |message| +- BroadcastMessage.current&.each do |message| = broadcast_message(message) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 81f83b74826..38b95d11fd4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: I18n.locale, class: "#{page_class}" } = render "layouts/head" - %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } + %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } = render "layouts/init_auto_complete" if @gfm_form - if show_new_nav? = render "layouts/header/new" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 25acdb700ef..bc3293fd100 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -91,8 +91,3 @@ = yield :header_content = render 'shared/outdated_browser' - -- if @project && !@project.empty_repo? - - if ref = @ref || @project.repository.root_ref - :javascript - var findFileURL = "#{project_find_file_path(@project, ref)}"; diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml index 5bc15d68631..4697d91724b 100644 --- a/app/views/layouts/header/_new.html.haml +++ b/app/views/layouts/header/_new.html.haml @@ -84,8 +84,3 @@ = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;') = render 'shared/outdated_browser' - -- if @project && !@project.empty_repo? - - if ref = @ref || @project.repository.root_ref - :javascript - var findFileURL = "#{project_find_file_path(@project, ref)}"; diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index ae1e1361f0f..424905ea890 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -29,7 +29,7 @@ = link_to profile_emails_path, title: 'Emails' do %span Emails - - unless current_user.ldap_user? + - if current_user.allow_password_authentication? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do %span diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index f3f11b5b405..7338468967f 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -7,4 +7,4 @@ = nav_link(path: 'commit#pipelines') do = link_to pipelines_project_commit_path(@project, @commit.id) do Pipelines - %span.badge= @commit.pipelines.size + %span.badge.js-pipelines-mr-count= @commit.pipelines.size diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index bddb587ddc6..f2db71e8838 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -11,7 +11,7 @@ #js-details-block-vue - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) - .block{ class: ("block-first" if !@build.coverage) } + .block .title Job artifacts - if @build.artifacts_expired? @@ -37,7 +37,7 @@ Browse - if @build.trigger_request - .build-widget + .build-widget.block %h4.title Trigger @@ -55,7 +55,7 @@ .js-build-variable.trigger-build-variable= key .js-build-value.trigger-build-value= value - .block + %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } %p Commit = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' @@ -69,7 +69,7 @@ - if @build.pipeline.stages_count > 1 .dropdown.build-dropdown - .title + %div %span{ class: "ci-status-icon-#{@build.pipeline.status}" } = ci_icon_for_status(@build.pipeline.status) Pipeline diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 13012151349..2efc1d68190 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -47,7 +47,7 @@ %li.pipelines-tab = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines - %span.badge= @pipelines.size + %span.badge.js-pipelines-mr-count= @pipelines.size %li.diffs-tab = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do Changes diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index cf0db943865..5377d745371 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -1,28 +1,4 @@ -.panel.panel-default.protected-branches-list - - if @protected_branches.empty? - .panel-heading - %h3.panel-title - Protected branch (#{@protected_branches.size}) - %p.settings-message.text-center - There are currently no protected branches, protect a branch with the form above. - - else - - can_admin_project = can?(current_user, :admin_project, @project) +- can_admin_project = can?(current_user, :admin_project, @project) - %table.table.table-bordered - %colgroup - %col{ width: "25%" } - %col{ width: "30%" } - %col{ width: "25%" } - %col{ width: "20%" } - %thead - %tr - %th Protected branch (#{@protected_branches.size}) - %th Last commit - %th Allowed to merge - %th Allowed to push - - if can_admin_project - %th - %tbody - = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} - - = paginate @protected_branches, theme: 'gitlab' += render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do + = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index 99bc2516366..98d56a3e5c5 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -1,41 +1,14 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| - .panel.panel-default - .panel-heading - %h3.panel-title - Protect a branch - .panel-body - .form-horizontal - = form_errors(@protected_branch) - .form-group - = f.label :name, class: 'col-md-2 text-right' do - Branch: - .col-md-10 - = render partial: "projects/protected_branches/dropdown", locals: { f: f } - .help-block - = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches') - such as - %code *-stable - or - %code production/* - are supported - .form-group - %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } - Allowed to merge: - .col-md-10 - .merge_access_levels-container - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-merge wide', - dropdown_class: 'dropdown-menu-selectable capitalize-header', - data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) - .form-group - %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } - Allowed to push: - .col-md-10 - .push_access_levels-container - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-push wide', - dropdown_class: 'dropdown-menu-selectable capitalize-header', - data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) +- content_for :merge_access_levels do + .merge_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-merge wide', + dropdown_class: 'dropdown-menu-selectable capitalize-header', + data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) +- content_for :push_access_levels do + .push_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-push wide', + dropdown_class: 'dropdown-menu-selectable capitalize-header', + data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) - .panel-footer - = f.submit 'Protect', class: 'btn-create btn', disabled: true += render 'projects/protected_branches/shared/create_protected_branch' diff --git a/app/views/projects/protected_branches/_index.html.haml b/app/views/projects/protected_branches/_index.html.haml index 5d2422bdf54..2f30fe33a97 100644 --- a/app/views/projects/protected_branches/_index.html.haml +++ b/app/views/projects/protected_branches/_index.html.haml @@ -1,26 +1,10 @@ -- expanded = Rails.env.test? - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('protected_branches') -%section.settings - .settings-header - %h4 - Protected Branches - %button.btn.js-settings-toggle - = expanded ? 'Collapse' : 'Expand' - %p - Keep stable branches secure and force developers to use merge requests. - .settings-content.no-animate{ class: ('expanded' if expanded) } - %p - By default, protected branches are designed to: - %ul - %li prevent their creation, if not already created, from everybody except Masters - %li prevent pushes from everybody except Masters - %li prevent <strong>anyone</strong> from force pushing to the branch - %li prevent <strong>anyone</strong> from deleting the branch - %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}. +- content_for :create_protected_branch do + = render 'projects/protected_branches/create_protected_branch' - - if can? current_user, :admin_project, @project - = render 'projects/protected_branches/create_protected_branch' +- content_for :branches_list do + = render "projects/protected_branches/branches_list" - = render "projects/protected_branches/branches_list" += render 'projects/protected_branches/shared/index' diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index e4dadc42cc0..b12ae995ece 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -1,22 +1,2 @@ -%tr.js-protected-branch-edit-form{ data: { url: project_protected_branch_path(@project, protected_branch) } } - %td - %span.ref-name= protected_branch.name - - - if @project.root_ref?(protected_branch.name) - %span.label.label-info.prepend-left-5 default - %td - - if protected_branch.wildcard? - - matching_branches = protected_branch.matching(repository.branches) - = link_to pluralize(matching_branches.count, "matching branch"), project_protected_branch_path(@project, protected_branch) - - else - - if commit = protected_branch.commit - = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') - = time_ago_with_tooltip(commit.committed_date) - - else - (branch was removed from repository) - += render layout: 'projects/protected_branches/shared/protected_branch', locals: { protected_branch: protected_branch } do = render partial: 'projects/protected_branches/update_protected_branch', locals: { protected_branch: protected_branch } - - - if can_admin_project - %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml new file mode 100644 index 00000000000..5c00bb6883c --- /dev/null +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -0,0 +1,28 @@ +.panel.panel-default.protected-branches-list + - if @protected_branches.empty? + .panel-heading + %h3.panel-title + Protected branch (#{@protected_branches.size}) + %p.settings-message.text-center + There are currently no protected branches, protect a branch with the form above. + - else + %table.table.table-bordered + %colgroup + %col{ width: "20%" } + %col{ width: "20%" } + %col{ width: "20%" } + %col{ width: "20%" } + - if can_admin_project + %col + %thead + %tr + %th Protected branch (#{@protected_branches.size}) + %th Last commit + %th Allowed to merge + %th Allowed to push + - if can_admin_project + %th + %tbody + = yield + + = paginate @protected_branches, theme: 'gitlab' diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml new file mode 100644 index 00000000000..b619fa57e05 --- /dev/null +++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml @@ -0,0 +1,33 @@ += form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| + .panel.panel-default + .panel-heading + %h3.panel-title + Protect a branch + .panel-body + .form-horizontal + = form_errors(@protected_branch) + .form-group + = f.label :name, class: 'col-md-2 text-right' do + Branch: + .col-md-10 + = render partial: "projects/protected_branches/shared/dropdown", locals: { f: f } + .help-block + = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches') + such as + %code *-stable + or + %code production/* + are supported + .form-group + %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } + Allowed to merge: + .col-md-10 + = yield :merge_access_levels + .form-group + %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } + Allowed to push: + .col-md-10 + = yield :push_access_levels + + .panel-footer + = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/shared/_dropdown.html.haml index 6e9c473494e..6e9c473494e 100644 --- a/app/views/projects/protected_branches/_dropdown.html.haml +++ b/app/views/projects/protected_branches/shared/_dropdown.html.haml diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml new file mode 100644 index 00000000000..6a47cbdf724 --- /dev/null +++ b/app/views/projects/protected_branches/shared/_index.html.haml @@ -0,0 +1,24 @@ +- expanded = Rails.env.test? + +%section.settings + .settings-header + %h4 + Protected Branches + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Keep stable branches secure and force developers to use merge requests. + .settings-content.no-animate{ class: ('expanded' if expanded) } + %p + By default, protected branches are designed to: + %ul + %li prevent their creation, if not already created, from everybody except Masters + %li prevent pushes from everybody except Masters + %li prevent <strong>anyone</strong> from force pushing to the branch + %li prevent <strong>anyone</strong> from deleting the branch + %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}. + + - if can? current_user, :admin_project, @project + = content_for :create_protected_branch + + = content_for :branches_list diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/shared/_matching_branch.html.haml index 98793d632e6..98793d632e6 100644 --- a/app/views/projects/protected_branches/_matching_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_matching_branch.html.haml diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml new file mode 100644 index 00000000000..10b81e42572 --- /dev/null +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -0,0 +1,24 @@ +- can_admin_project = can?(current_user, :admin_project, @project) + +%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } + %td + %span.ref-name= protected_branch.name + + - if @project.root_ref?(protected_branch.name) + %span.label.label-info.prepend-left-5 default + %td + - if protected_branch.wildcard? + - matching_branches = protected_branch.matching(repository.branches) + = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) + - else + - if commit = protected_branch.commit + = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha') + = time_ago_with_tooltip(commit.committed_date) + - else + (branch was removed from repository) + + = yield + + - if can_admin_project + %td + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml index a806a0756ec..1012ceefe93 100644 --- a/app/views/projects/protected_branches/show.html.haml +++ b/app/views/projects/protected_branches/show.html.haml @@ -19,7 +19,7 @@ %th Last commit %tbody - @matching_refs.each do |matching_branch| - = render partial: "matching_branch", object: matching_branch + = render partial: "projects/protected_branches/shared/matching_branch", object: matching_branch - else %p.settings-message.text-center Couldn't find any matching branches. diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml index dd5b346d922..ea91e8af70e 100644 --- a/app/views/projects/protected_tags/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml @@ -1,32 +1,8 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f| - .panel.panel-default - .panel-heading - %h3.panel-title - Protect a tag - .panel-body - .form-horizontal - = form_errors(@protected_tag) - .form-group - = f.label :name, class: 'col-md-2 text-right' do - Tag: - .col-md-10.protected-tags-dropdown - = render partial: "projects/protected_tags/dropdown", locals: { f: f } - .help-block - = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags') - such as - %code v* - or - %code *-release - are supported - .form-group - %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } - Allowed to create: - .col-md-10 - .create_access_levels-container - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-create wide', - dropdown_class: 'dropdown-menu-selectable', - data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }}) +- content_for :create_access_levels do + .create_access_levels-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-create wide', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }}) - .panel-footer - = f.submit 'Protect', class: 'btn-create btn', disabled: true += render 'projects/protected_tags/shared/create_protected_tag' diff --git a/app/views/projects/protected_tags/_index.html.haml b/app/views/projects/protected_tags/_index.html.haml index 8250f692a69..955220562a6 100644 --- a/app/views/projects/protected_tags/_index.html.haml +++ b/app/views/projects/protected_tags/_index.html.haml @@ -1,26 +1,10 @@ -- expanded = Rails.env.test? - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('protected_tags') -%section.settings - .settings-header - %h4 - Protected Tags - %button.btn.js-settings-toggle - = expanded ? 'Collapse' : 'Expand' - %p - Limit access to creating and updating tags. - .settings-content.no-animate{ class: ('expanded' if expanded) } - %p - By default, protected tags are designed to: - %ul - %li Prevent tag creation by everybody except Masters - %li Prevent <strong>anyone</strong> from updating the tag - %li Prevent <strong>anyone</strong> from deleting the tag +- content_for :create_protected_tag do + = render 'projects/protected_tags/create_protected_tag' - %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}. +- content_for :tag_list do + = render "projects/protected_tags/tags_list" - - if can? current_user, :admin_project, @project - = render 'projects/protected_tags/create_protected_tag' - - = render "projects/protected_tags/tags_list" += render 'projects/protected_tags/shared/index' diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml index 5162da5e429..da1f97c8d6a 100644 --- a/app/views/projects/protected_tags/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_protected_tag.html.haml @@ -1,22 +1,2 @@ -%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } } - %td - %span.ref-name= protected_tag.name - - - if @project.root_ref?(protected_tag.name) - %span.label.label-info.prepend-left-5 default - %td - - if protected_tag.wildcard? - - matching_tags = protected_tag.matching(repository.tags) - = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag) - - else - - if commit = protected_tag.commit - = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') - = time_ago_with_tooltip(commit.committed_date) - - else - (tag was removed from repository) - += render layout: 'projects/protected_tags/shared/protected_tag', locals: { protected_tag: protected_tag } do = render partial: 'projects/protected_tags/update_protected_tag', locals: { protected_tag: protected_tag } - - - if can_admin_project - %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_tags/_tags_list.html.haml b/app/views/projects/protected_tags/_tags_list.html.haml index d432a5c9113..a6b18cc9f8f 100644 --- a/app/views/projects/protected_tags/_tags_list.html.haml +++ b/app/views/projects/protected_tags/_tags_list.html.haml @@ -1,30 +1,4 @@ -.panel.panel-default.protected-tags-list - - if @protected_tags.empty? - .panel-heading - %h3.panel-title - Protected tag (#{@protected_tags.size}) - %p.settings-message.text-center - There are currently no protected tags, protect a tag with the form above. - - else - - can_admin_project = can?(current_user, :admin_project, @project) +- can_admin_project = can?(current_user, :admin_project, @project) - %table.table.table-bordered - %colgroup - %col{ width: "25%" } - %col{ width: "25%" } - %col{ width: "50%" } - - if can_admin_project - %col - %thead - %tr - %th Protected tag (#{@protected_tags.size}) - %th Last commit - %th Allowed to create - - if can_admin_project - %th - %tbody - %tr - %td.flash-container{ colspan: 4 } - = render partial: 'projects/protected_tags/protected_tag', collection: @protected_tags, locals: { can_admin_project: can_admin_project} - - = paginate @protected_tags, theme: 'gitlab' += render layout: 'projects/protected_tags/shared/tags_list' do + = render partial: 'projects/protected_tags/protected_tag', collection: @protected_tags, locals: { can_admin_project: can_admin_project} diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml new file mode 100644 index 00000000000..5a53c704fcb --- /dev/null +++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml @@ -0,0 +1,29 @@ += form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f| + .panel.panel-default + .panel-heading + %h3.panel-title + Protect a tag + .panel-body + .form-horizontal + = form_errors(@protected_tag) + .form-group + = f.label :name, class: 'col-md-2 text-right' do + Tag: + .col-md-10.protected-tags-dropdown + = render partial: "projects/protected_tags/shared/dropdown", locals: { f: f } + .help-block + = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags') + such as + %code v* + or + %code *-release + are supported + .form-group + %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } + Allowed to create: + .col-md-10 + .create_access_levels-container + = yield :create_access_levels + + .panel-footer + = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/protected_tags/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml index 9b6923210f7..9b6923210f7 100644 --- a/app/views/projects/protected_tags/_dropdown.html.haml +++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml new file mode 100644 index 00000000000..c07bd454ff6 --- /dev/null +++ b/app/views/projects/protected_tags/shared/_index.html.haml @@ -0,0 +1,24 @@ +- expanded = Rails.env.test? + +%section.settings + .settings-header + %h4 + Protected Tags + %button.btn.js-settings-toggle + = expanded ? 'Collapse' : 'Expand' + %p + Limit access to creating and updating tags. + .settings-content.no-animate{ class: ('expanded' if expanded) } + %p + By default, protected tags are designed to: + %ul + %li Prevent tag creation by everybody except Masters + %li Prevent <strong>anyone</strong> from updating the tag + %li Prevent <strong>anyone</strong> from deleting the tag + + %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}. + + - if can? current_user, :admin_project, @project + = yield :create_protected_tag + + = yield :tag_list diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/shared/_matching_tag.html.haml index 05f102d1ca3..05f102d1ca3 100644 --- a/app/views/projects/protected_tags/_matching_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_matching_tag.html.haml diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml new file mode 100644 index 00000000000..c778f7b9781 --- /dev/null +++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml @@ -0,0 +1,22 @@ +%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } } + %td + %span.ref-name= protected_tag.name + + - if @project.root_ref?(protected_tag.name) + %span.label.label-info.prepend-left-5 default + %td + - if protected_tag.wildcard? + - matching_tags = protected_tag.matching(repository.tags) + = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag) + - else + - if commit = protected_tag.commit + = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') + = time_ago_with_tooltip(commit.committed_date) + - else + (tag was removed from repository) + + = yield + + - if can? current_user, :admin_project, @project + %td + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml new file mode 100644 index 00000000000..6e3cd4ada71 --- /dev/null +++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml @@ -0,0 +1,30 @@ +.panel.panel-default.protected-tags-list + - if @protected_tags.empty? + .panel-heading + %h3.panel-title + Protected tag (#{@protected_tags.size}) + %p.settings-message.text-center + There are currently no protected tags, protect a tag with the form above. + - else + - can_admin_project = can?(current_user, :admin_project, @project) + + %table.table.table-bordered + %colgroup + %col{ width: "25%" } + %col{ width: "25%" } + %col{ width: "50%" } + - if can_admin_project + %col + %thead + %tr + %th Protected tag (#{@protected_tags.size}) + %th Last commit + %th Allowed to create + - if can_admin_project + %th + %tbody + %tr + %td.flash-container{ colspan: 4 } + = yield + + = paginate @protected_tags, theme: 'gitlab' diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml index 16fc02fe9f4..86629f1753b 100644 --- a/app/views/projects/protected_tags/show.html.haml +++ b/app/views/projects/protected_tags/show.html.haml @@ -19,7 +19,7 @@ %th Last commit %tbody - @matching_refs.each do |matching_tag| - = render partial: "matching_tag", object: matching_tag + = render partial: "projects/protected_tags/shared/matching_tag", object: matching_tag - else %p.settings-message.text-center Couldn't find any matching tags. diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 40ea02abce9..0f20ecf8c69 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -6,6 +6,10 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('deploy_keys') +-# Protected branches & tags use a lot of nested partials. +-# The shared parts of the views can be found in the `shared` directory. +-# Those are used throughout the actual views. These `shared` views are then +-# reused in EE. = render "projects/protected_branches/index" = render "projects/protected_tags/index" = render @deploy_keys diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml index ab7a2db002e..c5e4d6e2871 100644 --- a/app/views/shared/_personal_access_tokens_table.html.haml +++ b/app/views/shared/_personal_access_tokens_table.html.haml @@ -39,22 +39,3 @@ - else .settings-message.text-center This user has no active #{type} Tokens. - -%hr - -%h5 Inactive #{type} Tokens (#{inactive_tokens.length}) -- if inactive_tokens.present? - .table-responsive - %table.table.inactive-tokens - %thead - %tr - %th Name - %th Created - %tbody - - inactive_tokens.each do |token| - %tr - %td= token.name - %td= token.created_at.to_date.to_s(:medium) -- else - .settings-message.text-center - This user has no inactive #{type} Tokens. diff --git a/changelogs/unreleased/19629-remove-inactive-tokens-list.yml b/changelogs/unreleased/19629-remove-inactive-tokens-list.yml new file mode 100644 index 00000000000..414e3d49e29 --- /dev/null +++ b/changelogs/unreleased/19629-remove-inactive-tokens-list.yml @@ -0,0 +1,4 @@ +--- +title: Remove Inactive Personal Access Tokens list from Access Tokens page +merge_request: 12866 +author: diff --git a/changelogs/unreleased/34075-pipelines-count-mt.yml b/changelogs/unreleased/34075-pipelines-count-mt.yml new file mode 100644 index 00000000000..3846e7b06a4 --- /dev/null +++ b/changelogs/unreleased/34075-pipelines-count-mt.yml @@ -0,0 +1,5 @@ +--- +title: Update Pipeline's badge count in Merge Request and Commits view to match real-time + content +merge_request: +author: diff --git a/changelogs/unreleased/35035-sidebar-job-spaces.yml b/changelogs/unreleased/35035-sidebar-job-spaces.yml new file mode 100644 index 00000000000..a9a0211efd9 --- /dev/null +++ b/changelogs/unreleased/35035-sidebar-job-spaces.yml @@ -0,0 +1,4 @@ +--- +title: Fix vertical space in job details sidebar +merge_request: +author: diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml deleted file mode 100644 index b359a25053a..00000000000 --- a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause -merge_request: -author: diff --git a/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml b/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml new file mode 100644 index 00000000000..2e0573beab6 --- /dev/null +++ b/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml @@ -0,0 +1,4 @@ +--- +title: Exact matches of username and email are now on top of the user search +merge_request: 12868 +author: diff --git a/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml b/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml new file mode 100644 index 00000000000..7adc53eb8fa --- /dev/null +++ b/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml @@ -0,0 +1,4 @@ +--- +title: Recover from renaming project that has container images +merge_request: 12840 +author: diff --git a/changelogs/unreleased/fixes-for-internal-auth-disabled.yml b/changelogs/unreleased/fixes-for-internal-auth-disabled.yml new file mode 100644 index 00000000000..188d2770455 --- /dev/null +++ b/changelogs/unreleased/fixes-for-internal-auth-disabled.yml @@ -0,0 +1,4 @@ +--- +title: Fixes needed when GitLab sign-in is not enabled +merge_request: 12491 +author: Robin Bobbitt diff --git a/changelogs/unreleased/pass-before-script-as-is.yml b/changelogs/unreleased/pass-before-script-as-is.yml new file mode 100644 index 00000000000..ac6513dcff6 --- /dev/null +++ b/changelogs/unreleased/pass-before-script-as-is.yml @@ -0,0 +1,4 @@ +--- +title: Pass before_script and script as-is preserving arrays +merge_request: +author: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index eb4a1e390a9..ec7ce51b542 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -223,7 +223,7 @@ rescue ArgumentError # no user configured end Settings.gitlab['time_zone'] ||= nil Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? -Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? +Settings.gitlab['password_authentication_enabled'] ||= true if Settings.gitlab['password_authentication_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb index 5e34aac6527..25630b298ce 100644 --- a/config/initializers/8_metrics.rb +++ b/config/initializers/8_metrics.rb @@ -123,7 +123,7 @@ Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_ Gitlab::Application.configure do |config| # 0 should be Sentry to catch errors in this middleware - config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware) + config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware) end if Gitlab::Metrics.enabled? @@ -174,6 +174,10 @@ if Gitlab::Metrics.enabled? loc && loc[0].start_with?(models) && method.source =~ regex end end + + # Ability is in app/models, is not an ActiveRecord model, but should still + # be instrumented. + Gitlab::Metrics::Instrumentation.instrument_methods(Ability) end Gitlab::Metrics::Instrumentation.configure do |config| diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml index d33fae4182d..60355e9140c 100644 --- a/config/prometheus/additional_metrics.yml +++ b/config/prometheus/additional_metrics.yml @@ -7,7 +7,7 @@ - aws_elb_request_count_sum weight: 1 queries: - - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) * 60' + - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60' label: Total unit: req / sec - title: "Latency" diff --git a/config/webpack.config.js b/config/webpack.config.js index c3fdca59a86..1113241e402 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -66,7 +66,7 @@ var config = { stl_viewer: './blob/stl_viewer.js', terminal: './terminal/terminal_bundle.js', u2f: ['vendor/u2f'], - users: './users/users_bundle.js', + users: './users/index.js', raven: './raven/index.js', vue_merge_request_widget: './vue_merge_request_widget/index.js', test: './test.js', diff --git a/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb b/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb new file mode 100644 index 00000000000..858b3bebace --- /dev/null +++ b/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb @@ -0,0 +1,15 @@ +class RenameApplicationSettingsSigninEnabledToPasswordAuthenticationEnabled < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + rename_column_concurrently :application_settings, :signin_enabled, :password_authentication_enabled + end + + def down + cleanup_concurrent_column_rename :application_settings, :password_authentication_enabled, :signin_enabled + end +end diff --git a/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb b/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb new file mode 100644 index 00000000000..52a773ddfee --- /dev/null +++ b/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb @@ -0,0 +1,15 @@ +class CleanupApplicationSettingsSigninEnabledRename < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + cleanup_concurrent_column_rename :application_settings, :signin_enabled, :password_authentication_enabled + end + + def down + rename_column_concurrently :application_settings, :password_authentication_enabled, :signin_enabled + end +end diff --git a/db/schema.rb b/db/schema.rb index 3dbe52c9c80..5264fc99557 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -41,7 +41,6 @@ ActiveRecord::Schema.define(version: 20170707184244) do create_table "application_settings", force: :cascade do |t| t.integer "default_projects_limit" t.boolean "signup_enabled" - t.boolean "signin_enabled" t.boolean "gravatar_enabled" t.text "sign_in_text" t.datetime "created_at" @@ -127,6 +126,7 @@ ActiveRecord::Schema.define(version: 20170707184244) do t.boolean "help_page_hide_commercial_content", default: false t.string "help_page_support_url" t.integer "performance_bar_allowed_group_id" + t.boolean "password_authentication_enabled" end create_table "audit_events", force: :cascade do |t| diff --git a/doc/README.md b/doc/README.md index 9b81c409570..1a7638b3d7e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -50,8 +50,7 @@ Shortcuts to GitLab's most visited docs: - [Fork a project](gitlab-basics/fork-project.md) - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private. -- [Groups](workflow/groups.md): Organize your projects in groups. - - [Create a group](gitlab-basics/create-group.md) +- [Groups](user/group/index.md): Organize your projects in groups. - [GitLab Subgroups](user/group/subgroups/index.md) - [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards. - [Snippets](user/snippets.md): Snippets allow you to create little bits of code. diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 725fc1f6076..c8987dea5e2 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -228,9 +228,14 @@ Tip: If you want to limit access to the nested members of an Active Directory group you can use the following syntax: ``` -(memberOf=CN=My Group,DC=Example,DC=com) +(memberOf:1.2.840.113556.1.4.1941=CN=My Group,DC=Example,DC=com) ``` +Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at +https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for +nested members in the user filter should not be confused with +[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes). + Please note that GitLab does not support the custom filter syntax used by omniauth-ldap. diff --git a/doc/administration/monitoring/ip_whitelist.md b/doc/administration/monitoring/ip_whitelist.md new file mode 100644 index 00000000000..ad2773de132 --- /dev/null +++ b/doc/administration/monitoring/ip_whitelist.md @@ -0,0 +1,39 @@ +# IP whitelist + +> Introduced in GitLab 9.4. + +GitLab provides some [monitoring endpoints] that provide health check information +when probed. + +To control access to those endpoints via IP whitelisting, you can add single +hosts or use IP ranges: + +**For Omnibus installations** + +1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following: + + ```ruby + gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1'] + ``` + +1. Save the file and [reconfigure] GitLab for the changes to take effect. + +--- + +**For installations from source** + +1. Edit `config/gitlab.yml`: + + ```yaml + monitoring: + # by default only local IPs are allowed to access monitoring resources + ip_whitelist: + - 127.0.0.0/8 + - 192.168.0.1 + ``` + +1. Save the file and [restart] GitLab for the changes to take effect. + +[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: ../restart_gitlab.md#installations-from-source +[monitoring endpoints]: ../../user/admin_area/monitoring/health_check.md diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 07c05b5a6fb..7c5505de8a2 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -1,10 +1,8 @@ # GitLab Prometheus metrics >**Note:** -Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For installations from source -you'll have to configure it yourself. - -GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other [Prometheus] exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic. +Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For +installations from source you'll have to configure it yourself. To enable the GitLab Prometheus metrics: @@ -15,33 +13,42 @@ To enable the GitLab Prometheus metrics: ## Collecting the metrics -Since the metrics endpoint is available on the same host and port as other traffic, it requires authentication. The token and URL to access is displayed on the [Health Check][health-check] page. +GitLab monitors its own internal service metrics, and makes them available at the +`/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access +it, the client IP needs to be [included in a whitelist][whitelist]. -Currently the embedded Prometheus server is not automatically configured to collect metrics from this endpoint. We recommend setting up another Prometheus server, because the embedded server configuration is overwritten one every reconfigure of GitLab. In the future this will not be required. +Currently the embedded Prometheus server is not automatically configured to +collect metrics from this endpoint. We recommend setting up another Prometheus +server, because the embedded server configuration is overwritten once every +[reconfigure of GitLab][reconfigure]. In the future this will not be required. ## Metrics available In this experimental phase, only a few metrics are available: -| Metric | Type | Description | -| ------ | ---- | ----------- | -| db_ping_timeout | Gauge | Whether or not the last database ping timed out | -| db_ping_success | Gauge | Whether or not the last database ping succeeded | -| db_ping_latency | Gauge | Round trip time of the database ping | -| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out | -| redis_ping_success | Gauge | Whether or not the last redis ping succeeded | -| redis_ping_latency | Gauge | Round trip time of the redis ping | -| filesystem_access_latency | gauge | Latency in accessing a specific filesystem | -| filesystem_accessible | gauge | Whether or not a specific filesystem is accessible | -| filesystem_write_latency | gauge | Write latency of a specific filesystem | -| filesystem_writable | gauge | Whether or not the filesystem is writable | -| filesystem_read_latency | gauge | Read latency of a specific filesystem | -| filesystem_readable | gauge | Whether or not the filesystem is readable | -| user_sessions_logins | Counter | Counter of how many users have logged in | +| Metric | Type | Description | +| --------------------------------- | --------- | ----------- | +| db_ping_timeout | Gauge | Whether or not the last database ping timed out | +| db_ping_success | Gauge | Whether or not the last database ping succeeded | +| db_ping_latency_seconds | Gauge | Round trip time of the database ping | +| filesystem_access_latency_seconds | Gauge | Latency in accessing a specific filesystem | +| filesystem_accessible | Gauge | Whether or not a specific filesystem is accessible | +| filesystem_write_latency_seconds | Gauge | Write latency of a specific filesystem | +| filesystem_writable | Gauge | Whether or not the filesystem is writable | +| filesystem_read_latency_seconds | Gauge | Read latency of a specific filesystem | +| filesystem_readable | Gauge | Whether or not the filesystem is readable | +| http_requests_total | Counter | Rack request count | +| http_request_duration_seconds | Histogram | HTTP response time from rack middleware | +| rack_uncaught_errors_total | Counter | Rack connections handling uncaught errors count | +| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out | +| redis_ping_success | Gauge | Whether or not the last redis ping succeeded | +| redis_ping_latency_seconds | Gauge | Round trip time of the redis ping | +| user_session_logins_total | Counter | Counter of how many users have logged in | [← Back to the main Prometheus page](index.md) [29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118 [Prometheus]: https://prometheus.io [restart]: ../../restart_gitlab.md#omnibus-gitlab-restart -[health-check]: ../../../user/admin_area/monitoring/health_check.md +[whitelist]: ../ip_whitelist.md +[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure diff --git a/doc/api/settings.md b/doc/api/settings.md index eefbdda42ce..0b4cc98cea6 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -25,7 +25,7 @@ Example response: "id" : 1, "default_branch_protection" : 2, "restricted_visibility_levels" : [], - "signin_enabled" : true, + "password_authentication_enabled" : true, "after_sign_out_path" : null, "max_attachment_size" : 10, "user_oauth_applications" : true, @@ -63,7 +63,7 @@ PUT /application/settings | --------- | ---- | :------: | ----------- | | `default_projects_limit` | integer | no | Project limit per user. Default is `100000` | | `signup_enabled` | boolean | no | Enable registration. Default is `true`. | -| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. | +| `password_authentication_enabled` | boolean | no | Enable authentication via a GitLab account password. Default is `true`. | | `gravatar_enabled` | boolean | no | Enable Gravatar | | `sign_in_text` | string | no | Text on login page | | `home_page_url` | string | no | Redirect to this URL when not logged in | @@ -102,7 +102,7 @@ Example response: "id": 1, "default_projects_limit": 100000, "signup_enabled": true, - "signin_enabled": true, + "password_authentication_enabled": true, "gravatar_enabled": true, "sign_in_text": "", "created_at": "2015-06-12T15:51:55.432Z", diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 5b09f79f143..36c55cbaceb 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -388,8 +388,8 @@ the style below as a guide: 1. Save the file and [restart] GitLab for the changes to take effect. -[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure -[restart]: path/to/administration/gitlab_restart.md#installations-from-source +[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: path/to/administration/restart_gitlab.md#installations-from-source ```` In this case: diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md index a549603c20d..94f3c8254a8 100644 --- a/doc/development/fe_guide/droplab/plugins/input_setter.md +++ b/doc/development/fe_guide/droplab/plugins/input_setter.md @@ -1,6 +1,6 @@ # InputSetter -`InputSetter` is a plugin that allows for udating DOM out of the scope of droplab when a list item is clicked. +`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked. ## Usage diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 12466437edc..3d893ba53dd 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -6,7 +6,7 @@ Step-by-step guides on the basics of working with Git and GitLab. - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) - [Create a project](create-project.md) -- [Create a group](create-group.md) +- [Create a group](../user/group/index.md#create-a-new-group) - [Create a branch](create-branch.md) - [Fork a project](fork-project.md) - [Add a file](add-file.md) diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index b4889bb8818..985a52d88f5 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,50 +1,2 @@ -# How to create a group in GitLab -Your projects in GitLab can be organized in 2 different ways: -under your own namespace for single projects, such as `your-name/project-1` or -under groups. - -If you organize your projects under a group, it works like a folder. You can -manage your group members' permissions and access to the projects. - ---- - -To create a group: - -1. Expand the left sidebar by clicking the three bars at the upper left corner - and then navigate to **Groups**. - - ![Go to groups](img/create_new_group_sidebar.png) - -1. Once in your groups dashboard, click on **New group**. - - ![Create new group information](img/create_new_group_info.png) - -1. Fill out the needed information: - - 1. Set the "Group path" which will be the namespace under which your projects - will be hosted (path can contain only letters, digits, underscores, dashes - and dots; it cannot start with dashes or end in dot). - 1. The "Group name" will populate with the path. Optionally, you can change - it. This is the name that will display in the group views. - 1. Optionally, you can add a description so that others can briefly understand - what this group is about. - 1. Optionally, choose and avatar for your project. - 1. Choose the [visibility level](../public_access/public_access.md). - -1. Finally, click the **Create group** button. - -## Add a new project to a group - -There are 2 different ways to add a new project to a group: - -- Select a group and then click on the **New project** button. - - ![New project](img/create_new_project_from_group.png) - - You can then continue on [creating a project](create-project.md). - -- While you are [creating a project](create-project.md), select a group namespace - you've already created from the dropdown menu. - - ![Select group](img/select_group_dropdown.png) +This document was moved to [another location](../user/group/index.md#create-a-new-group). diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png Binary files differdeleted file mode 100644 index fa88d1d51c0..00000000000 --- a/doc/gitlab-basics/img/create_new_group_sidebar.png +++ /dev/null diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md index 35220119e9b..c6b767fff02 100644 --- a/doc/install/google_cloud_platform/index.md +++ b/doc/install/google_cloud_platform/index.md @@ -2,13 +2,13 @@ ![GCP landing page](img/gcp_landing.png) ->**Important note:** -GitLab has no official images in Google Cloud Platform yet. This guide serves -as a template for when the GitLab VM will be available. - The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through the [Google Cloud Launcher][launcher] program. +GitLab's official Google Launcher apps: +1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public) +2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public) + ## Prerequisites There are only two prerequisites in order to install GitLab on GCP: diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md index 69a9dfc3500..70934f9960a 100644 --- a/doc/user/admin_area/monitoring/health_check.md +++ b/doc/user/admin_area/monitoring/health_check.md @@ -6,7 +6,7 @@ be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior) section. - [Access token](#access-token) has been deprecated in GitLab 9.4 - in favor of [IP Whitelist](#ip-whitelist) + in favor of [IP whitelist](#ip-whitelist) GitLab provides liveness and readiness probes to indicate service health and reachability to required services. These probes report on the status of the @@ -14,109 +14,101 @@ database connection, Redis connection, and access to the filesystem. These endpoints [can be provided to schedulers like Kubernetes][kubernetes] to hold traffic until the system is ready or restart the container as needed. -## IP Whitelist +## IP whitelist -To access monitoring resources the client IP needs to be included in the whitelist. -To add or remove hosts or IP ranges from the list you can edit `gitlab.rb` or `gitlab.yml`. +To access monitoring resources, the client IP needs to be included in a whitelist. -Example whitelist configuration: -```yaml -monitoring: - ip_whitelist: - - 127.0.0.0/8 # by default only local IPs are allowed to access monitoring resources -``` +[Read how to add IPs to a whitelist for the monitoring endpoints.][admin]. -## Access Token (Deprecated) +## Using the endpoint -An access token needs to be provided while accessing the probe endpoints. The current -accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check** -(`admin/health_check`) page of your GitLab instance. +With default whitelist settings, the probes can be accessed from localhost: -![access token](img/health_check_token.png) +- `http://localhost/-/readiness` +- `http://localhost/-/liveness` -The access token can be passed as a URL parameter: +which will then provide a report of system health in JSON format. + +Readiness example output: ``` -https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN +{ + "queues_check" : { + "status" : "ok" + }, + "redis_check" : { + "status" : "ok" + }, + "shared_state_check" : { + "status" : "ok" + }, + "fs_shards_check" : { + "labels" : { + "shard" : "default" + }, + "status" : "ok" + }, + "db_check" : { + "status" : "ok" + }, + "cache_check" : { + "status" : "ok" + } +} ``` -which will then provide a report of system health in JSON format: +Liveness example output: ``` { - "db_check": { - "status": "ok" - }, - "redis_check": { - "status": "ok" - }, - "fs_shards_check": { - "status": "ok", - "labels": { - "shard": "default" - } - } + "fs_shards_check" : { + "status" : "ok" + }, + "cache_check" : { + "status" : "ok" + }, + "db_check" : { + "status" : "ok" + }, + "redis_check" : { + "status" : "ok" + }, + "queues_check" : { + "status" : "ok" + }, + "shared_state_check" : { + "status" : "ok" + } } ``` -## Using the Endpoint - -With default whitelist settings, the probes can be accessed from localhost: - -- `http://localhost/-/readiness` -- `http://localhost/-/liveness` - ## Status On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint will return a valid successful HTTP status code, and a `success` message. -## Old behavior +## Access token (Deprecated) ->**Notes:** - - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1. - - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will - be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior) - section. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -Once you have the [access token](#access-token) or your client IP is [whitelisted](#ip-whitelist), -health information can be retrieved as plain text, JSON, or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` +>**Note:** +Access token has been deprecated in GitLab 9.4 +in favor of [IP whitelist](#ip-whitelist) -For example, the JSON output of the following health check: +An access token needs to be provided while accessing the probe endpoints. The current +accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check** +(`admin/health_check`) page of your GitLab instance. -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` +![access token](img/health_check_token.png) -would be like: +The access token can be passed as a URL parameter: ``` -{"healthy":true,"message":"success"} +https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN ``` -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - [ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416 [ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 [pingdom]: https://www.pingdom.com [nagios-health]: https://nagios-plugins.org/doc/man/check_http.html [newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring [kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +[admin]: ../../../administration/monitoring/ip_whitelist.md diff --git a/doc/workflow/groups/access_requests_management.png b/doc/user/group/img/access_requests_management.png Binary files differindex 36deaa89a70..36deaa89a70 100644 --- a/doc/workflow/groups/access_requests_management.png +++ b/doc/user/group/img/access_requests_management.png diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png Binary files differnew file mode 100644 index 00000000000..53f5596de23 --- /dev/null +++ b/doc/user/group/img/add_new_members.png diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/user/group/img/create_new_group_info.png Binary files differindex 8d2501d9f7a..8d2501d9f7a 100644 --- a/doc/gitlab-basics/img/create_new_group_info.png +++ b/doc/user/group/img/create_new_group_info.png diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/user/group/img/create_new_project_from_group.png Binary files differindex c35234660db..c35234660db 100644 --- a/doc/gitlab-basics/img/create_new_project_from_group.png +++ b/doc/user/group/img/create_new_project_from_group.png diff --git a/doc/user/group/img/group_settings.png b/doc/user/group/img/group_settings.png Binary files differnew file mode 100644 index 00000000000..629cd0729aa --- /dev/null +++ b/doc/user/group/img/group_settings.png diff --git a/doc/user/group/img/groups.png b/doc/user/group/img/groups.png Binary files differnew file mode 100644 index 00000000000..6211f999d5e --- /dev/null +++ b/doc/user/group/img/groups.png diff --git a/doc/user/group/img/membership_lock.png b/doc/user/group/img/membership_lock.png Binary files differnew file mode 100644 index 00000000000..d31fbb43375 --- /dev/null +++ b/doc/user/group/img/membership_lock.png diff --git a/doc/workflow/groups/new_group_form.png b/doc/user/group/img/new_group_form.png Binary files differindex 91727ab5336..91727ab5336 100644 --- a/doc/workflow/groups/new_group_form.png +++ b/doc/user/group/img/new_group_form.png diff --git a/doc/user/group/img/new_group_from_groups.png b/doc/user/group/img/new_group_from_groups.png Binary files differnew file mode 100644 index 00000000000..baf34244cb2 --- /dev/null +++ b/doc/user/group/img/new_group_from_groups.png diff --git a/doc/user/group/img/new_group_from_other_pages.png b/doc/user/group/img/new_group_from_other_pages.png Binary files differnew file mode 100644 index 00000000000..014a7088af2 --- /dev/null +++ b/doc/user/group/img/new_group_from_other_pages.png diff --git a/doc/workflow/groups/request_access_button.png b/doc/user/group/img/request_access_button.png Binary files differindex f1aae6afed7..f1aae6afed7 100644 --- a/doc/workflow/groups/request_access_button.png +++ b/doc/user/group/img/request_access_button.png diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/user/group/img/select_group_dropdown.png Binary files differindex 68fc950304c..68fc950304c 100644 --- a/doc/gitlab-basics/img/select_group_dropdown.png +++ b/doc/user/group/img/select_group_dropdown.png diff --git a/doc/user/group/img/share_with_group_lock.png b/doc/user/group/img/share_with_group_lock.png Binary files differnew file mode 100644 index 00000000000..8df41bf9465 --- /dev/null +++ b/doc/user/group/img/share_with_group_lock.png diff --git a/doc/user/group/img/transfer_project_to_other_group.png b/doc/user/group/img/transfer_project_to_other_group.png Binary files differnew file mode 100644 index 00000000000..042c002f83f --- /dev/null +++ b/doc/user/group/img/transfer_project_to_other_group.png diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/user/group/img/withdraw_access_request_button.png Binary files differindex c5d8ef6c04f..c5d8ef6c04f 100644 --- a/doc/workflow/groups/withdraw_access_request_button.png +++ b/doc/user/group/img/withdraw_access_request_button.png diff --git a/doc/user/group/index.md b/doc/user/group/index.md new file mode 100644 index 00000000000..2691cf7d671 --- /dev/null +++ b/doc/user/group/index.md @@ -0,0 +1,208 @@ +# Groups + +With GitLab Groups you can assemble related projects together +and grant members access to several projects at once. + +Groups can also be nested in [subgroups](subgroups/index.md). + +Find your groups by expanding the left menu and clicking **Groups**: + +![GitLab Groups](img/groups.png) + +The Groups page displays all groups you are a member of, how many projects it holds, +how many members it has, the group visibility, and, if you have enough permissions, +a link to the group settings. By clicking the last button you can leave that group. + +## Use cases + +You can create groups for numerous reasons. To name a few: + +- Organize related projects under the same [namespace](#namespaces), add members to that +group and grant access to all their projects at once +- Create a group, include members of your team, and make it easier to +`@mention` all the team at once in issues and merge requests + - Create a group for your company members, and create [subgroups](subgroups/index.md) + for each individual team. Let's say you create a group called `company-team`, and among others, + you created subgroups in this group for each individual team `backend-team`, + `frontend-team`, and `production-team`: + 1. When you start a new implementation from an issue, you add a comment: + _"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_ + 1. When your backend team needs help from frontend, they add a comment: + _"`@company-team/frontend-team` could you help us here please?"_ + 1. When the frontend team completes their implementation, they comment: + _"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_ + +## Namespaces + +In GitLab, a namespace is a unique name to be used as a user name, a group name, or a subgroup name. + +- `http://gitlab.example.com/username` +- `http://gitlab.example.com/groupname` +- `http://gitlab.example.com/groupname/subgroup_name` + +For example, consider a user called John: + +1. John creates his account on GitLab.com with the username `jonh`; +his profile will be accessed under `https://gitlab.example.com/john` +1. John creates a group for his team with the groupname `john-team`; +his group and its projects will be accessed under `https://gitlab.example.com/john-team` +1. John creates a subgroup of `john-team` with the subgroup name `marketing`; +his subgroup and its projects will be accessed under `https://gitlab.example.com/john-team/marketing` + +By doing so: + +- Any team member mentions John with `@john` +- John mentions everyone from his team with `@john-team` +- John mentions only his marketing team with `@john-team/marketing` + +## Create a new group + +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**: + + ![new group from groups page](img/new_group_from_groups.png) + +1. Elsewhere: expand the `plus` sign button on the top navbar and choose **New group**: + + ![new group from elsewhere](img/new_group_from_other_pages.png) + +Add the following information: + +![new group info](img/create_new_group_info.png) + +1. Set the **Group path** which will be the **namespace** under which your projects + will be hosted (path can contain only letters, digits, underscores, dashes + and dots; it cannot start with dashes or end in dot). +1. The **Group name** will populate with the path. Optionally, you can change + it. This is the name that will display in the group views. +1. Optionally, you can add a description so that others can briefly understand + what this group is about. +1. Optionally, choose an avatar for your project. +1. Choose the [visibility level](../../public_access/public_access.md). + +## Add users to a group + +Add members to a group by navigating to the group's dashboard, and clicking **Members**: + +![add members to group](img/add_new_members.png) + +Select the [permission level][permissions] and add the new member. You can also set the expiring +date for that user, from which they will no longer have access to your group. + +One of the benefits of putting multiple projects in one group is that you can +give a user to access to all projects in the group with one action. + +Consider we have a group with two projects: + +- On the **Group Members** page we can now add a new user to the group. +- Now because this user is a **Developer** member of the group, he automatically +gets **Developer** access to **all projects** within that group. + +If necessary, you can increase the access level of an individual user for a specific project, +by adding them again as a new member to the project with the new permission levels. + +## Request access to a group + +As a group owner you can enable or disable non members to request access to +your group. Go to the group settings and click on **Allow users to request access**. + +As a user, you can request to be a member of a group. Go to the group you'd +like to be a member of, and click the **Request Access** button on the right +side of your screen. + +![Request access button](img/request_access_button.png) + +--- + +Group owners and masters will be notified of your request and will be able to approve or +decline it on the members page. + +![Manage access requests](img/access_requests_management.png) + +--- + +If you change your mind before your request is approved, just click the +**Withdraw Access Request** button. + +![Withdraw access request button](img/withdraw_access_request_button.png) + +## Add projects to a group + +There are two different ways to add a new project to a group: + +- Select a group and then click on the **New project** button. + + ![New project](img/create_new_project_from_group.png) + + You can then continue on [creating a project](../../gitlab-basics/create-project.md). + +- While you are creating a project, select a group namespace + you've already created from the dropdown menu. + + ![Select group](img/select_group_dropdown.png) + +## Transfer an existing project into a group + +You can transfer an existing project into a group as long as you have at least **Master** [permissions][permissions] to that group +and if you are an **Owner** of the project. + +![Transfer a project to a new namespace](img/transfer_project_to_other_group.png) + +Find this option under your project's settings. + +GitLab administrators can use the admin interface to move any project to any namespace if needed. + +## Manage group memberships via LDAP + +In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. +See [the GitLab Enterprise Edition documentation](../../integration/ldap.md) for more information. + +## Group settings + +Once you have created a group, you can manage its settings by navigating to +the group's dashboard, and clicking **Settings**. + +![group settings](img/group_settings.png) + +### General settings + +Besides giving you the option to edit any settings you've previously +set when [creating the group](#create-a-new-group), you can also +access further configurations for your group. + +#### Enforce 2FA to group members + +Add a secury layer to your group by +[enforcing two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group) +to all group members. + +#### Member Lock (EES/EEP) + +Available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), +with **Member Lock** it is possible to lock membership in project to the +level of members in group. + +Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock-ees-eep). + +#### Share with group lock (EES/EEP) + +In [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/) +it is possible to prevent projects in a group from [sharing +a project with another group](../../workflow/share_projects_with_other_groups.md). +This allows for tighter control over project access. + +Learn more about [Share with group lock](https://docs.gitlab.com/ee/user/group/index.html#share-with-group-lock-ees-eep). + +### Advanced settings + +- **Projects**: view all projects within that group, add members to each project, +access each project's settings, and remove any project from the same screen. +- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) +and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter][ee].) +- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events) +for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee]). +- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group + +[permissions]: ../permissions.md#permissions +[ee]: https://about.gitlab.com/products/
\ No newline at end of file diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index e55e2aea023..1f78849a92c 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -5,13 +5,13 @@ for tracking the evolution of a new idea or the process of solving a problem. It allows you, your team, and your collaborators to share -and discuss proposals, before and while implementing them. +and discuss proposals before and while implementing them. Issues and the GitLab Issue Tracker are available in all [GitLab Products](https://about.gitlab.com/products/) as part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/). -## Use-Cases +## Use cases Issues can have endless applications. Just to exemplify, these are some cases for which creating issues are most used: @@ -23,7 +23,28 @@ some cases for which creating issues are most used: - Obtaining support - Elaborating new code implementations -See also the blog post [Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/). +See also the blog post "[Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)". + +### Keep private things private + +For instance, let's assume you have a public project but want to start a discussion on something +you don't want to be public. With [Confidential Issues](#confidential-issues), +you can discuss private matters among the project members, and still keep +your project public, open to collaboration. + +### Streamline collaboration + +With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), +available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/) +you can streamline collaboration and allow shared responsibilities to be clearly displayed. +All assignees are shown across your workflows and receive notifications (as they +would as single assignees), simplifying communication and ownership. + +### Consistent collaboration + +Create [issue templates](#issue-templates) to make collaboration consistent and +containing all information you need. For example, you can create a template +for feature proposals and another one for bug reports. ## Issue Tracker @@ -96,8 +117,8 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue Read through the documentation for [Issue Boards](../issue_board.md) to find out more about this feature. -[Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) -are available only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). +With [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), you can also +create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards). ### Issue's API diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index 294176e61f9..138276edf07 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -43,7 +43,7 @@ assigned to them if they created the issue themselves. ##### 3.1. Multiple Assignees (EES/EEP) -Issue Weights are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). +Multiple Assignees are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). Often multiple people likely work on the same issue together, which can especially be difficult to track in large teams @@ -52,9 +52,7 @@ where there is shared ownership of an issue. In GitLab Enterprise Edition, you can also select multiple assignees to an issue. -> **Note:** -Multiple Assignees was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1904) -in [GitLab Enterprise Edition 9.2](https://about.gitlab.com/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues). +Learn more on the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html). #### 4. Milestone diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 954454f7e7a..9bdf2a998d3 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -3,6 +3,59 @@ Merge requests allow you to exchange changes you made to source code and collaborate with other people on the same project. +## Overview + +A Merge Request (**MR**) is the basis of GitLab as a code collaboration +and version control platform. +Is it simple as the name implies: a _request_ to _merge_ one branch into another. + +With GitLab merge requests, you can: + +- Compare the changes between two [branches](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell#_git_branching) +- [Review and discuss](../../discussions/index.md#discussions) the proposed modifications inline +- Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project +- Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md) +- Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests) +- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs) +- [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request +- Assign it to any registered user, and change the assignee how many times you need +- Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation +- Organize your issues and merge requests consistently throughout the project with [labels](../../project/labels.md) +- Add a time estimation and the time spent with that merge request with [Time Tracking](../../../workflow/time_tracking.html#time-tracking) +- [Resolve merge conflicts from the UI](#resolve-conflicts) + +With **[GitLab Enterprise Edition][ee]**, you can also: + +- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Enterprise Edition Premium) +- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Enterprise Edition Starter) +- Enable [fast-forward merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/fast_forward_merge.html) (available in GitLab Enterprise Edition Starter) +- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Enterprise Edition Starter) +- Enable [semi-linear history merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch (available in GitLab Enterprise Edition Starter) +- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter) + +## Use cases + +A. Consider you are a software developer working in a team: + +1. You checkout a new branch, and submit your changes through a merge request +1. You gather feedback from your team +1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter) +1. You build and test your changes with GitLab CI/CD +1. You request the approval from your manager +1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Enterprise Edition Starter) +1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD +1. Your implementations were successfully shipped to your customer + +B. Consider you're a web developer writing a webpage for your company's: + +1. You checkout a new branch, and submit a new page through a merge request +1. You gather feedback from your reviewers +1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md) +1. You request your web designers for their implementation +1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Enterprise Edition Starter) +1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Enterprise Edition Starter) +1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production + ## Authorization for merge requests There are two main ways to have a merge request flow with GitLab: @@ -79,6 +132,16 @@ specific commit page. You can append `?w=1` while on the diffs page of a merge request to ignore any whitespace changes. +## Live preview with Review Apps + +If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project, +you can preview the changes submitted to a feature-branch through a merge request +in a per-branch basis. No need to checkout the branch, install and preview locally; +all your changes will be available to preview by anyone with the Review Apps link. + +[Read more about Review Apps.](../../../ci/review_apps/index.md) + + ## Tips Here are some tips that will help you be more efficient with merge requests in @@ -167,3 +230,4 @@ git checkout origin/merge-requests/1 ``` [protected branches]: ../protected_branches.md +[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition" diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md index 2f104c7becc..46fa4378fe7 100644 --- a/doc/user/project/pages/getting_started_part_one.md +++ b/doc/user/project/pages/getting_started_part_one.md @@ -41,7 +41,7 @@ server up and running for your GitLab instance. Before we begin, let's understand a few concepts first. -### Static sites +## Static sites GitLab Pages only supports static websites, meaning, your output files must be HTML, CSS, and JavaScript only. @@ -51,14 +51,14 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/) to simplify your code and build the static site for you, which is highly recommendable and much faster than hardcoding. -#### Further Reading +### Further reading - Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) - Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site - You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) - Fork an [example project](https://gitlab.com/pages) to build your website based upon -### GitLab Pages domain +## GitLab Pages domain If you set up a GitLab Pages project on GitLab.com, it will automatically be accessible under a @@ -73,9 +73,9 @@ Pages wildcard domain. This guide is valid for any GitLab instance, you just need to replace Pages wildcard domain on GitLab.com (`*.gitlab.io`) with your own. -#### Practical examples +### Practical examples -**Project Websites:** +#### Project Websites - You created a project called `blog` under your username `john`, therefore your project URL is `https://gitlab.com/john/blog/`. @@ -87,16 +87,21 @@ URL is `https://gitlab.com/websites/blog/`. Once you enable GitLab Pages for this project, the site will live under `https://websites.gitlab.io/blog/`. -**User and Group Websites:** +#### User and Group Websites - Under your username, `john`, you created a project called `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. Once you enable GitLab Pages for your project, your website will be published under `https://john.gitlab.io`. - Under your group `websites`, you created a project called -`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project, +`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. +Once you enable GitLab Pages for your project, your website will be published under `https://websites.gitlab.io`. +>**Note:** +GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations). +You can only create the highest level group website. + **General example:** - On GitLab.com, a project site will always be available under diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index deaceabb7c5..9ecf7a3a8e7 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -398,6 +398,9 @@ don't redirect HTTP to HTTPS. [rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" +GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations). +You can only create the highest level group website. + ## Redirects in GitLab Pages Since you cannot use any custom server configuration files, like `.htaccess` or diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 54d4028a50a..925bbf76d49 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -6,7 +6,7 @@ - [Description templates](../user/project/description_templates.md) - [Feature branch workflow](workflow.md) - [GitLab Flow](gitlab_flow.md) -- [Groups](groups.md) +- [Groups](../user/group/index.md) - Issues - The GitLab Issue Tracker is an advanced and complete tool for tracking the evolution of a new idea or the process of solving a problem. - [Confidential issues](../user/project/issues/confidential_issues.md) diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 1645e7e8d65..06eec1ed928 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -1,97 +1,2 @@ -# GitLab Groups -GitLab groups allow you to group projects into directories and give users access to several projects at once. - -When you create a new project in GitLab, the default namespace for the project is the personal namespace associated with your GitLab user. -In this document we will see how to create groups, put projects in groups and manage who can access the projects in a group. - -## Creating groups - -You can create a group by going to the 'Groups' tab of the GitLab dashboard and clicking the 'New group' button. - -![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png) - -Next, enter the path and name (required) and the optional description and group avatar. - -![Fill in the path for your new group](groups/new_group_form.png) - -When your group has been created you are presented with the group dashboard feed, which will be empty. - -![Group dashboard](groups/group_dashboard.png) - -You can use the 'New project' button to add a project to the new group. - -## Transferring an existing project into a group - -You can transfer an existing project into a group you have at least Master access in from the project settings page. -The option to transfer a project is only available if you are the Owner of the project. -First scroll down to the 'Dangerous settings' and click 'Show them to me'. -Now you can pick any of the groups you have at least Master access in as the new namespace for the group. - -![Transfer a project to a new namespace](groups/transfer_project.png) - -GitLab administrators can use the admin interface to move any project to any namespace if needed. - -## Adding users to a group - -One of the benefits of putting multiple projects in one group is that you can give a user to access to all projects in the group with one action. - -Suppose we have a group with two projects. - -![Group with two projects](groups/group_with_two_projects.png) - -On the 'Group Members' page we can now add a new user Barry to the group. - -![Add user Barry to the group](groups/add_member_to_group.png) - -Now because Barry is a 'Developer' member of the 'Open Source' group, he automatically gets 'Developer' access to all projects in the 'Open Source' group. - -![Barry has 'Developer' access to GitLab CI](groups/project_members_via_group.png) - -If necessary, you can increase the access level of an individual user for a specific project, by adding them as a Member to the project. - -![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png) - -## Requesting access to a group - -As a group owner you can enable or disable non members to request access to -your group. Go to the group settings and click on **Allow users to request access**. - -As a user, you can request to be a member of a group. Go to the group you'd -like to be a member of, and click the **Request Access** button on the right -side of your screen. - -![Request access button](groups/request_access_button.png) - ---- - -Group owners & masters will be notified of your request and will be able to approve or -decline it on the members page. - -![Manage access requests](groups/access_requests_management.png) - ---- - -If you change your mind before your request is approved, just click the -**Withdraw Access Request** button. - -![Withdraw access request button](groups/withdraw_access_request_button.png) - -## Managing group memberships via LDAP - -In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. -See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information. - -## Allowing only admins to create groups - -By default, any GitLab user can create new groups. -This ability can be disabled for individual users from the admin panel. -It is also possible to configure GitLab so that new users default to not being able to create groups: - -``` -# For omnibus-gitlab, put the following in /etc/gitlab/gitlab.rb -gitlab_rails['gitlab_default_can_create_group'] = false - -# For installations from source, uncomment the 'default_can_create_group' -# line in /home/git/gitlab/config/gitlab.yml -``` +This document was moved to [another location](../user/group/index.md). diff --git a/doc/workflow/groups/add_member_to_group.png b/doc/workflow/groups/add_member_to_group.png Binary files differdeleted file mode 100644 index a10d5032bb0..00000000000 --- a/doc/workflow/groups/add_member_to_group.png +++ /dev/null diff --git a/doc/workflow/groups/group_dashboard.png b/doc/workflow/groups/group_dashboard.png Binary files differdeleted file mode 100644 index a5829f25808..00000000000 --- a/doc/workflow/groups/group_dashboard.png +++ /dev/null diff --git a/doc/workflow/groups/group_with_two_projects.png b/doc/workflow/groups/group_with_two_projects.png Binary files differdeleted file mode 100644 index 76d0a1b8ab2..00000000000 --- a/doc/workflow/groups/group_with_two_projects.png +++ /dev/null diff --git a/doc/workflow/groups/new_group_button.png b/doc/workflow/groups/new_group_button.png Binary files differdeleted file mode 100644 index 7155d6280bd..00000000000 --- a/doc/workflow/groups/new_group_button.png +++ /dev/null diff --git a/doc/workflow/groups/override_access_level.png b/doc/workflow/groups/override_access_level.png Binary files differdeleted file mode 100644 index 2b3e9a49842..00000000000 --- a/doc/workflow/groups/override_access_level.png +++ /dev/null diff --git a/doc/workflow/groups/project_members_via_group.png b/doc/workflow/groups/project_members_via_group.png Binary files differdeleted file mode 100644 index 878c9a03ac9..00000000000 --- a/doc/workflow/groups/project_members_via_group.png +++ /dev/null diff --git a/doc/workflow/groups/transfer_project.png b/doc/workflow/groups/transfer_project.png Binary files differdeleted file mode 100644 index 52161817f11..00000000000 --- a/doc/workflow/groups/transfer_project.png +++ /dev/null diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md index 8e50cb03e63..40d756bc199 100644 --- a/doc/workflow/share_projects_with_other_groups.md +++ b/doc/workflow/share_projects_with_other_groups.md @@ -5,7 +5,7 @@ to a project with a single action. ## Groups as collections of users -Groups are used primarily to [create collections of projects](groups.md), but you can also +Groups are used primarily to [create collections of projects](../user/group/index.md), but you can also take advantage of the fact that groups define collections of _users_, namely the group members. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 47742a164fc..09a88869063 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -621,7 +621,8 @@ module API expose :id expose :default_projects_limit expose :signup_enabled - expose :signin_enabled + expose :password_authentication_enabled + expose :password_authentication_enabled, as: :signin_enabled expose :gravatar_enabled expose :sign_in_text expose :after_sign_up_text diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ef2c08e902c..465363da582 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -101,7 +101,7 @@ module API end get "/broadcast_message" do - if message = BroadcastMessage.current.last + if message = BroadcastMessage.current&.last present message, with: Entities::BroadcastMessage else {} diff --git a/lib/api/settings.rb b/lib/api/settings.rb index d598f9a62a2..b19095d1252 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -65,6 +65,7 @@ module API :shared_runners_enabled, :sidekiq_throttling_enabled, :sign_in_text, + :password_authentication_enabled, :signin_enabled, :signup_enabled, :terminal_max_session_time, @@ -95,7 +96,9 @@ module API requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' end optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' - optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + mutually_exclusive :password_authentication_enabled, :signin_enabled optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -176,6 +179,10 @@ module API put "application/settings" do attrs = declared_params(include_missing: false) + if attrs.has_key?(:signin_enabled) + attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled) + end + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index c848f52723b..3759250f7f6 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -161,7 +161,8 @@ module API expose :id expose :default_projects_limit expose :signup_enabled - expose :signin_enabled + expose :password_authentication_enabled + expose :password_authentication_enabled, as: :signin_enabled expose :gravatar_enabled expose :sign_in_text expose :after_sign_up_text diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb index 748d6b97d4f..202011cfcbe 100644 --- a/lib/api/v3/settings.rb +++ b/lib/api/v3/settings.rb @@ -44,7 +44,9 @@ module API requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' end optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' - optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled' + mutually_exclusive :password_authentication_enabled, :signin_enabled optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -116,7 +118,7 @@ module API :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources, :user_oauth_applications, :user_default_external, :signup_enabled, :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, - :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, + :after_sign_up_text, :password_authentication_enabled, :signin_enabled, :require_two_factor_authentication, :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay, :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, @@ -126,7 +128,13 @@ module API :housekeeping_enabled, :terminal_max_session_time end put "application/settings" do - if current_settings.update_attributes(declared_params(include_missing: false)) + attrs = declared_params(include_missing: false) + + if attrs.has_key?(:signin_enabled) + attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled) + end + + if current_settings.update_attributes(attrs) present current_settings, with: Entities::ApplicationSetting else render_validation_error!(current_settings) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 56ad2c77c7d..cf3a0336792 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -80,6 +80,8 @@ module Ci artifacts: job[:artifacts], cache: job[:cache], dependencies: job[:dependencies], + before_script: job[:before_script], + script: job[:script], after_script: job[:after_script], environment: job[:environment] }.compact } diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index ccb5d886bab..9bed81e7327 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -37,7 +37,7 @@ module Gitlab rate_limit!(ip, success: result.success?, login: login) Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor) - return result if result.success? || current_application_settings.signin_enabled? || Gitlab::LDAP::Config.enabled? + return result if result.success? || current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled? # If sign-in is disabled and LDAP is not configured, recommend a # personal access token on failed auth attempts @@ -48,6 +48,10 @@ module Gitlab # Avoid resource intensive login checks if password is not provided return unless password.present? + # Nothing to do here if internal auth is disabled and LDAP is + # not configured + return unless current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled? + Gitlab::Auth::UniqueIpsLimiter.limit_user! do user = User.by_login(login) diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb index ee034d9cc56..411f67f8ce7 100644 --- a/lib/gitlab/ci/build/step.rb +++ b/lib/gitlab/ci/build/step.rb @@ -12,7 +12,8 @@ module Gitlab class << self def from_commands(job) self.new(:script).tap do |step| - step.script = job.commands.split("\n") + step.script = job.options[:before_script].to_a + job.options[:script].to_a + step.script = job.commands.split("\n") if step.script.empty? step.timeout = job.timeout step.when = WHEN_ON_SUCCESS end diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 8ab3b0498ff..d0f04d25db2 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -38,7 +38,7 @@ module Gitlab repo = options.delete(:repo) raise 'Gitlab::Git::Repository is required' unless repo.respond_to?(:log) - repo.log(options).map { |c| decorate(c) } + repo.log(options) end # Get single commit diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index bd9993f275d..639f5625d59 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -331,85 +331,10 @@ module Gitlab # ) # def log(options) - default_options = { - limit: 10, - offset: 0, - path: nil, - follow: false, - skip_merges: false, - disable_walk: false, - after: nil, - before: nil - } - - options = default_options.merge(options) - options[:limit] ||= 0 - options[:offset] ||= 0 - actual_ref = options[:ref] || root_ref - begin - sha = sha_from_ref(actual_ref) - rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError - # Return an empty array if the ref wasn't found - return [] - end - - if log_using_shell?(options) - log_by_shell(sha, options) - else - log_by_walk(sha, options) - end - end - - def log_using_shell?(options) - options[:path].present? || - options[:disable_walk] || - options[:skip_merges] || - options[:after] || - options[:before] - end - - def log_by_walk(sha, options) - walk_options = { - show: sha, - sort: Rugged::SORT_NONE, - limit: options[:limit], - offset: options[:offset] - } - Rugged::Walker.walk(rugged, walk_options).to_a - end - - def log_by_shell(sha, options) - limit = options[:limit].to_i - offset = options[:offset].to_i - use_follow_flag = options[:follow] && options[:path].present? - - # We will perform the offset in Ruby because --follow doesn't play well with --skip. - # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 - offset_in_ruby = use_follow_flag && options[:offset].present? - limit += offset if offset_in_ruby - - cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log] - cmd << "--max-count=#{limit}" - cmd << '--format=%H' - cmd << "--skip=#{offset}" unless offset_in_ruby - cmd << '--follow' if use_follow_flag - cmd << '--no-merges' if options[:skip_merges] - cmd << "--after=#{options[:after].iso8601}" if options[:after] - cmd << "--before=#{options[:before].iso8601}" if options[:before] - cmd << sha - - # :path can be a string or an array of strings - if options[:path].present? - cmd << '--' - cmd += Array(options[:path]) - end - - raw_output = IO.popen(cmd) { |io| io.read } - lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines - - lines.map! { |c| Rugged::Commit.new(rugged, c.strip) } + raw_log(options).map { |c| Commit.decorate(c) } end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/382 def count_commits(options) cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list] cmd << "--after=#{options[:after].iso8601}" if options[:after] @@ -454,7 +379,7 @@ module Gitlab # Counts the amount of commits between `from` and `to`. def count_commits_between(from, to) - commits_between(from, to).size + Commit.between(self, from, to).size end # Returns the SHA of the most recent common ancestor of +from+ and +to+ @@ -912,6 +837,89 @@ module Gitlab private + def raw_log(options) + default_options = { + limit: 10, + offset: 0, + path: nil, + follow: false, + skip_merges: false, + disable_walk: false, + after: nil, + before: nil + } + + options = default_options.merge(options) + options[:limit] ||= 0 + options[:offset] ||= 0 + actual_ref = options[:ref] || root_ref + begin + sha = sha_from_ref(actual_ref) + rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError + # Return an empty array if the ref wasn't found + return [] + end + + if log_using_shell?(options) + log_by_shell(sha, options) + else + log_by_walk(sha, options) + end + end + + def log_using_shell?(options) + options[:path].present? || + options[:disable_walk] || + options[:skip_merges] || + options[:after] || + options[:before] + end + + def log_by_walk(sha, options) + walk_options = { + show: sha, + sort: Rugged::SORT_NONE, + limit: options[:limit], + offset: options[:offset] + } + Rugged::Walker.walk(rugged, walk_options).to_a + end + + # Gitaly note: JV: although #log_by_shell shells out to Git I think the + # complexity is such that we should migrate it as Ruby before trying to + # do it in Go. + def log_by_shell(sha, options) + limit = options[:limit].to_i + offset = options[:offset].to_i + use_follow_flag = options[:follow] && options[:path].present? + + # We will perform the offset in Ruby because --follow doesn't play well with --skip. + # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 + offset_in_ruby = use_follow_flag && options[:offset].present? + limit += offset if offset_in_ruby + + cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log] + cmd << "--max-count=#{limit}" + cmd << '--format=%H' + cmd << "--skip=#{offset}" unless offset_in_ruby + cmd << '--follow' if use_follow_flag + cmd << '--no-merges' if options[:skip_merges] + cmd << "--after=#{options[:after].iso8601}" if options[:after] + cmd << "--before=#{options[:before].iso8601}" if options[:before] + cmd << sha + + # :path can be a string or an array of strings + if options[:path].present? + cmd << '--' + cmd += Array(options[:path]) + end + + raw_output = IO.popen(cmd) { |io| io.read } + lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines + + lines.map! { |c| Rugged::Commit.new(rugged, c.strip) } + end + # We are trying to deprecate this method because it does a lot of work # but it seems to be used only to look up submodule URL's. # https://gitlab.com/gitlab-org/gitaly/issues/329 diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 0e87ee30c98..a3c6b21a6a1 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -1,3 +1,5 @@ +# Gitaly note: JV: does not need to be migrated, works without a repo. + module Gitlab module GitRefValidator extend self diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb index 70da4080cae..bebde857b16 100644 --- a/lib/gitlab/health_checks/fs_shards_check.rb +++ b/lib/gitlab/health_checks/fs_shards_check.rb @@ -35,9 +35,9 @@ module Gitlab repository_storages.flat_map do |storage_name| tmp_file_path = tmp_file_path(storage_name) [ - operation_metrics(:filesystem_accessible, :filesystem_access_latency, -> { storage_stat_test(storage_name) }, shard: storage_name), - operation_metrics(:filesystem_writable, :filesystem_write_latency, -> { storage_write_test(tmp_file_path) }, shard: storage_name), - operation_metrics(:filesystem_readable, :filesystem_read_latency, -> { storage_read_test(tmp_file_path) }, shard: storage_name) + operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, -> { storage_stat_test(storage_name) }, shard: storage_name), + operation_metrics(:filesystem_writable, :filesystem_write_latency_seconds, -> { storage_write_test(tmp_file_path) }, shard: storage_name), + operation_metrics(:filesystem_readable, :filesystem_read_latency_seconds, -> { storage_read_test(tmp_file_path) }, shard: storage_name) ].flatten end end diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb index fbe1645c1b1..3dcb28a193c 100644 --- a/lib/gitlab/health_checks/simple_abstract_check.rb +++ b/lib/gitlab/health_checks/simple_abstract_check.rb @@ -20,7 +20,7 @@ module Gitlab [ metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0), metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0), - metric("#{metric_prefix}_latency", elapsed) + metric("#{metric_prefix}_latency_seconds", elapsed) ] end end diff --git a/lib/gitlab/metrics/connection_rack_middleware.rb b/lib/gitlab/metrics/connection_rack_middleware.rb deleted file mode 100644 index b3da360be8f..00000000000 --- a/lib/gitlab/metrics/connection_rack_middleware.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Gitlab - module Metrics - class ConnectionRackMiddleware - def initialize(app) - @app = app - end - - def self.rack_request_count - @rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count') - end - - def self.rack_response_count - @rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count') - end - - def self.rack_uncaught_errors_count - @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count') - end - - def self.rack_execution_time - @rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time', - {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10]) - end - - def call(env) - method = env['REQUEST_METHOD'].downcase - started = Time.now.to_f - begin - ConnectionRackMiddleware.rack_request_count.increment(method: method) - - status, headers, body = @app.call(env) - - ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status) - [status, headers, body] - rescue - ConnectionRackMiddleware.rack_uncaught_errors_count.increment - raise - ensure - elapsed = Time.now.to_f - started - ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed) - end - end - end - end -end diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb new file mode 100644 index 00000000000..0dc19f31d03 --- /dev/null +++ b/lib/gitlab/metrics/requests_rack_middleware.rb @@ -0,0 +1,40 @@ +module Gitlab + module Metrics + class RequestsRackMiddleware + def initialize(app) + @app = app + end + + def self.http_request_total + @http_request_total ||= Gitlab::Metrics.counter(:http_requests_total, 'Request count') + end + + def self.rack_uncaught_errors_count + @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count') + end + + def self.http_request_duration_seconds + @http_request_duration_seconds ||= Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time', + {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 2.5, 5, 10, 25]) + end + + def call(env) + method = env['REQUEST_METHOD'].downcase + started = Time.now.to_f + begin + RequestsRackMiddleware.http_request_total.increment(method: method) + + status, headers, body = @app.call(env) + + elapsed = Time.now.to_f - started + RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method, status: status }, elapsed) + + [status, headers, body] + rescue + RequestsRackMiddleware.rack_uncaught_errors_count.increment + raise + end + end + end + end +end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 0baea092e6a..4366ff336ef 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -1,3 +1,6 @@ +# Gitaly note: JV: two sets of straightforward RPC's. 1 Hard RPC: fork_repository. +# SSH key operations are not part of Gitaly so will never be migrated. + require 'securerandom' module Gitlab @@ -68,6 +71,7 @@ module Gitlab # Ex. # add_repository("/path/to/storage", "gitlab/gitlab-ci") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def add_repository(storage, name) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'add-project', storage, "#{name}.git"]) @@ -81,6 +85,7 @@ module Gitlab # Ex. # import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def import_repository(storage, name, url) # Timeout should be less than 900 ideally, to prevent the memory killer # to silently kill the process without knowing we are timing out here. @@ -99,6 +104,7 @@ module Gitlab # Ex. # fetch_remote("gitlab/gitlab-ci", "upstream") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def fetch_remote(storage, name, remote, forced: false, no_tags: false) args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"] args << '--force' if forced @@ -115,6 +121,7 @@ module Gitlab # Ex. # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def mv_repository(storage, path, new_path) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'mv-project', storage, "#{path}.git", "#{new_path}.git"]) @@ -129,6 +136,7 @@ module Gitlab # Ex. # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx") # + # Gitaly note: JV: not easy to migrate because this involves two Gitaly servers, not one. def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project', forked_from_storage, "#{path}.git", forked_to_storage, @@ -143,6 +151,7 @@ module Gitlab # Ex. # remove_repository("/path/to/storage", "gitlab/gitlab-ci") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def remove_repository(storage, name) gitlab_shell_fast_execute([gitlab_shell_projects_path, 'rm-project', storage, "#{name}.git"]) @@ -194,6 +203,7 @@ module Gitlab # Ex. # add_namespace("/path/to/storage", "gitlab") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def add_namespace(storage, name) path = full_path(storage, name) FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name) @@ -207,6 +217,7 @@ module Gitlab # Ex. # rm_namespace("/path/to/storage", "gitlab") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def rm_namespace(storage, name) FileUtils.rm_r(full_path(storage, name), force: true) end @@ -216,6 +227,7 @@ module Gitlab # Ex. # mv_namespace("/path/to/storage", "gitlab", "gitlabhq") # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def mv_namespace(storage, old_name, new_name) return false if exists?(storage, new_name) || !exists?(storage, old_name) @@ -241,6 +253,7 @@ module Gitlab # exists?(storage, 'gitlab') # exists?(storage, 'gitlab/cookies.git') # + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def exists?(storage, dir_name) File.exist?(full_path(storage, dir_name)) end diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 7406629ef9d..0cc16404e1b 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -15,11 +15,11 @@ msgstr "" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "%d подаване беше пропуснато, за да не се натоварва системата." -msgstr[1] "%d подавания бяха пропуснати, за да не се натоварва системата." +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "%s подаване беше пропуснато, за да не се натоварва системата." +msgstr[1] "%s подавания бяха пропуснати, за да не се натоварва системата." msgid "%d commit" msgid_plural "%d commits" diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po index 7065b7a635f..46bf4e33997 100644 --- a/locale/en/gitlab.po +++ b/locale/en/gitlab.po @@ -17,8 +17,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "\n" -msgid "%d additional commit has been omitted to prevent performance issues." -msgid_plural "%d additional commits have been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 015d96d6e53..5218f6ae7b9 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -15,11 +15,11 @@ msgstr "" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "%d enmetado estis transsaltita, por ne troŝarĝi la sistemon." -msgstr[1] "%d enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon." +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "%s enmetado estis transsaltita, por ne troŝarĝi la sistemon." +msgstr[1] "%s enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon." msgid "%d commit" msgid_plural "%d commits" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index c5600721d49..d4fac6ab34e 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -15,14 +15,14 @@ msgstr "" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." +"%s additional commits have been omitted to prevent performance issues." msgstr[0] "" -"%d commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli " +"%s commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli " "issues." msgstr[1] "" -"%d commit aggiuntivi sono stati omessi per evitare degradi di prestazioni " +"%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni " "negli issues." msgid "%d commit" diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 8a76121e0c1..b7a88aadeb9 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -15,10 +15,10 @@ msgstr "" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "为提高页面加载速度及性能,已省略了 %d 次提交。" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "为提高页面加载速度及性能,已省略了 %s 次提交。" msgid "%d commit" msgid_plural "%d commits" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index b679ed3f26b..f6add31db99 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -14,10 +14,10 @@ msgstr "" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "為提高頁面加載速度及性能,已省略了 %d 次提交。" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "為提高頁面加載速度及性能,已省略了 %s 次提交。" msgid "%d commit" msgid_plural "%d commits" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index d5a3c5ea0b9..e61cf0e5152 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -18,10 +18,10 @@ msgstr "" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." -msgstr[0] "因效能考量,不顯示 %d 個更動 (commit)。" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "因效能考量,不顯示 %s 個更動 (commit)。" msgid "%d commit" msgid_plural "%d commits" diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index a2720c9b81e..1641bddea11 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -30,6 +30,15 @@ describe ApplicationController do expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end + + it 'does not redirect if the user is over their password expiry but sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + user.password_expires_at = Time.new(2002) + allow(controller).to receive(:current_user).and_return(user) + expect(controller).not_to receive(:redirect_to) + + controller.send(:check_password_expiration) + end end describe "#authenticate_user_from_token!" do diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index b40f647644d..58486f33229 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -97,6 +97,21 @@ describe AutocompleteController do it { expect(body.size).to eq User.count } end + context 'user order' do + it 'shows exact matches first' do + reported_user = create(:user, username: 'reported_user', name: 'Doug') + user = create(:user, username: 'user', name: 'User') + user1 = create(:user, username: 'user1', name: 'Ian') + + sign_in(user) + get(:users, search: 'user') + + response_usernames = JSON.parse(response.body).map { |user| user['username'] } + + expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username]) + end + end + context 'limited users per page' do let(:per_page) { 2 } diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 86847c07c09..8964d89b438 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -24,7 +24,7 @@ describe MetricsController do expect(response.body).to match(/^db_ping_timeout 0$/) expect(response.body).to match(/^db_ping_success 1$/) - expect(response.body).to match(/^db_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/) end it 'returns Redis ping metrics' do @@ -32,7 +32,7 @@ describe MetricsController do expect(response.body).to match(/^redis_ping_timeout 0$/) expect(response.body).to match(/^redis_ping_success 1$/) - expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/) end it 'returns Caching ping metrics' do @@ -40,7 +40,7 @@ describe MetricsController do expect(response.body).to match(/^redis_cache_ping_timeout 0$/) expect(response.body).to match(/^redis_cache_ping_success 1$/) - expect(response.body).to match(/^redis_cache_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/) end it 'returns Queues ping metrics' do @@ -48,7 +48,7 @@ describe MetricsController do expect(response.body).to match(/^redis_queues_ping_timeout 0$/) expect(response.body).to match(/^redis_queues_ping_success 1$/) - expect(response.body).to match(/^redis_queues_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/) end it 'returns SharedState ping metrics' do @@ -56,17 +56,17 @@ describe MetricsController do expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/) expect(response.body).to match(/^redis_shared_state_ping_success 1$/) - expect(response.body).to match(/^redis_shared_state_ping_latency [0-9\.]+$/) + expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/) end it 'returns file system check metrics' do get :index - expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/) + expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/) expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/) end diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb new file mode 100644 index 00000000000..2955d01fad0 --- /dev/null +++ b/spec/controllers/passwords_controller_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe PasswordsController do + describe '#check_password_authentication_available' do + before do + @request.env["devise.mapping"] = Devise.mappings[:user] + end + + context 'when password authentication is disabled' do + it 'prevents a password reset' do + stub_application_setting(password_authentication_enabled: false) + + post :create + + expect(flash[:alert]).to eq 'Password authentication is unavailable.' + end + end + + context 'when reset email belongs to an ldap user' do + let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') } + + it 'prevents a password reset' do + post :create, user: { email: user.email } + + expect(flash[:alert]).to eq 'Password authentication is unavailable.' + end + end + end +end diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index eb61a0c080c..df53863482d 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -343,7 +343,8 @@ describe Projects::CommitController do get_pipelines(id: commit.id, format: :json) expect(response).to be_ok - expect(JSON.parse(response.body)).not_to be_empty + expect(JSON.parse(response.body)['pipelines']).not_to be_empty + expect(JSON.parse(response.body)['count']['all']).to eq 1 end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6f9ce60cf75..c193babead0 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -481,7 +481,8 @@ describe Projects::MergeRequestsController do end it 'responds with serialized pipelines' do - expect(json_response).not_to be_empty + expect(json_response['pipelines']).not_to be_empty + expect(json_response['count']['all']).to eq 1 end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index f96fe7ad5cb..192cca45d99 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -211,24 +211,43 @@ describe ProjectsController do let(:admin) { create(:admin) } let(:project) { create(:project, :repository) } - let(:new_path) { 'renamed_path' } - let(:project_params) { { path: new_path } } before do sign_in(admin) end - it "sets the repository to the right path after a rename" do - controller.instance_variable_set(:@project, project) + context 'when only renaming a project path' do + it "sets the repository to the right path after a rename" do + expect { update_project path: 'renamed_path' } + .to change { project.reload.path } - put :update, - namespace_id: project.namespace, - id: project.id, - project: project_params + expect(project.path).to include 'renamed_path' + expect(assigns(:repository).path).to include project.path + expect(response).to have_http_status(302) + end + end - expect(project.repository.path).to include(new_path) - expect(assigns(:repository).path).to eq(project.repository.path) - expect(response).to have_http_status(302) + context 'when project has container repositories with tags' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end + + it 'does not allow to rename the project' do + expect { update_project path: 'renamed_path' } + .not_to change { project.reload.path } + + expect(controller).to set_flash[:alert].to(/container registry tags/) + expect(response).to have_http_status(200) + end + end + + def update_project(**parameters) + put :update, + namespace_id: project.namespace.path, + id: project.path, + project: parameters end end diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index c3a29d8bf04..40c4663c6d8 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -2,13 +2,6 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do sequence(:token) { |n| "token#{n}" } - - factory :ci_trigger_for_trigger_schedule do - token { SecureRandom.hex(15) } - owner factory: :user - project factory: :project - ref 'master' - end end end end diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index cd7040891e9..d01722805c4 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -8,8 +8,8 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do find(".table.active-tokens") end - def inactive_impersonation_tokens - find(".table.inactive-tokens") + def no_personal_access_tokens_message + find(".settings-message") end before do @@ -60,15 +60,17 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do click_on "Revoke" - expect(inactive_impersonation_tokens).to have_text(impersonation_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") end - it "moves expired tokens to the 'inactive' section" do + it "removes expired tokens from 'active' section" do impersonation_token.update(expires_at: 5.days.ago) visit admin_user_impersonation_tokens_path(user_id: user.username) - expect(inactive_impersonation_tokens).to have_text(impersonation_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") end end end diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 67975a68ee2..26d6d6658aa 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -1,44 +1,74 @@ require 'spec_helper' describe 'Profile > Password', feature: true do - let(:user) { create(:user, password_automatically_set: true) } + context 'Password authentication enabled' do + let(:user) { create(:user, password_automatically_set: true) } - before do - sign_in(user) - visit edit_profile_password_path - end + before do + sign_in(user) + visit edit_profile_password_path + end - def fill_passwords(password, confirmation) - fill_in 'New password', with: password - fill_in 'Password confirmation', with: confirmation + def fill_passwords(password, confirmation) + fill_in 'New password', with: password + fill_in 'Password confirmation', with: confirmation - click_button 'Save password' - end + click_button 'Save password' + end + + context 'User with password automatically set' do + describe 'User puts different passwords in the field and in the confirmation' do + it 'shows an error message' do + fill_passwords('mypassword', 'mypassword2') - context 'User with password automatically set' do - describe 'User puts different passwords in the field and in the confirmation' do - it 'shows an error message' do - fill_passwords('mypassword', 'mypassword2') + page.within('.alert-danger') do + expect(page).to have_content("Password confirmation doesn't match Password") + end + end + + it 'does not contain the current password field after an error' do + fill_passwords('mypassword', 'mypassword2') - page.within('.alert-danger') do - expect(page).to have_content("Password confirmation doesn't match Password") + expect(page).to have_no_field('user[current_password]') end end - it 'does not contain the current password field after an error' do - fill_passwords('mypassword', 'mypassword2') + describe 'User puts the same passwords in the field and in the confirmation' do + it 'shows a success message' do + fill_passwords('mypassword', 'mypassword') - expect(page).to have_no_field('user[current_password]') + page.within('.flash-notice') do + expect(page).to have_content('Password was successfully updated. Please login with it') + end + end end end + end - describe 'User puts the same passwords in the field and in the confirmation' do - it 'shows a success message' do - fill_passwords('mypassword', 'mypassword') + context 'Password authentication unavailable' do + before do + gitlab_sign_in(user) + end - page.within('.flash-notice') do - expect(page).to have_content('Password was successfully updated. Please login with it') - end + context 'Regular user' do + let(:user) { create(:user) } + + it 'renders 404 when sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + + visit edit_profile_password_path + + expect(page).to have_http_status(404) + end + end + + context 'LDAP user' do + let(:user) { create(:omniauth_user, provider: 'ldapmain') } + + it 'renders 404' do + visit edit_profile_password_path + + expect(page).to have_http_status(404) end end end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 44b7ee101c9..3c08b6bc091 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -7,8 +7,8 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do find(".table.active-tokens") end - def inactive_personal_access_tokens - find(".table.inactive-tokens") + def no_personal_access_tokens_message + find(".settings-message") end def created_personal_access_token @@ -80,14 +80,16 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do visit profile_personal_access_tokens_path click_on "Revoke" - expect(inactive_personal_access_tokens).to have_text(personal_access_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") end - it "moves expired tokens to the 'inactive' section" do + it "removes expired tokens from 'active' section" do personal_access_token.update(expires_at: 5.days.ago) visit profile_personal_access_tokens_path - expect(inactive_personal_access_tokens).to have_text(personal_access_token.name) + expect(page).to have_selector(".settings-message") + expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") end context "when revocation fails" do diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb index 53ac18fa7cc..d22a6daac08 100644 --- a/spec/features/projects/no_password_spec.rb +++ b/spec/features/projects/no_password_spec.rb @@ -30,7 +30,7 @@ feature 'No Password Alert' do let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') } before do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config]) end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 661327d4432..7ecb75da8ce 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -35,7 +35,7 @@ describe ButtonHelper do context 'with internal auth disabled' do before do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) end context 'when user has no personal access tokens' do diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index c462d9006ea..45066a60f50 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -160,7 +160,7 @@ describe ProjectsHelper do context 'user requires a personal access token' do it 'returns true' do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) expect(helper.show_no_password_message?).to be_truthy end @@ -184,7 +184,7 @@ describe ProjectsHelper do let(:user) { create(:user) } it 'returns link to create a personal access token' do - stub_application_setting(signin_enabled?: false) + stub_application_setting(password_authentication_enabled?: false) expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>} end diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index 694f94efcff..a34cadec0ab 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -85,6 +85,41 @@ describe('Pipelines table in Commits and Merge requests', () => { }, 0); }); }); + + describe('pipeline badge counts', () => { + const pipelinesResponse = (request, next) => { + next(request.respondWith(JSON.stringify([pipeline]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, pipelinesResponse); + this.component.$destroy(); + }); + + it('should receive update-pipelines-count event', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + element.addEventListener('update-pipelines-count', (event) => { + expect(event.detail.pipelines).toEqual([pipeline]); + done(); + }); + + this.component = new PipelinesTable({ + propsData: { + endpoint: 'endpoint', + helpPagePath: 'foo', + }, + }).$mount(); + element.appendChild(this.component.$el); + }); + }); }); describe('unsuccessfull request', () => { diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ef58ef1b0cd..ea79389e67e 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -163,7 +163,10 @@ module Ci commands: "pwd\nrspec", coverage_regex: nil, tag_list: [], - options: {}, + options: { + before_script: ["pwd"], + script: ["rspec"] + }, allow_failure: false, when: "on_success", environment: nil, @@ -616,10 +619,12 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] }, - services: [{ name: "mysql" }, - { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"], - command: ["/usr/local/bin/init", "run"] }] + before_script: ["pwd"], + script: ["rspec"], + image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "mysql" }, + { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }] }, allow_failure: false, when: "on_success", @@ -649,10 +654,12 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] }, - services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"], - command: ["/usr/local/bin/init", "run"] }, - { name: "docker:dind" }] + before_script: ["pwd"], + script: ["rspec"], + image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] }, + services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"], + command: ["/usr/local/bin/init", "run"] }, + { name: "docker:dind" }] }, allow_failure: false, when: "on_success", @@ -680,6 +687,8 @@ module Ci coverage_regex: nil, tag_list: [], options: { + before_script: ["pwd"], + script: ["rspec"], image: { name: "ruby:2.1" }, services: [{ name: "mysql" }, { name: "docker:dind" }] }, @@ -707,8 +716,10 @@ module Ci coverage_regex: nil, tag_list: [], options: { - image: { name: "ruby:2.5" }, - services: [{ name: "postgresql" }, { name: "docker:dind" }] + before_script: ["pwd"], + script: ["rspec"], + image: { name: "ruby:2.5" }, + services: [{ name: "postgresql" }, { name: "docker:dind" }] }, allow_failure: false, when: "on_success", @@ -951,6 +962,8 @@ module Ci coverage_regex: nil, tag_list: [], options: { + before_script: ["pwd"], + script: ["rspec"], image: { name: "ruby:2.1" }, services: [{ name: "mysql" }], artifacts: { @@ -1162,7 +1175,9 @@ module Ci commands: "test", coverage_regex: nil, tag_list: [], - options: {}, + options: { + script: ["test"] + }, when: "on_success", allow_failure: false, environment: nil, @@ -1208,7 +1223,9 @@ module Ci commands: "execute-script-for-job", coverage_regex: nil, tag_list: [], - options: {}, + options: { + script: ["execute-script-for-job"] + }, when: "on_success", allow_failure: false, environment: nil, @@ -1221,7 +1238,9 @@ module Ci commands: "execute-script-for-job", coverage_regex: nil, tag_list: [], - options: {}, + options: { + script: ["execute-script-for-job"] + }, when: "on_success", allow_failure: false, environment: nil, diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index d09da951869..55780518230 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -206,7 +206,7 @@ describe Gitlab::Auth, lib: true do end it 'throws an error suggesting user create a PAT when internal auth is disabled' do - allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalTokenError) end @@ -279,6 +279,16 @@ describe Gitlab::Auth, lib: true do gl_auth.find_with_user_password('ldap_user', 'password') end end + + context "with sign-in disabled" do + before do + stub_application_setting(password_authentication_enabled: false) + end + + it "does not find user by valid login/password" do + expect(gl_auth.find_with_user_password(username, password)).to be_nil + end + end end private diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb index 49457b129e3..5a21282712a 100644 --- a/spec/lib/gitlab/ci/build/step_spec.rb +++ b/spec/lib/gitlab/ci/build/step_spec.rb @@ -1,21 +1,50 @@ require 'spec_helper' describe Gitlab::Ci::Build::Step do - let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") } - describe '#from_commands' do - subject { described_class.from_commands(job) } - - it 'fabricates an object' do - expect(subject.name).to eq(:script) - expect(subject.script).to eq(['ls -la', 'date']) - expect(subject.timeout).to eq(job.timeout) - expect(subject.when).to eq('on_success') - expect(subject.allow_failure).to be_falsey + shared_examples 'has correct script' do + subject { described_class.from_commands(job) } + + it 'fabricates an object' do + expect(subject.name).to eq(:script) + expect(subject.script).to eq(script) + expect(subject.timeout).to eq(job.timeout) + expect(subject.when).to eq('on_success') + expect(subject.allow_failure).to be_falsey + end + end + + context 'when commands are specified' do + it_behaves_like 'has correct script' do + let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") } + let(:script) { ['ls -la', 'date'] } + end + end + + context 'when script option is specified' do + it_behaves_like 'has correct script' do + let(:job) { create(:ci_build, :no_options, options: { script: ["ls -la\necho aaa", "date"] }) } + let(:script) { ["ls -la\necho aaa", 'date'] } + end + end + + context 'when before and script option is specified' do + it_behaves_like 'has correct script' do + let(:job) do + create(:ci_build, options: { + before_script: ["ls -la\necho aaa"], + script: ["date"] + }) + end + + let(:script) { ["ls -la\necho aaa", 'date'] } + end end end describe '#from_after_script' do + let(:job) { create(:ci_build) } + subject { described_class.from_after_script(job) } context 'when after_script is empty' do diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb index b793176d84a..34322c2a693 100644 --- a/spec/lib/gitlab/fake_application_settings_spec.rb +++ b/spec/lib/gitlab/fake_application_settings_spec.rb @@ -1,25 +1,25 @@ require 'spec_helper' describe Gitlab::FakeApplicationSettings do - let(:defaults) { { signin_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } } + let(:defaults) { { password_authentication_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } } subject { described_class.new(defaults) } it 'wraps OpenStruct variables properly' do - expect(subject.signin_enabled).to be_falsey + expect(subject.password_authentication_enabled).to be_falsey expect(subject.signup_enabled).to be_truthy expect(subject.foobar).to eq('asdf') end it 'defines predicate methods' do - expect(subject.signin_enabled?).to be_falsey + expect(subject.password_authentication_enabled?).to be_falsey expect(subject.signup_enabled?).to be_truthy end it 'predicate method changes when value is updated' do - subject.signin_enabled = true + subject.password_authentication_enabled = true - expect(subject.signin_enabled?).to be_truthy + expect(subject.password_authentication_enabled?).to be_truthy end it 'does not define a predicate method' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index acffd335e43..10fa5f4044b 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -705,9 +705,9 @@ describe Gitlab::Git::Repository, seed_helper: true do # Add new commits so that there's a renamed file in the commit history repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged - commit_with_old_name = new_commit_edit_old_file(repo) - rename_commit = new_commit_move_file(repo) - commit_with_new_name = new_commit_edit_new_file(repo) + commit_with_old_name = Gitlab::Git::Commit.decorate(new_commit_edit_old_file(repo)) + rename_commit = Gitlab::Git::Commit.decorate(new_commit_move_file(repo)) + commit_with_new_name = Gitlab::Git::Commit.decorate(new_commit_edit_new_file(repo)) end after(:context) do @@ -880,8 +880,8 @@ describe Gitlab::Git::Repository, seed_helper: true do context "compare results between log_by_walk and log_by_shell" do let(:options) { { ref: "master" } } - let(:commits_by_walk) { repository.log(options).map(&:oid) } - let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:oid) } + let(:commits_by_walk) { repository.log(options).map(&:id) } + let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) } it { expect(commits_by_walk).to eq(commits_by_shell) } @@ -924,7 +924,7 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(commits.size).to be > 0 expect(commits).to satisfy do |commits| - commits.all? { |commit| commit.time >= options[:after] } + commits.all? { |commit| commit.committed_date >= options[:after] } end end end @@ -937,7 +937,7 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(commits.size).to be > 0 expect(commits).to satisfy do |commits| - commits.all? { |commit| commit.time <= options[:before] } + commits.all? { |commit| commit.committed_date <= options[:before] } end end end @@ -946,7 +946,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } def commit_files(commit) - commit.diff(commit.parent_ids.first).deltas.flat_map do |delta| + commit.diff_from_parent.deltas.flat_map do |delta| [delta.old_file[:path], delta.new_file[:path]].uniq.compact end end diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index b333e162909..3de73a9ff65 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -109,9 +109,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) end end @@ -127,9 +127,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) end end end @@ -159,9 +159,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) - expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0)) + expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0)) end end end diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb index 1abebeac4dd..e2643458aca 100644 --- a/spec/lib/gitlab/health_checks/simple_check_shared.rb +++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb @@ -8,7 +8,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 1)) } it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) } - it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) } + it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) } end context 'Check is misbehaving' do @@ -18,7 +18,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) } it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) } - it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) } + it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) } end context 'Check is timeouting' do @@ -28,7 +28,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) } it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 1)) } - it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) } + it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) } end end diff --git a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb index 94251af305f..461b1e4182a 100644 --- a/spec/lib/gitlab/metrics/connection_rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Metrics::ConnectionRackMiddleware do +describe Gitlab::Metrics::RequestsRackMiddleware do let(:app) { double('app') } subject { described_class.new(app) } @@ -22,14 +22,8 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do allow(app).to receive(:call).and_return([200, nil, nil]) end - it 'increments response count with status label' do - expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get')) - - subject.call(env) - end - it 'increments requests count' do - expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get') + expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get') subject.call(env) end @@ -38,20 +32,21 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do execution_time = 10 allow(app).to receive(:call) do |*args| Timecop.freeze(execution_time.seconds) + [200, nil, nil] end - expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time) + expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, execution_time) subject.call(env) end end context '@app.call throws exception' do - let(:rack_response_count) { double('rack_response_count') } + let(:http_request_duration_seconds) { double('http_request_duration_seconds') } before do allow(app).to receive(:call).and_raise(StandardError) - allow(described_class).to receive(:rack_response_count).and_return(rack_response_count) + allow(described_class).to receive(:http_request_duration_seconds).and_return(http_request_duration_seconds) end it 'increments exceptions count' do @@ -61,25 +56,13 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do end it 'increments requests count' do - expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get') - - expect { subject.call(env) }.to raise_error(StandardError) - end - - it "does't increment response count" do - expect(described_class.rack_response_count).not_to receive(:increment) + expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get') expect { subject.call(env) }.to raise_error(StandardError) end - it 'measures execution time' do - execution_time = 10 - allow(app).to receive(:call) do |*args| - Timecop.freeze(execution_time.seconds) - raise StandardError - end - - expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time) + it "does't measure request execution time" do + expect(described_class.http_request_duration_seconds).not_to receive(:increment) expect { subject.call(env) }.to raise_error(StandardError) end diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb deleted file mode 100644 index d1e17c4f684..00000000000 --- a/spec/models/concerns/sortable_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' - -describe Sortable do - let(:relation) { Issue.all } - - describe '#where' do - it 'orders by id, descending' do - order_node = relation.where(iid: 1).order_values.first - expect(order_node).to be_a(Arel::Nodes::Descending) - expect(order_node.expr.name).to eq(:id) - end - end - - describe '#find_by' do - it 'does not order' do - expect(relation).to receive(:unscope).with(:order).and_call_original - - relation.find_by(iid: 1) - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c4bc129dd37..e636250c37d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1236,7 +1236,7 @@ describe Project, models: true do subject { project.rename_repo } - it { expect{subject}.to raise_error(Exception) } + it { expect{subject}.to raise_error(StandardError) } end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c70f916a8bd..69f2570eec2 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -763,7 +763,7 @@ describe User, models: true do end it 'returns users with a partially matching name' do - expect(described_class.search(user.name[0..2])).to eq([user2, user]) + expect(described_class.search(user.name[0..2])).to eq([user, user2]) end it 'returns users with a matching name regardless of the casing' do @@ -777,7 +777,7 @@ describe User, models: true do end it 'returns users with a partially matching Email' do - expect(described_class.search(user.email[0..2])).to eq([user2, user]) + expect(described_class.search(user.email[0..2])).to eq([user, user2]) end it 'returns users with a matching Email regardless of the casing' do @@ -791,7 +791,7 @@ describe User, models: true do end it 'returns users with a partially matching username' do - expect(described_class.search(user.username[0..2])).to eq([user2, user]) + expect(described_class.search(user.username[0..2])).to eq([user, user2]) end it 'returns users with a matching username regardless of the casing' do @@ -1932,4 +1932,26 @@ describe User, models: true do user.invalidate_merge_request_cache_counts end end + + describe '#allow_password_authentication?' do + context 'regular user' do + let(:user) { build(:user) } + + it 'returns true when sign-in is enabled' do + expect(user.allow_password_authentication?).to be_truthy + end + + it 'returns false when sign-in is disabled' do + stub_application_setting(password_authentication_enabled: false) + + expect(user.allow_password_authentication?).to be_falsey + end + end + + it 'returns false for ldap user' do + user = create(:omniauth_user, provider: 'ldapmain') + + expect(user.allow_password_authentication?).to be_falsey + end + end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 453eb4683a0..beaaf346283 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -35,6 +35,17 @@ describe API::Internal do expect(json_response).to be_empty end end + + context 'nil broadcast message' do + it 'returns nothing' do + allow(BroadcastMessage).to receive(:current).and_return(nil) + + get api('/internal/broadcast_message'), secret_token: secret_token + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end end describe 'GET /internal/broadcast_messages' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index ede48b1c888..b71ac6c30b5 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -10,7 +10,7 @@ describe API::Settings, 'Settings' do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['signin_enabled']).to be_truthy + expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['repository_storage']).to eq('default') expect(json_response['koding_enabled']).to be_falsey expect(json_response['koding_url']).to be_nil @@ -32,7 +32,7 @@ describe API::Settings, 'Settings' do it "updates application settings" do put api("/application/settings", admin), default_projects_limit: 3, - signin_enabled: false, + password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', @@ -46,7 +46,7 @@ describe API::Settings, 'Settings' do help_page_support_url: 'http://example.com/help' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey + expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storage']).to eq('custom') expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['koding_enabled']).to be_truthy diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb index 41d039b7da0..291f6dcc2aa 100644 --- a/spec/requests/api/v3/settings_spec.rb +++ b/spec/requests/api/v3/settings_spec.rb @@ -10,7 +10,7 @@ describe API::V3::Settings, 'Settings' do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) - expect(json_response['signin_enabled']).to be_truthy + expect(json_response['password_authentication_enabled']).to be_truthy expect(json_response['repository_storage']).to eq('default') expect(json_response['koding_enabled']).to be_falsey expect(json_response['koding_url']).to be_nil @@ -28,11 +28,11 @@ describe API::V3::Settings, 'Settings' do it "updates application settings" do put v3_api("/application/settings", admin), - default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', + default_projects_limit: 3, password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com' expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey + expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storage']).to eq('custom') expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['koding_enabled']).to be_truthy diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index d0443a450a2..d043ab2a974 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -463,7 +463,7 @@ describe 'Git HTTP requests', lib: true do context 'when internal auth is disabled' do before do - allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } end it 'rejects pulls with personal access token error message' do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 5e4cf05748e..8d79ea3dd40 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -101,7 +101,7 @@ describe JwtController do context 'when internal auth is disabled' do it 'rejects the authorization attempt with personal access token message' do - allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } get '/jwt/auth', parameters, headers expect(response).to have_http_status(401) diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 05b18fef061..fd4011ad606 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,11 +1,14 @@ require 'spec_helper' -describe Projects::UpdateService, services: true do +describe Projects::UpdateService, '#execute', :services do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } - describe 'update_by_user' do + let(:project) do + create(:empty_project, creator: user, namespace: user.namespace) + end + + context 'when changing visibility level' do context 'when visibility_level is INTERNAL' do it 'updates the project to internal' do result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) @@ -40,7 +43,7 @@ describe Projects::UpdateService, services: true do it 'does not update the project to public' do result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC) - expect(result).to eq({ status: :error, message: 'Visibility level unallowed' }) + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) expect(project).to be_private end @@ -55,12 +58,13 @@ describe Projects::UpdateService, services: true do end end - describe 'visibility_level' do + describe 'when updating project that has forks' do let(:project) { create(:empty_project, :internal) } let(:forked_project) { create(:forked_project_with_submodules, :internal) } before do - forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id) + forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, + forked_from_project_id: project.id) forked_project.save end @@ -89,10 +93,38 @@ describe Projects::UpdateService, services: true do end end - it 'returns an error result when record cannot be updated' do - result = update_project(project, admin, { name: 'foo&bar' }) + context 'when updating a default branch' do + let(:project) { create(:project, :repository) } + + it 'changes a default branch' do + update_project(project, admin, default_branch: 'feature') + + expect(Project.find(project.id).default_branch).to eq 'feature' + end + end - expect(result).to eq({ status: :error, message: 'Project could not be updated' }) + context 'when renaming project that contains container images' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end + + it 'does not allow to rename the project' do + result = update_project(project, admin, path: 'renamed') + + expect(result).to include(status: :error) + expect(result[:message]).to match(/contains container registry tags/) + end + end + + context 'when passing invalid parameters' do + it 'returns an error result when record cannot be updated' do + result = update_project(project, admin, { name: 'foo&bar' }) + + expect(result).to eq({ status: :error, + message: 'Project could not be updated!' }) + end end def update_project(project, user, opts) |