Merge remote-tracking branch 'origin/master' into 18608-lock-issues
This commit is contained in:
commit
ff7b545c8e
|
@ -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:
|
||||||
# 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:
|
||||||
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:
|
||||||
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:
|
||||||
|
|
||||||
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:
|
||||||
<<: *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
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
3.0.0
|
3.1.0
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -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
|
||||||
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
|
||||||
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)
|
||||||
|
|
6
LICENSE
6
LICENSE
|
@ -1,5 +1,7 @@
|
||||||
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
|
||||||
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.
|
|
@ -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 @@
|
||||||
|
|
||||||
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 @@
|
||||||
|
|
||||||
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: {
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
|
@ -1,57 +0,0 @@
|
||||||
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);
|
|
||||||
};
|
|
|
@ -1,12 +0,0 @@
|
||||||
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));
|
|
|
@ -1,68 +1,61 @@
|
||||||
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: # }
|
||||||
/*
|
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day
|
||||||
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
|
* or week length.
|
||||||
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day
|
*/
|
||||||
* or week length.
|
|
||||||
*/
|
|
||||||
parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
|
|
||||||
const DAYS_PER_WEEK = daysPerWeek;
|
|
||||||
const HOURS_PER_DAY = hoursPerDay;
|
|
||||||
const MINUTES_PER_HOUR = 60;
|
|
||||||
const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
|
|
||||||
const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
|
|
||||||
|
|
||||||
const timePeriodConstraints = {
|
export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
|
||||||
weeks: MINUTES_PER_WEEK,
|
const DAYS_PER_WEEK = daysPerWeek;
|
||||||
days: MINUTES_PER_DAY,
|
const HOURS_PER_DAY = hoursPerDay;
|
||||||
hours: MINUTES_PER_HOUR,
|
const MINUTES_PER_HOUR = 60;
|
||||||
minutes: 1,
|
const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
|
||||||
};
|
const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
|
||||||
|
|
||||||
let unorderedMinutes = prettyTime.secondsToMinutes(seconds);
|
const timePeriodConstraints = {
|
||||||
|
weeks: MINUTES_PER_WEEK,
|
||||||
return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
|
days: MINUTES_PER_DAY,
|
||||||
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
|
hours: MINUTES_PER_HOUR,
|
||||||
|
minutes: 1,
|
||||||
unorderedMinutes -= (periodCount * minutesPerPeriod);
|
|
||||||
|
|
||||||
return periodCount;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Accepts a timeObject and returns a condensed string representation of it
|
|
||||||
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
|
|
||||||
*/
|
|
||||||
|
|
||||||
stringifyTime(timeObject) {
|
|
||||||
const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
|
|
||||||
const isNonZero = !!unitValue;
|
|
||||||
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
|
|
||||||
}, '').trim();
|
|
||||||
return reducedTime.length ? reducedTime : '0m';
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbreviateTime(timeStr) {
|
|
||||||
return timeStr.split(' ')
|
|
||||||
.filter(unitStr => unitStr.charAt(0) !== '0')[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
secondsToMinutes(seconds) {
|
|
||||||
return Math.abs(seconds / 60);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
})(window.gl || (window.gl = {}));
|
|
||||||
|
let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
|
||||||
|
|
||||||
|
return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
|
||||||
|
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
|
||||||
|
|
||||||
|
unorderedMinutes -= (periodCount * minutesPerPeriod);
|
||||||
|
|
||||||
|
return periodCount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function stringifyTime(timeObject) {
|
||||||
|
const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
|
||||||
|
const isNonZero = !!unitValue;
|
||||||
|
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
|
||||||
|
}, '').trim();
|
||||||
|
return reducedTime.length ? reducedTime : '0m';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function abbreviateTime(timeStr) {
|
||||||
|
return timeStr.split(' ')
|
||||||
|
.filter(unitStr => unitStr.charAt(0) !== '0')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
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 {
|
||||||
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() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* 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';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
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 {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
abbreviateTime(timeStr) {
|
abbreviateTime(timeStr) {
|
||||||
return gl.utils.prettyTime.abbreviateTime(timeStr);
|
return abbreviateTime(timeStr);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
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 {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 {
|
||||||
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 {
|
||||||
<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 {
|
||||||
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 {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 {
|
||||||
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 {
|
||||||
<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 {
|
||||||
<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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
},
|
},
|
||||||
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 {
|
||||||
<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 {
|
||||||
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 {
|
||||||
</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 {
|
||||||
</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 {
|
||||||
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 {
|
||||||
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 @@
|
||||||
@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 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 @@
|
||||||
.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 @@
|
||||||
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 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
.fa {
|
.fa {
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
.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 @@
|
||||||
.media-body {
|
.media-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-body-wrap {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
.info-well {
|
||||||
|
.admin-well-statistics,
|
||||||
|
.admin-well-features {
|
||||||
|
padding-bottom: 46px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: $gray-darkest;
|
fill: currentColor;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 @@
|
||||||
|
|
||||||
.tree-content-holder {
|
.tree-content-holder {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-height: 100vh;
|
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 @@
|
||||||
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 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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
|
||||||
@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
|
||||||
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,10 +15,14 @@ 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|
|
||||||
|
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
||||||
|
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
||||||
|
end
|
||||||
|
|
||||||
@max_commits = @branches.reduce(0) do |memo, branch|
|
render
|
||||||
diverging_commit_counts = repository.diverging_commit_counts(branch)
|
|
||||||
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
format.json do
|
format.json do
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
@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
|
||||||
.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,7 +10,10 @@ 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
|
||||||
|
|
||||||
render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
|
# 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") }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def diff_for_path
|
def diff_for_path
|
||||||
|
|
|
@ -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
|
||||||
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,19 +8,24 @@ class Projects::NetworkController < Projects::ApplicationController
|
||||||
before_action :assign_commit
|
before_action :assign_commit
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@url = project_network_path(@project, @ref, @options.merge(format: :json))
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602
|
||||||
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
|
@url = project_network_path(@project, @ref, @options.merge(format: :json))
|
||||||
|
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
if @options[:extended_sha1] && !@commit
|
if @options[:extended_sha1] && !@commit
|
||||||
flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
|
flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
format.json do
|
||||||
|
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
render
|
||||||
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -51,13 +51,16 @@ 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
|
||||||
file = @path ? File.join(@path, content.name) : content.name
|
@logs = Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
last_commit = @repo.last_commit_for_path(@commit.id, file)
|
contents[@offset, @limit].to_a.map do |content|
|
||||||
{
|
file = @path ? File.join(@path, content.name) : content.name
|
||||||
file_name: content.name,
|
last_commit = @repo.last_commit_for_path(@commit.id, file)
|
||||||
commit: last_commit
|
{
|
||||||
}
|
file_name: content.name,
|
||||||
|
commit: last_commit
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
offset = (@offset + @limit)
|
offset = (@offset + @limit)
|
||||||
|
|
|
@ -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,7 +13,10 @@ 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
|
||||||
super
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37434
|
||||||
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
|
super
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
namespace = @project.namespace.full_path
|
namespace = @project.namespace.full_path
|
||||||
end
|
end
|
||||||
|
|
||||||
[
|
begin
|
||||||
namespace_project_path(namespace, base),
|
[
|
||||||
namespace_project_tree_path(namespace, base, commit)
|
namespace_project_path(namespace, base),
|
||||||
]
|
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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,8 +415,11 @@ class MergeRequest < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_merge_request_diff
|
def create_merge_request_diff
|
||||||
merge_request_diffs.create
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37435
|
||||||
reload_merge_request_diff
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
|
merge_request_diffs.create
|
||||||
|
reload_merge_request_diff
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload_merge_request_diff
|
def reload_merge_request_diff
|
||||||
|
@ -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
|
||||||
# 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
|
||||||
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,8 +61,11 @@ module Network
|
||||||
@reserved[i] = []
|
@reserved[i] = []
|
||||||
end
|
end
|
||||||
|
|
||||||
commits_sort_by_ref.each do |commit|
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37436
|
||||||
place_chain(commit)
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
|
commits_sort_by_ref.each do |commit|
|
||||||
|
place_chain(commit)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# find parent spaces for not overlap lines
|
# find parent spaces for not overlap lines
|
||||||
|
|
|
@ -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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
}
|
}
|
||||||
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
|
||||||
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)
|
with_cache_hooks do
|
||||||
unless revert_tree_id
|
raw_repository.revert(
|
||||||
raise Repository::CreateTreeError.new('Failed to revert 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.revert_message(user),
|
start_repository: start_project.repository.raw_repository
|
||||||
author: committer,
|
)
|
||||||
committer: committer,
|
|
||||||
tree: revert_tree_id,
|
|
||||||
parents: [start_commit.sha])
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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,15 +6,18 @@ 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)
|
||||||
|
|
||||||
branches = project.repository.branch_names
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37438
|
||||||
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
# Prevent deletion of branches relevant to open merge requests
|
branches = project.repository.branch_names
|
||||||
branches -= merge_request_branch_names
|
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
|
||||||
# Prevent deletion of protected branches
|
# Prevent deletion of branches relevant to open merge requests
|
||||||
branches = branches.reject { |branch| project.protected_for?(branch) }
|
branches -= merge_request_branch_names
|
||||||
|
# Prevent deletion of protected branches
|
||||||
|
branches = branches.reject { |branch| project.protected_for?(branch) }
|
||||||
|
|
||||||
branches.each do |branch|
|
branches.each do |branch|
|
||||||
DeleteBranchService.new(project, current_user).execute(branch)
|
DeleteBranchService.new(project, current_user).execute(branch)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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,7 +13,10 @@ 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)
|
||||||
|
|
||||||
create(merge_request)
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37439
|
||||||
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||||
|
create(merge_request)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def before_create(merge_request)
|
def before_create(merge_request)
|
||||||
|
|
|
@ -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
|
||||||
# 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
|
||||||
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
|
||||||
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
|
||||||
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 @@
|
||||||
= 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 @@
|
||||||
= 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 @@
|
||||||
.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 @@
|
||||||
= 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 @@
|
||||||
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 @@
|
||||||
- @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
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
<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>
|
|
Before Width: | Height: | Size: 8.8 KiB |
|
@ -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 @@
|
||||||
%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 @@
|
||||||
|
|
||||||
- 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 @@
|
||||||
- if issue.milestone
|
- if issue.milestone
|
||||||
%span.issuable-milestone.hidden-xs
|
%span.issuable-milestone.hidden-xs
|
||||||
|
|
||||||
= 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 @@
|
||||||
- if merge_request.milestone
|
- if merge_request.milestone
|
||||||
%span.issuable-milestone.hidden-xs
|
%span.issuable-milestone.hidden-xs
|
||||||
|
|
||||||
= 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 @@
|
||||||
":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 @@
|
||||||
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 @@
|
||||||
= 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 @@
|
||||||
%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 @@
|
||||||
%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 @@
|
||||||
%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 @@
|
||||||
|
|
||||||
- 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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- 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 @@
|
||||||
= 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")
|
= dropdown_input _("Create a new branch")
|
||||||
%li
|
= dropdown_title _("Select existing branch"), options: {close: false}
|
||||||
= dropdown_input _("Create a new branch")
|
= dropdown_filter _("Search branches and tags")
|
||||||
%li
|
= dropdown_content
|
||||||
= dropdown_title _("Select existing branch"), options: {close: false}
|
= dropdown_loading
|
||||||
%li
|
|
||||||
= dropdown_filter _("Search branches and tags")
|
|
||||||
= dropdown_content
|
|
||||||
= dropdown_loading
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<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>
|
|
Before Width: | Height: | Size: 368 B |
|
@ -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 @@
|
||||||
= 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 @@
|
||||||
|
|
||||||
%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: '')
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add an API endpoint to determine the forks of a project
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix errors thrown in merge request widget with external CI service/integration
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fixes project denial of service via gitmodules using Extended ASCII.
|
||||||
|
merge_request: 14301
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: made read-only APIs for public merge requests available without authentication
|
||||||
|
merge_request: 13291
|
||||||
|
author: haseebeqx
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue