diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-23 18:09:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-23 18:09:28 +0300 |
commit | c46b011d3f578d2455443dfabf24226c738c8903 (patch) | |
tree | 89107fa4ccf5340dc14a7d0d2f74a0372e56985f /app | |
parent | b38fc20ae0e90d5b1c538a139aa0a7da1b7b5726 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
38 files changed, 368 insertions, 243 deletions
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index d8504dcfb0f..8c354c61fa2 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -107,7 +107,7 @@ export default { }; }, computed: { - ...mapGetters(['isEpicBoard', 'isGroupBoard', 'isProjectBoard']), + ...mapGetters(['isIssueBoard', 'isGroupBoard', 'isProjectBoard']), isNewForm() { return this.currentPage === formType.new; }, @@ -182,7 +182,7 @@ export default { groupPath: this.isGroupBoard ? this.fullPath : undefined, }; }, - boardScopeMutationVariables() { + issueBoardScopeMutationVariables() { /* eslint-disable @gitlab/require-i18n-strings */ return { weight: this.board.weight, @@ -193,13 +193,18 @@ export default { this.board.milestone?.id || this.board.milestone?.id === 0 ? convertToGraphQLId('Milestone', this.board.milestone.id) : null, - labelIds: this.board.labels.map(fullLabelId), iterationId: this.board.iteration_id ? convertToGraphQLId('Iteration', this.board.iteration_id) : null, }; /* eslint-enable @gitlab/require-i18n-strings */ }, + boardScopeMutationVariables() { + return { + labelIds: this.board.labels.map(fullLabelId), + ...(this.isIssueBoard && this.issueBoardScopeMutationVariables), + }; + }, mutationVariables() { return { ...this.baseMutationVariables, @@ -324,7 +329,7 @@ export default { /> <board-scope - v-if="scopedIssueBoardFeatureEnabled && !isEpicBoard" + v-if="scopedIssueBoardFeatureEnabled" :collapse-scope="isNewForm" :board="board" :can-admin-board="canAdminBoard" diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue index 7ec99e51f5b..fdb60d0ae6a 100644 --- a/app/assets/javascripts/boards/components/config_toggle.vue +++ b/app/assets/javascripts/boards/components/config_toggle.vue @@ -15,7 +15,8 @@ export default { props: { boardsStore: { type: Object, - required: true, + required: false, + default: null, }, canAdminList: { type: Boolean, @@ -26,11 +27,6 @@ export default { required: true, }, }, - data() { - return { - state: this.boardsStore.state, - }; - }, computed: { buttonText() { return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope'); @@ -42,7 +38,9 @@ export default { methods: { showPage() { eventHub.$emit('showBoardModal', formType.edit); - return this.boardsStore.showPage(formType.edit); + if (this.boardsStore) { + this.boardsStore.showPage(formType.edit); + } }, }, }; diff --git a/app/assets/javascripts/boards/config_toggle.js b/app/assets/javascripts/boards/config_toggle.js index 7f327c5764d..41938d8e284 100644 --- a/app/assets/javascripts/boards/config_toggle.js +++ b/app/assets/javascripts/boards/config_toggle.js @@ -2,14 +2,15 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; import ConfigToggle from './components/config_toggle.vue'; -export default (boardsStore) => { +export default (boardsStore = undefined) => { const el = document.querySelector('.js-board-config'); if (!el) { return; } - gl.boardConfigToggle = new Vue({ + // eslint-disable-next-line no-new + new Vue({ el, render(h) { return h(ConfigToggle, { diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js index caa518f91ce..c819717dade 100644 --- a/app/assets/javascripts/boards/stores/getters.js +++ b/app/assets/javascripts/boards/stores/getters.js @@ -1,5 +1,5 @@ import { find } from 'lodash'; -import { BoardType, inactiveId } from '../constants'; +import { BoardType, inactiveId, issuableTypes } from '../constants'; export default { isGroupBoard: (state) => state.boardType === BoardType.group, @@ -44,6 +44,10 @@ export default { return find(state.boardLists, (l) => l.title === title); }, + isIssueBoard: (state) => { + return state.issuableType === issuableTypes.issue; + }, + isEpicBoard: () => { return false; }, diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index dcc98f86410..50c32b9c4bf 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -35,6 +35,7 @@ export default class CreateMergeRequestDropdown { this.branchInput = this.wrapperEl.querySelector('.js-branch-name'); this.branchMessage = this.wrapperEl.querySelector('.js-branch-message'); this.createMergeRequestButton = this.wrapperEl.querySelector('.js-create-merge-request'); + this.createMergeRequestLoading = this.createMergeRequestButton.querySelector('.js-spinner'); this.createTargetButton = this.wrapperEl.querySelector('.js-create-target'); this.dropdownList = this.wrapperEl.querySelector('.dropdown-menu'); this.dropdownToggle = this.wrapperEl.querySelector('.js-dropdown-toggle'); @@ -179,6 +180,10 @@ export default class CreateMergeRequestDropdown { this.disableCreateAction(); } + setLoading(loading) { + this.createMergeRequestLoading.classList.toggle('gl-display-none', !loading); + } + disableCreateAction() { this.createMergeRequestButton.classList.add('disabled'); this.createMergeRequestButton.setAttribute('disabled', 'disabled'); @@ -387,8 +392,10 @@ export default class CreateMergeRequestDropdown { this.isCreatingBranch = false; this.enable(); + this.setLoading(false); }); + this.setLoading(true); this.disable(); } diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 56e69ab9418..ed08a024b59 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -313,7 +313,11 @@ export default class LabelsSelect { return; } - if ($('html').hasClass('issue-boards-page')) { + if ( + $('html') + .attr('class') + .match(/issue-boards-page|epic-boards-page/) + ) { return; } if ($dropdown.hasClass('js-multiselect')) { diff --git a/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue new file mode 100644 index 00000000000..d92ccbd5f67 --- /dev/null +++ b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue @@ -0,0 +1,106 @@ +<script> +import { GlDropdown, GlDropdownItem, GlDropdownDivider, GlDropdownSectionHeader } from '@gitlab/ui'; +import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '../constants'; +import eventHub from '../event_hub'; + +export default { + components: { + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlDropdownSectionHeader, + }, + inject: { + newProjectTagPath: { + default: '', + }, + emailPatchesPath: { + default: '', + }, + plainDiffPath: { + default: '', + }, + }, + props: { + canRevert: { + type: Boolean, + required: true, + }, + canCherryPick: { + type: Boolean, + required: true, + }, + canTag: { + type: Boolean, + required: true, + }, + canEmailPatches: { + type: Boolean, + required: true, + }, + }, + computed: { + showDivider() { + return this.canRevert || this.canCherryPick || this.canTag; + }, + }, + methods: { + showModal(modalId) { + eventHub.$emit(modalId); + }, + }, + openRevertModal: OPEN_REVERT_MODAL, + openCherryPickModal: OPEN_CHERRY_PICK_MODAL, +}; +</script> + +<template> + <gl-dropdown + :text="__('Options')" + right + data-testid="commit-options-dropdown" + data-qa-selector="options_button" + class="gl-xs-w-full" + > + <gl-dropdown-item + v-if="canRevert" + data-testid="revert-link" + @click="showModal($options.openRevertModal)" + > + {{ s__('ChangeTypeAction|Revert') }} + </gl-dropdown-item> + <gl-dropdown-item + v-if="canCherryPick" + data-testid="cherry-pick-link" + @click="showModal($options.openCherryPickModal)" + > + {{ s__('ChangeTypeAction|Cherry-pick') }} + </gl-dropdown-item> + <gl-dropdown-item v-if="canTag" :href="newProjectTagPath" data-testid="tag-link"> + {{ s__('CreateTag|Tag') }} + </gl-dropdown-item> + <gl-dropdown-divider v-if="showDivider" /> + <gl-dropdown-section-header> + {{ __('Download') }} + </gl-dropdown-section-header> + <gl-dropdown-item + v-if="canEmailPatches" + :href="emailPatchesPath" + download + rel="nofollow" + data-testid="email-patches-link" + data-qa-selector="email_patches" + > + {{ s__('DownloadCommit|Email Patches') }} + </gl-dropdown-item> + <gl-dropdown-item + :href="plainDiffPath" + download + rel="nofollow" + data-testid="plain-diff-link" + data-qa-selector="plain_diff" + > + {{ s__('DownloadCommit|Plain Diff') }} + </gl-dropdown-item> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/projects/commit/components/form_trigger.vue b/app/assets/javascripts/projects/commit/components/form_trigger.vue deleted file mode 100644 index 3561b5c2473..00000000000 --- a/app/assets/javascripts/projects/commit/components/form_trigger.vue +++ /dev/null @@ -1,35 +0,0 @@ -<script> -import { GlLink } from '@gitlab/ui'; -import eventHub from '../event_hub'; - -export default { - components: { - GlLink, - }, - inject: { - displayText: { - default: '', - }, - testId: { - default: '', - }, - }, - props: { - openModal: { - type: String, - required: true, - }, - }, - methods: { - showModal() { - eventHub.$emit(this.openModal); - }, - }, -}; -</script> - -<template> - <gl-link data-is-link="true" :data-testid="testId" @click="showModal"> - {{ displayText }} - </gl-link> -</template> diff --git a/app/assets/javascripts/projects/commit/constants.js b/app/assets/javascripts/projects/commit/constants.js index d6bb4e9483f..d553bca360e 100644 --- a/app/assets/javascripts/projects/commit/constants.js +++ b/app/assets/javascripts/projects/commit/constants.js @@ -2,10 +2,8 @@ import { s__, __ } from '~/locale'; export const OPEN_REVERT_MODAL = 'openRevertModal'; export const REVERT_MODAL_ID = 'revert-commit-modal'; -export const REVERT_LINK_TEST_ID = 'revert-commit-link'; export const OPEN_CHERRY_PICK_MODAL = 'openCherryPickModal'; export const CHERRY_PICK_MODAL_ID = 'cherry-pick-commit-modal'; -export const CHERRY_PICK_LINK_TEST_ID = 'cherry-pick-commit-link'; export const I18N_MODAL = { startMergeRequest: s__('ChangeTypeAction|Start a %{newMergeRequest} with these changes'), diff --git a/app/assets/javascripts/projects/commit/index.js b/app/assets/javascripts/projects/commit/index.js index b5fdfc25236..4eb51d566c5 100644 --- a/app/assets/javascripts/projects/commit/index.js +++ b/app/assets/javascripts/projects/commit/index.js @@ -1,11 +1,9 @@ import initCherryPickCommitModal from './init_cherry_pick_commit_modal'; -import initCherryPickCommitTrigger from './init_cherry_pick_commit_trigger'; +import initCommitOptionsDropdown from './init_commit_options_dropdown'; import initRevertCommitModal from './init_revert_commit_modal'; -import initRevertCommitTrigger from './init_revert_commit_trigger'; export default () => { initRevertCommitModal(); - initRevertCommitTrigger(); initCherryPickCommitModal(); - initCherryPickCommitTrigger(); + initCommitOptionsDropdown(); }; diff --git a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js deleted file mode 100644 index 942451dc96a..00000000000 --- a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js +++ /dev/null @@ -1,20 +0,0 @@ -import Vue from 'vue'; -import CommitFormTrigger from './components/form_trigger.vue'; -import { OPEN_CHERRY_PICK_MODAL, CHERRY_PICK_LINK_TEST_ID } from './constants'; - -export default function initInviteMembersTrigger() { - const el = document.querySelector('.js-cherry-pick-commit-trigger'); - - if (!el) { - return false; - } - - const { displayText } = el.dataset; - - return new Vue({ - el, - provide: { displayText, testId: CHERRY_PICK_LINK_TEST_ID }, - render: (createElement) => - createElement(CommitFormTrigger, { props: { openModal: OPEN_CHERRY_PICK_MODAL } }), - }); -} diff --git a/app/assets/javascripts/projects/commit/init_commit_options_dropdown.js b/app/assets/javascripts/projects/commit/init_commit_options_dropdown.js new file mode 100644 index 00000000000..339918e7661 --- /dev/null +++ b/app/assets/javascripts/projects/commit/init_commit_options_dropdown.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import CommitOptionsDropdown from './components/commit_options_dropdown.vue'; + +export default function initCommitOptionsDropdown() { + const el = document.querySelector('#js-commit-options-dropdown'); + + if (!el) { + return false; + } + + const { + newProjectTagPath, + emailPatchesPath, + plainDiffPath, + canRevert, + canCherryPick, + canTag, + canEmailPatches, + } = el.dataset; + + return new Vue({ + el, + provide: { newProjectTagPath, emailPatchesPath, plainDiffPath }, + render: (createElement) => + createElement(CommitOptionsDropdown, { + props: { + canRevert: parseBoolean(canRevert), + canCherryPick: parseBoolean(canCherryPick), + canTag: parseBoolean(canTag), + canEmailPatches: parseBoolean(canEmailPatches), + }, + }), + }); +} diff --git a/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js b/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js deleted file mode 100644 index dc5168524ca..00000000000 --- a/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js +++ /dev/null @@ -1,20 +0,0 @@ -import Vue from 'vue'; -import CommitFormTrigger from './components/form_trigger.vue'; -import { OPEN_REVERT_MODAL, REVERT_LINK_TEST_ID } from './constants'; - -export default function initInviteMembersTrigger() { - const el = document.querySelector('.js-revert-commit-trigger'); - - if (!el) { - return false; - } - - const { displayText } = el.dataset; - - return new Vue({ - el, - provide: { displayText, testId: REVERT_LINK_TEST_ID }, - render: (createElement) => - createElement(CommitFormTrigger, { props: { openModal: OPEN_REVERT_MODAL } }), - }); -} diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index 4640259314b..99cf16c8350 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -32,7 +32,9 @@ export default { <status-filter /> <confidentiality-filter /> <div class="gl-display-flex gl-align-items-center gl-mt-3"> - <gl-button variant="success" type="submit">{{ __('Apply') }}</gl-button> + <gl-button category="primary" variant="confirm" size="small" type="submit"> + {{ __('Apply') }} + </gl-button> <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{ __('Reset filters') }}</gl-link> diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue index 987735ed811..2439ab55923 100644 --- a/app/assets/javascripts/search/topbar/components/app.vue +++ b/app/assets/javascripts/search/topbar/components/app.vue @@ -65,9 +65,9 @@ export default { <label class="gl-display-block">{{ __('Project') }}</label> <project-filter :initial-data="projectInitialData" /> </div> - <gl-button class="btn-search gl-lg-ml-2" variant="success" type="submit">{{ - __('Search') - }}</gl-button> + <gl-button class="btn-search gl-lg-ml-2" category="primary" variant="confirm" type="submit" + >{{ __('Search') }} + </gl-button> </section> </gl-form> </template> diff --git a/app/assets/stylesheets/framework/page_header.scss b/app/assets/stylesheets/framework/page_header.scss index c0847382544..c8b4e306a2e 100644 --- a/app/assets/stylesheets/framework/page_header.scss +++ b/app/assets/stylesheets/framework/page_header.scss @@ -12,25 +12,6 @@ } } - .header-action-buttons { - i { - color: $gl-text-color-secondary; - font-size: 13px; - margin-right: 3px; - } - - @include media-breakpoint-down(xs) { - .btn { - width: 100%; - margin-top: 10px; - } - - .dropdown { - width: 100%; - } - } - } - .avatar { float: none; display: inline-block; diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss index 6c050f33b07..8270db9966e 100644 --- a/app/assets/stylesheets/highlight/common.scss +++ b/app/assets/stylesheets/highlight/common.scss @@ -1,5 +1,6 @@ @import '../framework/variables'; @import './conflict_colors'; +@import 'page_bundles/mixins_and_variables_and_functions'; @mixin diff-background($background, $idiff, $border) { background: $background; @@ -93,3 +94,30 @@ } } } + +@mixin line-number-link($color) { + &::before { + @include gl-visibility-hidden; + @include gl-display-inline-block; + @include gl-align-self-center; + @include gl-mt-2; + @include gl-mr-2; + @include gl-w-4; + @include gl-h-4; + @include gl-float-left; + background-color: $color; + mask-image: asset_url('icons-stacked.svg#link'); + mask-repeat: no-repeat; + mask-size: cover; + mask-position: center; + content: ''; + } + + &:hover::before { + @include gl-visibility-visible; + } + + &:focus::before { + @include gl-visibility-visible; + } +} diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index 0dc01213606..d6523265a43 100644 --- a/app/assets/stylesheets/highlight/themes/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -90,6 +90,10 @@ $dark-il: #de935f; .code.dark { // Line numbers + .file-line-num { + @include line-number-link($dark-line-num-color); + } + .line-numbers, .diff-line-num { background-color: $dark-main-bg; diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index 95c3e8e9103..027f2fa63d3 100644 --- a/app/assets/stylesheets/highlight/themes/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -91,6 +91,10 @@ $monokai-gh: #75715e; .code.monokai { // Line numbers + .file-line-num { + @include line-number-link($monokai-line-num-color); + } + .line-numbers, .diff-line-num { background-color: $monokai-bg; diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss index 4fc6e5dba39..5002726bbc5 100644 --- a/app/assets/stylesheets/highlight/themes/none.scss +++ b/app/assets/stylesheets/highlight/themes/none.scss @@ -11,6 +11,10 @@ .code.none { // Line numbers + .file-line-num { + @include line-number-link($black-transparent); + } + .line-numbers, .diff-line-num { background-color: $gray-light; diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index f95f5393323..cd0cb65e4e2 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -94,6 +94,10 @@ $solarized-dark-il: #2aa198; .code.solarized-dark { // Line numbers + .file-line-num { + @include line-number-link($solarized-dark-line-color); + } + .line-numbers, .diff-line-num { background-color: $solarized-dark-line-bg; diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index dc4bc2f32c2..77e88053424 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -101,6 +101,10 @@ $solarized-light-il: #2aa198; .code.solarized-light { // Line numbers + .file-line-num { + @include line-number-link($solarized-light-line-color); + } + .line-numbers, .diff-line-num { background-color: $solarized-light-line-bg; diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss index 128fe0cc046..18b2f0a5d58 100644 --- a/app/assets/stylesheets/highlight/white_base.scss +++ b/app/assets/stylesheets/highlight/white_base.scss @@ -78,6 +78,10 @@ $white-gc-bg: #eaf2f5; } // Line numbers +.file-line-num { + @include line-number-link($black-transparent); +} + .line-numbers, .diff-line-num { background-color: $gray-light; diff --git a/app/assets/stylesheets/page_bundles/build.scss b/app/assets/stylesheets/page_bundles/build.scss index 34f88f9405a..307386e2c01 100644 --- a/app/assets/stylesheets/page_bundles/build.scss +++ b/app/assets/stylesheets/page_bundles/build.scss @@ -86,18 +86,6 @@ padding: 10px 0 9px; } - .header-action-buttons { - display: flex; - - @include media-breakpoint-down(xs) { - .sidebar-toggle-btn { - margin-top: 0; - margin-left: 10px; - max-height: 34px; - } - } - } - .header-content { a { color: $gl-text-color; diff --git a/app/finders/repositories/changelog_tag_finder.rb b/app/finders/repositories/changelog_tag_finder.rb new file mode 100644 index 00000000000..3c110e6c65d --- /dev/null +++ b/app/finders/repositories/changelog_tag_finder.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module Repositories + # A finder class for getting the tag of the last release before a given + # version, used when generating changelogs. + # + # Imagine a project with the following tags: + # + # * v1.0.0 + # * v1.1.0 + # * v2.0.0 + # + # If the version supplied is 2.1.0, the tag returned will be v2.0.0. And when + # the version is 1.1.1, or 1.2.0, the returned tag will be v1.1.0. + # + # To obtain the tags, this finder requires a regular expression (using the re2 + # syntax) to be provided. This regex must produce the following named + # captures: + # + # - major (required) + # - minor (required) + # - patch (required) + # - pre + # - meta + # + # If the `pre` group has a value, the tag is ignored. If any of the required + # capture groups don't have a value, the tag is also ignored. + class ChangelogTagFinder + def initialize(project, regex: Gitlab::Changelog::Config::DEFAULT_TAG_REGEX) + @project = project + @regex = regex + end + + def execute(new_version) + tags = {} + versions = [new_version] + + begin + regex = Gitlab::UntrustedRegexp.new(@regex) + rescue RegexpError => ex + # The error messages produced by default are not very helpful, so we + # raise a better one here. We raise the specific error here so its + # message is displayed in the API (where we catch this specific + # error). + raise( + Gitlab::Changelog::Error, + "The regular expression to use for finding the previous tag for a version is invalid: #{ex.message}" + ) + end + + @project.repository.tags.each do |tag| + matches = regex.match(tag.name) + + next unless matches + + # When using this class for generating changelog data for a range of + # commits, we want to compare against the tag of the last _stable_ + # release; not some random RC that came after that. + next if matches[:pre] + + major = matches[:major] + minor = matches[:minor] + patch = matches[:patch] + build = matches[:meta] + + next unless major && minor && patch + + version = "#{major}.#{minor}.#{patch}" + version += "+#{build}" if build + + tags[version] = tag + versions << version + end + + VersionSorter.sort!(versions) + + index = versions.index(new_version) + + tags[versions[index - 1]] if index&.positive? + end + end +end diff --git a/app/finders/repositories/previous_tag_finder.rb b/app/finders/repositories/previous_tag_finder.rb deleted file mode 100644 index b5e786c30e9..00000000000 --- a/app/finders/repositories/previous_tag_finder.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -module Repositories - # A finder class for getting the tag of the last release before a given - # version. - # - # Imagine a project with the following tags: - # - # * v1.0.0 - # * v1.1.0 - # * v2.0.0 - # - # If the version supplied is 2.1.0, the tag returned will be v2.0.0. And when - # the version is 1.1.1, or 1.2.0, the returned tag will be v1.1.0. - # - # This finder expects that all tags to consider meet the following - # requirements: - # - # * They start with the letter "v" followed by a version, or immediately start - # with a version - # * They use semantic versioning for the version format - # - # Tags not meeting these requirements are ignored. - class PreviousTagFinder - TAG_REGEX = /\Av?(?<version>#{Gitlab::Regex.unbounded_semver_regex})\z/.freeze - - def initialize(project) - @project = project - end - - def execute(new_version) - tags = {} - versions = [new_version] - - @project.repository.tags.each do |tag| - matches = tag.name.match(TAG_REGEX) - - next unless matches - - # When using this class for generating changelog data for a range of - # commits, we want to compare against the tag of the last _stable_ - # release; not some random RC that came after that. - next if matches[:prerelease] - - version = matches[:version] - tags[version] = tag - versions << version - end - - VersionSorter.sort!(versions) - - index = versions.index(new_version) - - tags[versions[index - 1]] if index&.positive? - end - end -end diff --git a/app/graphql/types/ci/pipeline_config_source_enum.rb b/app/graphql/types/ci/pipeline_config_source_enum.rb index e1575cb2f99..96c8a5f2941 100644 --- a/app/graphql/types/ci/pipeline_config_source_enum.rb +++ b/app/graphql/types/ci/pipeline_config_source_enum.rb @@ -4,7 +4,8 @@ module Types module Ci class PipelineConfigSourceEnum < BaseEnum ::Enums::Ci::Pipeline.config_sources.keys.each do |state_symbol| - value state_symbol.to_s.upcase, value: state_symbol.to_s + description = state_symbol == :auto_devops_source ? "Auto DevOps source." : "#{state_symbol.to_s.titleize.capitalize}." # This is needed to avoid failure in doc lint + value state_symbol.to_s.upcase, value: state_symbol.to_s, description: description end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9af45daaca4..1115bee0843 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -281,6 +281,7 @@ module ApplicationHelper def page_class class_names = [] class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards) + class_names << 'epic-boards-page' if current_controller?(:epic_boards) class_names << 'environment-logs-page' if current_controller?(:logs) class_names << 'with-performance-bar' if performance_bar_enabled? class_names << system_message_class diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 42e4844cc8d..652ba9950bc 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -110,16 +110,18 @@ module CommitsHelper end end - def revert_commit_link - return unless current_user - - tag(:div, data: { display_text: 'Revert' }, class: "js-revert-commit-trigger") - end - - def cherry_pick_commit_link - return unless current_user - - tag(:div, data: { display_text: 'Cherry-pick' }, class: "js-cherry-pick-commit-trigger") + def commit_options_dropdown_data(project, commit) + can_collaborate = current_user && can_collaborate_with_project?(project) + + { + new_project_tag_path: new_project_tag_path(project, ref: commit), + email_patches_path: project_commit_path(project, commit, format: :patch), + plain_diff_path: project_commit_path(project, commit, format: :diff), + can_revert: "#{can_collaborate && !commit.has_been_reverted?(current_user)}", + can_cherry_pick: can_collaborate.to_s, + can_tag: can?(current_user, :push_code, project).to_s, + can_email_patches: (commit.parents.length < 2).to_s + } end def commit_signature_badge_classes(additional_classes) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index b63ec0c8a97..92e3a3709ff 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -510,6 +510,12 @@ module Ci end end + def git_author_full_text + strong_memoize(:git_author_full_text) do + commit.try(:author_full_text) + end + end + def git_commit_message strong_memoize(:git_commit_message) do commit.try(:message) @@ -822,6 +828,7 @@ module Ci variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s) variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s) variables.append(key: 'CI_COMMIT_TIMESTAMP', value: git_commit_timestamp.to_s) + variables.append(key: 'CI_COMMIT_AUTHOR', value: git_author_full_text.to_s) # legacy variables variables.append(key: 'CI_BUILD_REF', value: sha) diff --git a/app/models/commit.rb b/app/models/commit.rb index 5b1f236b333..9d3b89c5996 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -222,6 +222,14 @@ class Commit end end + def author_full_text + return unless author_name && author_email + + strong_memoize(:author_full_text) do + "#{author_name} <#{author_email}>" + end + end + # Returns full commit message if title is truncated (greater than 99 characters) # otherwise returns commit message without first line def description diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 7556cb1e69b..e7fb20631e3 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -112,7 +112,7 @@ module Ci if Feature.enabled?(:ci_register_job_service_one_by_one, runner, default_enabled: true) build_ids = retrieve_queue(-> { builds.pluck(:id) }) - @metrics.observe_queue_size(-> { build_ids.size }) + @metrics.observe_queue_size(-> { build_ids.size }, @runner.runner_type) build_ids.each do |build_id| yield Ci::Build.find(build_id) @@ -120,7 +120,7 @@ module Ci else builds_array = retrieve_queue(-> { builds.to_a }) - @metrics.observe_queue_size(-> { builds_array.size }) + @metrics.observe_queue_size(-> { builds_array.size }, @runner.runner_type) builds_array.each(&blk) end diff --git a/app/services/members/invite_service.rb b/app/services/members/invite_service.rb index 169500d08f0..140d8c35219 100644 --- a/app/services/members/invite_service.rb +++ b/app/services/members/invite_service.rb @@ -13,9 +13,9 @@ module Members end def execute(source) + @source = source validate_emails! - @source = source emails.each(&method(:process_email)) result rescue BlankEmailsError, TooManyEmailsError => e @@ -96,3 +96,5 @@ module Members end end end + +Members::InviteService.prepend_if_ee('EE::Members::InviteService') diff --git a/app/services/repositories/changelog_service.rb b/app/services/repositories/changelog_service.rb index 3981e91e7f3..0122bfb154d 100644 --- a/app/services/repositories/changelog_service.rb +++ b/app/services/repositories/changelog_service.rb @@ -61,14 +61,14 @@ module Repositories # rubocop: enable Metrics/ParameterLists def execute - from = start_of_commit_range + config = Gitlab::Changelog::Config.from_git(@project) + from = start_of_commit_range(config) # For every entry we want to only include the merge request that # originally introduced the commit, which is the oldest merge request that # contains the commit. We fetch there merge requests in batches, reducing # the number of SQL queries needed to get this data. mrs_finder = MergeRequests::OldestPerCommitFinder.new(@project) - config = Gitlab::Changelog::Config.from_git(@project) release = Gitlab::Changelog::Release .new(version: @version, date: @date, config: config) @@ -98,10 +98,12 @@ module Repositories .commit(release: release, file: @file, branch: @branch, message: @message) end - def start_of_commit_range + def start_of_commit_range(config) return @from if @from - if (prev_tag = PreviousTagFinder.new(@project).execute(@version)) + finder = ChangelogTagFinder.new(@project, regex: config.tag_regex) + + if (prev_tag = finder.execute(@version)) return prev_tag.target_commit.id end diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index c902c687378..2032d1e95a6 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -25,7 +25,7 @@ = hidden_field_tag :group_id, search_context.for_group? ? search_context.group.id : '', class: 'js-search-group-options', data: search_context.group_metadata = hidden_field_tag :project_id, search_context.for_project? ? search_context.project.id : '', id: 'search_project_id', class: 'js-search-project-options', data: search_context.project_metadata - - if search_context.for_project? + - if search_context.for_project? || search_context.for_group? = hidden_field_tag :scope, search_context.scope = hidden_field_tag :search_code, search_context.code_search? diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 974393b893b..ff51f3f4a21 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,5 +1,3 @@ -- can_collaborate = can_collaborate_with_project?(@project) - .page-content-header .header-main-content = render partial: 'signature', object: @commit.signature @@ -20,36 +18,12 @@ = commit_committer_link(@commit, avatar: true, size: 24) #{time_ago_with_tooltip(@commit.committed_date)} - .header-action-buttons - - if defined?(@notes_count) && @notes_count > 0 - %span.btn.gl-button.btn-default.disabled.gl-button.btn-icon.d-none.d-sm-inline.gl-mr-3.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count } - = sprite_icon('comment') - = @notes_count - = link_to project_tree_path(@project, @commit), class: "btn gl-button btn-default gl-mr-3 d-none d-md-inline" do - #{ _('Browse files') } - .dropdown.inline - %a.btn.gl-button.btn-default.dropdown-toggle.qa-options-button.d-md-inline{ data: { toggle: "dropdown" } } - %span= _('Options') - = sprite_icon('chevron-down', css_class: 'gl-text-gray-500') - %ul.dropdown-menu.dropdown-menu-right - %li.d-block.d-sm-none - = link_to project_tree_path(@project, @commit) do - #{ _('Browse Files') } - - if can_collaborate && !@commit.has_been_reverted?(current_user) - %li.clearfix - = revert_commit_link - - if can_collaborate - %li.clearfix - = cherry_pick_commit_link - - if can?(current_user, :push_code, @project) - %li.clearfix - = link_to s_('CreateTag|Tag'), new_project_tag_path(@project, ref: @commit) - %li.divider - %li.dropdown-header - #{ _('Download') } - - unless @commit.parents.length > 1 - %li= link_to s_('DownloadCommit|Email Patches'), project_commit_path(@project, @commit, format: :patch), class: "qa-email-patches", rel: 'nofollow', download: '' - %li= link_to s_('DownloadCommit|Plain Diff'), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff", rel: 'nofollow', download: '' + - if defined?(@notes_count) && @notes_count > 0 + %span.btn.gl-button.btn-default.disabled.gl-button.btn-icon.d-none.d-sm-inline.gl-mr-3.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count } + = sprite_icon('comment') + = @notes_count + = link_to _('Browse files'), project_tree_path(@project, @commit), class: "btn gl-button btn-default gl-mr-3 gl-xs-w-full gl-xs-mb-3" + #js-commit-options-dropdown{ data: commit_options_dropdown_data(@project, @commit) } .commit-box{ data: { project_path: project_path(@project) } } %h3.commit-title diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index d299d2846c6..15685291cde 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -18,7 +18,8 @@ Checking branch availability… .btn-group.btn-group-sm.available.hidden - %button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } } + %button.gl-button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } } + .spinner.js-spinner.gl-mr-2.gl-display-none = value %button.btn.gl-button.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle.gl-flex-grow-0.gl-h-7{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } } diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index b1f53e4d0f6..f8ac3832a77 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -2,13 +2,11 @@ - offset = defined?(first_line_number) ? first_line_number : 1 .line-numbers - if blob.data.present? - - link_icon = sprite_icon('link', size: 12) - link = blob_link if defined?(blob_link) - blob.data.each_line.each_with_index do |_, index| - i = index + offset -# We're not using `link_to` because it is too slow once we get to thousands of lines. - %a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } - = link_icon + %a.file-line-num.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } = i - highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil .blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } } |