Commit 930ca2d5 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-support-bitbucket-server-import

parents a6dfed3a 98eccfc4
...@@ -2,6 +2,13 @@ ...@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 11.0.4 (2018-07-17)
### Security (1 change)
- Fix symlink vulnerability in project import.
## 11.0.3 (2018-07-05) ## 11.0.3 (2018-07-05)
### Fixed (14 changes, 1 of them is from the community) ### Fixed (14 changes, 1 of them is from the community)
...@@ -295,6 +302,14 @@ entry. ...@@ -295,6 +302,14 @@ entry.
- Workhorse to send raw diff and patch for commits. - Workhorse to send raw diff and patch for commits.
## 10.8.6 (2018-07-17)
### Security (2 changes)
- Fix symlink vulnerability in project import.
- Merge branch 'fix-mr-widget-border' into 'master'.
## 10.8.5 (2018-06-21) ## 10.8.5 (2018-06-21)
### Security (5 changes) ### Security (5 changes)
...@@ -524,6 +539,13 @@ entry. ...@@ -524,6 +539,13 @@ entry.
- Gitaly handles repository forks by default. - Gitaly handles repository forks by default.
## 10.7.7 (2018-07-17)
### Security (1 change)
- Fix symlink vulnerability in project import.
## 10.7.6 (2018-06-21) ## 10.7.6 (2018-06-21)
### Security (6 changes) ### Security (6 changes)
......
...@@ -53,4 +53,8 @@ export default class Autosave { ...@@ -53,4 +53,8 @@ export default class Autosave {
return window.localStorage.removeItem(this.key); return window.localStorage.removeItem(this.key);
} }
dispose() {
this.field.off('input');
}
} }
...@@ -162,8 +162,10 @@ export default class Clusters { ...@@ -162,8 +162,10 @@ export default class Clusters {
if (type === 'password') { if (type === 'password') {
this.tokenField.setAttribute('type', 'text'); this.tokenField.setAttribute('type', 'text');
this.showTokenButton.textContent = s__('ClusterIntegration|Hide');
} else { } else {
this.tokenField.setAttribute('type', 'password'); this.tokenField.setAttribute('type', 'password');
this.showTokenButton.textContent = s__('ClusterIntegration|Show');
} }
} }
......
...@@ -77,7 +77,8 @@ export default { ...@@ -77,7 +77,8 @@ export default {
diffViewType: state => state.diffs.diffViewType, diffViewType: state => state.diffs.diffViewType,
diffFiles: state => state.diffs.diffFiles, diffFiles: state => state.diffs.diffFiles,
}), }),
...mapGetters(['isLoggedIn', 'discussionsByLineCode']), ...mapGetters(['isLoggedIn']),
...mapGetters('diffs', ['discussionsByLineCode']),
lineHref() { lineHref() {
return this.lineCode ? `#${this.lineCode}` : '#'; return this.lineCode ? `#${this.lineCode}` : '#';
}, },
...@@ -189,7 +190,6 @@ export default { ...@@ -189,7 +190,6 @@ export default {
</button> </button>
<a <a
v-if="lineNumber" v-if="lineNumber"
v-once
:data-linenumber="lineNumber" :data-linenumber="lineNumber"
:href="lineHref" :href="lineHref"
> >
......
<script> <script>
import $ from 'jquery';
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import noteForm from '../../notes/components/note_form.vue'; import noteForm from '../../notes/components/note_form.vue';
import { getNoteFormData } from '../store/utils'; import { getNoteFormData } from '../store/utils';
import Autosave from '../../autosave'; import autosave from '../../notes/mixins/autosave';
import { DIFF_NOTE_TYPE, NOTE_TYPE } from '../constants'; import { DIFF_NOTE_TYPE } from '../constants';
export default { export default {
components: { components: {
noteForm, noteForm,
}, },
mixins: [autosave],
props: { props: {
diffFileHash: { diffFileHash: {
type: String, type: String,
...@@ -41,28 +41,35 @@ export default { ...@@ -41,28 +41,35 @@ export default {
}, },
mounted() { mounted() {
if (this.isLoggedIn) { if (this.isLoggedIn) {
const noteableData = this.getNoteableData;
const keys = [ const keys = [
NOTE_TYPE, this.noteableData.diff_head_sha,
this.noteableType,
noteableData.id,
noteableData.diff_head_sha,
DIFF_NOTE_TYPE, DIFF_NOTE_TYPE,
noteableData.source_project_id, this.noteableData.source_project_id,
this.line.lineCode, this.line.lineCode,
]; ];
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), keys); this.initAutoSave(this.noteableData, keys);
} }
}, },
methods: { methods: {
...mapActions('diffs', ['cancelCommentForm']), ...mapActions('diffs', ['cancelCommentForm']),
...mapActions(['saveNote', 'refetchDiscussionById']), ...mapActions(['saveNote', 'refetchDiscussionById']),
handleCancelCommentForm() { handleCancelCommentForm(shouldConfirm, isDirty) {
this.autosave.reset(); if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
// eslint-disable-next-line no-alert
if (!window.confirm(msg)) {
return;
}
}
this.cancelCommentForm({ this.cancelCommentForm({
lineCode: this.line.lineCode, lineCode: this.line.lineCode,
}); });
this.$nextTick(() => {
this.resetAutoSave();
});
}, },
handleSaveNote(note) { handleSaveNote(note) {
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash); const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
......
...@@ -26,13 +26,16 @@ export default { ...@@ -26,13 +26,16 @@ export default {
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
...mapGetters(['discussionsByLineCode']), ...mapGetters('diffs', ['discussionsByLineCode']),
discussions() { discussions() {
return this.discussionsByLineCode[this.line.lineCode] || []; return this.discussionsByLineCode[this.line.lineCode] || [];
}, },
className() { className() {
return this.discussions.length ? '' : 'js-temp-notes-holder'; return this.discussions.length ? '' : 'js-temp-notes-holder';
}, },
hasCommentForm() {
return this.diffLineCommentForms[this.line.lineCode];
},
}, },
}; };
</script> </script>
...@@ -53,7 +56,7 @@ export default { ...@@ -53,7 +56,7 @@ export default {
:discussions="discussions" :discussions="discussions"
/> />
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[line.lineCode]" v-if="hasCommentForm"
:diff-file-hash="diffFileHash" :diff-file-hash="diffFileHash"
:line="line" :line="line"
:note-target-line="line" :note-target-line="line"
......
...@@ -101,7 +101,6 @@ export default { ...@@ -101,7 +101,6 @@ export default {
class="diff-line-num new_line" class="diff-line-num new_line"
/> />
<td <td
v-once
:class="line.type" :class="line.type"
class="line_content" class="line_content"
v-html="line.richText" v-html="line.richText"
......
...@@ -20,8 +20,7 @@ export default { ...@@ -20,8 +20,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters('diffs', ['commitId']), ...mapGetters('diffs', ['commitId', 'discussionsByLineCode']),
...mapGetters(['discussionsByLineCode']),
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
......
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
...mapGetters(['discussionsByLineCode']), ...mapGetters('diffs', ['discussionsByLineCode']),
leftLineCode() { leftLineCode() {
return this.line.left.lineCode; return this.line.left.lineCode;
}, },
......
...@@ -119,7 +119,6 @@ export default { ...@@ -119,7 +119,6 @@ export default {
class="diff-line-num old_line" class="diff-line-num old_line"
/> />
<td <td
v-once
:id="line.left.lineCode" :id="line.left.lineCode"
:class="parallelViewLeftLineType" :class="parallelViewLeftLineType"
class="line_content parallel left-side" class="line_content parallel left-side"
...@@ -140,7 +139,6 @@ export default { ...@@ -140,7 +139,6 @@ export default {
class="diff-line-num new_line" class="diff-line-num new_line"
/> />
<td <td
v-once
:id="line.right.lineCode" :id="line.right.lineCode"
:class="line.right.type" :class="line.right.type"
class="line_content parallel right-side" class="line_content parallel right-side"
......
...@@ -21,8 +21,7 @@ export default { ...@@ -21,8 +21,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters('diffs', ['commitId']), ...mapGetters('diffs', ['commitId', 'discussionsByLineCode']),
...mapGetters(['discussionsByLineCode']),
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
......
import _ from 'underscore'; import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants';
import { getDiffRefsByLineCode } from './utils';
export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE; export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
...@@ -56,6 +58,44 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) = ...@@ -56,6 +58,44 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash), discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
) || []; ) || [];
/**
* Returns an Object with discussions by their diff line code
* To avoid rendering outdated discussions on the Changes tab we should do a bunch of SHA
* comparisions. `note.position.formatter` have the current version diff refs but
* `note.original_position.formatter` will have the first version's diff refs.
* If line diff refs matches with one of them, we should render it as a discussion on Changes tab.
*
* @param {Object} diff
* @returns {Array}
*/
export const discussionsByLineCode = (state, getters, rootState, rootGetters) => {
const diffRefsByLineCode = getDiffRefsByLineCode(state.diffFiles);
return rootGetters.discussions.reduce((acc, note) => {
const isDiffDiscussion = note.diff_discussion;
const hasLineCode = note.line_code;
const isResolvable = note.resolvable;
const diffRefs = diffRefsByLineCode[note.line_code];
if (isDiffDiscussion && hasLineCode && isResolvable && diffRefs) {
const refs = convertObjectPropsToCamelCase(note.position.formatter);
const originalRefs = convertObjectPropsToCamelCase(note.original_position.formatter);
if (_.isEqual(refs, diffRefs) || _.isEqual(originalRefs, diffRefs)) {
const lineCode = note.line_code;
if (acc[lineCode]) {
acc[lineCode].push(note);
} else {
acc[lineCode] = [note];
}
}
}
return acc;
}, {});
};
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests // prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
export const getDiffFileByHash = state => fileHash => export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.fileHash === fileHash); state.diffFiles.find(file => file.fileHash === fileHash);
......
...@@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) { ...@@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine; return parsedLine;
} }
export function getDiffRefsByLineCode(diffFiles) {
return diffFiles.reduce((acc, diffFile) => {
const { baseSha, headSha, startSha } = diffFile.diffRefs;
const { newPath, oldPath } = diffFile;
// We can only use highlightedDiffLines to create the map of diff lines because
// highlightedDiffLines will also include every parallel diff line in it.
if (diffFile.highlightedDiffLines) {
diffFile.highlightedDiffLines.forEach(line => {
const { lineCode, oldLine, newLine } = line;
if (lineCode) {
acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine };
}
});
}
return acc;
}, {});
}
...@@ -125,6 +125,7 @@ export default { ...@@ -125,6 +125,7 @@ export default {
:class="flagOrientation" :class="flagOrientation"
class="prometheus-graph-flag popover" class="prometheus-graph-flag popover"
> >
<div class="arrow-shadow"></div>
<div class="arrow"></div> <div class="arrow"></div>
<div class="popover-title"> <div class="popover-title">
<h5 v-if="deploymentFlagData"> <h5 v-if="deploymentFlagData">
......
import _ from 'underscore'; import _ from 'underscore';
function sortMetrics(metrics) { function sortMetrics(metrics) {
return _.chain(metrics).sortBy('title').sortBy('weight').value(); return _.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
} }
function normalizeMetrics(metrics) { function normalizeMetrics(metrics) {
...@@ -39,7 +42,9 @@ export default class MonitoringStore { ...@@ -39,7 +42,9 @@ export default class MonitoringStore {
} }
storeEnvironmentsData(environmentsData = []) { storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData; this.environmentsData = environmentsData.filter(
environment => !!environment.latest.last_deployment,
);
} }
getMetricsCount() { getMetricsCount() {
......
...@@ -7,7 +7,7 @@ import issuableStateMixin from '../mixins/issuable_state'; ...@@ -7,7 +7,7 @@ import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
export default { export default {
name: 'IssueNoteForm', name: 'NoteForm',
components: { components: {
issueWarning, issueWarning,
markdownField, markdownField,
......
...@@ -6,6 +6,7 @@ import nextDiscussionsSvg from 'icons/_next_discussion.svg'; ...@@ -6,6 +6,7 @@ import nextDiscussionsSvg from 'icons/_next_discussion.svg';
import { convertObjectPropsToCamelCase, scrollToElement } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase, scrollToElement } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility'; import { truncateSha } from '~/lib/utils/text_utility';
import systemNote from '~/vue_shared/components/notes/system_note.vue'; import systemNote from '~/vue_shared/components/notes/system_note.vue';
import { s__ } from '~/locale';
import Flash from '../../flash'; import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
...@@ -144,19 +145,17 @@ export default { ...@@ -144,19 +145,17 @@ export default {
return this.isDiffDiscussion ? '' : 'card discussion-wrapper'; return this.isDiffDiscussion ? '' : 'card discussion-wrapper';
}, },
}, },
mounted() { watch: {
if (this.isReplying) { isReplying() {
this.initAutoSave(this.transformedDiscussion); if (this.isReplying) {
} this.$nextTick(() => {
}, // Pass an extra key to separate reply and note edit forms
updated() { this.initAutoSave(this.transformedDiscussion, ['Reply']);
if (this.isReplying) { });
if (!this.autosave) {
this.initAutoSave(this.transformedDiscussion);
} else { } else {
this.setAutoSave(); this.disposeAutoSave();
} }
} },
}, },
created() { created() {
this.resolveDiscussionsSvg = resolveDiscussionsSvg; this.resolveDiscussionsSvg = resolveDiscussionsSvg;
...@@ -194,16 +193,18 @@ export default { ...@@ -194,16 +193,18 @@ export default {
showReplyForm() { showReplyForm() {
this.isReplying = true; this.isReplying = true;
}, },
cancelReplyForm(shouldConfirm) { cancelReplyForm(shouldConfirm, isDirty) {
if (shouldConfirm && this.$refs.noteForm.isDirty) { if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (!window.confirm('Are you sure you want to cancel creating this comment?')) { if (!window.confirm(msg)) {
return; return;
} }
} }
this.resetAutoSave();
this.isReplying = false; this.isReplying = false;
this.resetAutoSave();
}, },
saveReply(noteText, form, callback) { saveReply(noteText, form, callback) {
const postData = { const postData = {
...@@ -420,7 +421,8 @@ Please check your network connection and try again.`; ...@@ -420,7 +421,8 @@ Please check your network connection and try again.`;
:is-editing="false" :is-editing="false"
save-button-title="Comment" save-button-title="Comment"
@handleFormUpdate="saveReply" @handleFormUpdate="saveReply"
@cancelForm="cancelReplyForm" /> @cancelForm="cancelReplyForm"
/>
<note-signed-out-widget v-if="!canReply" /> <note-signed-out-widget v-if="!canReply" />
</div> </div>
</div> </div>
......
...@@ -4,12 +4,18 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility'; ...@@ -4,12 +4,18 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
export default { export default {
methods: { methods: {
initAutoSave(noteable) { initAutoSave(noteable, extraKeys = []) {
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), [ let keys = [
'Note', 'Note',
capitalizeFirstCharacter(noteable.noteable_type), capitalizeFirstCharacter(noteable.noteable_type || noteable.noteableType),
noteable.id, noteable.id,
]); ];
if (extraKeys) {
keys = keys.concat(extraKeys);
}
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), keys);
}, },
resetAutoSave() { resetAutoSave() {
this.autosave.reset(); this.autosave.reset();
...@@ -17,5 +23,8 @@ export default { ...@@ -17,5 +23,8 @@ export default {
setAutoSave() { setAutoSave() {
this.autosave.save(); this.autosave.save();
}, },
disposeAutoSave() {
this.autosave.dispose();
},
}, },
}; };
...@@ -28,18 +28,6 @@ export const notesById = state => ...@@ -28,18 +28,6 @@ export const notesById = state =>
return acc; return acc;
}, {}); }, {});
export const discussionsByLineCode = state =>
state.discussions.reduce((acc, note) => {
if (note.diff_discussion && note.line_code && note.resolvable) {
// For context about line notes: there might be multiple notes with the same line code
const items = acc[note.line_code] || [];
items.push(note);
Object.assign(acc, { [note.line_code]: items });
}
return acc;
}, {});
export const noteableType = state => { export const noteableType = state => {
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants; const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
......
<script> <script>
/* This is a re-usable vue component for rendering a svg sprite
icon
Sample configuration:
<icon
name="retry"
:size="32"
css-classes="top"
/>
*/
// only allow classes in images.scss e.g. s12 // only allow classes in images.scss e.g. s12
const validSizes = [8, 12, 16, 18, 24, 32, 48, 72]; const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true;
/*
During development/tests we want to validate that we are just using icons that are actually defined
*/
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line global-require
const data = require('@gitlab-org/gitlab-svgs/dist/icons.json');
const { icons } = data;
iconValidator = value => {
if (icons.includes(value)) {
return true;
}
// eslint-disable-next-line no-console
console.warn(`Icon '${value}' is not a known icon of @gitlab/gitlab-svg`);
return false;
};
}
/** This is a re-usable vue component for rendering a svg sprite icon
* @example
* <icon
* name="retry"
* :size="32"
* css-classes="top"
* />
*/
export default { export default {
props: { props: {
name: { name: {
type: String, type: String,
required: true, required: true,
validator: iconValidator,
}, },
size: { size: {
...@@ -83,6 +99,6 @@ export default { ...@@ -83,6 +99,6 @@ export default {
:x="x" :x="x"
:y="y" :y="y"
> >
<use v-bind="{ 'xlink:href':spriteHref }" /> <use v-bind="{ 'xlink:href':spriteHref }"/>
</svg> </svg>
</template> </template>
<script>
import $ from 'jquery';
import Icon from '~/vue_shared/components/icon.vue';
import { inserted } from '~/feature_highlight/feature_highlight_helper';
import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
export default {
name: 'ReportsHelpPopover',
components: {
Icon,
},
props: {
options: {
type: Object,
required: true,
},
},
mounted() {
const $el = $(this.$el);
$el
.popover({
html: true,
trigger: 'focus',
container: 'body',
placement: 'top',
template:
'<div class="popover" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>',
...this.options,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave(300))
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
});
},
};
</script>
<template>
<button
type="button"
class="btn btn-blank btn-transparent btn-help"
tabindex="0"
>
<icon name="question" />
</button>
</template>
<script>
import IssuesBlock from './report_issues.vue';
/**
* Renders block of issues
*/
export default {
components: {
IssuesBlock,
},
props: {
unresolvedIssues: {
type: Array,
required: false,
default: () => [],
},
resolvedIssues: {
type: Array,
required: false,
default: () => [],
},
neutralIssues: {
type: Array,
required: false,
default: () => [],
},
allIssues: {
type: Array,
required: false,
default: () => [],
},
type: {
type: String,
required: true,
},
},
data() {
return {
isFullReportVisible: false,
};
},
computed: {
unresolvedIssuesStatus() {
return this.type === 'license' ? 'neutral' : 'failed';
},
},
methods: {
openFullReport() {
this.isFullReportVisible = true;
},
},
};
</script>
<template>
<div class="report-block-container">
<issues-block
v-if="unresolvedIssues.length"
:type="type"
:status="unresolvedIssuesStatus"
:issues="unresolvedIssues"
class="js-mr-code-new-issues"
/>
<issues-block
v-if="isFullReportVisible"
:type="type"
:issues="allIssues"
class="js-mr-code-all-issues"
status="failed"
/>
<issues-block
v-if="neutralIssues.length"
:type="type"
:issues="neutralIssues"
class="js-mr-code-non-issues"
status="neutral"
/>
<issues-block
v-if="resolvedIssues.length"
:type="type"
:issues="resolvedIssues"
class="js-mr-code-resolved-issues"
status="success"
/>
<button
v-if="allIssues.length && !isFullReportVisible"
type="button"
class="btn-link btn-blank prepend-left-10 js-expand-full-list break-link"
@click="openFullReport"
>
{{ s__("ciReport|Show complete code vulnerabilities report") }}
</button>
</div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
props: {
issue: {
type: Object,
required: true,
},
// failed || success
status: {
type: String,
required: true,
},
},
methods: {
...mapActions(['openModal']),
handleIssueClick() {
const { issue, status, openModal } = this;
openModal({ issue, status });
},
},
};
</script>
<template>
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
@click="handleIssueClick()"
>
{{ issue.title }}
</button>
</template>
<script>
import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'ReportIssues',
components: {
Icon,
},
props: {
issues: {
type: Array,
required: true,
},
type: {
type: String,
required: true,
},
// failed || success
status: {
type: String,
required: true,
},
},
computed: {
iconName() {
if (this.isStatusFailed) {
return 'status_failed_borderless';
} else if (this.isStatusSuccess) {
return 'status_success_borderless';
}
return 'status_created_borderless';
},
isStatusFailed() {
return this.status === 'failed';
},
isStatusSuccess() {
return this.status === 'success';
},
isStatusNeutral() {
return this.status === 'neutral';
},
},
};
</script>
<template>
<div>
<ul class="report-block-list">
<li
v-for="(issue, index) in issues"
:class="{ 'is-dismissed': issue.isDismissed }"
:key="index"
class="report-block-list-issue"
>
<div
:class="{
failed: isStatusFailed,
success: isStatusSuccess,
neutral: isStatusNeutral,
}"
class="report-block-list-icon append-right-5"
>
<icon
:name="iconName"
:size="32"
/>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'ReportIssueLink',
props: {
issue: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div class="report-block-list-issue-description-link">
in
<a
v-if="issue.urlPath"
:href="issue.urlPath"
target="_blank"
rel="noopener noreferrer nofollow"
class="break-link"
>
{{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template>
</a>
<template v-else>
{{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template>
</template>
</div>
</template>
<script>
import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import IssuesList from './issues_list.vue';
import Popover from './help_popover.vue';
const LOADING = 'LOADING';
const ERROR = 'ERROR';
const SUCCESS = 'SUCCESS';
export default {
name: 'ReportSection',
components: {
IssuesList,
StatusIcon,
Popover,
},
props: {
alwaysOpen: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: '',
},
status: {
type: String,
required: true,
},
loadingText: {
type: String,
required: false,
default: '',
},
errorText: {
type: String,
required: false,
default: '',
},
successText: {
type: String,
required: true,
},
unresolvedIssues: {
type: Array,
required: false,
default: () => [],
},
resolvedIssues: {
type: Array,
required: false,
default: () => [],
},
neutralIssues: {
type: Array,
required: false,
default: () => [],
},
allIssues: {
type: Array,
required: false,
default: () => [],
},
infoText: {
type: [String, Boolean],
required: false,
default: false,
},
hasIssues: {
type: Boolean,
required: true,
},
popoverOptions: {
type: Object,
default: () => ({}),
required: false,
},
},
data() {
return {
isCollapsed: true,
};
},
computed: {
collapseText() {
return this.isCollapsed ? __('Expand') : __('Collapse');
},
isLoading() {
return this.status === LOADING;
},
loadingFailed() {
return this.status === ERROR;
},
isSuccess() {
return this.status === SUCCESS;
},
isCollapsible() {
return !this.alwaysOpen && this.hasIssues;
},
isExpanded() {
return this.alwaysOpen || !this.isCollapsed;
},
statusIconName() {
if (this.isLoading) {
return 'loading';
}
if (this.loadingFailed || this.unresolvedIssues.length || this.neutralIssues.length) {
return 'warning';
}
return 'success';
},
headerText() {
if (this.isLoading) {
return this.loadingText;
}
if (this.isSuccess) {
return this.successText;
}
if (this.loadingFailed) {
return this.errorText;
}
return '';
},
hasPopover() {
return Object.keys(this.popoverOptions).length > 0;
},
},
methods: {
toggleCollapsed() {
this.isCollapsed = !this.isCollapsed;
},
},
};
</script>
<template>
<section class="media-section">
<div
class="media"
>
<status-icon
:status="statusIconName"
/>
<div
class="media-body space-children d-flex"
>
<span
class="js-code-text code-text"
>
{{ headerText }}
<popover
v-if="hasPopover"
:options="popoverOptions"
class="prepend-left-5"
/>
</span>
<button
v-if="isCollapsible"
type="button"
class="js-collapse-btn btn bt-default float-right btn-sm"
@click="toggleCollapsed"
>
{{ collapseText }}
</button>
</div>
</div>
<div
v-if="hasIssues"
v-show="isExpanded"
class="js-report-section-container"
>
<slot name="body">
<issues-list
:unresolved-issues="unresolvedIssues"
:resolved-issues="resolvedIssues"
:all-issues="allIssues"
:type="type"
/>
</slot>
</div>
</section>
</template>
<script>
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Popover from './help_popover.vue';
/**
* Renders the summary row for each report
*
* Used both in MR widget and Pipeline's view for:
* - Unit tests reports
* - Security reports
*/
export default {
name: 'ReportSummaryRow',
components: {
CiIcon,
LoadingIcon,
Popover,
},
props: {
summary: {
type: String,
required: true,
},
statusIcon: {
type: String,
required: true,
},
popoverOptions: {
type: Object,
required: true,
},
},
computed: {
iconStatus() {
return {
group: this.statusIcon,
icon: `status_${this.statusIcon}`,
};
},
},
};
</script>
<template>
<div class="report-block-list-issue report-block-list-issue-parent">
<div class="report-block-list-icon append-right-10 prepend-left-5">
<loading-icon
v-if="statusIcon === 'loading'"
css-class="report-block-list-loading-icon"
/>
<ci-icon
v-else
:status="iconStatus"
/>
</div>
<div class="report-block-list-issue-description">
<div class="report-block-list-issue-description-text">
{{ summary }}
</div>
<popover :options="popoverOptions" />
</div>
</div>
</template>
...@@ -47,7 +47,6 @@ ...@@ -47,7 +47,6 @@
.card-body { .card-body {
padding: $gl-padding; padding: $gl-padding;
background-color: $white-light;
.form-actions { .form-actions {
margin: -$gl-padding; margin: -$gl-padding;
......
...@@ -297,7 +297,6 @@ $performance-bar-height: 35px; ...@@ -297,7 +297,6 @@ $performance-bar-height: 35px;
$flash-height: 52px; $flash-height: 52px;
$context-header-height: 60px; $context-header-height: 60px;
$breadcrumb-min-height: 48px; $breadcrumb-min-height: 48px;
$gcp-signup-offer-icon-max-width: 125px;
/* /*
* Common component specific colors * Common component specific colors
......
...@@ -32,49 +32,23 @@ ...@@ -32,49 +32,23 @@
} }
.gcp-signup-offer { .gcp-signup-offer {
background-color: $blue-50; border-left-color: $blue-500;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
// TODO: To be superceded by cssLab svg {
&.alert { fill: $blue-500;
padding: 24px 16px; vertical-align: middle;
&-dismissable {
padding-right: 32px;
.close {
top: -8px;
right: -16px;
color: $blue-500;
opacity: 1;
}
}
}
.gcp-logo {
margin-bottom: $gl-padding;
text-align: center;
}
img {
max-width: $gcp-signup-offer-icon-max-width;
} }
a:not(.btn) { .gcp-signup-offer--content {
color: $gl-link-color; display: flex;
font-weight: normal;
text-decoration: none;
}
@include media-breakpoint-up(sm) { h4 {
> div { font-size: 16px;
display: flex; line-height: 24px;
align-items: center;
} }
.gcp-logo { .gcp-signup-offer--icon {
margin: 0; align-self: flex-start;
} }
} }
} }
...@@ -293,6 +293,8 @@ ...@@ -293,6 +293,8 @@
.prometheus-graph-flag { .prometheus-graph-flag {
display: block; display: block;
min-width: 160px; min-width: 160px;
border: 0;
box-shadow: 0 1px 4px 0 $black-transparent;
h5 { h5 {
padding: 0; padding: 0;
...@@ -312,7 +314,6 @@ ...@@ -312,7 +314,6 @@
&.popover { &.popover {
padding: 0; padding: 0;
border: 1px solid $border-color;
&.left { &.left {
left: auto; left: auto;
...@@ -320,12 +321,19 @@ ...@@ -320,12 +321,19 @@
margin-right: 10px; margin-right: 10px;
> .arrow { > .arrow {
right: -16px; right: -14px;
border-left-color: $border-color; border-left-color: $border-color;
} }
> .arrow::after { > .arrow::after {
border-left-color: $theme-gray-50; border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 4px solid $theme-gray-50;
}
.arrow-shadow {
right: -3px;
box-shadow: 1px 0 9px 0 $black-transparent;
} }
} }
...@@ -335,19 +343,35 @@ ...@@ -335,19 +343,35 @@
margin-left: 10px; margin-left: 10px;
> .arrow { > .arrow {
left: -16px; left: -7px;
border-right-color: $border-color; border-right-color: $border-color;
} }
> .arrow::after { > .arrow::after {
border-right-color: $theme-gray-50; border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-right: 4px solid $theme-gray-50;
}
.arrow-shadow {
left: -3px;
box-shadow: 1px 0 8px 0 $black-transparent;
} }
} }
> .arrow { > .arrow {
top: 16px; top: 10px;
margin-top: -8px; margin: 0;
border-width: 8px; }
.arrow-shadow {
content: "";
position: absolute;
width: 7px;
height: 7px;
background-color: transparent;
transform: rotate(45deg);
top: 13px;
} }
> .popover-title, > .popover-title,
...@@ -355,10 +379,12 @@ ...@@ -355,10 +379,12 @@
padding: 8px; padding: 8px;
font-size: 12px; font-size: 12px;
white-space: nowrap; white-space: nowrap;
position: relative;
} }
> .popover-title { > .popover-title {
background-color: $theme-gray-50; background-color: $theme-gray-50;
border-radius: $border-radius-default $border-radius-default 0 0;
} }
} }
......
...@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base ...@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include WithPerformanceBar include WithPerformanceBar
before_action :limit_unauthenticated_session_times
before_action :authenticate_sessionless_user! before_action :authenticate_sessionless_user!
before_action :authenticate_user! before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms? before_action :enforce_terms!, if: :should_enforce_terms?
...@@ -86,6 +87,24 @@ class ApplicationController < ActionController::Base ...@@ -86,6 +87,24 @@ class ApplicationController < ActionController::Base
end end
end end
# By default, all sessions are given the same expiration time configured in
# the session store (e.g. 1 week). However, unauthenticated users can
# generate a lot of sessions, primarily for CSRF verification. It makes
# sense to reduce the TTL for unauthenticated to something much lower than
# the default (e.g. 1 hour) to limit Redis memory. In addition, Rails
# creates a new session after login, so the short TTL doesn't even need to
# be extended.
def limit_unauthenticated_session_times
return if current_user
# Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259
return unless request.env['rack.session.options']
# This works because Rack uses these options every time a request is handled:
# https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
end
protected protected
def append_info_to_payload(payload) def append_info_to_payload(payload)
......
require 'json'
module IconsHelper module IconsHelper
extend self extend self
include FontAwesome::Rails::IconHelper include FontAwesome::Rails::IconHelper
...@@ -38,6 +40,13 @@ module IconsHelper ...@@ -38,6 +40,13 @@ module IconsHelper
end end
def sprite_icon(icon_name, size: nil, css_class: nil) def sprite_icon(icon_name, size: nil, css_class: nil)
if Gitlab::Sentry.should_raise?
unless known_sprites.include?(icon_name)
exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg")
raise exception
end
end
css_classes = size ? "s#{size}" : "" css_classes = size ? "s#{size}" : ""
css_classes << " #{css_class}" unless css_class.blank? css_classes << " #{css_class}" unless css_class.blank?
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes) content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
...@@ -134,4 +143,10 @@ module IconsHelper ...@@ -134,4 +143,10 @@ module IconsHelper
icon_class icon_class
end end
private
def known_sprites
@known_sprites ||= JSON.parse(File.read(Rails.root.join('node_modules/@gitlab-org/gitlab-svgs/dist/icons.json')))['icons']
end
end end
...@@ -105,7 +105,8 @@ module SearchHelper ...@@ -105,7 +105,8 @@ module SearchHelper
category: "Groups", category: "Groups",
id: group.id, id: group.id,
label: "#{search_result_sanitize(group.full_name)}", label: "#{search_result_sanitize(group.full_name)}",
url: group_path(group) url: group_path(group),
avatar_url: group.avatar_url || ''
} }
end end
end end
...@@ -119,7 +120,8 @@ module SearchHelper ...@@ -119,7 +120,8 @@ module SearchHelper
id: p.id, id: p.id,
value: "#{search_result_sanitize(p.name)}", value: "#{search_result_sanitize(p.name)}",
label: "#{search_result_sanitize(p.full_name)}", label: "#{search_result_sanitize(p.full_name)}",
url: project_path(p) url: project_path(p),
avatar_url: p.avatar_url || ''
} }
end end
end end
......
...@@ -182,7 +182,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -182,7 +182,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def diffs(diff_options = nil) def diffs(diff_options = nil)
if without_files? && comparison = diff_refs.compare_in(project) if without_files? && comparison = diff_refs&.compare_in(project)
# It should fetch the repository when diffs are cleaned by the system. # It should fetch the repository when diffs are cleaned by the system.
# We don't keep these for storage overload purposes. # We don't keep these for storage overload purposes.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/37639 # See https://gitlab.com/gitlab-org/gitlab-ce/issues/37639
......
...@@ -154,12 +154,9 @@ class Repository ...@@ -154,12 +154,9 @@ class Repository
# Returns a list of commits that are not present in any reference # Returns a list of commits that are not present in any reference
def new_commits(newrev) def new_commits(newrev)
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 commits = raw.new_commits(newrev)
refs = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
::Gitlab::Git::RevList.new(raw, newrev: newrev).new_refs
end
refs.map { |sha| commit(sha.strip) } ::Commit.decorate(commits, project)
end end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
......
...@@ -4,6 +4,7 @@ class DiscussionEntity < Grape::Entity ...@@ -4,6 +4,7 @@ class DiscussionEntity < Grape::Entity
expose :id, :reply_id expose :id, :reply_id
expose :position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? } expose :position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? }
expose :original_position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? }
expose :line_code, if: -> (d, _) { d.diff_discussion? } expose :line_code, if: -> (d, _) { d.diff_discussion? }
expose :expanded?, as: :expanded expose :expanded?, as: :expanded
expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? } expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? }
......
# frozen_string_literal: true
module Lfs module Lfs
# Usage: Calling `new_file` check to see if a file should be in LFS and # Usage: Calling `new_file` check to see if a file should be in LFS and
# return a transformed result with `content` and `encoding` to commit. # return a transformed result with `content` and `encoding` to commit.
......
# frozen_string_literal: true
module Lfs module Lfs
class LockFileService < BaseService class LockFileService < BaseService
def execute def execute
......
# frozen_string_literal: true
module Lfs module Lfs
class LocksFinderService < BaseService class LocksFinderService < BaseService
def execute def execute
......
# frozen_string_literal: true
module Lfs module Lfs
class UnlockFileService < BaseService class UnlockFileService < BaseService
def execute def execute
......
# frozen_string_literal: true
module Mattermost module Mattermost
class CreateTeamService < ::BaseService class CreateTeamService < ::BaseService
def initialize(group, current_user) def initialize(group, current_user)
......
# frozen_string_literal: true
module Members module Members
class ApproveAccessRequestService < Members::BaseService class ApproveAccessRequestService < Members::BaseService
def execute(access_requester, skip_authorization: false, skip_log_audit_event: false) def execute(access_requester, skip_authorization: false, skip_log_audit_event: false)
......
# frozen_string_literal: true
module Members module Members
class BaseService < ::BaseService class BaseService < ::BaseService
# current_user - The user that performs the action # current_user - The user that performs the action
......
# frozen_string_literal: true
module Members module Members
class CreateService < Members::BaseService class CreateService < Members::BaseService
DEFAULT_LIMIT = 100 DEFAULT_LIMIT = 100
......
# frozen_string_literal: true
module Members module Members
class DestroyService < Members::BaseService class DestroyService < Members::BaseService
def execute(member, skip_authorization: false) def execute(member, skip_authorization: false)
......
# frozen_string_literal: true
module Members module Members
class RequestAccessService < Members::BaseService class RequestAccessService < Members::BaseService
def execute(source) def execute(source)
......
# frozen_string_literal: true
module Members module Members
class UpdateService < Members::BaseService class UpdateService < Members::BaseService
# returns the updated member # returns the updated member
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class AddTodoWhenBuildFailsService < MergeRequests::BaseService class AddTodoWhenBuildFailsService < MergeRequests::BaseService
# Adds a todo to the parent merge_request when a CI build fails # Adds a todo to the parent merge_request when a CI build fails
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class AssignIssuesService < BaseService class AssignIssuesService < BaseService
def assignable_issues def assignable_issues
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class BaseService < ::IssuableBaseService class BaseService < ::IssuableBaseService
def create_note(merge_request, state = merge_request.state) def create_note(merge_request, state = merge_request.state)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class BuildService < MergeRequests::BaseService class BuildService < MergeRequests::BaseService
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
...@@ -140,7 +142,8 @@ module MergeRequests ...@@ -140,7 +142,8 @@ module MergeRequests
closes_issue = "Closes #{issue.to_reference}" closes_issue = "Closes #{issue.to_reference}"
if description.present? if description.present?
merge_request.description += closes_issue.prepend("\n\n") descr_parts = [merge_request.description, closes_issue]
merge_request.description = descr_parts.join("\n\n")
else else
merge_request.description = closes_issue merge_request.description = closes_issue
end end
...@@ -164,9 +167,11 @@ module MergeRequests ...@@ -164,9 +167,11 @@ module MergeRequests
return if merge_request.title.present? return if merge_request.title.present?
if issue_iid.present? if issue_iid.present?
merge_request.title = "Resolve #{issue.to_reference}" title_parts = ["Resolve #{issue.to_reference}"]
branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize
merge_request.title += " \"#{branch_title}\"" if branch_title.present?
title_parts << "\"#{branch_title}\"" if branch_title.present?
merge_request.title = title_parts.join(' ')
end end
end end
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class CloseService < MergeRequests::BaseService class CloseService < MergeRequests::BaseService
def execute(merge_request, commit = nil) def execute(merge_request, commit = nil)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
module Conflicts module Conflicts
class BaseService class BaseService
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
module Conflicts module Conflicts
class ListService < MergeRequests::Conflicts::BaseService class ListService < MergeRequests::Conflicts::BaseService
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
module Conflicts module Conflicts
class ResolveService < MergeRequests::Conflicts::BaseService class ResolveService < MergeRequests::Conflicts::BaseService
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class CreateFromIssueService < MergeRequests::CreateService class CreateFromIssueService < MergeRequests::CreateService
def initialize(project, user, params) def initialize(project, user, params)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class CreateService < MergeRequests::BaseService class CreateService < MergeRequests::BaseService
def execute def execute
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class DeleteNonLatestDiffsService class DeleteNonLatestDiffsService
BATCH_SIZE = 10 BATCH_SIZE = 10
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
# MergeService class # MergeService class
# #
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class GetUrlsService < BaseService class GetUrlsService < BaseService
attr_reader :project attr_reader :project
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
# MergeService class # MergeService class
# #
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class MergeWhenPipelineSucceedsService < MergeRequests::BaseService class MergeWhenPipelineSucceedsService < MergeRequests::BaseService
# Marks the passed `merge_request` to be merged when the pipeline succeeds or # Marks the passed `merge_request` to be merged when the pipeline succeeds or
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
# PostMergeService class # PostMergeService class
# #
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class RebaseService < MergeRequests::WorkingCopyBaseService class RebaseService < MergeRequests::WorkingCopyBaseService
REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class RefreshService < MergeRequests::BaseService class RefreshService < MergeRequests::BaseService
def execute(oldrev, newrev, ref) def execute(oldrev, newrev, ref)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class ReloadDiffsService class ReloadDiffsService
def initialize(merge_request, current_user) def initialize(merge_request, current_user)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class ReopenService < MergeRequests::BaseService class ReopenService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class ResolvedDiscussionNotificationService < MergeRequests::BaseService class ResolvedDiscussionNotificationService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class SquashService < MergeRequests::WorkingCopyBaseService class SquashService < MergeRequests::WorkingCopyBaseService
def execute(merge_request) def execute(merge_request)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class UpdateService < MergeRequests::BaseService class UpdateService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
......
# frozen_string_literal: true
module MergeRequests module MergeRequests
class WorkingCopyBaseService < MergeRequests::BaseService class WorkingCopyBaseService < MergeRequests::BaseService
attr_reader :merge_request attr_reader :merge_request
......
# frozen_string_literal: true
module Milestones module Milestones
class BaseService < ::BaseService class BaseService < ::BaseService
# Parent can either a group or a project # Parent can either a group or a project
......
# frozen_string_literal: true
module Milestones module Milestones
class CloseService < Milestones::BaseService class CloseService < Milestones::BaseService
def execute(milestone) def execute(milestone)
......
# frozen_string_literal: true
module Milestones module Milestones
class CreateService < Milestones::BaseService class CreateService < Milestones::BaseService
def execute def execute
......
# frozen_string_literal: true
module Milestones module Milestones
class DestroyService < Milestones::BaseService class DestroyService < Milestones::BaseService
def execute(milestone) def execute(milestone)
......
# frozen_string_literal: true
module Milestones module Milestones
class PromoteService < Milestones::BaseService class PromoteService < Milestones::BaseService
PromoteMilestoneError = Class.new(StandardError) PromoteMilestoneError = Class.new(StandardError)
......
# frozen_string_literal: true
module Milestones module Milestones
class ReopenService < Milestones::BaseService class ReopenService < Milestones::BaseService
def execute(milestone) def execute(milestone)
......
# frozen_string_literal: true
module Milestones module Milestones
class UpdateService < Milestones::BaseService class UpdateService < Milestones::BaseService
def execute(milestone) def execute(milestone)
......
# frozen_string_literal: true
module Notes module Notes
class BuildService < ::BaseService class BuildService < ::BaseService
def execute def execute
......
# frozen_string_literal: true
module Notes module Notes
class CreateService < ::BaseService class CreateService < ::BaseService
def execute def execute
......
# frozen_string_literal: true
module Notes module Notes
class DestroyService < BaseService class DestroyService < BaseService
def execute(note) def execute(note)
......
# frozen_string_literal: true
module Notes module Notes
class PostProcessService class PostProcessService
attr_accessor :note attr_accessor :note
......
# frozen_string_literal: true
module Notes module Notes
class QuickActionsService < BaseService class QuickActionsService < BaseService
UPDATE_SERVICES = { UPDATE_SERVICES = {
......
# frozen_string_literal: true
module Notes module Notes
class RenderService < BaseRenderer class RenderService < BaseRenderer
# Renders a collection of Note instances. # Renders a collection of Note instances.
......
# frozen_string_literal: true
module Notes module Notes
class ResolveService < ::BaseService class ResolveService < ::BaseService
def execute(note) def execute(note)
......
# frozen_string_literal: true
module Notes module Notes
class UpdateService < BaseService class UpdateService < BaseService
def execute(note) def execute(note)
......
# frozen_string_literal: true
module Projects module Projects
class AfterImportService class AfterImportService
RESERVED_REF_PREFIXES = Repository::RESERVED_REFS_NAMES.map { |n| File.join('refs', n, '/') } RESERVED_REF_PREFIXES = Repository::RESERVED_REFS_NAMES.map { |n| File.join('refs', n, '/') }
......
# frozen_string_literal: true
module Projects module Projects
class AutocompleteService < BaseService class AutocompleteService < BaseService
def issues def issues
......
# frozen_string_literal: true
module Projects module Projects
class BaseMoveRelationsService < BaseService class BaseMoveRelationsService < BaseService
attr_reader :source_project attr_reader :source_project
......
# frozen_string_literal: true
# Service class for getting and caching the number of elements of several projects # Service class for getting and caching the number of elements of several projects
# Warning: do not user this service with a really large set of projects # Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids. # because the service use maps to retrieve the project ids.
......
# frozen_string_literal: true
# Service class for getting and caching the number of forks of several projects # Service class for getting and caching the number of forks of several projects
# Warning: do not user this service with a really large set of projects # Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids # because the service use maps to retrieve the project ids
......
# frozen_string_literal: true
# Service class for getting and caching the number of issues of several projects # Service class for getting and caching the number of issues of several projects
# Warning: do not user this service with a really large set of projects # Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids # because the service use maps to retrieve the project ids
......
# frozen_string_literal: true
module Projects 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).
......
# frozen_string_literal: true
module Projects module Projects
class CreateFromTemplateService < BaseService class CreateFromTemplateService < BaseService
def initialize(user, params) def initialize(user, params)
......
# frozen_string_literal: true
module Projects module Projects
class CreateService < BaseService class CreateService < BaseService
def initialize(user, params) def initialize(user, params)
......
# frozen_string_literal: true
module Projects module Projects
class DestroyService < BaseService class DestroyService < BaseService
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
......
# frozen_string_literal: true
module Projects module Projects
class DownloadService < BaseService class DownloadService < BaseService
WHITELIST = [ WHITELIST = [
......
# frozen_string_literal: true
module Projects module Projects
class EnableDeployKeyService < BaseService class EnableDeployKeyService < BaseService
def execute def execute
......
# frozen_string_literal: true
module Projects module Projects
class ForkService < BaseService class ForkService < BaseService
def execute(fork_to_project = nil) def execute(fork_to_project = nil)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment