Commit ff7b545c authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge remote-tracking branch 'origin/master' into 18608-lock-issues

parents 55a75296 ff1deab6
...@@ -128,7 +128,7 @@ stages: ...@@ -128,7 +128,7 @@ stages:
- export CACHE_CLASSES=true - export CACHE_CLASSES=true
- cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- scripts/gitaly-test-spawn - scripts/gitaly-test-spawn
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' - knapsack spinach "-r rerun" -b || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -b -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always when: always
...@@ -174,7 +174,8 @@ build-package: ...@@ -174,7 +174,8 @@ build-package:
# Review docs base # Review docs base
.review-docs: &review-docs .review-docs: &review-docs
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script:
- gem install gitlab --no-doc
services: [] services: []
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
...@@ -193,10 +194,9 @@ review-docs-deploy: ...@@ -193,10 +194,9 @@ review-docs-deploy:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693 # Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$CI_COMMIT_REF_SLUG-built-from-ce-ee.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX url: http://preview-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
script: script:
- gem install gitlab --no-doc
- scripts/trigger-build-docs deploy - scripts/trigger-build-docs deploy
# Cleanup remote environment of gitlab-docs # Cleanup remote environment of gitlab-docs
...@@ -207,7 +207,6 @@ review-docs-cleanup: ...@@ -207,7 +207,6 @@ review-docs-cleanup:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
action: stop action: stop
script: script:
- gem install gitlab --no-doc
- scripts/trigger-build-docs cleanup - scripts/trigger-build-docs cleanup
# Retrieve knapsack and rspec_flaky reports # Retrieve knapsack and rspec_flaky reports
...@@ -413,12 +412,12 @@ downtime_check: ...@@ -413,12 +412,12 @@ downtime_check:
ee_compat_check: ee_compat_check:
<<: *rake-exec <<: *rake-exec
only:
- branches@gitlab-org/gitlab-ce
except: except:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?/ - /^[\d-]+-stable(-ee)?/
- branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee
allow_failure: yes allow_failure: yes
cache: cache:
key: "ee_compat_check_repo" key: "ee_compat_check_repo"
...@@ -517,6 +516,12 @@ db:seed_fu-mysql: ...@@ -517,6 +516,12 @@ db:seed_fu-mysql:
<<: *db-seed_fu <<: *db-seed_fu
<<: *use-mysql <<: *use-mysql
db:check-schema-pg:
<<: *db-migrate-reset
<<: *use-pg
script:
- source scripts/schema_changed.sh
# Frontend-related jobs # Frontend-related jobs
gitlab:assets:compile: gitlab:assets:compile:
<<: *dedicated-runner <<: *dedicated-runner
......
...@@ -362,6 +362,7 @@ group :test do ...@@ -362,6 +362,7 @@ group :test do
gem 'sham_rack', '~> 1.3.6' gem 'sham_rack', '~> 1.3.6'
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
gem 'concurrent-ruby', '~> 1.0.5' gem 'concurrent-ruby', '~> 1.0.5'
gem 'test-prof', '~> 0.2.5'
end end
gem 'octokit', '~> 4.6.2' gem 'octokit', '~> 4.6.2'
......
...@@ -882,6 +882,7 @@ GEM ...@@ -882,6 +882,7 @@ GEM
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
temple (0.7.7) temple (0.7.7)
test-prof (0.2.5)
test_after_commit (1.1.0) test_after_commit (1.1.0)
activerecord (>= 3.2) activerecord (>= 3.2)
text (1.3.1) text (1.3.1)
...@@ -1163,6 +1164,7 @@ DEPENDENCIES ...@@ -1163,6 +1164,7 @@ DEPENDENCIES
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
test-prof (~> 0.2.5)
test_after_commit (~> 1.1) test_after_commit (~> 1.1)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
......
Copyright (c) 2011-2017 GitLab B.V. Copyright (c) 2011-2017 GitLab B.V.
With regard to the GitLab Software:
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
...@@ -17,3 +19,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ...@@ -17,3 +19,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
For all third party components incorporated into the GitLab Software, those
components are licensed under the original license provided by the owner of the
applicable component.
\ No newline at end of file
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
</span> </span>
<a <a
v-if="deployKey.can_edit" v-if="deployKey.can_edit"
class="btn btn-small" class="btn btn-sm"
:href="editDeployKeyPath" :href="editDeployKeyPath"
> >
Edit Edit
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import Vue from 'vue'; import Vue from 'vue';
import '../mixins/discussion';
const JumpToDiscussion = Vue.extend({ const JumpToDiscussion = Vue.extend({
mixins: [DiscussionMixins], mixins: [DiscussionMixins],
props: { props: {
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import Vue from 'vue'; import Vue from 'vue';
import '../mixins/discussion';
window.ResolveCount = Vue.extend({ window.ResolveCount = Vue.extend({
mixins: [DiscussionMixins], mixins: [DiscussionMixins],
props: { props: {
......
import Cookies from 'js-cookie';
import _ from 'underscore';
import {
getCookieName,
getSelector,
hidePopover,
setupDismissButton,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
export const setupFeatureHighlightPopover = (id, debounceTimeout = 300) => {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = hidePopover.bind($selector);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector
// Setup popover
.data('content', $popoverContent.prop('outerHTML'))
.popover({
html: true,
// Override the existing template to add custom CSS classes
template: `
<div class="popover feature-highlight-popover" role="tooltip">
<div class="arrow"></div>
<div class="popover-content"></div>
</div>
`,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave)
.on('inserted.bs.popover', setupDismissButton)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
})
// Display feature highlight
.removeAttr('disabled');
};
export const shouldHighlightFeature = (id) => {
const element = document.querySelector(getSelector(id));
const previouslyDismissed = Cookies.get(getCookieName(id)) === 'true';
return element && !previouslyDismissed;
};
export const highlightFeatures = (highlightOrder) => {
const featureId = highlightOrder.find(shouldHighlightFeature);
if (featureId) {
setupFeatureHighlightPopover(featureId);
return true;
}
return false;
};
import Cookies from 'js-cookie';
export const getCookieName = cookieId => `feature-highlighted-${cookieId}`;
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export const showPopover = function showPopover() {
if (this.hasClass('js-popover-show')) {
return false;
}
this.popover('show');
this.addClass('disable-animation js-popover-show');
return true;
};
export const hidePopover = function hidePopover() {
if (!this.hasClass('js-popover-show')) {
return false;
}
this.popover('hide');
this.removeClass('disable-animation js-popover-show');
return true;
};
export const dismiss = function dismiss(cookieId) {
Cookies.set(getCookieName(cookieId), true);
hidePopover.call(this);
this.hide();
};
export const mouseleave = function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
hidePopover.call($featureHighlight);
}
};
export const mouseenter = function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = showPopover.call($featureHighlight);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
};
export const setupDismissButton = function setupDismissButton() {
const popoverId = this.getAttribute('aria-describedby');
const cookieId = this.dataset.highlight;
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, cookieId);
$(`#${popoverId} .dismiss-feature-highlight`)
.on('click', dismissWrapper);
};
import { highlightFeatures } from './feature_highlight';
import bp from '../breakpoints';
const highlightOrder = ['issue-boards'];
export default function domContentLoaded(order) {
if (bp.getBreakpointSize() === 'lg') {
highlightFeatures(order);
}
}
document.addEventListener('DOMContentLoaded', domContentLoaded.bind(this, highlightOrder));
import _ from 'underscore'; import _ from 'underscore';
(() => { /*
/*
* TODO: Make these methods more configurable (e.g. stringifyTime condensed or * TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* non-condensed, abbreviateTimelengths) * non-condensed, abbreviateTimelengths)
* */ * */
const utils = window.gl.utils = gl.utils || {}; /*
const prettyTime = utils.prettyTime = {
/*
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day * Seconds can be negative or positive, zero or non-zero. Can be configured for any day
* or week length. * or week length.
*/ */
parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
const DAYS_PER_WEEK = daysPerWeek; const DAYS_PER_WEEK = daysPerWeek;
const HOURS_PER_DAY = hoursPerDay; const HOURS_PER_DAY = hoursPerDay;
const MINUTES_PER_HOUR = 60; const MINUTES_PER_HOUR = 60;
...@@ -27,7 +25,7 @@ import _ from 'underscore'; ...@@ -27,7 +25,7 @@ import _ from 'underscore';
minutes: 1, minutes: 1,
}; };
let unorderedMinutes = prettyTime.secondsToMinutes(seconds); let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
...@@ -36,33 +34,28 @@ import _ from 'underscore'; ...@@ -36,33 +34,28 @@ import _ from 'underscore';
return periodCount; return periodCount;
}); });
}, }
/* /*
* Accepts a timeObject and returns a condensed string representation of it * Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
*/ */
stringifyTime(timeObject) { export function stringifyTime(timeObject) {
const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
const isNonZero = !!unitValue; const isNonZero = !!unitValue;
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
}, '').trim(); }, '').trim();
return reducedTime.length ? reducedTime : '0m'; return reducedTime.length ? reducedTime : '0m';
}, }
/* /*
* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
* the first non-zero unit/value pair. * the first non-zero unit/value pair.
*/ */
abbreviateTime(timeStr) { export function abbreviateTime(timeStr) {
return timeStr.split(' ') return timeStr.split(' ')
.filter(unitStr => unitStr.charAt(0) !== '0')[0]; .filter(unitStr => unitStr.charAt(0) !== '0')[0];
}, }
secondsToMinutes(seconds) {
return Math.abs(seconds / 60);
},
};
})(window.gl || (window.gl = {}));
...@@ -101,7 +101,6 @@ import './label_manager'; ...@@ -101,7 +101,6 @@ import './label_manager';
import './labels'; import './labels';
import './labels_select'; import './labels_select';
import './layout_nav'; import './layout_nav';
import './feature_highlight/feature_highlight_options';
import LazyLoader from './lazy_loader'; import LazyLoader from './lazy_loader';
import './line_highlighter'; import './line_highlighter';
import './logo'; import './logo';
......
...@@ -11,6 +11,7 @@ export default class NewNavSidebar { ...@@ -11,6 +11,7 @@ export default class NewNavSidebar {
initDomElements() { initDomElements() {
this.$page = $('.page-with-sidebar'); this.$page = $('.page-with-sidebar');
this.$sidebar = $('.nav-sidebar'); this.$sidebar = $('.nav-sidebar');
this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar);
this.$overlay = $('.mobile-overlay'); this.$overlay = $('.mobile-overlay');
this.$openSidebar = $('.toggle-mobile-nav'); this.$openSidebar = $('.toggle-mobile-nav');
this.$closeSidebar = $('.close-nav-button'); this.$closeSidebar = $('.close-nav-button');
...@@ -55,6 +56,16 @@ export default class NewNavSidebar { ...@@ -55,6 +56,16 @@ export default class NewNavSidebar {
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
NewNavSidebar.setCollapsedCookie(collapsed); NewNavSidebar.setCollapsedCookie(collapsed);
this.toggleSidebarOverflow();
}
toggleSidebarOverflow() {
if (this.$innerScroll.prop('scrollHeight') > this.$innerScroll.prop('offsetHeight')) {
this.$innerScroll.css('overflow-y', 'scroll');
} else {
this.$innerScroll.css('overflow-y', '');
}
} }
render() { render() {
......
/* 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 */ /* 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 Mousetrap */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
......
import stopwatchSvg from 'icons/_icon_stopwatch.svg'; import stopwatchSvg from 'icons/_icon_stopwatch.svg';
import { abbreviateTime } from '../../../lib/utils/pretty_time';
import '../../../lib/utils/pretty_time';
export default { export default {
name: 'time-tracking-collapsed-state', name: 'time-tracking-collapsed-state',
...@@ -79,7 +78,7 @@ export default { ...@@ -79,7 +78,7 @@ export default {
}, },
methods: { methods: {
abbreviateTime(timeStr) { abbreviateTime(timeStr) {
return gl.utils.prettyTime.abbreviateTime(timeStr); return abbreviateTime(timeStr);
}, },
}, },
template: ` template: `
......
import '../../../lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
const prettyTime = gl.utils.prettyTime;
export default { export default {
name: 'time-tracking-comparison-pane', name: 'time-tracking-comparison-pane',
...@@ -23,12 +21,12 @@ export default { ...@@ -23,12 +21,12 @@ export default {
}, },
}, },
computed: { computed: {
parsedRemaining() { parsedTimeRemaining() {
const diffSeconds = this.timeEstimate - this.timeSpent; const diffSeconds = this.timeEstimate - this.timeSpent;
return prettyTime.parseSeconds(diffSeconds); return parseSeconds(diffSeconds);
}, },
timeRemainingHumanReadable() { timeRemainingHumanReadable() {
return prettyTime.stringifyTime(this.parsedRemaining); return stringifyTime(this.parsedTimeRemaining);
}, },
timeRemainingTooltip() { timeRemainingTooltip() {
const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:'; const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
...@@ -44,13 +42,6 @@ export default { ...@@ -44,13 +42,6 @@ export default {
timeRemainingStatusClass() { timeRemainingStatusClass() {
return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
}, },
/* Parsed time values */
parsedEstimate() {
return prettyTime.parseSeconds(this.timeEstimate);
},
parsedSpent() {
return prettyTime.parseSeconds(this.timeSpent);
},
}, },
template: ` template: `
<div class="time-tracking-comparison-pane"> <div class="time-tracking-comparison-pane">
......
...@@ -72,12 +72,12 @@ export default { ...@@ -72,12 +72,12 @@ export default {
<a <a
href="#modal_merge_info" href="#modal_merge_info"
data-toggle="modal" data-toggle="modal"
class="btn btn-small inline"> class="btn btn-sm inline">
Check out branch Check out branch
</a> </a>
<span class="dropdown prepend-left-10"> <span class="dropdown prepend-left-10">
<a <a
class="btn btn-small inline dropdown-toggle" class="btn btn-sm inline dropdown-toggle"
data-toggle="dropdown" data-toggle="dropdown"
aria-label="Download as" aria-label="Download as"
role="button"> role="button">
......
...@@ -12,6 +12,9 @@ export default { ...@@ -12,6 +12,9 @@ export default {
ciIcon, ciIcon,
}, },
computed: { computed: {
hasPipeline() {
return this.mr.pipeline && Object.keys(this.mr.pipeline).length > 0;
},
hasCIError() { hasCIError() {
const { hasCI, ciStatus } = this.mr; const { hasCI, ciStatus } = this.mr;
...@@ -28,7 +31,9 @@ export default { ...@@ -28,7 +31,9 @@ export default {
}, },
}, },
template: ` template: `
<div class="mr-widget-heading"> <div
v-if="hasPipeline || hasCIError"
class="mr-widget-heading">
<div class="ci-widget media"> <div class="ci-widget media">
<template v-if="hasCIError"> <template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
...@@ -40,7 +45,7 @@ export default { ...@@ -40,7 +45,7 @@ export default {
Could not connect to the CI server. Please check your settings and try again Could not connect to the CI server. Please check your settings and try again
</div> </div>
</template> </template>
<template v-else> <template v-else-if="hasPipeline">
<div class="ci-status-icon append-right-10"> <div class="ci-status-icon append-right-10">
<a <a
class="icon-link" class="icon-link"
......
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
<button <button
v-if="showDisabledButton" v-if="showDisabledButton"
type="button" type="button"
class="btn btn-success btn-small" class="btn btn-success btn-sm"
disabled="true"> disabled="true">
Merge Merge
</button> </button>
......
...@@ -11,7 +11,7 @@ export default { ...@@ -11,7 +11,7 @@ export default {
<status-icon status="failed" /> <status-icon status="failed" />
<button <button
type="button" type="button"
class="btn btn-success btn-small" class="btn btn-success btn-sm"
disabled="true"> disabled="true">
Merge Merge
</button> </button>
......
...@@ -29,6 +29,9 @@ export default { ...@@ -29,6 +29,9 @@ export default {
statusIcon, statusIcon,
}, },
computed: { computed: {
shouldShowMergeWhenPipelineSucceedsText() {
return this.mr.isPipelineActive;
},
commitMessageLinkTitle() { commitMessageLinkTitle() {
const withDesc = 'Include description in commit message'; const withDesc = 'Include description in commit message';
const withoutDesc = "Don't include description in commit message"; const withoutDesc = "Don't include description in commit message";
...@@ -36,7 +39,7 @@ export default { ...@@ -36,7 +39,7 @@ export default {
return this.useCommitMessageWithDescription ? withoutDesc : withDesc; return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
}, },
mergeButtonClass() { mergeButtonClass() {
const defaultClass = 'btn btn-small btn-success accept-merge-request'; const defaultClass = 'btn btn-sm btn-success accept-merge-request';
const failedClass = `${defaultClass} btn-danger`; const failedClass = `${defaultClass} btn-danger`;
const inActionClass = `${defaultClass} btn-info`; const inActionClass = `${defaultClass} btn-info`;
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr; const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
...@@ -56,7 +59,7 @@ export default { ...@@ -56,7 +59,7 @@ export default {
mergeButtonText() { mergeButtonText() {
if (this.isMergingImmediately) { if (this.isMergingImmediately) {
return 'Merge in progress'; return 'Merge in progress';
} else if (this.mr.isPipelineActive) { } else if (this.shouldShowMergeWhenPipelineSucceedsText) {
return 'Merge when pipeline succeeds'; return 'Merge when pipeline succeeds';
} }
...@@ -68,7 +71,7 @@ export default { ...@@ -68,7 +71,7 @@ export default {
isMergeButtonDisabled() { isMergeButtonDisabled() {
const { commitMessage } = this; const { commitMessage } = this;
return Boolean(!commitMessage.length return Boolean(!commitMessage.length
|| !this.isMergeAllowed() || !this.shouldShowMergeControls()
|| this.isMakingRequest || this.isMakingRequest
|| this.mr.preventMerge); || this.mr.preventMerge);
}, },
...@@ -82,7 +85,12 @@ export default { ...@@ -82,7 +85,12 @@ export default {
}, },
methods: { methods: {
isMergeAllowed() { isMergeAllowed() {
return !(this.mr.onlyAllowMergeIfPipelineSucceeds && this.mr.isPipelineFailed); return !this.mr.onlyAllowMergeIfPipelineSucceeds ||
this.mr.isPipelinePassing ||
this.mr.isPipelineSkipped;
},
shouldShowMergeControls() {
return this.isMergeAllowed() || this.shouldShowMergeWhenPipelineSucceedsText;
}, },
updateCommitMessage() { updateCommitMessage() {
const cmwd = this.mr.commitMessageWithDescription; const cmwd = this.mr.commitMessageWithDescription;
...@@ -202,8 +210,8 @@ export default { ...@@ -202,8 +210,8 @@ export default {
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="success" /> <status-icon status="success" />
<div class="media-body"> <div class="media-body">
<div class="media space-children"> <div class="mr-widget-body-controls media space-children">
<span class="btn-group"> <span class="btn-group append-bottom-5">
<button <button
@click="handleMergeButtonClick()" @click="handleMergeButtonClick()"
:disabled="isMergeButtonDisabled" :disabled="isMergeButtonDisabled"
...@@ -219,7 +227,7 @@ export default { ...@@ -219,7 +227,7 @@ export default {
v-if="shouldShowMergeOptionsDropdown" v-if="shouldShowMergeOptionsDropdown"
:disabled="isMergeButtonDisabled" :disabled="isMergeButtonDisabled"
type="button" type="button"
class="btn btn-small btn-info dropdown-toggle js-merge-moment" class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
data-toggle="dropdown" data-toggle="dropdown"
aria-label="Select merge moment"> aria-label="Select merge moment">
<i <i
...@@ -260,8 +268,8 @@ export default { ...@@ -260,8 +268,8 @@ export default {
</li> </li>
</ul> </ul>
</span> </span>
<div class="media-body space-children"> <div class="media-body-wrap space-children">
<template v-if="isMergeAllowed()"> <template v-if="shouldShowMergeControls()">
<label> <label>
<input <input
id="remove-source-branch-input" id="remove-source-branch-input"
...@@ -286,7 +294,7 @@ export default { ...@@ -286,7 +294,7 @@ export default {
</template> </template>
<template v-else> <template v-else>
<span class="bold"> <span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure The pipeline for this merge request has not succeeded yet
</span> </span>
</template> </template>
</div> </div>
......
...@@ -57,7 +57,7 @@ export default { ...@@ -57,7 +57,7 @@ export default {
return stateMaps.statesToShowHelpWidget.indexOf(this.mr.state) > -1; return stateMaps.statesToShowHelpWidget.indexOf(this.mr.state) > -1;
}, },
shouldRenderPipelines() { shouldRenderPipelines() {
return Object.keys(this.mr.pipeline).length || this.mr.hasCI; return this.mr.hasCI;
}, },
shouldRenderRelatedLinks() { shouldRenderRelatedLinks() {
return this.mr.relatedLinks; return this.mr.relatedLinks;
......
...@@ -85,7 +85,9 @@ export default class MergeRequestStore { ...@@ -85,7 +85,9 @@ export default class MergeRequestStore {
this.ciEnvironmentsStatusPath = data.ci_environments_status_path; this.ciEnvironmentsStatusPath = data.ci_environments_status_path;
this.hasCI = data.has_ci; this.hasCI = data.has_ci;
this.ciStatus = data.ci_status; this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus ? (this.ciStatus === 'failed' || this.ciStatus === 'canceled') : false; this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled';
this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings';
this.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus; this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false; this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false; this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false;
......
...@@ -52,4 +52,3 @@ ...@@ -52,4 +52,3 @@
@import "framework/snippets"; @import "framework/snippets";
@import "framework/memory_graph"; @import "framework/memory_graph";
@import "framework/responsive-tables"; @import "framework/responsive-tables";
@import "framework/feature_highlight";
...@@ -46,15 +46,6 @@ ...@@ -46,15 +46,6 @@
} }
} }
@mixin btn-svg {
svg {
height: 15px;
width: 15px;
position: relative;
top: 2px;
}
}
@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { @mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light; background-color: $light;
border-color: $border-light; border-color: $border-light;
...@@ -132,7 +123,6 @@ ...@@ -132,7 +123,6 @@
.btn { .btn {
@include btn-default; @include btn-default;
@include btn-white; @include btn-white;
@include btn-svg;
color: $gl-text-color; color: $gl-text-color;
...@@ -140,7 +130,6 @@ ...@@ -140,7 +130,6 @@
outline: 0; outline: 0;
} }
&.btn-small,
&.btn-sm { &.btn-sm {
padding: 4px 10px; padding: 4px 10px;
font-size: 13px; font-size: 13px;
...@@ -232,6 +221,13 @@ ...@@ -232,6 +221,13 @@
} }
} }
svg {
height: 15px;
width: 15px;
position: relative;
top: 2px;
}
svg, svg,
.fa { .fa {
&:not(:last-child) { &:not(:last-child) {
......
.feature-highlight {
position: relative;
margin-left: $gl-padding;
width: 20px;
height: 20px;
cursor: pointer;
&::before {
content: '';
display: block;
position: absolute;
top: 6px;
left: 6px;
width: 8px;
height: 8px;
background-color: $blue-500;
border-radius: 50%;
box-shadow: 0 0 0 rgba($blue-500, 0.4);
animation: pulse-highlight 2s infinite;
}
&:hover::before,
&.disable-animation::before {
animation: none;
}
&[disabled]::before {
display: none;
}
}
.is-showing-fly-out {
.feature-highlight {
display: none;
}
}
.feature-highlight-popover-content {
display: none;
hr {
margin: $gl-padding * 0.5 0;
}
.btn-link {
@include btn-svg;
svg path {
fill: currentColor;
}
}
.dismiss-feature-highlight {
padding: 0;
}
svg:first-child {
width: 100%;
background-color: $indigo-50;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom: 1px solid darken($gray-normal, 8%);
}
}
.popover .feature-highlight-popover-content {
display: block;
}
.feature-highlight-popover {
padding: 0;
.popover-content {
padding: 0;
}
}
.feature-highlight-popover-sub-content {
padding: 9px 14px;
}
@include keyframes(pulse-highlight) {
0% {
box-shadow: 0 0 0 0 rgba($blue-200, 0.4);
}
70% {
box-shadow: 0 0 0 10px transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}
...@@ -6,3 +6,7 @@ ...@@ -6,3 +6,7 @@
.media-body { .media-body {
flex: 1; flex: 1;
} }
.media-body-wrap {
flex-grow: 1;
}
...@@ -192,7 +192,11 @@ $new-sidebar-collapsed-width: 50px; ...@@ -192,7 +192,11 @@ $new-sidebar-collapsed-width: 50px;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: scroll; overflow: auto;
@media (min-width: $screen-sm-min) {
overflow: hidden;
}
} }
.with-performance-bar .nav-sidebar { .with-performance-bar .nav-sidebar {
......
.info-well {
.admin-well-statistics,
.admin-well-features {
padding-bottom: 46px;
}
}
...@@ -356,6 +356,10 @@ ...@@ -356,6 +356,10 @@
} }
} }
.mr-widget-body-controls {
flex-wrap: wrap;
}
.mr_source_commit, .mr_source_commit,
.mr_target_commit { .mr_target_commit {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -784,6 +784,7 @@ ul.notes { ...@@ -784,6 +784,7 @@ ul.notes {
background-color: transparent; background-color: transparent;
border: none; border: none;
outline: 0; outline: 0;
color: $gray-darkest;
transition: color $general-hover-transition-duration $general-hover-transition-curve; transition: color $general-hover-transition-duration $general-hover-transition-curve;
&.is-disabled { &.is-disabled {
...@@ -807,7 +808,7 @@ ul.notes { ...@@ -807,7 +808,7 @@ ul.notes {
} }
svg { svg {
fill: $gray-darkest; fill: currentColor;
height: 16px; height: 16px;
width: 16px; width: 16px;
} }
......
...@@ -209,6 +209,11 @@ ...@@ -209,6 +209,11 @@
} }
.stage-cell { .stage-cell {
@media (min-width: $screen-md-min) {
min-width: 148px;
margin-right: -4px;
}
.mini-pipeline-graph-dropdown-toggle svg { .mini-pipeline-graph-dropdown-toggle svg {
height: $ci-action-icon-size; height: $ci-action-icon-size;
width: $ci-action-icon-size; width: $ci-action-icon-size;
......
...@@ -56,7 +56,6 @@ ...@@ -56,7 +56,6 @@
.tree-content-holder { .tree-content-holder {
display: flex; display: flex;
max-height: 100vh;
min-height: 300px; min-height: 300px;
} }
...@@ -156,7 +155,7 @@ ...@@ -156,7 +155,7 @@
list-style-type: none; list-style-type: none;
background: $gray-normal; background: $gray-normal;
display: inline-block; display: inline-block;
padding: 10px 18px; padding: #{$gl-padding / 2} $gl-padding;
border-right: 1px solid $white-dark; border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
white-space: nowrap; white-space: nowrap;
...@@ -180,10 +179,9 @@ ...@@ -180,10 +179,9 @@
a { a {
@include str-truncated(100px); @include str-truncated(100px);
color: $black; color: $black;
width: 100px;
text-align: center;
vertical-align: middle; vertical-align: middle;
text-decoration: none; text-decoration: none;
margin-right: 12px;
&.close { &.close {
width: auto; width: auto;
...@@ -193,6 +191,10 @@ ...@@ -193,6 +191,10 @@
} }
} }
.close-icon:hover {
color: $hint-color;
}
.close-icon, .close-icon,
.unsaved-icon { .unsaved-icon {
float: right; float: right;
......
...@@ -29,7 +29,7 @@ class Admin::LabelsController < Admin::ApplicationController ...@@ -29,7 +29,7 @@ class Admin::LabelsController < Admin::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label) @label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid? if @label.valid?
redirect_to admin_labels_path, notice: 'label was successfully updated.' redirect_to admin_labels_path, notice: 'Label was successfully updated.'
else else
render :edit render :edit
end end
......
...@@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end end
def set_index_vars def set_index_vars
@scopes = Gitlab::Auth::AVAILABLE_SCOPES @scopes = Gitlab::Auth.available_scopes
@personal_access_token = finder.build @personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute @inactive_personal_access_tokens = finder(state: 'inactive').execute
......
...@@ -15,11 +15,15 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -15,11 +15,15 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37429
Gitlab::GitalyClient.allow_n_plus_1_calls do
@max_commits = @branches.reduce(0) do |memo, branch| @max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch) diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end end
render
end
end end
format.json do format.json do
render json: @branches.map(&:name) render json: @branches.map(&:name)
......
...@@ -20,7 +20,12 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -20,7 +20,12 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie! apply_diff_view_cookie!
respond_to do |format| respond_to do |format|
format.html format.html do
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37599
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
end
format.diff { render text: @commit.to_diff } format.diff { render text: @commit.to_diff }
format.patch { render text: @commit.to_patch } format.patch { render text: @commit.to_patch }
end end
......
...@@ -17,6 +17,10 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -17,6 +17,10 @@ class Projects::CompareController < Projects::ApplicationController
def show def show
apply_diff_view_cookie! apply_diff_view_cookie!
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
end end
def diff_for_path def diff_for_path
......
...@@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController
def index def index
base_query = project.forks.includes(:creator) base_query = project.forks.includes(:creator)
@forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute) forks = ForkProjectsFinder.new(project, params: params.merge(search: params[:filter_projects]), current_user: current_user).execute
@total_forks_count = base_query.size @total_forks_count = base_query.size
@private_forks_count = @total_forks_count - @forks.size @private_forks_count = @total_forks_count - forks.size
@public_forks_count = @total_forks_count - @private_forks_count @public_forks_count = @total_forks_count - @private_forks_count
@sort = params[:sort] || 'id_desc' @forks = forks.page(params[:page])
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
@forks = @forks.order_by(@sort).page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -71,9 +71,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -71,9 +71,6 @@ class Projects::IssuesController < Projects::ApplicationController
@noteable = @issue @noteable = @issue
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@discussions = @issue.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
...@@ -87,9 +84,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -87,9 +84,9 @@ class Projects::IssuesController < Projects::ApplicationController
.inc_relations_for_view .inc_relations_for_view
.includes(:noteable) .includes(:noteable)
.fresh .fresh
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
prepare_notes_for_rendering(notes) notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
discussions = Discussion.build_collection(notes, @issue) discussions = Discussion.build_collection(notes, @issue)
......
...@@ -10,8 +10,11 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -10,8 +10,11 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def show def show
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user).last
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37431
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") } render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
end end
end
def diff_for_path def diff_for_path
render_diff_for_path(@diffs) render_diff_for_path(@diffs)
......
...@@ -56,6 +56,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -56,6 +56,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
close_merge_request_without_source_project close_merge_request_without_source_project
check_if_can_be_merged check_if_can_be_merged
# Return if the response has already been rendered
return if response_body
respond_to do |format| respond_to do |format|
format.html do format.html do
# Build a note object for comment form # Build a note object for comment form
...@@ -70,6 +73,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -70,6 +73,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
labels labels
set_pipeline_variables set_pipeline_variables
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37432
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
end end
format.json do format.json do
......
...@@ -8,6 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -8,6 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :assign_commit before_action :assign_commit
def show def show
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602
Gitlab::GitalyClient.allow_n_plus_1_calls do
@url = project_network_path(@project, @ref, @options.merge(format: :json)) @url = project_network_path(@project, @ref, @options.merge(format: :json))
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
...@@ -22,6 +24,9 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -22,6 +24,9 @@ class Projects::NetworkController < Projects::ApplicationController
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
end end
end end
render
end
end end
def assign_commit def assign_commit
......
...@@ -51,7 +51,9 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -51,7 +51,9 @@ class Projects::RefsController < Projects::ApplicationController
contents.push(*tree.blobs) contents.push(*tree.blobs)
contents.push(*tree.submodules) contents.push(*tree.submodules)
@logs = contents[@offset, @limit].to_a.map do |content| # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37433
@logs = Gitlab::GitalyClient.allow_n_plus_1_calls do
contents[@offset, @limit].to_a.map do |content|
file = @path ? File.join(@path, content.name) : content.name file = @path ? File.join(@path, content.name) : content.name
last_commit = @repo.last_commit_for_path(@commit.id, file) last_commit = @repo.last_commit_for_path(@commit.id, file)
{ {
...@@ -59,6 +61,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -59,6 +61,7 @@ class Projects::RefsController < Projects::ApplicationController
commit: last_commit commit: last_commit
} }
end end
end
offset = (@offset + @limit) offset = (@offset + @limit)
if contents.size > offset if contents.size > offset
......
...@@ -28,7 +28,7 @@ class Projects::UploadsController < Projects::ApplicationController ...@@ -28,7 +28,7 @@ class Projects::UploadsController < Projects::ApplicationController
end end
def image_or_video? def image_or_video?
uploader && uploader.file.exists? && uploader.image_or_video? uploader && uploader.exists? && uploader.image_or_video?
end end
def uploader_class def uploader_class
......
...@@ -13,8 +13,11 @@ class RootController < Dashboard::ProjectsController ...@@ -13,8 +13,11 @@ class RootController < Dashboard::ProjectsController
before_action :redirect_logged_user, if: -> { current_user.present? } before_action :redirect_logged_user, if: -> { current_user.present? }
def index def index
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37434
Gitlab::GitalyClient.allow_n_plus_1_calls do
super super
end end
end
private private
......
class ForkProjectsFinder < ProjectsFinder
def initialize(project, params: {}, current_user: nil)
project_ids = project.forks.includes(:creator).select(:id)
super(params: params, current_user: current_user, project_ids_relation: project_ids)
end
end
...@@ -57,7 +57,7 @@ class GroupsFinder < UnionFinder ...@@ -57,7 +57,7 @@ class GroupsFinder < UnionFinder
end end
def owned_groups def owned_groups
current_user&.groups || Group.none current_user&.owned_groups || Group.none
end end
def include_public_groups? def include_public_groups?
......
...@@ -244,6 +244,8 @@ class IssuableFinder ...@@ -244,6 +244,8 @@ class IssuableFinder
end end
def by_scope(items) def by_scope(items)
return items.none if current_user_related? && !current_user
case params[:scope] case params[:scope]
when 'created-by-me', 'authored' when 'created-by-me', 'authored'
items.where(author_id: current_user.id) items.where(author_id: current_user.id)
......
...@@ -5,6 +5,25 @@ module AutoDevopsHelper ...@@ -5,6 +5,25 @@ module AutoDevopsHelper
can?(current_user, :admin_pipeline, project) && can?(current_user, :admin_pipeline, project) &&
project.has_auto_devops_implicitly_disabled? && project.has_auto_devops_implicitly_disabled? &&
!project.repository.gitlab_ci_yml && !project.repository.gitlab_ci_yml &&
project.ci_services.active.none? !project.ci_service
end
def auto_devops_warning_message(project)
missing_domain = !project.auto_devops&.has_domain?
missing_service = !project.kubernetes_service&.active?
if missing_service
params = {
kubernetes: link_to('Kubernetes service', edit_project_service_path(project, 'kubernetes'))
}
if missing_domain
_('Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly.') % params
else
_('Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly.') % params
end
elsif missing_domain
_('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
end
end end
end end
...@@ -94,6 +94,12 @@ module MilestonesHelper ...@@ -94,6 +94,12 @@ module MilestonesHelper
end end
end end
def milestone_tooltip_title(milestone)
if milestone.due_date
[milestone.due_date.to_s(:medium), "(#{milestone_remaining_days(milestone)})"].join(' ')
end
end
def milestone_remaining_days(milestone) def milestone_remaining_days(milestone)
if milestone.expired? if milestone.expired?
content_tag(:strong, 'Past due') content_tag(:strong, 'Past due')
......
...@@ -87,10 +87,14 @@ module SubmoduleHelper ...@@ -87,10 +87,14 @@ module SubmoduleHelper
namespace = @project.namespace.full_path namespace = @project.namespace.full_path
end end
begin
[ [
namespace_project_path(namespace, base), namespace_project_path(namespace, base),
namespace_project_tree_path(namespace, base, commit) namespace_project_tree_path(namespace, base, commit)
] ]
rescue ActionController::UrlGenerationError
[nil, nil]
end
end end
def sanitize_submodule_url(url) def sanitize_submodule_url(url)
......
...@@ -31,6 +31,7 @@ module Ci ...@@ -31,6 +31,7 @@ module Ci
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id' has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
delegate :id, to: :project, prefix: true delegate :id, to: :project, prefix: true
delegate :full_path, to: :project, prefix: true
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
...@@ -336,7 +337,7 @@ module Ci ...@@ -336,7 +337,7 @@ module Ci
return @config_processor if defined?(@config_processor) return @config_processor if defined?(@config_processor)
@config_processor ||= begin @config_processor ||= begin
Gitlab::Ci::YamlProcessor.new(ci_yaml_file, project.full_path) Gitlab::Ci::YamlProcessor.new(ci_yaml_file)
rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e
self.yaml_errors = e.message self.yaml_errors = e.message
nil nil
......
...@@ -6,9 +6,7 @@ class Environment < ActiveRecord::Base ...@@ -6,9 +6,7 @@ class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true belongs_to :project, required: true, validate: true
has_many :deployments, has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
-> (env) { where(project_id: env.project_id) },
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment'
......
...@@ -275,8 +275,6 @@ class Issue < ActiveRecord::Base ...@@ -275,8 +275,6 @@ class Issue < ActiveRecord::Base
end end
def update_project_counter_caches def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenIssuesCountService.new(project).refresh_cache Projects::OpenIssuesCountService.new(project).refresh_cache
end end
......
...@@ -415,9 +415,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -415,9 +415,12 @@ class MergeRequest < ActiveRecord::Base
end end
def create_merge_request_diff def create_merge_request_diff
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37435
Gitlab::GitalyClient.allow_n_plus_1_calls do
merge_request_diffs.create merge_request_diffs.create
reload_merge_request_diff reload_merge_request_diff
end end
end
def reload_merge_request_diff def reload_merge_request_diff
merge_request_diff(true) merge_request_diff(true)
...@@ -955,8 +958,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -955,8 +958,6 @@ class MergeRequest < ActiveRecord::Base
end end
def update_project_counter_caches def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end end
......
...@@ -162,9 +162,7 @@ class Milestone < ActiveRecord::Base ...@@ -162,9 +162,7 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
# #
def to_reference(from_project = nil, format: :iid, full: false) def to_reference(from_project = nil, format: :name, full: false)
return if group_milestone? && format != :name
format_reference = milestone_format_reference(format) format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
...@@ -241,6 +239,10 @@ class Milestone < ActiveRecord::Base ...@@ -241,6 +239,10 @@ class Milestone < ActiveRecord::Base
def milestone_format_reference(format = :iid) def milestone_format_reference(format = :iid)
raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format) raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
if group_milestone? && format == :iid
raise ArgumentError, 'Cannot refer to a group milestone by an internal id!'
end
if format == :name && !name.include?('"') if format == :name && !name.include?('"')
%("#{name}") %("#{name}")
else else
......
...@@ -61,9 +61,12 @@ module Network ...@@ -61,9 +61,12 @@ module Network
@reserved[i] = [] @reserved[i] = []
end end
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37436
Gitlab::GitalyClient.allow_n_plus_1_calls do
commits_sort_by_ref.each do |commit| commits_sort_by_ref.each do |commit|
place_chain(commit) place_chain(commit)
end end
end
# find parent spaces for not overlap lines # find parent spaces for not overlap lines
@commits.each do |c| @commits.each do |c|
......
...@@ -28,7 +28,7 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -28,7 +28,7 @@ class PersonalAccessToken < ActiveRecord::Base
protected protected
def validate_scopes def validate_scopes
unless revoked || scopes.all? { |scope| Gitlab::Auth::AVAILABLE_SCOPES.include?(scope.to_sym) } unless revoked || scopes.all? { |scope| Gitlab::Auth.available_scopes.include?(scope.to_sym) }
errors.add :scopes, "can only contain available scopes" errors.add :scopes, "can only contain available scopes"
end end
end end
......
...@@ -192,7 +192,7 @@ class Project < ActiveRecord::Base ...@@ -192,7 +192,7 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops accepts_nested_attributes_for :auto_devops, update_only: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
......
...@@ -6,6 +6,10 @@ class ProjectAutoDevops < ActiveRecord::Base ...@@ -6,6 +6,10 @@ class ProjectAutoDevops < ActiveRecord::Base
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
def has_domain?
domain.present?
end
def variables def variables
variables = [] variables = []
variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain, public: true } if domain.present? variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain, public: true } if domain.present?
......
...@@ -146,7 +146,7 @@ class ProjectTeam ...@@ -146,7 +146,7 @@ class ProjectTeam
def member?(user, min_access_level = Gitlab::Access::GUEST) def member?(user, min_access_level = Gitlab::Access::GUEST)
return false unless user return false unless user
user.authorized_project?(project, min_access_level) max_member_access(user.id) >= min_access_level
end end
def human_max_access(user_id) def human_max_access(user_id)
......
...@@ -834,10 +834,6 @@ class Repository ...@@ -834,10 +834,6 @@ class Repository
} }
end end
def user_to_committer(user)
Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
def can_be_merged?(source_sha, target_branch) def can_be_merged?(source_sha, target_branch)
our_commit = rugged.branches[target_branch].target our_commit = rugged.branches[target_branch].target
their_commit = rugged.lookup(source_sha) their_commit = rugged.lookup(source_sha)
...@@ -859,54 +855,34 @@ class Repository ...@@ -859,54 +855,34 @@ class Repository
end end
def revert( def revert(
user, commit, branch_name, user, commit, branch_name, message,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project)
with_branch(
user,
branch_name,
start_branch_name: start_branch_name,
start_repository: start_project.repository.raw_repository) do |start_commit|
revert_tree_id = check_revert_content(commit, start_commit.sha)
unless revert_tree_id
raise Repository::CreateTreeError.new('Failed to revert commit')
end
committer = user_to_committer(user)
create_commit(message: commit.revert_message(user), with_cache_hooks do
author: committer, raw_repository.revert(
committer: committer, user: user,
tree: revert_tree_id, commit: commit.raw,
parents: [start_commit.sha]) branch_name: branch_name,
message: message,
start_branch_name: start_branch_name,
start_repository: start_project.repository.raw_repository
)
end end
end end
def cherry_pick( def cherry_pick(
user, commit, branch_name, user, commit, branch_name, message,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project)
with_branch(
user,
branch_name,
start_branch_name: start_branch_name,
start_repository: start_project.repository.raw_repository) do |start_commit|
cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) with_cache_hooks do
unless cherry_pick_tree_id raw_repository.cherry_pick(
raise Repository::CreateTreeError.new('Failed to cherry-pick commit') user: user,
end commit: commit.raw,
branch_name: branch_name,
committer = user_to_committer(user) message: message,
start_branch_name: start_branch_name,
create_commit(message: commit.cherry_pick_message(user), start_repository: start_project.repository.raw_repository
author: { )
email: commit.author_email,
name: commit.author_name,
time: commit.authored_date
},
committer: committer,
tree: cherry_pick_tree_id,
parents: [start_commit.sha])
end end
end end
...@@ -918,36 +894,6 @@ class Repository ...@@ -918,36 +894,6 @@ class Repository
end end
end end
def check_revert_content(target_commit, source_sha)
args = [target_commit.sha, source_sha]
args << { mainline: 1 } if target_commit.merge_commit?
revert_index = rugged.revert_commit(*args)
return false if revert_index.conflicts?
tree_id = revert_index.write_tree(rugged)
return false unless diff_exists?(source_sha, tree_id)
tree_id
end
def check_cherry_pick_content(target_commit, source_sha)
args = [target_commit.sha, source_sha]
args << 1 if target_commit.merge_commit?
cherry_pick_index = rugged.cherrypick_commit(*args)
return false if cherry_pick_index.conflicts?
tree_id = cherry_pick_index.write_tree(rugged)
return false unless diff_exists?(source_sha, tree_id)
tree_id
end
def diff_exists?(sha1, sha2)
rugged.diff(sha1, sha2).size > 0
end
def merged_to_root_ref?(branch_name) def merged_to_root_ref?(branch_name)
branch_commit = commit(branch_name) branch_commit = commit(branch_name)
root_ref_commit = commit(root_ref) root_ref_commit = commit(root_ref)
...@@ -983,6 +929,7 @@ class Repository ...@@ -983,6 +929,7 @@ class Repository
def empty_repo? def empty_repo?
!exists? || !has_visible_content? !exists? || !has_visible_content?
end end
cache_method :empty_repo?, memoize_only: true
def search_files_by_content(query, ref) def search_files_by_content(query, ref)
return [] if empty_repo? || query.blank? return [] if empty_repo? || query.blank?
......
...@@ -9,6 +9,7 @@ class GroupPolicy < BasePolicy ...@@ -9,6 +9,7 @@ class GroupPolicy < BasePolicy
condition(:has_access) { access_level != GroupMember::NO_ACCESS } condition(:has_access) { access_level != GroupMember::NO_ACCESS }
condition(:guest) { access_level >= GroupMember::GUEST } condition(:guest) { access_level >= GroupMember::GUEST }
condition(:developer) { access_level >= GroupMember::DEVELOPER }
condition(:owner) { access_level >= GroupMember::OWNER } condition(:owner) { access_level >= GroupMember::OWNER }
condition(:master) { access_level >= GroupMember::MASTER } condition(:master) { access_level >= GroupMember::MASTER }
condition(:reporter) { access_level >= GroupMember::REPORTER } condition(:reporter) { access_level >= GroupMember::REPORTER }
...@@ -33,11 +34,11 @@ class GroupPolicy < BasePolicy ...@@ -33,11 +34,11 @@ class GroupPolicy < BasePolicy
rule { admin } .enable :read_group rule { admin } .enable :read_group
rule { has_projects } .enable :read_group rule { has_projects } .enable :read_group
rule { developer }.enable :admin_milestones
rule { reporter }.enable :admin_label rule { reporter }.enable :admin_label
rule { master }.policy do rule { master }.policy do
enable :create_projects enable :create_projects
enable :admin_milestones
enable :admin_pipeline enable :admin_pipeline
enable :admin_build enable :admin_build
end end
......
...@@ -155,6 +155,7 @@ class ProjectPolicy < BasePolicy ...@@ -155,6 +155,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:developer_access) }.policy do rule { can?(:developer_access) }.policy do
enable :admin_merge_request enable :admin_merge_request
enable :admin_milestone
enable :update_merge_request enable :update_merge_request
enable :create_commit_status enable :create_commit_status
enable :update_commit_status enable :update_commit_status
...@@ -178,7 +179,6 @@ class ProjectPolicy < BasePolicy ...@@ -178,7 +179,6 @@ class ProjectPolicy < BasePolicy
enable :update_project_snippet enable :update_project_snippet
enable :update_environment enable :update_environment
enable :update_deployment enable :update_deployment
enable :admin_milestone
enable :admin_project_snippet enable :admin_project_snippet
enable :admin_project_member enable :admin_project_member
enable :admin_note enable :admin_note
......
...@@ -11,15 +11,19 @@ module Commits ...@@ -11,15 +11,19 @@ module Commits
def commit_change(action) def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action) raise NotImplementedError unless repository.respond_to?(action)
# rubocop:disable GitlabSecurity/PublicSend
message = @commit.public_send(:"#{action}_message", current_user)
# rubocop:disable GitlabSecurity/PublicSend # rubocop:disable GitlabSecurity/PublicSend
repository.public_send( repository.public_send(
action, action,
current_user, current_user,
@commit, @commit,
@branch_name, @branch_name,
message,
start_project: @start_project, start_project: @start_project,
start_branch_name: @start_branch) start_branch_name: @start_branch)
rescue Repository::CreateTreeError rescue Gitlab::Git::Repository::CreateTreeError
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
This #{@commit.change_type_title(current_user)} may already have been #{action.to_s.dasherize}ed, or a more recent commit may have updated some of its content." This #{@commit.change_type_title(current_user)} may already have been #{action.to_s.dasherize}ed, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg raise ChangeError, error_msg
......
...@@ -6,6 +6,8 @@ class DeleteMergedBranchesService < BaseService ...@@ -6,6 +6,8 @@ class DeleteMergedBranchesService < BaseService
def execute def execute
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project)
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37438
Gitlab::GitalyClient.allow_n_plus_1_calls do
branches = project.repository.branch_names branches = project.repository.branch_names
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests # Prevent deletion of branches relevant to open merge requests
...@@ -17,6 +19,7 @@ class DeleteMergedBranchesService < BaseService ...@@ -17,6 +19,7 @@ class DeleteMergedBranchesService < BaseService
DeleteBranchService.new(project, current_user).execute(branch) DeleteBranchService.new(project, current_user).execute(branch)
end end
end end
end
private private
......
...@@ -187,6 +187,7 @@ class IssuableBaseService < BaseService ...@@ -187,6 +187,7 @@ class IssuableBaseService < BaseService
after_create(issuable) after_create(issuable)
execute_hooks(issuable) execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees) invalidate_cache_counts(issuable, users: issuable.assignees)
issuable.update_project_counter_caches
end end
issuable issuable
...@@ -198,8 +199,6 @@ class IssuableBaseService < BaseService ...@@ -198,8 +199,6 @@ class IssuableBaseService < BaseService
def after_create(issuable) def after_create(issuable)
# To be overridden by subclasses # To be overridden by subclasses
issuable.update_project_counter_caches
end end
def before_update(issuable) def before_update(issuable)
...@@ -208,8 +207,6 @@ class IssuableBaseService < BaseService ...@@ -208,8 +207,6 @@ class IssuableBaseService < BaseService
def after_update(issuable) def after_update(issuable)
# To be overridden by subclasses # To be overridden by subclasses
issuable.update_project_counter_caches
end end
def update(issuable) def update(issuable)
...@@ -234,6 +231,10 @@ class IssuableBaseService < BaseService ...@@ -234,6 +231,10 @@ class IssuableBaseService < BaseService
before_update(issuable) before_update(issuable)
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.update_project_counter_caches?
if issuable.with_transaction_returning_status { issuable.save } if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field # We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
...@@ -255,6 +256,8 @@ class IssuableBaseService < BaseService ...@@ -255,6 +256,8 @@ class IssuableBaseService < BaseService
after_update(issuable) after_update(issuable)
issuable.create_new_cross_references!(current_user) issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update') execute_hooks(issuable, 'update')
issuable.update_project_counter_caches if update_project_counters
end end
end end
......
...@@ -29,6 +29,7 @@ module Issues ...@@ -29,6 +29,7 @@ module Issues
todo_service.close_issue(issue, current_user) todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close') execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees) invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
end end
issue issue
......
...@@ -14,6 +14,7 @@ module MergeRequests ...@@ -14,6 +14,7 @@ module MergeRequests
todo_service.close_merge_request(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
end end
merge_request merge_request
......
...@@ -13,8 +13,11 @@ module MergeRequests ...@@ -13,8 +13,11 @@ module MergeRequests
merge_request.source_branch = params[:source_branch] merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37439
Gitlab::GitalyClient.allow_n_plus_1_calls do
create(merge_request) create(merge_request)
end end
end
def before_create(merge_request) def before_create(merge_request)
# current_user (defined in BaseService) is not available within run_after_commit block # current_user (defined in BaseService) is not available within run_after_commit block
......
...@@ -4,7 +4,13 @@ module Notes ...@@ -4,7 +4,13 @@ module Notes
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
note = Notes::BuildService.new(project, current_user, params).execute note = Notes::BuildService.new(project, current_user, params).execute
return note unless note.valid?
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37440
note_valid = Gitlab::GitalyClient.allow_n_plus_1_calls do
note.valid?
end
return note unless note_valid
# We execute commands (extracted from `params[:note]`) on the noteable # We execute commands (extracted from `params[:note]`) on the noteable
# **before** we save the note because if the note consists of commands # **before** we save the note because if the note consists of commands
......
...@@ -2,6 +2,11 @@ module Projects ...@@ -2,6 +2,11 @@ module Projects
# Base class for the various service classes that count project data (e.g. # Base class for the various service classes that count project data (e.g.
# issues or forks). # issues or forks).
class CountService class CountService
# The version of the cache format. This should be bumped whenever the
# underlying logic changes. This removes the need for explicitly flushing
# all caches.
VERSION = 1
def initialize(project) def initialize(project)
@project = project @project = project
end end
...@@ -37,7 +42,7 @@ module Projects ...@@ -37,7 +42,7 @@ module Projects
end end
def cache_key def cache_key
['projects', @project.id, cache_key_name] ['projects', 'count_service', VERSION, @project.id, cache_key_name]
end end
end end
end end
...@@ -9,7 +9,7 @@ class AvatarUploader < GitlabUploader ...@@ -9,7 +9,7 @@ class AvatarUploader < GitlabUploader
end end
def exists? def exists?
model.avatar.file && model.avatar.file.exists? model.avatar.file && model.avatar.file.present?
end end
# We set move_to_store and move_to_cache to 'false' to prevent stealing # We set move_to_store and move_to_cache to 'false' to prevent stealing
......
...@@ -51,7 +51,7 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -51,7 +51,7 @@ class GitlabUploader < CarrierWave::Uploader::Base
end end
def exists? def exists?
file.try(:exists?) file.present?
end end
# Override this if you don't want to save files by default to the Rails.root directory # Override this if you don't want to save files by default to the Rails.root directory
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= image_tag @appearance.logo_url, class: 'appearance-logo-preview' = image_tag @appearance.logo_url, class: 'appearance-logo-preview'
- if @appearance.persisted? - if @appearance.persisted?
%br %br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo"
%hr %hr
= f.hidden_field :logo_cache = f.hidden_field :logo_cache
= f.file_field :logo, class: "" = f.file_field :logo, class: ""
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
= image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
- if @appearance.persisted? - if @appearance.persisted?
%br %br
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo"
%hr %hr
= f.hidden_field :header_logo_cache = f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: "" = f.file_field :header_logo, class: ""
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.row .row
.col-md-4 .col-md-4
.info-well .info-well
.well-segment.admin-well .well-segment.admin-well.admin-well-statistics
%h4 Statistics %h4 Statistics
%p %p
Forks Forks
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
= number_with_delimiter(User.active.count) = number_with_delimiter(User.active.count)
.col-md-4 .col-md-4
.info-well .info-well
.well-segment.admin-well .well-segment.admin-well.admin-well-features
%h4 Features %h4 Features
- sign_up = "Sign up" - sign_up = "Sign up"
%p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
...@@ -111,6 +111,10 @@ ...@@ -111,6 +111,10 @@
GitLab API GitLab API
%span.pull-right %span.pull-right
= API::API::version = API::API::version
%p
Gitaly
%span.pull-right
= Gitlab::GitalyClient.expected_server_version
- if Gitlab.config.pages.enabled - if Gitlab.config.pages.enabled
%p %p
GitLab Pages GitLab Pages
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
- @hooks.each do |hook| - @hooks.each do |hook|
%li %li
.controls .controls
= render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-small' = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-sm'
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm' = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm' = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
.monospace= hook.url .monospace= hook.url
......
<svg xmlns="http://www.w3.org/2000/svg" width="214" height="102" viewBox="0 0 214 102" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="b" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,27 C48,28.1045695 47.1045695,29 46,29 L2,29 C0.8954305,29 1.3527075e-16,28.1045695 0,27 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="a" width="102.1%" height="106.9%" x="-1%" y="-1.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="d" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="c" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="e" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<path id="h" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="g" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="j" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="i" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="l" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="k" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="n" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="m" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="p" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="o" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<path fill="#D6D4DE" d="M14,21 L62,21 C64.7614237,21 67,23.2385763 67,26 L67,112 C67,114.761424 64.7614237,117 62,117 L14,117 C11.2385763,117 9,114.761424 9,112 L9,26 C9,23.2385763 11.2385763,21 14,21 Z"/>
<g transform="translate(11 23)">
<path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#a)" xlink:href="#b"/>
<use fill="#F9F9F9" xlink:href="#b"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#c)" xlink:href="#d"/>
<use fill="#FEF0E8" xlink:href="#d"/>
<path fill="#FEE1D3" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/>
<path fill="#FDC4A8" d="M9,17 L17,17 C18.1045695,17 19,17.8954305 19,19 C19,20.1045695 18.1045695,21 17,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/>
<path fill="#FC6D26" d="M24,17 L32,17 C33.1045695,17 34,17.8954305 34,19 C34,20.1045695 33.1045695,21 32,21 L24,21 C22.8954305,21 22,20.1045695 22,19 C22,17.8954305 22.8954305,17 24,17 Z"/>
</g>
</g>
<path fill="#D6D4DE" d="M148,26 L196,26 C198.761424,26 201,28.2385763 201,31 L201,117 C201,119.761424 198.761424,122 196,122 L148,122 C145.238576,122 143,119.761424 143,117 L143,31 C143,28.2385763 145.238576,26 148,26 Z"/>
<g transform="translate(145 28)">
<mask id="f" fill="white">
<use xlink:href="#e"/>
</mask>
<use fill="#FFFFFF" xlink:href="#e"/>
<path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" mask="url(#f)"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#g)" xlink:href="#h"/>
<use fill="#F9F9F9" xlink:href="#h"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#i)" xlink:href="#j"/>
<use fill="#FEF0E8" xlink:href="#j"/>
<path fill="#FEE1D3" d="M9 8L33 8C34.1045695 8 35 8.8954305 35 10 35 11.1045695 34.1045695 12 33 12L9 12C7.8954305 12 7 11.1045695 7 10 7 8.8954305 7.8954305 8 9 8zM9 17L13 17C14.1045695 17 15 17.8954305 15 19 15 20.1045695 14.1045695 21 13 21L9 21C7.8954305 21 7 20.1045695 7 19 7 17.8954305 7.8954305 17 9 17z"/>
<path fill="#FC6D26" d="M20,17 L24,17 C25.1045695,17 26,17.8954305 26,19 C26,20.1045695 25.1045695,21 24,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/>
<path fill="#FDC4A8" d="M31,17 L35,17 C36.1045695,17 37,17.8954305 37,19 C37,20.1045695 36.1045695,21 35,21 L31,21 C29.8954305,21 29,20.1045695 29,19 C29,17.8954305 29.8954305,17 31,17 Z"/>
</g>
</g>
<path fill="#D6D4DE" d="M81,14 L129,14 C131.761424,14 134,16.2385763 134,19 L134,105 C134,107.761424 131.761424,110 129,110 L81,110 C78.2385763,110 76,107.761424 76,105 L76,19 C76,16.2385763 78.2385763,14 81,14 Z"/>
<g transform="translate(78 16)">
<path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#k)" xlink:href="#l"/>
<use fill="#EFEDF8" xlink:href="#l"/>
<path fill="#E1DBF1" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/>
<path fill="#6B4FBB" d="M9,17 L13,17 C14.1045695,17 15,17.8954305 15,19 C15,20.1045695 14.1045695,21 13,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/>
<path fill="#C3B8E3" d="M20,17 L28,17 C29.1045695,17 30,17.8954305 30,19 C30,20.1045695 29.1045695,21 28,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#m)" xlink:href="#n"/>
<use fill="#F9F9F9" xlink:href="#n"/>
</g>
<g transform="translate(5 74)">
<rect width="34" height="4" x="7" y="7" fill="#E1DBF1" rx="2"/>
<use fill="black" filter="url(#o)" xlink:href="#p"/>
<use fill="#F9F9F9" xlink:href="#p"/>
</g>
<path fill="#6B4FBB" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/>
</g>
</g>
</svg>
...@@ -117,20 +117,6 @@ ...@@ -117,20 +117,6 @@
= link_to project_boards_path(@project), title: boards_link_text do = link_to project_boards_path(@project), title: boards_link_text do
%span %span
= boards_link_text = boards_link_text
.feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } }
.feature-highlight-popover-content
= render 'feature_highlight/issue_boards.svg'
.feature-highlight-popover-sub-content
%span= _('Use')
= link_to 'Issue Boards', project_boards_path(@project)
%span= _('to create customized software development workflows like')
%strong= _('Scrum')
%span= _('or')
%strong= _('Kanban')
%hr
%button.btn-link.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it! Don't show this again")
= custom_icon('thumbs_up')
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= link_to project_labels_path(@project), title: 'Labels' do = link_to project_labels_path(@project), title: 'Labels' do
......
...@@ -74,7 +74,9 @@ ...@@ -74,7 +74,9 @@
%h4.prepend-top-0.warning-title %h4.prepend-top-0.warning-title
Change username Change username
%p %p
Changing your username will change path to all personal projects! Changing your username can have unintended side effects.
= succeed '.' do
= link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
.col-lg-8 .col-lg-8
= form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f| = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group .form-group
......
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%div{ class: container_class } %div{ class: container_class }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
.prepend-top-20 .prepend-top-20
.empty_wrapper .empty_wrapper
%h3.page-title-empty %h3.page-title-empty
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
- if issue.milestone - if issue.milestone
%span.issuable-milestone.hidden-xs %span.issuable-milestone.hidden-xs
&nbsp; &nbsp;
= link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(issue.milestone) } do
= icon('clock-o') = icon('clock-o')
= issue.milestone.title = issue.milestone.title
- if issue.due_date - if issue.due_date
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
- if merge_request.milestone - if merge_request.milestone
%span.issuable-milestone.hidden-xs %span.issuable-milestone.hidden-xs
&nbsp; &nbsp;
= link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(merge_request.milestone) } do
= icon('clock-o') = icon('clock-o')
= merge_request.milestone.title = merge_request.milestone.title
- if merge_request.target_project.default_branch != merge_request.target_branch - if merge_request.target_project.default_branch != merge_request.target_branch
......
...@@ -62,7 +62,10 @@ ...@@ -62,7 +62,10 @@
":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
%span.line-resolve-btn.is-disabled{ type: "button", %span.line-resolve-btn.is-disabled{ type: "button",
":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
= render "shared/icons/icon_status_success.svg" %template{ 'v-if' => 'resolvedDiscussionCount === discussionCount' }
= render 'shared/icons/icon_status_success_solid.svg'
%template{ 'v-else' => '' }
= render 'shared/icons/icon_resolve_discussion.svg'
%span.line-resolve-text %span.line-resolve-text
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
= render "discussions/new_issue_for_all_discussions", merge_request: @merge_request = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request
......
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
New project New project
- if import_sources_enabled? - if import_sources_enabled?
%p %p
Create or Import your project from popular Git services A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
%p
All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
.col-lg-9.js-toggle-container .col-lg-9.js-toggle-container
= form_for @project, html: { class: 'new_project' } do |f| = form_for @project, html: { class: 'new_project' } do |f|
.create-project-options .create-project-options
......
...@@ -3,11 +3,15 @@ ...@@ -3,11 +3,15 @@
= form_for @project, url: project_pipelines_settings_path(@project) do |f| = form_for @project, url: project_pipelines_settings_path(@project) do |f|
%fieldset.builds-feature %fieldset.builds-feature
.form-group .form-group
%p Pipelines need to have Auto DevOps enabled or have a .gitlab-ci.yml configured before you can begin using Continuous Integration and Delivery.
%h5 Auto DevOps (Beta) %h5 Auto DevOps (Beta)
%p %p
Auto DevOps will automatically build, test, and deploy your application based on a predefined Continious Integration and Delivery configuration. Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.
This will happen starting with the next event (e.g.: push) that occurs to the project.
= link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md') = link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
- message = auto_devops_warning_message(@project)
- if message
%p.settings-message.text-center
= message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form| = f.fields_for :auto_devops_attributes, @auto_devops do |form|
.radio .radio
= form.label :enabled_true do = form.label :enabled_true do
...@@ -15,26 +19,24 @@ ...@@ -15,26 +19,24 @@
%strong Enable Auto DevOps %strong Enable Auto DevOps
%br %br
%span.descr %span.descr
The Auto DevOps pipeline configuration will be used when there is no .gitlab-ci.yml The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project.
in the project.
.radio .radio
= form.label :enabled_false do = form.label :enabled_false do
= form.radio_button :enabled, 'false' = form.radio_button :enabled, 'false'
%strong Disable Auto DevOps %strong Disable Auto DevOps
%br %br
%span.descr %span.descr
A specific .gitlab-ci.yml file needs to be specified before you can begin using Continious Integration and Delivery. An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continious Integration and Delivery.
.radio .radio
= form.label :enabled do = form.label :enabled_nil do
= form.radio_button :enabled, nil = form.radio_button :enabled, ''
%strong %strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'})
Instance default (status: #{current_application_settings.auto_devops_enabled?})
%br %br
%span.descr %span.descr
Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific .gitlab-ci.yml file specified. Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
%br %br
%p %p
Define a domain used by Auto DevOps to deploy towards, this is required for deploys to succeed. You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com' = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
%hr %hr
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Update your CI/CD configuration, like job timeout. Update your CI/CD configuration, like job timeout or Auto DevOps.
.settings-content.no-animate{ class: ('expanded' if expanded) } .settings-content.no-animate{ class: ('expanded' if expanded) }
= render 'projects/pipelines_settings/show' = render 'projects/pipelines_settings/show'
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%span.append-right-10.inline %span.append-right-10.inline
SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
= link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm' = link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm'
= render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-small' = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-sm'
= link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do
%span.sr-only Remove %span.sr-only Remove
= icon('trash') = icon('trash')
...@@ -22,11 +22,9 @@ ...@@ -22,11 +22,9 @@
- if @group.persisted? - if @group.persisted?
.alert.alert-warning.prepend-top-10 .alert.alert-warning.prepend-top-10
%ul Changing group path can have unintended side effects.
%li Changing group path can have unintended side effects. = succeed '.' do
%li Renaming group path will rename directory for all related projects = link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank'
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
.form-group.group-name-holder .form-group.group-name-holder
= f.label :name, class: 'control-label' do = f.label :name, class: 'control-label' do
......
- dropdown_toggle_text = @ref || @project.default_branch - dropdown_toggle_text = @ref || @project.default_branch
= form_tag nil, method: :get, style: { display: 'none' }, class: "project-refs-target-form" do = form_tag nil, method: :get, class: "project-refs-form project-refs-target-form" do
= hidden_field_tag :destination, destination = hidden_field_tag :destination, destination
- if defined?(path) - if defined?(path)
= hidden_field_tag :path, path = hidden_field_tag :path, path
...@@ -7,14 +7,10 @@ ...@@ -7,14 +7,10 @@
= hidden_field_tag key, value, id: nil = hidden_field_tag key, value, id: nil
.dropdown .dropdown
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" } = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" }
%ul.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
%li
= dropdown_title _("Create a new branch") = dropdown_title _("Create a new branch")
%li
= dropdown_input _("Create a new branch") = dropdown_input _("Create a new branch")
%li
= dropdown_title _("Select existing branch"), options: {close: false} = dropdown_title _("Select existing branch"), options: {close: false}
%li
= dropdown_filter _("Search branches and tags") = dropdown_filter _("Search branches and tags")
= dropdown_content = dropdown_content
= dropdown_loading = dropdown_loading
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
%span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } %span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
{{ list.issuesSize }} {{ list.issuesSize }}
- if can?(current_user, :admin_list, current_board_parent) - if can?(current_user, :admin_list, current_board_parent)
%button.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button", %button.issue-count-badge-add-button.btn.btn-sm.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button",
"@click" => "showNewIssueForm", "@click" => "showNewIssueForm",
"v-if" => 'list.type !== "closed"', "v-if" => 'list.type !== "closed"',
"aria-label" => "New issue", "aria-label" => "New issue",
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.104 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.486.5l.138.137a1 1 0 0 1 .28.87L8.33 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></svg>
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= icon('clock-o', 'aria-hidden': 'true') = icon('clock-o', 'aria-hidden': 'true')
%span.milestone-title %span.milestone-title
- if issuable.milestone - if issuable.milestone
%span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_remaining_days(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } } %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } }
= issuable.milestone.title = issuable.milestone.title
- else - else
None None
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed .value.hide-collapsed
- if issuable.milestone - if issuable.milestone
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 } = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 }
- else - else
%span.no-value None %span.no-value None
......
...@@ -26,6 +26,6 @@ ...@@ -26,6 +26,6 @@
%span.assignee-icon %span.assignee-icon
- assignees.each do |assignee| - assignees.each do |assignee|
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }), = link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }),
class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(assignee, 16), class: "avatar s16", alt: '') - image_tag(avatar_icon(assignee, 16), class: "avatar s16", alt: '')
---
title: Add an API endpoint to determine the forks of a project
merge_request:
author:
type: added
---
title: Fix errors thrown in merge request widget with external CI service/integration
merge_request:
author:
type: fixed
---
title: Fixes project denial of service via gitmodules using Extended ASCII.
merge_request: 14301
author:
type: fixed
---
title: made read-only APIs for public merge requests available without authentication
merge_request: 13291
author: haseebeqx
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment