Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml4
-rw-r--r--app/assets/javascripts/merge_conflicts/constants.js20
-rw-r--r--app/assets/javascripts/merge_conflicts/store/actions.js120
-rw-r--r--app/assets/javascripts/merge_conflicts/store/getters.js117
-rw-r--r--app/assets/javascripts/merge_conflicts/store/index.js16
-rw-r--r--app/assets/javascripts/merge_conflicts/store/mutation_types.js8
-rw-r--r--app/assets/javascripts/merge_conflicts/store/mutations.js40
-rw-r--r--app/assets/javascripts/merge_conflicts/store/state.js13
-rw-r--r--app/assets/javascripts/merge_conflicts/utils.js228
-rw-r--r--app/assets/javascripts/pages/groups/settings/repository/show/index.js10
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/components/help_icon.vue2
-rw-r--r--app/finders/repositories/changelog_commits_finder.rb (renamed from app/finders/repositories/commits_with_trailer_finder.rb)25
-rw-r--r--app/services/repositories/changelog_service.rb2
-rw-r--r--app/views/admin/appearances/_form.html.haml2
-rw-r--r--app/views/admin/appearances/preview_sign_in.html.haml2
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/admin/serverless/domains/_form.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/shared/empty_states/_deploy_keys.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml8
-rw-r--r--app/views/shared/empty_states/_merge_requests.html.haml6
-rw-r--r--app/views/shared/empty_states/_profile_tabs.html.haml4
-rw-r--r--app/views/shared/empty_states/_snippets.html.haml2
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml4
-rw-r--r--changelogs/unreleased/322770-change-security-mr-widget-icon.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-admin-appearances.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-admin-projects.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-admin-serverless.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-empty-states.yml5
-rw-r--r--changelogs/unreleased/cablett-restore-database-structure.yml5
-rw-r--r--changelogs/unreleased/changelog-ignore-reverted-commits.yml5
-rw-r--r--changelogs/unreleased/dora-metrics-modeling.yml5
-rw-r--r--changelogs/unreleased/remove-index-add-index.yml5
-rw-r--r--changelogs/unreleased/rename-vuln-fingerprint-indexes.yml5
-rw-r--r--config/feature_categories.yml1
-rw-r--r--config/feature_flags/development/dora_daily_metrics.yml8
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/migrate/20210225090801_create_dora_daily_metrics.rb31
-rw-r--r--db/migrate/20210302155904_remove_index_for_security_orchestration_policy.rb18
-rw-r--r--db/migrate/20210302160544_add_index_to_security_orchestration_policy.rb18
-rw-r--r--db/migrate/20210302212623_rename_vuln_fingerprints_indexes.rb35
-rw-r--r--db/migrate/20210303165201_add_index_for_succeeded_deployments.rb18
-rw-r--r--db/schema_migrations/202102250908011
-rw-r--r--db/schema_migrations/202103021559041
-rw-r--r--db/schema_migrations/202103021605441
-rw-r--r--db/schema_migrations/202103022126231
-rw-r--r--db/schema_migrations/202103031652011
-rw-r--r--db/structure.sql59
-rw-r--r--doc/administration/packages/container_registry.md4
-rw-r--r--doc/administration/raketasks/maintenance.md5
-rw-r--r--doc/administration/reference_architectures/10k_users.md16
-rw-r--r--doc/administration/reference_architectures/25k_users.md10
-rw-r--r--doc/administration/reference_architectures/2k_users.md10
-rw-r--r--doc/administration/reference_architectures/3k_users.md10
-rw-r--r--doc/administration/reference_architectures/50k_users.md10
-rw-r--r--doc/administration/reference_architectures/5k_users.md10
-rw-r--r--doc/api/README.md4
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/repositories.md20
-rw-r--r--doc/development/fe_guide/style/javascript.md21
-rw-r--r--doc/install/installation.md4
-rw-r--r--locale/gitlab.pot30
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb8
-rw-r--r--spec/finders/repositories/changelog_commits_finder_spec.rb67
-rw-r--r--spec/finders/repositories/commits_with_trailer_finder_spec.rb38
-rw-r--r--spec/frontend/merge_conflicts/store/actions_spec.js257
-rw-r--r--spec/services/projects/branches_by_mode_service_spec.rb4
-rw-r--r--spec/services/repositories/changelog_service_spec.rb44
-rw-r--r--spec/support/helpers/test_env.rb3
-rwxr-xr-x[-rw-r--r--]vendor/gitignore/C++.gitignore0
-rwxr-xr-x[-rw-r--r--]vendor/gitignore/Java.gitignore0
71 files changed, 1302 insertions, 160 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 16f8783c9a0..a078422690f 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -193,6 +193,7 @@
- "config.ru"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
+ - "data/whats_new/*.yml"
.qa-patterns: &qa-patterns
- ".dockerignore"
@@ -215,6 +216,7 @@
- "config.ru"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
+ - "data/whats_new/*.yml"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
@@ -240,6 +242,7 @@
- "config.ru"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
+ - "data/whats_new/*.yml"
# QA changes
- ".dockerignore"
- "qa/**/*"
@@ -261,6 +264,7 @@
- "config.ru"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
+ - "data/whats_new/*.yml"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
diff --git a/app/assets/javascripts/merge_conflicts/constants.js b/app/assets/javascripts/merge_conflicts/constants.js
new file mode 100644
index 00000000000..6f3ee339e36
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/constants.js
@@ -0,0 +1,20 @@
+import { s__ } from '~/locale';
+
+export const CONFLICT_TYPES = {
+ TEXT: 'text',
+ TEXT_EDITOR: 'text-editor',
+};
+
+export const VIEW_TYPES = {
+ INLINE: 'inline',
+ PARALLEL: 'parallel',
+};
+
+export const EDIT_RESOLVE_MODE = 'edit';
+export const INTERACTIVE_RESOLVE_MODE = 'interactive';
+export const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
+
+export const HEAD_HEADER_TEXT = s__('MergeConflict|HEAD//our changes');
+export const ORIGIN_HEADER_TEXT = s__('MergeConflict|origin//their changes');
+export const HEAD_BUTTON_TITLE = s__('MergeConflict|Use ours');
+export const ORIGIN_BUTTON_TITLE = s__('MergeConflict|Use theirs');
diff --git a/app/assets/javascripts/merge_conflicts/store/actions.js b/app/assets/javascripts/merge_conflicts/store/actions.js
new file mode 100644
index 00000000000..8036e90c58c
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/store/actions.js
@@ -0,0 +1,120 @@
+import Cookies from 'js-cookie';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import { INTERACTIVE_RESOLVE_MODE, EDIT_RESOLVE_MODE } from '../constants';
+import { decorateFiles, restoreFileLinesState, markLine } from '../utils';
+import * as types from './mutation_types';
+
+export const fetchConflictsData = async ({ commit, dispatch }, conflictsPath) => {
+ commit(types.SET_LOADING_STATE, true);
+ try {
+ const { data } = await axios.get(conflictsPath);
+ if (data.type === 'error') {
+ commit(types.SET_FAILED_REQUEST, data.message);
+ } else {
+ dispatch('setConflictsData', data);
+ }
+ } catch (e) {
+ commit(types.SET_FAILED_REQUEST);
+ }
+ commit(types.SET_LOADING_STATE, false);
+};
+
+export const setConflictsData = async ({ commit }, data) => {
+ const files = decorateFiles(data.files);
+ commit(types.SET_CONFLICTS_DATA, { ...data, files });
+};
+
+export const submitResolvedConflicts = async ({ commit, getters }, resolveConflictsPath) => {
+ commit(types.SET_SUBMIT_STATE, true);
+ try {
+ const { data } = await axios.post(resolveConflictsPath, getters.getCommitData);
+ window.location.assign(data.redirect_to);
+ } catch (e) {
+ commit(types.SET_SUBMIT_STATE, false);
+ createFlash({ message: __('Failed to save merge conflicts resolutions. Please try again!') });
+ }
+};
+
+export const setLoadingState = ({ commit }, isLoading) => {
+ commit(types.SET_LOADING_STATE, isLoading);
+};
+
+export const setErrorState = ({ commit }, hasError) => {
+ commit(types.SET_ERROR_STATE, hasError);
+};
+
+export const setFailedRequest = ({ commit }, message) => {
+ commit(types.SET_FAILED_REQUEST, message);
+};
+
+export const setViewType = ({ commit }, viewType) => {
+ commit(types.SET_VIEW_TYPE, viewType);
+ Cookies.set('diff_view', viewType);
+};
+
+export const setSubmitState = ({ commit }, isSubmitting) => {
+ commit(types.SET_SUBMIT_STATE, isSubmitting);
+};
+
+export const updateCommitMessage = ({ commit }, commitMessage) => {
+ commit(types.UPDATE_CONFLICTS_DATA, { commitMessage });
+};
+
+export const setFileResolveMode = ({ commit, state, getters }, { file, mode }) => {
+ const index = getters.getFileIndex(file);
+ const updated = { ...state.conflictsData.files[index] };
+ if (mode === INTERACTIVE_RESOLVE_MODE) {
+ updated.showEditor = false;
+ } else if (mode === EDIT_RESOLVE_MODE) {
+ // Restore Interactive mode when switching to Edit mode
+ updated.showEditor = true;
+ updated.loadEditor = true;
+ updated.resolutionData = {};
+
+ const { inlineLines, parallelLines } = restoreFileLinesState(updated);
+ updated.parallelLines = parallelLines;
+ updated.inlineLines = inlineLines;
+ }
+ updated.resolveMode = mode;
+ commit(types.UPDATE_FILE, { file: updated, index });
+};
+
+export const setPromptConfirmationState = (
+ { commit, state, getters },
+ { file, promptDiscardConfirmation },
+) => {
+ const index = getters.getFileIndex(file);
+ const updated = { ...state.conflictsData.files[index], promptDiscardConfirmation };
+ commit(types.UPDATE_FILE, { file: updated, index });
+};
+
+export const handleSelected = ({ commit, state, getters }, { file, line: { id, section } }) => {
+ const index = getters.getFileIndex(file);
+ const updated = { ...state.conflictsData.files[index] };
+ updated.resolutionData = { ...updated.resolutionData, [id]: section };
+
+ updated.inlineLines = file.inlineLines.map((line) => {
+ if (id === line.id && (line.hasConflict || line.isHeader)) {
+ return markLine(line, section);
+ }
+ return line;
+ });
+
+ updated.parallelLines = file.parallelLines.map((lines) => {
+ let left = { ...lines[0] };
+ let right = { ...lines[1] };
+ const hasSameId = right.id === id || left.id === id;
+ const isLeftMatch = left.hasConflict || left.isHeader;
+ const isRightMatch = right.hasConflict || right.isHeader;
+
+ if (hasSameId && (isLeftMatch || isRightMatch)) {
+ left = markLine(left, section);
+ right = markLine(right, section);
+ }
+ return [left, right];
+ });
+
+ commit(types.UPDATE_FILE, { file: updated, index });
+};
diff --git a/app/assets/javascripts/merge_conflicts/store/getters.js b/app/assets/javascripts/merge_conflicts/store/getters.js
new file mode 100644
index 00000000000..03e425fb478
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/store/getters.js
@@ -0,0 +1,117 @@
+import { s__ } from '~/locale';
+import { CONFLICT_TYPES, EDIT_RESOLVE_MODE, INTERACTIVE_RESOLVE_MODE } from '../constants';
+
+export const getConflictsCount = (state) => {
+ if (!state.conflictsData.files.length) {
+ return 0;
+ }
+
+ const { files } = state.conflictsData;
+ let count = 0;
+
+ files.forEach((file) => {
+ if (file.type === CONFLICT_TYPES.TEXT) {
+ file.sections.forEach((section) => {
+ if (section.conflict) {
+ count += 1;
+ }
+ });
+ } else {
+ count += 1;
+ }
+ });
+
+ return count;
+};
+
+export const getConflictsCountText = (state, getters) => {
+ const count = getters.getConflictsCount;
+ const text = count > 1 ? s__('MergeConflict|conflicts') : s__('MergeConflict|conflict');
+
+ return `${count} ${text}`;
+};
+
+export const isReadyToCommit = (state) => {
+ const { files } = state.conflictsData;
+ const hasCommitMessage = state.conflictsData.commitMessage.trim().length;
+ let unresolved = 0;
+
+ for (let i = 0, l = files.length; i < l; i += 1) {
+ const file = files[i];
+
+ if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
+ let numberConflicts = 0;
+ const resolvedConflicts = Object.keys(file.resolutionData).length;
+
+ // We only check for conflicts type 'text'
+ // since conflicts `text_editor` can´t be resolved in interactive mode
+ if (file.type === CONFLICT_TYPES.TEXT) {
+ for (let j = 0, k = file.sections.length; j < k; j += 1) {
+ if (file.sections[j].conflict) {
+ numberConflicts += 1;
+ }
+ }
+
+ if (resolvedConflicts !== numberConflicts) {
+ unresolved += 1;
+ }
+ }
+ } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
+ // Unlikely to happen since switching to Edit mode saves content automatically.
+ // Checking anyway in case the save strategy changes in the future
+ if (!file.content) {
+ unresolved += 1;
+ // eslint-disable-next-line no-continue
+ continue;
+ }
+ }
+ }
+
+ return !state.isSubmitting && hasCommitMessage && !unresolved;
+};
+
+export const getCommitButtonText = (state) => {
+ const initial = s__('MergeConflict|Commit to source branch');
+ const inProgress = s__('MergeConflict|Committing...');
+
+ return state.isSubmitting ? inProgress : initial;
+};
+
+export const getCommitData = (state) => {
+ let commitData = {};
+
+ commitData = {
+ commit_message: state.conflictsData.commitMessage,
+ files: [],
+ };
+
+ state.conflictsData.files.forEach((file) => {
+ const addFile = {
+ old_path: file.old_path,
+ new_path: file.new_path,
+ };
+
+ if (file.type === CONFLICT_TYPES.TEXT) {
+ // Submit only one data for type of editing
+ if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
+ addFile.sections = file.resolutionData;
+ } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
+ addFile.content = file.content;
+ }
+ } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
+ addFile.content = file.content;
+ }
+
+ commitData.files.push(addFile);
+ });
+
+ return commitData;
+};
+
+export const fileTextTypePresent = (state) => {
+ return state.conflictsData?.files.some((f) => f.type === CONFLICT_TYPES.TEXT);
+};
+
+export const getFileIndex = (state) => ({ blobPath }) => {
+ return state.conflictsData.files.findIndex((f) => f.blobPath === blobPath);
+};
diff --git a/app/assets/javascripts/merge_conflicts/store/index.js b/app/assets/javascripts/merge_conflicts/store/index.js
new file mode 100644
index 00000000000..18e3351ed13
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ state,
+ getters,
+ actions,
+ mutations,
+ });
diff --git a/app/assets/javascripts/merge_conflicts/store/mutation_types.js b/app/assets/javascripts/merge_conflicts/store/mutation_types.js
new file mode 100644
index 00000000000..ab80f8e52ad
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/store/mutation_types.js
@@ -0,0 +1,8 @@
+export const SET_LOADING_STATE = 'SET_LOADING_STATE';
+export const SET_ERROR_STATE = 'SET_ERROR_STATE';
+export const SET_FAILED_REQUEST = 'SET_FAILED_REQUEST';
+export const SET_VIEW_TYPE = 'SET_VIEW_TYPE';
+export const SET_SUBMIT_STATE = 'SET_SUBMIT_STATE';
+export const SET_CONFLICTS_DATA = 'SET_CONFLICTS_DATA';
+export const UPDATE_FILE = 'UPDATE_FILE';
+export const UPDATE_CONFLICTS_DATA = 'UPDATE_CONFLICTS_DATA';
diff --git a/app/assets/javascripts/merge_conflicts/store/mutations.js b/app/assets/javascripts/merge_conflicts/store/mutations.js
new file mode 100644
index 00000000000..2cee55319eb
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/store/mutations.js
@@ -0,0 +1,40 @@
+import { VIEW_TYPES } from '../constants';
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_LOADING_STATE]: (state, value) => {
+ state.isLoading = value;
+ },
+ [types.SET_ERROR_STATE]: (state, value) => {
+ state.hasError = value;
+ },
+ [types.SET_FAILED_REQUEST]: (state, value) => {
+ state.hasError = true;
+ state.conflictsData.errorMessage = value;
+ },
+ [types.SET_VIEW_TYPE]: (state, value) => {
+ state.diffView = value;
+ state.isParallel = value === VIEW_TYPES.PARALLEL;
+ },
+ [types.SET_SUBMIT_STATE]: (state, value) => {
+ state.isSubmitting = value;
+ },
+ [types.SET_CONFLICTS_DATA]: (state, data) => {
+ state.conflictsData = {
+ files: data.files,
+ commitMessage: data.commit_message,
+ sourceBranch: data.source_branch,
+ targetBranch: data.target_branch,
+ shortCommitSha: data.commit_sha.slice(0, 7),
+ };
+ },
+ [types.UPDATE_CONFLICTS_DATA]: (state, payload) => {
+ state.conflictsData = {
+ ...state.conflictsData,
+ ...payload,
+ };
+ },
+ [types.UPDATE_FILE]: (state, { file, index }) => {
+ state.conflictsData.files.splice(index, 1, file);
+ },
+};
diff --git a/app/assets/javascripts/merge_conflicts/store/state.js b/app/assets/javascripts/merge_conflicts/store/state.js
new file mode 100644
index 00000000000..8f700f58e54
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/store/state.js
@@ -0,0 +1,13 @@
+import Cookies from 'js-cookie';
+import { VIEW_TYPES } from '../constants';
+
+const diffViewType = Cookies.get('diff_view');
+
+export default () => ({
+ isLoading: true,
+ hasError: false,
+ isSubmitting: false,
+ isParallel: diffViewType === VIEW_TYPES.PARALLEL,
+ diffViewType,
+ conflictsData: {},
+});
diff --git a/app/assets/javascripts/merge_conflicts/utils.js b/app/assets/javascripts/merge_conflicts/utils.js
new file mode 100644
index 00000000000..e42703ef0a5
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/utils.js
@@ -0,0 +1,228 @@
+import {
+ ORIGIN_HEADER_TEXT,
+ ORIGIN_BUTTON_TITLE,
+ HEAD_HEADER_TEXT,
+ HEAD_BUTTON_TITLE,
+ DEFAULT_RESOLVE_MODE,
+ CONFLICT_TYPES,
+} from './constants';
+
+export const getFilePath = (file) => {
+ const { old_path, new_path } = file;
+ // eslint-disable-next-line babel/camelcase
+ return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
+};
+
+export const checkLineLengths = ({ left, right }) => {
+ const wLeft = [...left];
+ const wRight = [...right];
+ if (left.length !== right.length) {
+ if (left.length > right.length) {
+ const diff = left.length - right.length;
+ for (let i = 0; i < diff; i += 1) {
+ wRight.push({ lineType: 'emptyLine', richText: '' });
+ }
+ } else {
+ const diff = right.length - left.length;
+ for (let i = 0; i < diff; i += 1) {
+ wLeft.push({ lineType: 'emptyLine', richText: '' });
+ }
+ }
+ }
+ return { left: wLeft, right: wRight };
+};
+
+export const getHeadHeaderLine = (id) => {
+ return {
+ id,
+ richText: HEAD_HEADER_TEXT,
+ buttonTitle: HEAD_BUTTON_TITLE,
+ type: 'new',
+ section: 'head',
+ isHeader: true,
+ isHead: true,
+ isSelected: false,
+ isUnselected: false,
+ };
+};
+
+export const decorateLineForInlineView = (line, id, conflict) => {
+ const { type } = line;
+ return {
+ id,
+ hasConflict: conflict,
+ isHead: type === 'new',
+ isOrigin: type === 'old',
+ hasMatch: type === 'match',
+ richText: line.rich_text,
+ isSelected: false,
+ isUnselected: false,
+ };
+};
+
+export const getLineForParallelView = (line, id, lineType, isHead) => {
+ const { old_line, new_line, rich_text } = line;
+ const hasConflict = lineType === 'conflict';
+
+ return {
+ id,
+ lineType,
+ hasConflict,
+ isHead: hasConflict && isHead,
+ isOrigin: hasConflict && !isHead,
+ hasMatch: lineType === 'match',
+ // eslint-disable-next-line babel/camelcase
+ lineNumber: isHead ? new_line : old_line,
+ section: isHead ? 'head' : 'origin',
+ richText: rich_text,
+ isSelected: false,
+ isUnselected: false,
+ };
+};
+
+export const getOriginHeaderLine = (id) => {
+ return {
+ id,
+ richText: ORIGIN_HEADER_TEXT,
+ buttonTitle: ORIGIN_BUTTON_TITLE,
+ type: 'old',
+ section: 'origin',
+ isHeader: true,
+ isOrigin: true,
+ isSelected: false,
+ isUnselected: false,
+ };
+};
+
+export const setInlineLine = (file) => {
+ const inlineLines = [];
+
+ file.sections.forEach((section) => {
+ let currentLineType = 'new';
+ const { conflict, lines, id } = section;
+
+ if (conflict) {
+ inlineLines.push(getHeadHeaderLine(id));
+ }
+
+ lines.forEach((line) => {
+ const { type } = line;
+
+ if ((type === 'new' || type === 'old') && currentLineType !== type) {
+ currentLineType = type;
+ inlineLines.push({ lineType: 'emptyLine', richText: '' });
+ }
+
+ const decoratedLine = decorateLineForInlineView(line, id, conflict);
+ inlineLines.push(decoratedLine);
+ });
+
+ if (conflict) {
+ inlineLines.push(getOriginHeaderLine(id));
+ }
+ });
+
+ return inlineLines;
+};
+
+export const setParallelLine = (file) => {
+ const parallelLines = [];
+ let linesObj = { left: [], right: [] };
+
+ file.sections.forEach((section) => {
+ const { conflict, lines, id } = section;
+
+ if (conflict) {
+ linesObj.left.push(getOriginHeaderLine(id));
+ linesObj.right.push(getHeadHeaderLine(id));
+ }
+
+ lines.forEach((line) => {
+ const { type } = line;
+
+ if (conflict) {
+ if (type === 'old') {
+ linesObj.left.push(getLineForParallelView(line, id, 'conflict'));
+ } else if (type === 'new') {
+ linesObj.right.push(getLineForParallelView(line, id, 'conflict', true));
+ }
+ } else {
+ const lineType = type || 'context';
+
+ linesObj.left.push(getLineForParallelView(line, id, lineType));
+ linesObj.right.push(getLineForParallelView(line, id, lineType, true));
+ }
+ });
+
+ linesObj = checkLineLengths(linesObj);
+ });
+
+ for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
+ parallelLines.push([linesObj.right[i], linesObj.left[i]]);
+ }
+ return parallelLines;
+};
+
+export const decorateFiles = (files) => {
+ return files.map((file) => {
+ const f = { ...file };
+ f.content = '';
+ f.resolutionData = {};
+ f.promptDiscardConfirmation = false;
+ f.resolveMode = DEFAULT_RESOLVE_MODE;
+ f.filePath = getFilePath(file);
+ f.blobPath = f.blob_path;
+
+ if (f.type === CONFLICT_TYPES.TEXT) {
+ f.showEditor = false;
+ f.loadEditor = false;
+
+ f.inlineLines = setInlineLine(file);
+ f.parallelLines = setParallelLine(file);
+ } else if (f.type === CONFLICT_TYPES.TEXT_EDITOR) {
+ f.showEditor = true;
+ f.loadEditor = true;
+ }
+ return f;
+ });
+};
+
+export const restoreFileLinesState = (file) => {
+ const inlineLines = file.inlineLines.map((line) => {
+ if (line.hasConflict || line.isHeader) {
+ return { ...line, isSelected: false, isUnselected: false };
+ }
+ return { ...line };
+ });
+
+ const parallelLines = file.parallelLines.map((lines) => {
+ const left = { ...lines[0] };
+ const right = { ...lines[1] };
+ const isLeftMatch = left.hasConflict || left.isHeader;
+ const isRightMatch = right.hasConflict || right.isHeader;
+
+ if (isLeftMatch || isRightMatch) {
+ left.isSelected = false;
+ left.isUnselected = false;
+ right.isSelected = false;
+ right.isUnselected = false;
+ }
+ return [left, right];
+ });
+ return { inlineLines, parallelLines };
+};
+
+export const markLine = (line, selection) => {
+ const updated = { ...line };
+ if (selection === 'head' && line.isHead) {
+ updated.isSelected = true;
+ updated.isUnselected = false;
+ } else if (selection === 'origin' && updated.isOrigin) {
+ updated.isSelected = true;
+ updated.isUnselected = false;
+ } else {
+ updated.isSelected = false;
+ updated.isUnselected = true;
+ }
+ return updated;
+};
diff --git a/app/assets/javascripts/pages/groups/settings/repository/show/index.js b/app/assets/javascripts/pages/groups/settings/repository/show/index.js
index 33c5c40f2be..2c9867653de 100644
--- a/app/assets/javascripts/pages/groups/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/repository/show/index.js
@@ -2,11 +2,9 @@ import DueDateSelectors from '~/due_date_select';
import initSearchSettings from '~/search_settings';
import initSettingsPanels from '~/settings_panels';
-document.addEventListener('DOMContentLoaded', () => {
- // Initialize expandable settings panels
- initSettingsPanels();
+// Initialize expandable settings panels
+initSettingsPanels();
- new DueDateSelectors(); // eslint-disable-line no-new
+new DueDateSelectors(); // eslint-disable-line no-new
- initSearchSettings();
-});
+initSearchSettings();
diff --git a/app/assets/javascripts/vue_shared/security_reports/components/help_icon.vue b/app/assets/javascripts/vue_shared/security_reports/components/help_icon.vue
index 3c606283c7d..26bc9b5d60e 100644
--- a/app/assets/javascripts/vue_shared/security_reports/components/help_icon.vue
+++ b/app/assets/javascripts/vue_shared/security_reports/components/help_icon.vue
@@ -34,7 +34,7 @@ export default {
<span v-if="discoverProjectSecurityPath">
<gl-button
ref="discoverProjectSecurity"
- icon="information-o"
+ icon="question-o"
category="tertiary"
:aria-label="$options.i18n.upgradeToManageVulnerabilities"
/>
diff --git a/app/finders/repositories/commits_with_trailer_finder.rb b/app/finders/repositories/changelog_commits_finder.rb
index 4bd643c345b..08f1144701a 100644
--- a/app/finders/repositories/commits_with_trailer_finder.rb
+++ b/app/finders/repositories/changelog_commits_finder.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module Repositories
- # Finder for obtaining commits between two refs, with a Git trailer set.
- class CommitsWithTrailerFinder
+ # Finder for getting the commits to include in a changelog.
+ class ChangelogCommitsFinder
# The maximum number of commits to retrieve per page.
#
# This value is arbitrarily chosen. Lowering it means more Gitaly calls, but
@@ -20,6 +20,9 @@ module Repositories
# 5-10 Gitaly calls, while keeping memory usage at a reasonable amount.
COMMITS_PER_PAGE = 1024
+ # The regex to use for extracting the SHA of a reverted commit.
+ REVERT_REGEX = /^This reverts commit (?<sha>[0-9a-f]{40})/i.freeze
+
# The `project` argument specifies the project for which to obtain the
# commits.
#
@@ -44,7 +47,7 @@ module Repositories
#
# Example:
#
- # CommitsWithTrailerFinder.new(...).each_page('Signed-off-by') do |commits|
+ # ChangelogCommitsFinder.new(...).each_page('Changelog') do |commits|
# commits.each do |commit|
# ...
# end
@@ -53,12 +56,22 @@ module Repositories
return to_enum(__method__, trailer) unless block_given?
offset = 0
+ reverted = Set.new
response = fetch_commits
while response.any?
commits = []
response.each do |commit|
+ # If the commit is reverted in the same range (by a newer commit), we
+ # won't include it. This works here because commits are processed in
+ # reverse order (= newer first).
+ next if reverted.include?(commit.id)
+
+ if (sha = revert_commit_sha(commit))
+ reverted << sha
+ end
+
commits.push(commit) if commit.trailers.key?(trailer)
end
@@ -78,5 +91,11 @@ module Repositories
.repository
.commits(range, limit: @per_page, offset: offset, trailers: true)
end
+
+ def revert_commit_sha(commit)
+ matches = commit.description.match(REVERT_REGEX)
+
+ matches[:sha] if matches
+ end
end
end
diff --git a/app/services/repositories/changelog_service.rb b/app/services/repositories/changelog_service.rb
index cead0b00a44..3981e91e7f3 100644
--- a/app/services/repositories/changelog_service.rb
+++ b/app/services/repositories/changelog_service.rb
@@ -73,7 +73,7 @@ module Repositories
.new(version: @version, date: @date, config: config)
commits =
- CommitsWithTrailerFinder.new(project: @project, from: from, to: @to)
+ ChangelogCommitsFinder.new(project: @project, from: from, to: @to)
commits.each_page(@trailer) do |page|
mrs = mrs_finder.execute(page)
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index e6f12f4785a..1aaea1999e5 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -101,7 +101,7 @@
= parsed_with_gfm
.gl-mt-3.gl-mb-3
- = f.submit 'Update appearance settings', class: 'btn gl-button btn-success'
+ = f.submit 'Update appearance settings', class: 'btn gl-button btn-confirm'
- if @appearance.persisted? || @appearance.updated_at
.mt-4
- if @appearance.persisted?
diff --git a/app/views/admin/appearances/preview_sign_in.html.haml b/app/views/admin/appearances/preview_sign_in.html.haml
index 6e5bb45c3cc..f972b3b5cbf 100644
--- a/app/views/admin/appearances/preview_sign_in.html.haml
+++ b/app/views/admin/appearances/preview_sign_in.html.haml
@@ -8,5 +8,5 @@
= label_tag :password
= password_field_tag :password, nil, class: "form-control gl-form-input bottom", title: 'This field is required.'
.form-group
- = button_tag "Sign in", class: "btn gl-button btn-success"
+ = button_tag "Sign in", class: "btn gl-button btn-confirm"
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index d9ff4404519..50f3c94bcb3 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -30,7 +30,7 @@
= dropdown_content
= dropdown_loading
= render 'shared/projects/dropdown'
- = link_to new_project_path, class: 'gl-button btn btn-success' do
+ = link_to new_project_path, class: 'gl-button btn btn-confirm' do
New Project
= button_tag "Search", class: "gl-button btn btn-confirm btn-search hide"
diff --git a/app/views/admin/serverless/domains/_form.html.haml b/app/views/admin/serverless/domains/_form.html.haml
index e4b054c7480..85f2260163a 100644
--- a/app/views/admin/serverless/domains/_form.html.haml
+++ b/app/views/admin/serverless/domains/_form.html.haml
@@ -65,7 +65,7 @@
%span.form-text.text-muted
= _("Upload a private key for your certificate")
- = f.submit @domain.persisted? ? _('Save changes') : _('Add domain'), class: "gl-button btn btn-success js-serverless-domain-submit", disabled: @domain.persisted?
+ = f.submit @domain.persisted? ? _('Save changes') : _('Add domain'), class: "gl-button btn btn-confirm js-serverless-domain-submit", disabled: @domain.persisted?
- if @domain.persisted?
%button.gl-button.btn.btn-danger{ type: 'button', data: { toggle: 'modal', target: "#modal-delete-domain" } }
= _('Delete domain')
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index b36f088959e..9de0a90b950 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -286,7 +286,7 @@
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
- = nav_link(controller: [:clusters, :user, :gcp]) do
+ = nav_link(controller: [:cluster_agents, :clusters]) do
= link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
%span
= _('Kubernetes')
diff --git a/app/views/shared/empty_states/_deploy_keys.html.haml b/app/views/shared/empty_states/_deploy_keys.html.haml
index 6fca64d805b..6c615de9c56 100644
--- a/app/views/shared/empty_states/_deploy_keys.html.haml
+++ b/app/views/shared/empty_states/_deploy_keys.html.haml
@@ -6,4 +6,4 @@
.text-content.gl-mx-auto.gl-my-0.gl-p-5
%h4.h4= _('Deploy Keys')
%p= _('Deploy keys grant read/write access to all repositories in your instance')
- = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'btn btn-success btn-md gl-button'
+ = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'gl-button btn btn-confirm btn-md'
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 997bc7b8a98..ff42765f77a 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -20,7 +20,7 @@
= _("To widen your search, change or remove filters above")
- if show_new_issue_link?(@project)
.text-center
- = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success"
+ = link_to _("New issue"), new_project_issue_path(@project), class: "gl-button btn btn-confirm"
- elsif is_opened_state && opened_issues_count == 0 && closed_issues_count > 0
%h4.text-center
= _("There are no open issues")
@@ -28,7 +28,7 @@
= _("To keep this project going, create a new issue")
- if show_new_issue_link?(@project)
.text-center
- = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success"
+ = link_to _("New issue"), new_project_issue_path(@project), class: "gl-button btn btn-confirm"
- elsif is_closed_state && opened_issues_count > 0 && closed_issues_count == 0
%h4.text-center
= _("There are no closed issues")
@@ -42,7 +42,7 @@
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues'
- else
- = link_to _('New issue'), button_path, class: 'btn gl-button btn-success', id: 'new_issue_link'
+ = link_to _('New issue'), button_path, class: 'gl-button btn btn-confirm', id: 'new_issue_link'
- if show_import_button
= render 'projects/issues/import_csv/button', type: :text
@@ -62,7 +62,7 @@
%p
= _("The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.")
.text-center
- = link_to _('Register / Sign In'), new_user_session_path, class: 'btn btn-success'
+ = link_to _('Register / Sign In'), new_user_session_path, class: 'gl-button btn btn-confirm'
- if show_import_button
= render 'projects/issues/import_csv/modal'
diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml
index 837c3afc796..879447f16ae 100644
--- a/app/views/shared/empty_states/_merge_requests.html.haml
+++ b/app/views/shared/empty_states/_merge_requests.html.haml
@@ -20,7 +20,7 @@
= _("To widen your search, change or remove filters above")
.text-center
- if can_create_merge_request
- = link_to _("New merge request"), project_new_merge_request_path(@project), class: "btn btn-success", title: _("New merge request")
+ = link_to _("New merge request"), project_new_merge_request_path(@project), class: "gl-button btn btn-confirm", title: _("New merge request")
- elsif is_opened_state && opened_merged_count == 0 && closed_merged_count > 0
%h4.text-center
= _("There are no open merge requests")
@@ -28,7 +28,7 @@
= _("To keep this project going, create a new merge request")
.text-center
- if can_create_merge_request
- = link_to _("New merge request"), project_new_merge_request_path(@project), class: "btn btn-success", title: _("New merge request")
+ = link_to _("New merge request"), project_new_merge_request_path(@project), class: "gl-button btn btn-confirm", title: _("New merge request")
- elsif is_closed_state && opened_merged_count > 0 && closed_merged_count == 0
%h4.text-center
= _("There are no closed merge requests")
@@ -42,4 +42,4 @@
- if project_select_button
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('New merge request'), type: :merge_requests, with_feature_enabled: 'merge_requests'
- else
- = link_to _('New merge request'), button_path, class: 'btn btn-success', title: _('New merge request'), id: 'new_merge_request_link'
+ = link_to _('New merge request'), button_path, class: 'gl-button btn btn-confirm', title: _('New merge request'), id: 'new_merge_request_link'
diff --git a/app/views/shared/empty_states/_profile_tabs.html.haml b/app/views/shared/empty_states/_profile_tabs.html.haml
index abcf9740200..42a845846d1 100644
--- a/app/views/shared/empty_states/_profile_tabs.html.haml
+++ b/app/views/shared/empty_states/_profile_tabs.html.haml
@@ -13,9 +13,9 @@
%p= current_user_empty_message_description
- if secondary_button_link.present?
- = link_to secondary_button_label, secondary_button_link, class: 'gl-button btn btn-success btn-inverted'
+ = link_to secondary_button_label, secondary_button_link, class: 'gl-button btn btn-confirm btn-inverted'
- if primary_button_link.present?
- = link_to primary_button_label, primary_button_link, class: 'gl-button btn btn-success'
+ = link_to primary_button_label, primary_button_link, class: 'gl-button btn btn-confirm'
- else
%h5= visitor_empty_message
diff --git a/app/views/shared/empty_states/_snippets.html.haml b/app/views/shared/empty_states/_snippets.html.haml
index 105efcc3c88..20ca7954479 100644
--- a/app/views/shared/empty_states/_snippets.html.haml
+++ b/app/views/shared/empty_states/_snippets.html.haml
@@ -12,7 +12,7 @@
= s_('SnippetsEmptyState|Store, share, and embed small pieces of code and text.')
.mt-2<
- if button_path
- = link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn gl-button btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link', data: { qa_selector: 'create_first_snippet_link' }
+ = link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn gl-button btn-confirm', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link', data: { qa_selector: 'create_first_snippet_link' }
= link_to s_('SnippetsEmptyState|Documentation'), help_page_path('user/snippets.md'), class: 'btn gl-button btn-default', title: s_('SnippetsEmptyState|Documentation')
- else
%h4.text-center= s_('SnippetsEmptyState|There are no snippets to show.')
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index 4150406a4ea..0bddffa881a 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -3,7 +3,7 @@
- if can?(current_user, :create_wiki, @wiki.container)
- create_path = wiki_page_path(@wiki, params[:id], view: 'create')
- - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn gl-button btn-success qa-create-first-page-link', title: s_('WikiEmpty|Create your first page')
+ - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn gl-button btn-confirm qa-create-first-page-link', title: s_('WikiEmpty|Create your first page')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
%h4.text-left
@@ -18,7 +18,7 @@
- elsif @project && can?(current_user, :read_issue, @project)
- issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project)
- - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn gl-button btn-success', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
+ - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn gl-button btn-confirm', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4
diff --git a/changelogs/unreleased/322770-change-security-mr-widget-icon.yml b/changelogs/unreleased/322770-change-security-mr-widget-icon.yml
new file mode 100644
index 00000000000..07933f247c1
--- /dev/null
+++ b/changelogs/unreleased/322770-change-security-mr-widget-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Use more common help icon in security report MR widget
+merge_request: 55741
+author:
+type: other
diff --git a/changelogs/unreleased/btn-confirm-admin-appearances.yml b/changelogs/unreleased/btn-confirm-admin-appearances.yml
new file mode 100644
index 00000000000..a315f8bba5c
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-admin-appearances.yml
@@ -0,0 +1,5 @@
+---
+title: Move to btn-confirm from btn-success in admin/appearances directory
+merge_request: 55264
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/btn-confirm-admin-projects.yml b/changelogs/unreleased/btn-confirm-admin-projects.yml
new file mode 100644
index 00000000000..1718ea79164
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-admin-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Move to btn-confirm from btn-success in admin/projects directory
+merge_request: 55274
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/btn-confirm-admin-serverless.yml b/changelogs/unreleased/btn-confirm-admin-serverless.yml
new file mode 100644
index 00000000000..80ede18d4fe
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-admin-serverless.yml
@@ -0,0 +1,5 @@
+---
+title: Move to btn-confirm from btn-success in admin/serverless directory
+merge_request: 55275
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/btn-confirm-empty-states.yml b/changelogs/unreleased/btn-confirm-empty-states.yml
new file mode 100644
index 00000000000..746d6ae71a7
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-empty-states.yml
@@ -0,0 +1,5 @@
+---
+title: Move to btn-confirm in app/views/shared/empty_states directory
+merge_request: 55203
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/cablett-restore-database-structure.yml b/changelogs/unreleased/cablett-restore-database-structure.yml
new file mode 100644
index 00000000000..362cf7146a6
--- /dev/null
+++ b/changelogs/unreleased/cablett-restore-database-structure.yml
@@ -0,0 +1,5 @@
+---
+title: Restore accidental changes to structure.sql
+merge_request: 55352
+author:
+type: other
diff --git a/changelogs/unreleased/changelog-ignore-reverted-commits.yml b/changelogs/unreleased/changelog-ignore-reverted-commits.yml
new file mode 100644
index 00000000000..4b07e846f34
--- /dev/null
+++ b/changelogs/unreleased/changelog-ignore-reverted-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore reverted commits when generating changelogs
+merge_request: 55537
+author:
+type: added
diff --git a/changelogs/unreleased/dora-metrics-modeling.yml b/changelogs/unreleased/dora-metrics-modeling.yml
new file mode 100644
index 00000000000..12724faa7b7
--- /dev/null
+++ b/changelogs/unreleased/dora-metrics-modeling.yml
@@ -0,0 +1,5 @@
+---
+title: Add DORA daily metrics modeling
+merge_request: 55473
+author:
+type: added
diff --git a/changelogs/unreleased/remove-index-add-index.yml b/changelogs/unreleased/remove-index-add-index.yml
new file mode 100644
index 00000000000..57ed8a99772
--- /dev/null
+++ b/changelogs/unreleased/remove-index-add-index.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unique index and add composite key index to Security Orchestration
+merge_request: 55521
+author:
+type: other
diff --git a/changelogs/unreleased/rename-vuln-fingerprint-indexes.yml b/changelogs/unreleased/rename-vuln-fingerprint-indexes.yml
new file mode 100644
index 00000000000..59f9530a8a6
--- /dev/null
+++ b/changelogs/unreleased/rename-vuln-fingerprint-indexes.yml
@@ -0,0 +1,5 @@
+---
+title: Rename vulnerability fingerprints indexes
+merge_request: 55552
+author:
+type: other
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 5c7ae175d1d..0bcd28014ca 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -38,6 +38,7 @@
- design_management
- devops_reports
- disaster_recovery
+- dora_metrics
- dynamic_application_security_testing
- editor_extension
- epics
diff --git a/config/feature_flags/development/dora_daily_metrics.yml b/config/feature_flags/development/dora_daily_metrics.yml
new file mode 100644
index 00000000000..7ca3cf66ea4
--- /dev/null
+++ b/config/feature_flags/development/dora_daily_metrics.yml
@@ -0,0 +1,8 @@
+---
+name: dora_daily_metrics
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55473
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/291746
+milestone: '13.10'
+type: development
+group: group::release
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 310cccd2015..64941f4b69d 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -100,6 +100,8 @@
- 1
- - disallow_two_factor_for_subgroups
- 1
+- - dora_metrics
+ - 1
- - elastic_association_indexer
- 1
- - elastic_commit_indexer
diff --git a/db/migrate/20210225090801_create_dora_daily_metrics.rb b/db/migrate/20210225090801_create_dora_daily_metrics.rb
new file mode 100644
index 00000000000..65c1dbc23e4
--- /dev/null
+++ b/db/migrate/20210225090801_create_dora_daily_metrics.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class CreateDoraDailyMetrics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ create_table :dora_daily_metrics, if_not_exists: true do |t|
+ t.references :environment, null: false, foreign_key: { on_delete: :cascade }, index: false
+ t.date :date, null: false
+ t.integer :deployment_frequency
+ t.integer :lead_time_for_changes_in_seconds
+
+ t.index [:environment_id, :date], unique: true
+ end
+ end
+
+ add_check_constraint :dora_daily_metrics, "deployment_frequency >= 0", 'dora_daily_metrics_deployment_frequency_positive'
+ add_check_constraint :dora_daily_metrics, "lead_time_for_changes_in_seconds >= 0", 'dora_daily_metrics_lead_time_for_changes_in_seconds_positive'
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :dora_daily_metrics
+ end
+ end
+end
diff --git a/db/migrate/20210302155904_remove_index_for_security_orchestration_policy.rb b/db/migrate/20210302155904_remove_index_for_security_orchestration_policy.rb
new file mode 100644
index 00000000000..5d2594f2b9e
--- /dev/null
+++ b/db/migrate/20210302155904_remove_index_for_security_orchestration_policy.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveIndexForSecurityOrchestrationPolicy < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_sop_configs_on_security_policy_management_project_id'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:security_orchestration_policy_configurations, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:security_orchestration_policy_configurations, :security_policy_management_project_id, name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20210302160544_add_index_to_security_orchestration_policy.rb b/db/migrate/20210302160544_add_index_to_security_orchestration_policy.rb
new file mode 100644
index 00000000000..4750e2bdb79
--- /dev/null
+++ b/db/migrate/20210302160544_add_index_to_security_orchestration_policy.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexToSecurityOrchestrationPolicy < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX = 'index_sop_configurations_project_id_policy_project_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :security_orchestration_policy_configurations, [:security_policy_management_project_id, :project_id], name: INDEX
+ end
+
+ def down
+ remove_concurrent_index_by_name :security_orchestration_policy_configurations, INDEX
+ end
+end
diff --git a/db/migrate/20210302212623_rename_vuln_fingerprints_indexes.rb b/db/migrate/20210302212623_rename_vuln_fingerprints_indexes.rb
new file mode 100644
index 00000000000..0f431fbeda5
--- /dev/null
+++ b/db/migrate/20210302212623_rename_vuln_fingerprints_indexes.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class RenameVulnFingerprintsIndexes < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ FINGERPRINT_IDX = :idx_vuln_fingerprints_on_occurrences_id_and_fingerprint
+ FINGERPRINT_IDX_RENAMED = :idx_vuln_fingerprints_on_occurrences_id_and_fingerprint_sha256
+ UNIQ_IDX = :idx_vuln_fingerprints_uniqueness
+ UNIQ_IDX_RENAMED = :idx_vuln_fingerprints_uniqueness_fingerprint_sha256
+
+ disable_ddl_transaction!
+
+ def up
+ # These `unless` checks are necessary for re-running the migrations multiple times
+ unless index_exists_by_name?(:vulnerability_finding_fingerprints, FINGERPRINT_IDX_RENAMED)
+ rename_index :vulnerability_finding_fingerprints, FINGERPRINT_IDX, FINGERPRINT_IDX_RENAMED
+ end
+
+ unless index_exists_by_name?(:vulnerability_finding_fingerprints, UNIQ_IDX_RENAMED)
+ rename_index :vulnerability_finding_fingerprints, UNIQ_IDX, UNIQ_IDX_RENAMED
+ end
+ end
+
+ def down
+ unless index_exists_by_name?(:vulnerability_finding_fingerprints, FINGERPRINT_IDX)
+ rename_index :vulnerability_finding_fingerprints, FINGERPRINT_IDX_RENAMED, FINGERPRINT_IDX
+ end
+
+ unless index_exists_by_name?(:vulnerability_finding_fingerprints, UNIQ_IDX)
+ rename_index :vulnerability_finding_fingerprints, UNIQ_IDX_RENAMED, UNIQ_IDX
+ end
+ end
+end
diff --git a/db/migrate/20210303165201_add_index_for_succeeded_deployments.rb b/db/migrate/20210303165201_add_index_for_succeeded_deployments.rb
new file mode 100644
index 00000000000..1ae5b36d2f2
--- /dev/null
+++ b/db/migrate/20210303165201_add_index_for_succeeded_deployments.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexForSucceededDeployments < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_deployments_on_environment_id_status_and_finished_at'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:deployments, %i[environment_id status finished_at], name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:deployments, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20210225090801 b/db/schema_migrations/20210225090801
new file mode 100644
index 00000000000..364623d6634
--- /dev/null
+++ b/db/schema_migrations/20210225090801
@@ -0,0 +1 @@
+65f27401a76856d6cb284078204bb1b80797fa344e1a4ef3d9638c296f2d72d7 \ No newline at end of file
diff --git a/db/schema_migrations/20210302155904 b/db/schema_migrations/20210302155904
new file mode 100644
index 00000000000..b9f1297934c
--- /dev/null
+++ b/db/schema_migrations/20210302155904
@@ -0,0 +1 @@
+104e767518d55a7caa5ff517efe978287beb629649c681a2871ada8a677a0e13 \ No newline at end of file
diff --git a/db/schema_migrations/20210302160544 b/db/schema_migrations/20210302160544
new file mode 100644
index 00000000000..fd1b67c16bf
--- /dev/null
+++ b/db/schema_migrations/20210302160544
@@ -0,0 +1 @@
+e5c589decb2bc8d3e37451bf9977a810308ee37f5f02ed96226e0be87f8b908d \ No newline at end of file
diff --git a/db/schema_migrations/20210302212623 b/db/schema_migrations/20210302212623
new file mode 100644
index 00000000000..f7e2ab33416
--- /dev/null
+++ b/db/schema_migrations/20210302212623
@@ -0,0 +1 @@
+283645d1791a8d72fa7e327799ede933d89bd69d8db2a87b7ff437a2f5d74da3 \ No newline at end of file
diff --git a/db/schema_migrations/20210303165201 b/db/schema_migrations/20210303165201
new file mode 100644
index 00000000000..55181591e3f
--- /dev/null
+++ b/db/schema_migrations/20210303165201
@@ -0,0 +1 @@
+0a5d306735047101692bbdb37aa829bf70a225af6db7213a8c2eb8168f9a30e9 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index ea2b9de88d0..2ee32f716c5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9359,39 +9359,39 @@ CREATE TABLE application_settings (
elasticsearch_indexed_file_size_limit_kb integer DEFAULT 1024 NOT NULL,
enforce_namespace_storage_limit boolean DEFAULT false NOT NULL,
container_registry_delete_tags_service_timeout integer DEFAULT 250 NOT NULL,
+ kroki_url character varying,
+ kroki_enabled boolean,
+ elasticsearch_client_request_timeout integer DEFAULT 0 NOT NULL,
gitpod_enabled boolean DEFAULT false NOT NULL,
gitpod_url text DEFAULT 'https://gitpod.io/'::text,
- elasticsearch_client_request_timeout integer DEFAULT 0 NOT NULL,
abuse_notification_email character varying,
require_admin_approval_after_user_signup boolean DEFAULT true NOT NULL,
help_page_documentation_base_url text,
automatic_purchased_storage_allocation boolean DEFAULT false NOT NULL,
- container_registry_expiration_policies_worker_capacity integer DEFAULT 0 NOT NULL,
encrypted_ci_jwt_signing_key text,
encrypted_ci_jwt_signing_key_iv text,
- secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
- secret_detection_token_revocation_url text,
- encrypted_secret_detection_token_revocation_token text,
- encrypted_secret_detection_token_revocation_token_iv text,
+ container_registry_expiration_policies_worker_capacity integer DEFAULT 0 NOT NULL,
elasticsearch_analyzers_smartcn_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_smartcn_search boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_search boolean DEFAULT false NOT NULL,
- new_user_signups_cap integer,
+ secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
+ secret_detection_token_revocation_url text,
+ encrypted_secret_detection_token_revocation_token text,
+ encrypted_secret_detection_token_revocation_token_iv text,
domain_denylist_enabled boolean DEFAULT false,
domain_denylist text,
domain_allowlist text,
+ new_user_signups_cap integer,
encrypted_cloud_license_auth_token text,
encrypted_cloud_license_auth_token_iv text,
secret_detection_revocation_token_types_url text,
cloud_license_enabled boolean DEFAULT false NOT NULL,
- kroki_url text,
- kroki_enabled boolean DEFAULT false NOT NULL,
disable_feed_token boolean DEFAULT false NOT NULL,
personal_access_token_prefix text,
rate_limiting_response_text text,
- container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL,
invisible_captcha_enabled boolean DEFAULT false NOT NULL,
+ container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL,
enforce_ssh_key_expiration boolean DEFAULT false NOT NULL,
git_two_factor_session_expiry integer DEFAULT 15 NOT NULL,
keep_latest_artifact boolean DEFAULT true NOT NULL,
@@ -9402,7 +9402,7 @@ CREATE TABLE application_settings (
asset_proxy_whitelist text,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
- CONSTRAINT check_17d9558205 CHECK ((char_length(kroki_url) <= 1024)),
+ CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)),
CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
CONSTRAINT check_57123c9593 CHECK ((char_length(help_page_documentation_base_url) <= 255)),
@@ -12025,6 +12025,25 @@ CREATE SEQUENCE diff_note_positions_id_seq
ALTER SEQUENCE diff_note_positions_id_seq OWNED BY diff_note_positions.id;
+CREATE TABLE dora_daily_metrics (
+ id bigint NOT NULL,
+ environment_id bigint NOT NULL,
+ date date NOT NULL,
+ deployment_frequency integer,
+ lead_time_for_changes_in_seconds integer,
+ CONSTRAINT dora_daily_metrics_deployment_frequency_positive CHECK ((deployment_frequency >= 0)),
+ CONSTRAINT dora_daily_metrics_lead_time_for_changes_in_seconds_positive CHECK ((lead_time_for_changes_in_seconds >= 0))
+);
+
+CREATE SEQUENCE dora_daily_metrics_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE dora_daily_metrics_id_seq OWNED BY dora_daily_metrics.id;
+
CREATE TABLE draft_notes (
id bigint NOT NULL,
merge_request_id integer NOT NULL,
@@ -19007,6 +19026,8 @@ ALTER TABLE ONLY design_user_mentions ALTER COLUMN id SET DEFAULT nextval('desig
ALTER TABLE ONLY diff_note_positions ALTER COLUMN id SET DEFAULT nextval('diff_note_positions_id_seq'::regclass);
+ALTER TABLE ONLY dora_daily_metrics ALTER COLUMN id SET DEFAULT nextval('dora_daily_metrics_id_seq'::regclass);
+
ALTER TABLE ONLY draft_notes ALTER COLUMN id SET DEFAULT nextval('draft_notes_id_seq'::regclass);
ALTER TABLE ONLY elastic_reindexing_subtasks ALTER COLUMN id SET DEFAULT nextval('elastic_reindexing_subtasks_id_seq'::regclass);
@@ -20215,6 +20236,9 @@ ALTER TABLE ONLY design_user_mentions
ALTER TABLE ONLY diff_note_positions
ADD CONSTRAINT diff_note_positions_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY dora_daily_metrics
+ ADD CONSTRAINT dora_daily_metrics_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY draft_notes
ADD CONSTRAINT draft_notes_pkey PRIMARY KEY (id);
@@ -21459,9 +21483,9 @@ CREATE INDEX idx_security_scans_on_scan_type ON security_scans USING btree (scan
CREATE UNIQUE INDEX idx_serverless_domain_cluster_on_clusters_applications_knative ON serverless_domain_cluster USING btree (clusters_applications_knative_id);
-CREATE UNIQUE INDEX idx_vuln_fingerprints_on_occurrences_id_and_fingerprint ON vulnerability_finding_fingerprints USING btree (finding_id, fingerprint_sha256);
+CREATE UNIQUE INDEX idx_vuln_fingerprints_on_occurrences_id_and_fingerprint_sha256 ON vulnerability_finding_fingerprints USING btree (finding_id, fingerprint_sha256);
-CREATE UNIQUE INDEX idx_vuln_fingerprints_uniqueness ON vulnerability_finding_fingerprints USING btree (finding_id, algorithm_type, fingerprint_sha256);
+CREATE UNIQUE INDEX idx_vuln_fingerprints_uniqueness_fingerprint_sha256 ON vulnerability_finding_fingerprints USING btree (finding_id, algorithm_type, fingerprint_sha256);
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue ON vulnerability_external_issue_links USING btree (vulnerability_id, external_type, external_project_key, external_issue_key);
@@ -22095,6 +22119,8 @@ CREATE INDEX index_deployments_on_environment_id_and_id ON deployments USING btr
CREATE INDEX index_deployments_on_environment_id_and_iid_and_project_id ON deployments USING btree (environment_id, iid, project_id);
+CREATE INDEX index_deployments_on_environment_id_status_and_finished_at ON deployments USING btree (environment_id, status, finished_at);
+
CREATE INDEX index_deployments_on_environment_status_sha ON deployments USING btree (environment_id, status, sha);
CREATE INDEX index_deployments_on_id_and_status_and_created_at ON deployments USING btree (id, status, created_at);
@@ -22149,6 +22175,8 @@ CREATE UNIQUE INDEX index_design_user_mentions_on_note_id ON design_user_mention
CREATE UNIQUE INDEX index_diff_note_positions_on_note_id_and_diff_type ON diff_note_positions USING btree (note_id, diff_type);
+CREATE UNIQUE INDEX index_dora_daily_metrics_on_environment_id_and_date ON dora_daily_metrics USING btree (environment_id, date);
+
CREATE INDEX index_draft_notes_on_author_id ON draft_notes USING btree (author_id);
CREATE INDEX index_draft_notes_on_discussion_id ON draft_notes USING btree (discussion_id);
@@ -23535,7 +23563,7 @@ CREATE UNIQUE INDEX index_software_licenses_on_unique_name ON software_licenses
CREATE UNIQUE INDEX index_sop_configs_on_project_id ON security_orchestration_policy_configurations USING btree (project_id);
-CREATE UNIQUE INDEX index_sop_configs_on_security_policy_management_project_id ON security_orchestration_policy_configurations USING btree (security_policy_management_project_id);
+CREATE INDEX index_sop_configurations_project_id_policy_project_id ON security_orchestration_policy_configurations USING btree (security_policy_management_project_id, project_id);
CREATE INDEX index_sprints_iterations_cadence_id ON sprints USING btree (iterations_cadence_id);
@@ -25137,6 +25165,9 @@ ALTER TABLE ONLY boards_epic_board_positions
ALTER TABLE ONLY geo_repository_created_events
ADD CONSTRAINT fk_rails_1f49e46a61 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY dora_daily_metrics
+ ADD CONSTRAINT fk_rails_1fd07aff6f FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY boards_epic_lists
ADD CONSTRAINT fk_rails_1fe6b54909 FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index c3cefb8ed96..d73d4ed5d94 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -900,12 +900,12 @@ not directly accessible via `tag`:
sudo gitlab-ctl registry-garbage-collect -m
```
-Without the `-m` flag, the Container Registry only removes layers that are not referenced by any manifest, tagged or not.
-
Since this is a way more destructive operation, this behavior is disabled by default.
You are likely expecting this way of operation, but before doing that, ensure
that you have backed up all registry data.
+When the command is used without the `-m` flag, the Container Registry only removes layers that are not referenced by any manifest, tagged or not.
+
### Performing garbage collection without downtime
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/764) in GitLab 8.8.
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 3494ceb701e..f176c7e302d 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -246,8 +246,9 @@ have been corrupted, you should reinstall the omnibus package.
## Check TCP connectivity to a remote site
Sometimes you need to know if your GitLab installation can connect to a TCP
-service on another machine - perhaps a PostgreSQL or HTTPS server. A Rake task
-is included to help you with this:
+service on another machine (for example a PostgreSQL or web server)
+in order to troubleshoot proxy issues.
+A Rake task is included to help you with this.
**Omnibus Installation**
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index cdc9baf7573..c22d9d0d72b 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -2349,16 +2349,6 @@ as soon as possible.
</a>
</div>
-## Troubleshooting
-
-See the [troubleshooting documentation](troubleshooting.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
-
## Cloud Native Deployment (optional)
Hybrid installations leverage the benefits of both cloud native and traditional
@@ -2411,3 +2401,9 @@ Webservice pods. Expand available resources using the ratio of 1 vCPU to 1.25 GB
_per each worker process_ for each additional Webservice pod.
For further information on resource usage, see the [Webservice resources](https://docs.gitlab.com/charts/charts/gitlab/webservice/#resources).
+
+<div align="right">
+ <a type="button" class="btn btn-default" href="#setup-components">
+ Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
+ </a>
+</div>
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index 188d298c58b..7025dacdae1 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -2352,13 +2352,3 @@ as soon as possible.
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
-
-## Troubleshooting
-
-See the [troubleshooting documentation](troubleshooting.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index 62f0c993213..fc0afbff194 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -966,13 +966,3 @@ as soon as possible.
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
-
-## Troubleshooting
-
-See the [troubleshooting documentation](troubleshooting.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index 43c93000ed6..80460705eeb 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -2032,13 +2032,3 @@ as soon as possible.
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
-
-## Troubleshooting
-
-See the [troubleshooting documentation](troubleshooting.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index 475a4944492..bb01ed5c5b5 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -2366,13 +2366,3 @@ as soon as possible.
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
-
-## Troubleshooting
-
-See the [troubleshooting documentation](troubleshooting.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index fb9fdfcc3f1..71d74bbc923 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -2027,13 +2027,3 @@ as soon as possible.
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
-
-## Troubleshooting
-
-See the [troubleshooting documentation](troubleshooting.md).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#setup-components">
- Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
diff --git a/doc/api/README.md b/doc/api/README.md
index ddaa1e25414..35eeb5ae99b 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,6 +21,10 @@ Contributions are welcome.
For a list of the available resources and their endpoints, see
[API resources](api_resources.md).
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an introduction and basic steps, see
+[How to make GitLab API calls](https://www.youtube.com/watch?v=0LsMC3ZiXkA).
+
## SCIM **(PREMIUM SAAS)**
GitLab provides an [SCIM API](scim.md) that both implements
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 0e9750094d9..2291a7be381 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -903,6 +903,7 @@ Autogenerated return type of CiCdSettingsUpdate.
| `project` | Project | The project this cluster agent is associated with. |
| `tokens` | ClusterAgentTokenConnection | Tokens associated with the cluster agent. |
| `updatedAt` | Time | Timestamp the cluster agent was updated. |
+| `webPath` | String | Web path of the cluster agent. |
### ClusterAgentDeletePayload
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 49a53039847..50dc0803646 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -395,6 +395,26 @@ these as the changelog entries. You can enrich entries with additional data,
such as a link to the merge request or details about the commit author. You can
[customize the format of a changelog](#customize-the-changelog-output) section with a template.
+### Reverted commits
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55537) in GitLab 13.10.
+
+When generating a changelog for a range, GitLab ignores commits both added and
+reverted in that range. Revert commits themselves _are_ included if they use the
+Git trailer used for generating changelogs.
+
+Imagine the following scenario: you have three commits: A, B, and C. To generate
+changelogs, you use the default trailer `Changelog`. Both A and B use this
+trailer. Commit C is a commit that reverts commit B. When generating a changelog
+for this range, GitLab only includes commit A.
+
+Revert commits are detected by looking for commits where the message contains
+the pattern `This reverts commit SHA`, where `SHA` is the SHA of the commit that
+is reverted.
+
+If a revert commit includes the trailer used for generating changelogs
+(`Changelog` in the above example), the revert commit itself _is_ included.
+
### Customize the changelog output
The output is customized using a YAML configuration file stored in your
diff --git a/doc/development/fe_guide/style/javascript.md b/doc/development/fe_guide/style/javascript.md
index 460d8f58312..334372af1f4 100644
--- a/doc/development/fe_guide/style/javascript.md
+++ b/doc/development/fe_guide/style/javascript.md
@@ -294,3 +294,24 @@ Strive to write many small pure functions and minimize where mutations occur
var c = pureFunction(values.foo);
```
+
+## Export constants as primitives
+
+Prefer exporting constant primitives with a common namespace over exporting objects. This allows for better compile-time reference checks and helps to avoid accidential `undefined`s at runtime. In addition, it helps in reducing bundle sizes.
+
+Only export the constants as a collection (array, or object) when there is a need to iterate over them, for instance, for a prop validator.
+
+ ```javascript
+ // bad
+ export const VARIANT = {
+ WARNING: 'warning',
+ ERROR: 'error',
+ };
+
+ // good
+ export const VARIANT_WARNING = 'warning';
+ export const VARIANT_ERROR = 'error';
+
+ // good, if the constants need to be iterated over
+ export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];
+ ```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 5d42d497d4d..1dcb0901770 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -287,9 +287,9 @@ In GitLab 12.1 and later, only PostgreSQL is supported. In GitLab 13.0 and later
```shell
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
- RELEASE=$(lsb_release -cs) echo "deb http://apt.postgresql.org/pub/repos/apt/ ${RELEASE}"-pgdg main | sudo tee /etc/apt/sources.list.d/pgdg.list
+ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
sudo apt update
- sudo apt -y install postgresql-11 postgresql-client-11 libpq-dev
+ sudo apt -y install postgresql-12 postgresql-client-12 libpq-dev
```
1. Verify the PostgreSQL version you have is supported by the version of GitLab you're
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 93d07256f1a..0ac4f4fbf6e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6317,21 +6317,42 @@ msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
+msgid "ClusterAgents|Access tokens"
+msgstr ""
+
msgid "ClusterAgents|An error occurred while loading your GitLab Agents"
msgstr ""
+msgid "ClusterAgents|An error occurred while loading your agent"
+msgstr ""
+
msgid "ClusterAgents|Configuration"
msgstr ""
msgid "ClusterAgents|Connect your cluster with the GitLab Agent"
msgstr ""
+msgid "ClusterAgents|Created by"
+msgstr ""
+
+msgid "ClusterAgents|Created by %{name} %{time}"
+msgstr ""
+
+msgid "ClusterAgents|Date created"
+msgstr ""
+
+msgid "ClusterAgents|Description"
+msgstr ""
+
msgid "ClusterAgents|Integrate Kubernetes with a GitLab Agent"
msgstr ""
msgid "ClusterAgents|Integrate with the GitLab Agent"
msgstr ""
+msgid "ClusterAgents|Learn how to create an agent access token"
+msgstr ""
+
msgid "ClusterAgents|Name"
msgstr ""
@@ -6341,6 +6362,15 @@ msgstr ""
msgid "ClusterAgents|The GitLab Kubernetes Agent allows an Infrastructure as Code, GitOps approach to integrating Kubernetes clusters with GitLab. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
+msgid "ClusterAgents|This agent has no tokens"
+msgstr ""
+
+msgid "ClusterAgents|Unknown user"
+msgstr ""
+
+msgid "ClusterAgents|You will need to create a token to connect to your agent"
+msgstr ""
+
msgid "ClusterAgent|This feature is only available for premium plans"
msgstr ""
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 14a5e7da7d2..a99db2664a7 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -648,7 +648,9 @@ RSpec.describe Projects::BranchesController do
end
it 'sets active and stale branches' do
- expect(assigns[:active_branches]).to eq([])
+ expect(assigns[:active_branches].map(&:name)).not_to include(
+ "feature", "improve/awesome", "merge-test", "markdown", "feature_conflict", "'test'"
+ )
expect(assigns[:stale_branches].map(&:name)).to eq(
["feature", "improve/awesome", "merge-test", "markdown", "feature_conflict", "'test'"]
)
@@ -660,7 +662,9 @@ RSpec.describe Projects::BranchesController do
end
it 'sets active and stale branches' do
- expect(assigns[:active_branches]).to eq([])
+ expect(assigns[:active_branches].map(&:name)).not_to include(
+ "feature", "improve/awesome", "merge-test", "markdown", "feature_conflict", "'test'"
+ )
expect(assigns[:stale_branches].map(&:name)).to eq(
["feature", "improve/awesome", "merge-test", "markdown", "feature_conflict", "'test'"]
)
diff --git a/spec/finders/repositories/changelog_commits_finder_spec.rb b/spec/finders/repositories/changelog_commits_finder_spec.rb
new file mode 100644
index 00000000000..fe40666a955
--- /dev/null
+++ b/spec/finders/repositories/changelog_commits_finder_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Repositories::ChangelogCommitsFinder do
+ let_it_be(:project) { create(:project, :repository) }
+
+ describe '#each_page' do
+ it 'only yields commits with the given trailer' do
+ finder = described_class.new(
+ project: project,
+ from: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ to: 'c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd'
+ )
+
+ commits = finder.each_page('Signed-off-by').to_a.flatten
+
+ expect(commits.length).to eq(1)
+ expect(commits.first.id).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ expect(commits.first.trailers).to eq(
+ 'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>'
+ )
+ end
+
+ it 'ignores commits that are reverted' do
+ # This range of commits is found on the branch
+ # https://gitlab.com/gitlab-org/gitlab-test/-/commits/trailers.
+ finder = described_class.new(
+ project: project,
+ from: 'ddd0f15ae83993f5cb66a927a28673882e99100b',
+ to: '694e6c2f08cad00d183682d9dede99615998a630'
+ )
+
+ commits = finder.each_page('Changelog').to_a.flatten
+
+ expect(commits).to be_empty
+ end
+
+ it 'includes revert commits if they have a trailer' do
+ finder = described_class.new(
+ project: project,
+ from: 'ddd0f15ae83993f5cb66a927a28673882e99100b',
+ to: 'f0a5ed60d24c98ec6d00ac010c1f3f01ee0a8373'
+ )
+
+ initial_commit = project.commit('ed2e92bf50b3da2c7cbbab053f4977a4ecbd109a')
+ revert_commit = project.commit('f0a5ed60d24c98ec6d00ac010c1f3f01ee0a8373')
+
+ commits = finder.each_page('Changelog').to_a.flatten
+
+ expect(commits).to eq([revert_commit, initial_commit])
+ end
+
+ it 'supports paginating of commits' do
+ finder = described_class.new(
+ project: project,
+ from: 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8',
+ to: '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
+ per_page: 1
+ )
+
+ commits = finder.each_page('Signed-off-by')
+
+ expect(commits.count).to eq(4)
+ end
+ end
+end
diff --git a/spec/finders/repositories/commits_with_trailer_finder_spec.rb b/spec/finders/repositories/commits_with_trailer_finder_spec.rb
deleted file mode 100644
index 0c457aae340..00000000000
--- a/spec/finders/repositories/commits_with_trailer_finder_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Repositories::CommitsWithTrailerFinder do
- let(:project) { create(:project, :repository) }
-
- describe '#each_page' do
- it 'only yields commits with the given trailer' do
- finder = described_class.new(
- project: project,
- from: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
- to: 'c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd'
- )
-
- commits = finder.each_page('Signed-off-by').to_a.flatten
-
- expect(commits.length).to eq(1)
- expect(commits.first.id).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
- expect(commits.first.trailers).to eq(
- 'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>'
- )
- end
-
- it 'supports paginating of commits' do
- finder = described_class.new(
- project: project,
- from: 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8',
- to: '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
- per_page: 1
- )
-
- commits = finder.each_page('Signed-off-by')
-
- expect(commits.count).to eq(4)
- end
- end
-end
diff --git a/spec/frontend/merge_conflicts/store/actions_spec.js b/spec/frontend/merge_conflicts/store/actions_spec.js
new file mode 100644
index 00000000000..352f1783b87
--- /dev/null
+++ b/spec/frontend/merge_conflicts/store/actions_spec.js
@@ -0,0 +1,257 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import testAction from 'helpers/vuex_action_helper';
+import createFlash from '~/flash';
+import { INTERACTIVE_RESOLVE_MODE, EDIT_RESOLVE_MODE } from '~/merge_conflicts/constants';
+import * as actions from '~/merge_conflicts/store/actions';
+import * as types from '~/merge_conflicts/store/mutation_types';
+import { restoreFileLinesState, markLine, decorateFiles } from '~/merge_conflicts/utils';
+
+jest.mock('~/flash.js');
+jest.mock('~/merge_conflicts/utils');
+
+describe('merge conflicts actions', () => {
+ let mock;
+
+ const files = [
+ {
+ blobPath: 'a',
+ },
+ { blobPath: 'b' },
+ ];
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('fetchConflictsData', () => {
+ const conflictsPath = 'conflicts/path/mock';
+
+ it('on success dispatches setConflictsData', (done) => {
+ mock.onGet(conflictsPath).reply(200, {});
+ testAction(
+ actions.fetchConflictsData,
+ conflictsPath,
+ {},
+ [
+ { type: types.SET_LOADING_STATE, payload: true },
+ { type: types.SET_LOADING_STATE, payload: false },
+ ],
+ [{ type: 'setConflictsData', payload: {} }],
+ done,
+ );
+ });
+
+ it('when data has type equal to error ', (done) => {
+ mock.onGet(conflictsPath).reply(200, { type: 'error', message: 'error message' });
+ testAction(
+ actions.fetchConflictsData,
+ conflictsPath,
+ {},
+ [
+ { type: types.SET_LOADING_STATE, payload: true },
+ { type: types.SET_FAILED_REQUEST, payload: 'error message' },
+ { type: types.SET_LOADING_STATE, payload: false },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('when request fails ', (done) => {
+ mock.onGet(conflictsPath).reply(400);
+ testAction(
+ actions.fetchConflictsData,
+ conflictsPath,
+ {},
+ [
+ { type: types.SET_LOADING_STATE, payload: true },
+ { type: types.SET_FAILED_REQUEST },
+ { type: types.SET_LOADING_STATE, payload: false },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('submitResolvedConflicts', () => {
+ useMockLocationHelper();
+ const resolveConflictsPath = 'resolve/conflicts/path/mock';
+
+ it('on success reloads the page', (done) => {
+ mock.onPost(resolveConflictsPath).reply(200, { redirect_to: 'hrefPath' });
+ testAction(
+ actions.submitResolvedConflicts,
+ resolveConflictsPath,
+ {},
+ [{ type: types.SET_SUBMIT_STATE, payload: true }],
+ [],
+ () => {
+ expect(window.location.assign).toHaveBeenCalledWith('hrefPath');
+ done();
+ },
+ );
+ });
+
+ it('on errors shows flash', (done) => {
+ mock.onPost(resolveConflictsPath).reply(400);
+ testAction(
+ actions.submitResolvedConflicts,
+ resolveConflictsPath,
+ {},
+ [
+ { type: types.SET_SUBMIT_STATE, payload: true },
+ { type: types.SET_SUBMIT_STATE, payload: false },
+ ],
+ [],
+ () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'Failed to save merge conflicts resolutions. Please try again!',
+ });
+ done();
+ },
+ );
+ });
+ });
+
+ describe('setConflictsData', () => {
+ it('INTERACTIVE_RESOLVE_MODE updates the correct file ', (done) => {
+ decorateFiles.mockReturnValue([{ bar: 'baz' }]);
+ testAction(
+ actions.setConflictsData,
+ { files, foo: 'bar' },
+ {},
+ [
+ {
+ type: types.SET_CONFLICTS_DATA,
+ payload: { foo: 'bar', files: [{ bar: 'baz' }] },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setFileResolveMode', () => {
+ it('INTERACTIVE_RESOLVE_MODE updates the correct file ', (done) => {
+ testAction(
+ actions.setFileResolveMode,
+ { file: files[0], mode: INTERACTIVE_RESOLVE_MODE },
+ { conflictsData: { files }, getFileIndex: () => 0 },
+ [
+ {
+ type: types.UPDATE_FILE,
+ payload: {
+ file: { ...files[0], showEditor: false, resolveMode: INTERACTIVE_RESOLVE_MODE },
+ index: 0,
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('EDIT_RESOLVE_MODE updates the correct file ', (done) => {
+ restoreFileLinesState.mockReturnValue([]);
+ const file = {
+ ...files[0],
+ showEditor: true,
+ loadEditor: true,
+ resolutionData: {},
+ resolveMode: EDIT_RESOLVE_MODE,
+ };
+ testAction(
+ actions.setFileResolveMode,
+ { file: files[0], mode: EDIT_RESOLVE_MODE },
+ { conflictsData: { files }, getFileIndex: () => 0 },
+ [
+ {
+ type: types.UPDATE_FILE,
+ payload: {
+ file,
+ index: 0,
+ },
+ },
+ ],
+ [],
+ () => {
+ expect(restoreFileLinesState).toHaveBeenCalledWith(file);
+ done();
+ },
+ );
+ });
+ });
+
+ describe('setPromptConfirmationState', () => {
+ it('updates the correct file ', (done) => {
+ testAction(
+ actions.setPromptConfirmationState,
+ { file: files[0], promptDiscardConfirmation: true },
+ { conflictsData: { files }, getFileIndex: () => 0 },
+ [
+ {
+ type: types.UPDATE_FILE,
+ payload: {
+ file: { ...files[0], promptDiscardConfirmation: true },
+ index: 0,
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('handleSelected', () => {
+ const file = {
+ ...files[0],
+ inlineLines: [{ id: 1, hasConflict: true }, { id: 2 }],
+ parallelLines: [
+ [{ id: 1, hasConflict: true }, { id: 1 }],
+ [{ id: 2 }, { id: 3 }],
+ ],
+ };
+
+ it('updates the correct file ', (done) => {
+ const marLikeMockReturn = { foo: 'bar' };
+ markLine.mockReturnValue(marLikeMockReturn);
+
+ testAction(
+ actions.handleSelected,
+ { file, line: { id: 1, section: 'baz' } },
+ { conflictsData: { files }, getFileIndex: () => 0 },
+ [
+ {
+ type: types.UPDATE_FILE,
+ payload: {
+ file: {
+ ...file,
+ resolutionData: { 1: 'baz' },
+ inlineLines: [marLikeMockReturn, { id: 2 }],
+ parallelLines: [
+ [marLikeMockReturn, marLikeMockReturn],
+ [{ id: 2 }, { id: 3 }],
+ ],
+ },
+ index: 0,
+ },
+ },
+ ],
+ [],
+ () => {
+ expect(markLine).toHaveBeenCalledTimes(3);
+ done();
+ },
+ );
+ });
+ });
+});
diff --git a/spec/services/projects/branches_by_mode_service_spec.rb b/spec/services/projects/branches_by_mode_service_spec.rb
index 9199c3e0b3a..e8bcda8a9c4 100644
--- a/spec/services/projects/branches_by_mode_service_spec.rb
+++ b/spec/services/projects/branches_by_mode_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Projects::BranchesByModeService do
branches, prev_page, next_page = subject
- expect(branches.size).to eq(10)
+ expect(branches.size).to eq(11)
expect(next_page).to be_nil
expect(prev_page).to eq("/#{project.full_path}/-/branches/all?offset=2&page=3")
end
@@ -99,7 +99,7 @@ RSpec.describe Projects::BranchesByModeService do
it 'returns branches after the specified branch' do
branches, prev_page, next_page = subject
- expect(branches.size).to eq(14)
+ expect(branches.size).to eq(15)
expect(next_page).to be_nil
expect(prev_page).to eq("/#{project.full_path}/-/branches/all?offset=3&page=4&sort=name_asc")
end
diff --git a/spec/services/repositories/changelog_service_spec.rb b/spec/services/repositories/changelog_service_spec.rb
index 91bc248b20a..dab38445ccf 100644
--- a/spec/services/repositories/changelog_service_spec.rb
+++ b/spec/services/repositories/changelog_service_spec.rb
@@ -80,15 +80,43 @@ RSpec.describe Repositories::ChangelogService do
expect(changelog).to include('Title 1', 'Title 2')
end
- it 'uses the target branch when "to" is unspecified' do
- allow(MergeRequestDiffCommit)
- .to receive(:oldest_merge_request_id_per_commit)
- .with(project.id, [commit3.id, commit2.id, commit1.id])
- .and_return([
- { sha: sha2, merge_request_id: mr1.id },
- { sha: sha3, merge_request_id: mr2.id }
- ])
+ it "ignores a commit when it's both added and reverted in the same range" do
+ create_commit(
+ project,
+ author2,
+ commit_message: "Title 4\n\nThis reverts commit #{sha4}",
+ actions: [{ action: 'create', content: 'bar', file_path: 'd.txt' }]
+ )
+
+ described_class
+ .new(project, creator, version: '1.0.0', from: sha1)
+ .execute
+
+ changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
+ expect(changelog).to include('Title 1', 'Title 2')
+ expect(changelog).not_to include('Title 3', 'Title 4')
+ end
+
+ it 'includes a revert commit when it has a trailer' do
+ create_commit(
+ project,
+ author2,
+ commit_message: "Title 4\n\nThis reverts commit #{sha4}\n\nChangelog: added",
+ actions: [{ action: 'create', content: 'bar', file_path: 'd.txt' }]
+ )
+
+ described_class
+ .new(project, creator, version: '1.0.0', from: sha1)
+ .execute
+
+ changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
+
+ expect(changelog).to include('Title 1', 'Title 2', 'Title 4')
+ expect(changelog).not_to include('Title 3')
+ end
+
+ it 'uses the target branch when "to" is unspecified' do
described_class
.new(project, creator, version: '1.0.0', from: sha1)
.execute
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 0b1d4debd03..7eda6706b0c 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -77,7 +77,8 @@ module TestEnv
'sha-starting-with-large-number' => '8426165',
'invalid-utf8-diff-paths' => '99e4853',
'compare-with-merge-head-source' => 'f20a03d',
- 'compare-with-merge-head-target' => '2f1e176'
+ 'compare-with-merge-head-target' => '2f1e176',
+ 'trailers' => 'f0a5ed6'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100644..100755
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100644..100755
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore