diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 06fe4b7ff5e..b6002999855 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -66,6 +66,13 @@ docs-lint links:
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
+ # Delete the redirect files, rebuild, and check internal links again, to see if we are linking to redirects.
+ # Don't delete the documentation/index.md, which is a false positive for the simple grep.
+ - grep -rl "redirect_to:" /tmp/gitlab-docs/content/ee/ | grep -v "development/documentation/index.md" | xargs rm -f
+ - bundle exec nanoc
+ - echo -e "\e[96mThe following test fails when a doc links to a redirect file."
+ - echo -e "\e[96mMake sure all links point to the correct page."
+ - bundle exec nanoc check internal_links
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 656b97ce9ae..7eb6369677e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-3cbd24e3e2fd09eb526d04f8a419f6d103c440dc
+32bbe0bf214e052e107021742e801cffb09b8ca5
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 9218cc00303..53eb8cd8eb8 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -10,7 +10,10 @@ import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
import { updateHistory } from '~/lib/utils/url_utility';
-import eventHub from '../../notes/event_hub';
+
+import notesEventHub from '../../notes/event_hub';
+import eventHub from '../event_hub';
+
import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
import NoChanges from './no_changes.vue';
@@ -22,6 +25,7 @@ import MergeConflictWarning from './merge_conflict_warning.vue';
import CollapsedFilesWarning from './collapsed_files_warning.vue';
import { diffsApp } from '../utils/performance';
+import { fileByFile } from '../utils/preferences';
import {
TREE_LIST_WIDTH_STORAGE_KEY,
@@ -34,6 +38,7 @@ import {
ALERT_OVERFLOW_HIDDEN,
ALERT_MERGE_CONFLICT,
ALERT_COLLAPSED_FILES,
+ EVT_VIEW_FILE_BY_FILE,
} from '../constants';
export default {
@@ -114,7 +119,7 @@ export default {
required: false,
default: false,
},
- viewDiffsFileByFile: {
+ fileByFileUserPreference: {
type: Boolean,
required: false,
default: false,
@@ -154,6 +159,7 @@ export default {
'conflictResolutionPath',
'canMerge',
'hasConflicts',
+ 'viewDiffsFileByFile',
]),
...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
@@ -254,7 +260,7 @@ export default {
projectPath: this.projectPath,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
- viewDiffsFileByFile: this.viewDiffsFileByFile,
+ viewDiffsFileByFile: fileByFile(this.fileByFileUserPreference),
});
if (this.shouldShow) {
@@ -278,8 +284,10 @@ export default {
created() {
this.adjustView();
- eventHub.$once('fetchDiffData', this.fetchData);
- eventHub.$on('refetchDiffData', this.refetchDiffData);
+ notesEventHub.$once('fetchDiffData', this.fetchData);
+ notesEventHub.$on('refetchDiffData', this.refetchDiffData);
+ eventHub.$on(EVT_VIEW_FILE_BY_FILE, this.fileByFileListener);
+
this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
this.unwatchDiscussions = this.$watch(
@@ -300,8 +308,10 @@ export default {
beforeDestroy() {
diffsApp.deinstrument();
- eventHub.$off('fetchDiffData', this.fetchData);
- eventHub.$off('refetchDiffData', this.refetchDiffData);
+ eventHub.$off(EVT_VIEW_FILE_BY_FILE, this.fileByFileListener);
+ notesEventHub.$off('refetchDiffData', this.refetchDiffData);
+ notesEventHub.$off('fetchDiffData', this.fetchData);
+
this.removeEventListeners();
},
methods: {
@@ -319,7 +329,11 @@ export default {
'scrollToFile',
'setShowTreeList',
'navigateToDiffFileIndex',
+ 'setFileByFile',
]),
+ fileByFileListener({ setting } = {}) {
+ this.setFileByFile({ fileByFile: setting });
+ },
navigateToDiffFileNumber(number) {
this.navigateToDiffFileIndex(number - 1);
},
@@ -371,7 +385,7 @@ export default {
}
if (!this.isNotesFetched) {
- eventHub.$emit('fetchNotesData');
+ notesEventHub.$emit('fetchNotesData');
}
},
setDiscussions() {
diff --git a/app/assets/javascripts/diffs/components/settings_dropdown.vue b/app/assets/javascripts/diffs/components/settings_dropdown.vue
index 590b2127e6b..b8904de8049 100644
--- a/app/assets/javascripts/diffs/components/settings_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/settings_dropdown.vue
@@ -1,16 +1,38 @@
@@ -84,5 +109,10 @@ export default {
{{ __('Show whitespace changes') }}
+
+
+ {{ $options.i18n.fileByFile }}
+
+
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 79f8c08e389..07e27bd8e47 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -77,6 +77,11 @@ export const ALERT_COLLAPSED_FILES = 'collapsed';
export const DIFF_FILE_AUTOMATIC_COLLAPSE = 'automatic';
export const DIFF_FILE_MANUAL_COLLAPSE = 'manual';
+// Diff view single file mode
+export const DIFF_FILE_BY_FILE_COOKIE_NAME = 'fileViewMode';
+export const DIFF_VIEW_FILE_BY_FILE = 'single';
+export const DIFF_VIEW_ALL_FILES = 'all';
+
// State machine states
export const STATE_IDLING = 'idle';
export const STATE_LOADING = 'loading';
@@ -98,6 +103,7 @@ export const RENAMED_DIFF_TRANSITIONS = {
// MR Diffs known events
export const EVT_EXPAND_ALL_FILES = 'mr:diffs:expandAllFiles';
+export const EVT_VIEW_FILE_BY_FILE = 'mr:diffs:preference:fileByFile';
export const EVT_PERF_MARK_FILE_TREE_START = 'mr:diffs:perf:fileTreeStart';
export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd';
export const EVT_PERF_MARK_DIFF_FILES_START = 'mr:diffs:perf:filesStart';
diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js
index 4ec24d452bf..c4ac99ead91 100644
--- a/app/assets/javascripts/diffs/i18n.js
+++ b/app/assets/javascripts/diffs/i18n.js
@@ -16,3 +16,7 @@ export const DIFF_FILE = {
autoCollapsed: __('Files with large changes are collapsed by default.'),
expand: __('Expand file'),
};
+
+export const SETTINGS_DROPDOWN = {
+ fileByFile: __('Show one file at a time'),
+};
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 06a138b1e13..587220488be 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -116,7 +116,7 @@ export default function initDiffsApp(store) {
isFluidLayout: this.isFluidLayout,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
- viewDiffsFileByFile: this.viewDiffsFileByFile,
+ fileByFileUserPreference: this.viewDiffsFileByFile,
},
});
},
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 1a0e65bbb3e..51d9fe12102 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -44,6 +44,9 @@ import {
EVT_PERF_MARK_FILE_TREE_START,
EVT_PERF_MARK_FILE_TREE_END,
EVT_PERF_MARK_DIFF_FILES_START,
+ DIFF_VIEW_FILE_BY_FILE,
+ DIFF_VIEW_ALL_FILES,
+ DIFF_FILE_BY_FILE_COOKIE_NAME,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
import { isCollapsed } from '../diff_file';
@@ -57,6 +60,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
} = options;
commit(types.SET_BASE_CONFIG, {
endpoint,
@@ -66,6 +70,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
});
};
@@ -694,3 +699,14 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => {
commit(types.VIEW_DIFF_FILE, fileHash);
};
+
+export const setFileByFile = ({ commit }, { fileByFile }) => {
+ const fileViewMode = fileByFile ? DIFF_VIEW_FILE_BY_FILE : DIFF_VIEW_ALL_FILES;
+ commit(types.SET_FILE_BY_FILE, fileByFile);
+
+ Cookies.set(DIFF_FILE_BY_FILE_COOKIE_NAME, fileViewMode);
+
+ historyPushState(
+ mergeUrlParams({ [DIFF_FILE_BY_FILE_COOKIE_NAME]: fileViewMode }, window.location.href),
+ );
+};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 001d9d9f594..c331e52c887 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -5,6 +5,8 @@ import {
DIFF_VIEW_COOKIE_NAME,
DIFF_WHITESPACE_COOKIE_NAME,
} from '../../constants';
+
+import { fileByFile } from '../../utils/preferences';
import { getDefaultWhitespace } from '../utils';
const viewTypeFromQueryString = getParameterValues('view')[0];
@@ -39,6 +41,7 @@ export default () => ({
highlightedRow: null,
renderTreeList: true,
showWhitespace: getDefaultWhitespace(whiteSpaceFromQueryString, whiteSpaceFromCookie),
+ viewDiffsFileByFile: fileByFile(),
fileFinderVisible: false,
dismissEndpoint: '',
showSuggestPopover: true,
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index ed694419ab1..25184028799 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -28,6 +28,7 @@ export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
export const SET_TREE_DATA = 'SET_TREE_DATA';
export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST';
export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE';
+export const SET_FILE_BY_FILE = 'SET_FILE_BY_FILE';
export const TOGGLE_FILE_FINDER_VISIBLE = 'TOGGLE_FILE_FINDER_VISIBLE';
export const REQUEST_FULL_DIFF = 'REQUEST_FULL_DIFF';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 7b08c5e75e1..69ae3f705e3 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -36,6 +36,7 @@ export default {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
} = options;
Object.assign(state, {
endpoint,
@@ -45,6 +46,7 @@ export default {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
});
},
@@ -352,4 +354,7 @@ export default {
[types.SET_SHOW_SUGGEST_POPOVER](state) {
state.showSuggestPopover = false;
},
+ [types.SET_FILE_BY_FILE](state, fileByFile) {
+ state.viewDiffsFileByFile = fileByFile;
+ },
};
diff --git a/app/assets/javascripts/diffs/utils/preferences.js b/app/assets/javascripts/diffs/utils/preferences.js
new file mode 100644
index 00000000000..e440de3350a
--- /dev/null
+++ b/app/assets/javascripts/diffs/utils/preferences.js
@@ -0,0 +1,22 @@
+import Cookies from 'js-cookie';
+import { getParameterValues } from '~/lib/utils/url_utility';
+
+import { DIFF_FILE_BY_FILE_COOKIE_NAME, DIFF_VIEW_FILE_BY_FILE } from '../constants';
+
+export function fileByFile(pref = false) {
+ const search = getParameterValues(DIFF_FILE_BY_FILE_COOKIE_NAME)?.[0];
+ const cookie = Cookies.get(DIFF_FILE_BY_FILE_COOKIE_NAME);
+ let viewFileByFile = pref;
+
+ // use the cookie first, if it exists
+ if (cookie) {
+ viewFileByFile = cookie === DIFF_VIEW_FILE_BY_FILE;
+ }
+
+ // the search parameter of the URL should override, if it exists
+ if (search) {
+ viewFileByFile = search === DIFF_VIEW_FILE_BY_FILE;
+ }
+
+ return viewFileByFile;
+}
diff --git a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
index 790460c79f1..f2d68054e80 100644
--- a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
+++ b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
@@ -12,10 +12,12 @@ import {
GlLink,
GlDropdown,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
GlSprintf,
GlLoadingIcon,
} from '@gitlab/ui';
+import * as Sentry from '~/sentry/wrapper';
import { s__, __, n__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { redirectTo } from '~/lib/utils/url_utility';
@@ -46,6 +48,7 @@ export default {
GlLink,
GlDropdown,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
GlSprintf,
GlLoadingIcon,
@@ -59,11 +62,19 @@ export default {
type: String,
required: true,
},
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
projectId: {
type: String,
required: true,
},
- refs: {
+ branches: {
+ type: Array,
+ required: true,
+ },
+ tags: {
type: Array,
required: true,
},
@@ -94,7 +105,9 @@ export default {
data() {
return {
searchTerm: '',
- refValue: this.refParam,
+ refValue: {
+ shortName: this.refParam,
+ },
form: {},
error: null,
warnings: [],
@@ -104,9 +117,21 @@ export default {
};
},
computed: {
- filteredRefs() {
- const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
- return this.refs.filter(ref => ref.toLowerCase().includes(lowerCasedSearchTerm));
+ lowerCasedSearchTerm() {
+ return this.searchTerm.toLowerCase();
+ },
+ filteredBranches() {
+ return this.branches.filter(branch =>
+ branch.shortName.toLowerCase().includes(this.lowerCasedSearchTerm),
+ );
+ },
+ filteredTags() {
+ return this.tags.filter(tag =>
+ tag.shortName.toLowerCase().includes(this.lowerCasedSearchTerm),
+ );
+ },
+ hasTags() {
+ return this.tags.length > 0;
},
overMaxWarningsLimit() {
return this.totalWarnings > this.maxWarnings;
@@ -120,14 +145,27 @@ export default {
shouldShowWarning() {
return this.warnings.length > 0 && !this.isWarningDismissed;
},
+ refShortName() {
+ return this.refValue.shortName;
+ },
+ refFullName() {
+ return this.refValue.fullName;
+ },
variables() {
- return this.form[this.refValue]?.variables ?? [];
+ return this.form[this.refFullName]?.variables ?? [];
},
descriptions() {
- return this.form[this.refValue]?.descriptions ?? {};
+ return this.form[this.refFullName]?.descriptions ?? {};
},
},
created() {
+ // this is needed until we add support for ref type in url query strings
+ // ensure default branch is called with full ref on load
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/287815
+ if (this.refValue.shortName === this.defaultBranch) {
+ this.refValue.fullName = `refs/heads/${this.refValue.shortName}`;
+ }
+
this.setRefSelected(this.refValue);
},
methods: {
@@ -170,19 +208,19 @@ export default {
setRefSelected(refValue) {
this.refValue = refValue;
- if (!this.form[refValue]) {
- this.fetchConfigVariables(refValue)
+ if (!this.form[this.refFullName]) {
+ this.fetchConfigVariables(this.refFullName || this.refShortName)
.then(({ descriptions, params }) => {
- Vue.set(this.form, refValue, {
+ Vue.set(this.form, this.refFullName, {
variables: [],
descriptions,
});
// Add default variables from yml
- this.setVariableParams(refValue, VARIABLE_TYPE, params);
+ this.setVariableParams(this.refFullName, VARIABLE_TYPE, params);
})
.catch(() => {
- Vue.set(this.form, refValue, {
+ Vue.set(this.form, this.refFullName, {
variables: [],
descriptions: {},
});
@@ -190,20 +228,19 @@ export default {
.finally(() => {
// Add/update variables, e.g. from query string
if (this.variableParams) {
- this.setVariableParams(refValue, VARIABLE_TYPE, this.variableParams);
+ this.setVariableParams(this.refFullName, VARIABLE_TYPE, this.variableParams);
}
if (this.fileParams) {
- this.setVariableParams(refValue, FILE_TYPE, this.fileParams);
+ this.setVariableParams(this.refFullName, FILE_TYPE, this.fileParams);
}
// Adds empty var at the end of the form
- this.addEmptyVariable(refValue);
+ this.addEmptyVariable(this.refFullName);
});
}
},
-
isSelected(ref) {
- return ref === this.refValue;
+ return ref.fullName === this.refValue.fullName;
},
removeVariable(index) {
this.variables.splice(index, 1);
@@ -211,7 +248,6 @@ export default {
canRemove(index) {
return index < this.variables.length - 1;
},
-
fetchConfigVariables(refValue) {
if (!gon?.features?.newPipelineFormPrefilledVars) {
return Promise.resolve({ params: {}, descriptions: {} });
@@ -251,9 +287,11 @@ export default {
return { params, descriptions };
})
- .catch(() => {
+ .catch(error => {
this.isLoading = false;
+ Sentry.captureException(error);
+
return { params: {}, descriptions: {} };
});
},
@@ -268,7 +306,9 @@ export default {
return axios
.post(this.pipelinesPath, {
- ref: this.refValue,
+ // send shortName as fall back for query params
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/287815
+ ref: this.refValue.fullName || this.refShortName,
variables_attributes: filteredVariables,
})
.then(({ data }) => {
@@ -326,20 +366,29 @@ export default {
-
-
+
+
+ {{ __('Branches') }}
- {{ ref }}
+ {{ branch.shortName }}
+
+ {{ __('Tags') }}
+
+ {{ tag.shortName }}
@@ -372,7 +421,7 @@ export default {
:placeholder="s__('CiVariables|Input variable key')"
:class="$options.formElementClasses"
data-testid="pipeline-form-ci-variable-key"
- @change="addEmptyVariable(refValue)"
+ @change="addEmptyVariable(refFullName)"
/>
{
const el = document.getElementById('js-new-pipeline');
@@ -7,17 +8,20 @@ export default () => {
projectId,
pipelinesPath,
configVariablesPath,
+ defaultBranch,
refParam,
varParam,
fileParam,
- refNames,
+ branchRefs,
+ tagRefs,
settingsLink,
maxWarnings,
} = el?.dataset;
const variableParams = JSON.parse(varParam);
const fileParams = JSON.parse(fileParam);
- const refs = JSON.parse(refNames);
+ const branches = formatRefs(JSON.parse(branchRefs), 'branch');
+ const tags = formatRefs(JSON.parse(tagRefs), 'tag');
return new Vue({
el,
@@ -27,10 +31,12 @@ export default () => {
projectId,
pipelinesPath,
configVariablesPath,
+ defaultBranch,
refParam,
variableParams,
fileParams,
- refs,
+ branches,
+ tags,
settingsLink,
maxWarnings: Number(maxWarnings),
},
diff --git a/app/assets/javascripts/pipeline_new/utils/format_refs.js b/app/assets/javascripts/pipeline_new/utils/format_refs.js
new file mode 100644
index 00000000000..e217cd25413
--- /dev/null
+++ b/app/assets/javascripts/pipeline_new/utils/format_refs.js
@@ -0,0 +1,18 @@
+import { BRANCH_REF_TYPE, TAG_REF_TYPE } from '../constants';
+
+export default (refs, type) => {
+ let fullName;
+
+ return refs.map(ref => {
+ if (type === BRANCH_REF_TYPE) {
+ fullName = `refs/heads/${ref}`;
+ } else if (type === TAG_REF_TYPE) {
+ fullName = `refs/tags/${ref}`;
+ }
+
+ return {
+ shortName: ref,
+ fullName,
+ };
+ });
+};
diff --git a/app/assets/javascripts/registry/settings/components/expiration_textarea.vue b/app/assets/javascripts/registry/settings/components/expiration_textarea.vue
index 1e1194ebb5c..3f817e37dca 100644
--- a/app/assets/javascripts/registry/settings/components/expiration_textarea.vue
+++ b/app/assets/javascripts/registry/settings/components/expiration_textarea.vue
@@ -36,7 +36,8 @@ export default {
},
placeholder: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
description: {
type: String,
diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
index 264d39a406a..e236834d8e1 100644
--- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
+++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
@@ -1,6 +1,6 @@