summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/monitoring/components/charts/annotations.js15
-rw-r--r--app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql4
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js3
-rw-r--r--app/assets/javascripts/projects/commits/components/author_select.vue141
-rw-r--r--app/assets/javascripts/projects/commits/index.js26
-rw-r--r--app/assets/javascripts/projects/commits/store/actions.js31
-rw-r--r--app/assets/javascripts/projects/commits/store/index.js15
-rw-r--r--app/assets/javascripts/projects/commits/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/projects/commits/store/mutations.js10
-rw-r--r--app/assets/javascripts/projects/commits/store/state.js5
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss5
-rw-r--r--app/views/projects/commits/show.html.haml3
-rw-r--r--changelogs/unreleased/14984-show-commits-by-author.yml5
-rw-r--r--changelogs/unreleased/feat-x509-update-signatures-rake-task.yml5
-rw-r--r--doc/ci/docker/using_docker_build.md7
-rw-r--r--doc/ci/yaml/README.md13
-rw-r--r--doc/raketasks/README.md1
-rw-r--r--doc/raketasks/x509_signatures.md22
-rw-r--r--doc/subscriptions/index.md12
-rw-r--r--doc/user/project/merge_requests/code_quality.md6
-rw-r--r--lib/gitlab/git_access_wiki.rb8
-rw-r--r--lib/tasks/gitlab/x509/update.rake27
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/frontend/monitoring/mock_data.js12
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js216
-rw-r--r--spec/frontend/projects/commits/store/actions_spec.js69
-rw-r--r--spec/frontend/projects/commits/store/mutations_spec.js43
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb2
-rw-r--r--spec/support/import_export/common_util.rb12
-rw-r--r--spec/tasks/gitlab/x509/update_rake_spec.rb42
30 files changed, 736 insertions, 44 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/annotations.js b/app/assets/javascripts/monitoring/components/charts/annotations.js
index de2b0c69a88..947750b3721 100644
--- a/app/assets/javascripts/monitoring/components/charts/annotations.js
+++ b/app/assets/javascripts/monitoring/components/charts/annotations.js
@@ -45,19 +45,16 @@ export const annotationsYAxis = {
* Fetched list of annotations are parsed into a
* format the eCharts accepts to draw markLines
*
- * If Annotation is a single line, the `from` property
- * has a value and the `to` is null. Because annotations
- * only supports lines the from value does not exist yet.
+ * If Annotation is a single line, the `starting_at` property
+ * has a value and the `ending_at` is null. Because annotations
+ * only supports lines the `ending_at` value does not exist yet.
*
*
* @param {Object} annotation object
* @returns {Object} markLine object
*/
-export const parseAnnotations = ({
- from: annotationFrom = '',
- color = colorValues.primaryColor,
-}) => ({
- xAxis: annotationFrom,
+export const parseAnnotations = ({ starting_at = '', color = colorValues.primaryColor }) => ({
+ xAxis: starting_at,
lineStyle: {
color,
},
@@ -105,7 +102,7 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
const annotationsData = annotations.map(annotation => {
return {
name: 'annotations',
- value: [annotation.from, annotationsYAxisCoords.pos],
+ value: [annotation.starting_at, annotationsYAxisCoords.pos],
// style options
symbol: 'none',
// metadata that are accessible in `formatTooltipText` method
diff --git a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
index e2edaa707b2..2fd698eadf9 100644
--- a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
+++ b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
@@ -4,8 +4,8 @@ query getAnnotations($projectPath: ID!) {
annotations: nodes {
id
description
- from
- to
+ starting_at
+ ending_at
panelId
}
}
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
index ad671ce9351..b456baac612 100644
--- a/app/assets/javascripts/pages/projects/commits/show/index.js
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -2,8 +2,11 @@ import CommitsList from '~/commits';
import GpgBadges from '~/gpg_badges';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import mountCommits from '~/projects/commits';
+
document.addEventListener('DOMContentLoaded', () => {
new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
GpgBadges.fetch();
+ mountCommits(document.getElementById('js-author-dropdown'));
});
diff --git a/app/assets/javascripts/projects/commits/components/author_select.vue b/app/assets/javascripts/projects/commits/components/author_select.vue
new file mode 100644
index 00000000000..78f9389b80c
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/components/author_select.vue
@@ -0,0 +1,141 @@
+<script>
+import { debounce } from 'lodash';
+import { mapState, mapActions } from 'vuex';
+import {
+ GlNewDropdown,
+ GlNewDropdownHeader,
+ GlNewDropdownItem,
+ GlSearchBoxByType,
+ GlNewDropdownDivider,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { redirectTo } from '~/lib/utils/url_utility';
+import { urlParamsToObject } from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
+
+const tooltipMessage = __('Searching by both author and message is currently not supported.');
+
+export default {
+ name: 'AuthorSelect',
+ components: {
+ GlNewDropdown,
+ GlNewDropdownHeader,
+ GlNewDropdownItem,
+ GlSearchBoxByType,
+ GlNewDropdownDivider,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ projectCommitsEl: {
+ type: HTMLDivElement,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ hasSearchParam: false,
+ searchTerm: '',
+ authorInput: '',
+ currentAuthor: '',
+ };
+ },
+ computed: {
+ ...mapState(['commitsPath', 'commitsAuthors']),
+ dropdownText() {
+ return this.currentAuthor || __('Author');
+ },
+ tooltipTitle() {
+ return this.hasSearchParam && tooltipMessage;
+ },
+ },
+ mounted() {
+ this.fetchAuthors();
+ const params = urlParamsToObject(window.location.search);
+ const { search: searchParam, author: authorParam } = params;
+ const commitsSearchInput = this.projectCommitsEl.querySelector('#commits-search');
+
+ if (authorParam) {
+ commitsSearchInput.setAttribute('disabled', true);
+ commitsSearchInput.setAttribute('data-toggle', 'tooltip');
+ commitsSearchInput.setAttribute('title', tooltipMessage);
+ this.currentAuthor = authorParam;
+ }
+
+ if (searchParam) {
+ this.hasSearchParam = true;
+ }
+
+ commitsSearchInput.addEventListener(
+ 'keyup',
+ debounce(event => this.setSearchParam(event.target.value), 500), // keyup & time is to match effect of "filter by commit message"
+ );
+ },
+ methods: {
+ ...mapActions(['fetchAuthors']),
+ selectAuthor(author) {
+ const { name: user } = author || {};
+
+ // Follow up issue "Remove usage of $.fadeIn from the codebase"
+ // > https://gitlab.com/gitlab-org/gitlab/-/issues/214395
+
+ // Follow up issue "Refactor commit list to a Vue Component"
+ // To resolving mixing Vue + Vanilla JS
+ // > https://gitlab.com/gitlab-org/gitlab/-/issues/214010
+ const commitListElement = this.projectCommitsEl.querySelector('#commits-list');
+
+ // To mimick effect of "filter by commit message"
+ commitListElement.style.opacity = 0.5;
+ commitListElement.style.transition = 'opacity 200ms';
+
+ if (!user) {
+ return redirectTo(this.commitsPath);
+ }
+
+ return redirectTo(`${this.commitsPath}?author=${user}`);
+ },
+ searchAuthors() {
+ this.fetchAuthors(this.authorInput);
+ },
+ setSearchParam(value) {
+ this.hasSearchParam = Boolean(value);
+ },
+ },
+};
+</script>
+
+<template>
+ <div ref="dropdownContainer" v-gl-tooltip :title="tooltipTitle" :disabled="!hasSearchParam">
+ <gl-new-dropdown
+ :text="dropdownText"
+ :disabled="hasSearchParam"
+ class="gl-dropdown w-100 mt-2 mt-sm-0"
+ >
+ <gl-new-dropdown-header>
+ {{ __('Search by author') }}
+ </gl-new-dropdown-header>
+ <gl-new-dropdown-divider />
+ <gl-search-box-by-type
+ v-model.trim="authorInput"
+ class="m-2"
+ :placeholder="__('Search')"
+ @input="searchAuthors"
+ />
+ <gl-new-dropdown-item :is-checked="!currentAuthor" @click="selectAuthor(null)">
+ {{ __('Any Author') }}
+ </gl-new-dropdown-item>
+ <gl-new-dropdown-divider />
+ <gl-new-dropdown-item
+ v-for="author in commitsAuthors"
+ :key="author.id"
+ :is-checked="author.name === currentAuthor"
+ :avatar-url="author.avatar_url"
+ :secondary-text="author.username"
+ @click="selectAuthor(author)"
+ >
+ {{ author.name }}
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/commits/index.js b/app/assets/javascripts/projects/commits/index.js
new file mode 100644
index 00000000000..6f85432a77e
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/index.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import AuthorSelectApp from './components/author_select.vue';
+import store from './store';
+
+Vue.use(Vuex);
+
+export default el => {
+ if (!el) {
+ return null;
+ }
+
+ store.dispatch('setInitialData', el.dataset);
+
+ return new Vue({
+ el,
+ store,
+ render(h) {
+ return h(AuthorSelectApp, {
+ props: {
+ projectCommitsEl: document.querySelector('.js-project-commits-show'),
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js
new file mode 100644
index 00000000000..daeae071d6a
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/store/actions.js
@@ -0,0 +1,31 @@
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+
+export default {
+ setInitialData({ commit }, data) {
+ commit(types.SET_INITIAL_DATA, data);
+ },
+ receiveAuthorsSuccess({ commit }, authors) {
+ commit(types.COMMITS_AUTHORS, authors);
+ },
+ receiveAuthorsError() {
+ createFlash(__('An error occurred fetching the project authors.'));
+ },
+ fetchAuthors({ dispatch, state }, author = null) {
+ const { projectId } = state;
+ const path = '/autocomplete/users.json';
+
+ return axios
+ .get(path, {
+ params: {
+ project_id: projectId,
+ active: true,
+ search: author,
+ },
+ })
+ .then(({ data }) => dispatch('receiveAuthorsSuccess', data))
+ .catch(() => dispatch('receiveAuthorsError'));
+ },
+};
diff --git a/app/assets/javascripts/projects/commits/store/index.js b/app/assets/javascripts/projects/commits/store/index.js
new file mode 100644
index 00000000000..e864ef5716e
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/store/index.js
@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import actions from './actions';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export const createStore = () => ({
+ actions,
+ mutations,
+ state: state(),
+});
+
+export default new Vuex.Store(createStore());
diff --git a/app/assets/javascripts/projects/commits/store/mutation_types.js b/app/assets/javascripts/projects/commits/store/mutation_types.js
new file mode 100644
index 00000000000..0a6a5a0b902
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/store/mutation_types.js
@@ -0,0 +1,2 @@
+export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
+export const COMMITS_AUTHORS = 'COMMITS_AUTHORS';
diff --git a/app/assets/javascripts/projects/commits/store/mutations.js b/app/assets/javascripts/projects/commits/store/mutations.js
new file mode 100644
index 00000000000..11f703c0946
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/store/mutations.js
@@ -0,0 +1,10 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_INITIAL_DATA](state, data) {
+ Object.assign(state, data);
+ },
+ [types.COMMITS_AUTHORS](state, data) {
+ state.commitsAuthors = data;
+ },
+};
diff --git a/app/assets/javascripts/projects/commits/store/state.js b/app/assets/javascripts/projects/commits/store/state.js
new file mode 100644
index 00000000000..f074708ffa2
--- /dev/null
+++ b/app/assets/javascripts/projects/commits/store/state.js
@@ -0,0 +1,5 @@
+export default () => ({
+ commitsPath: null,
+ projectId: null,
+ commitsAuthors: [],
+});
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index b6edadb05a9..f746d7e6f69 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -317,7 +317,10 @@
}
}
- .dropdown-item {
+ // Temporary fix to ensure tick is aligned
+ // Follow up Issue to remove after the GlNewDropdownItem component is fixed
+ // > https://gitlab.com/gitlab-org/gitlab/-/issues/213948
+ li:not(.gl-new-dropdown-item) .dropdown-item {
@include dropdown-link;
}
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 3f1d44a488a..7722a3523a1 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -13,7 +13,8 @@
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
- .tree-controls.d-none.d-sm-none.d-md-block<
+ #js-author-dropdown{ data: { 'commits_path': project_commits_path(@project), 'project_id': @project.id } }
+ .tree-controls.d-none.d-sm-none.d-md-block
- if @merge_request.present?
.control
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
diff --git a/changelogs/unreleased/14984-show-commits-by-author.yml b/changelogs/unreleased/14984-show-commits-by-author.yml
new file mode 100644
index 00000000000..89f90c074ca
--- /dev/null
+++ b/changelogs/unreleased/14984-show-commits-by-author.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to filter commits by author
+merge_request: 28509
+author:
+type: added
diff --git a/changelogs/unreleased/feat-x509-update-signatures-rake-task.yml b/changelogs/unreleased/feat-x509-update-signatures-rake-task.yml
new file mode 100644
index 00000000000..6edc495a6e7
--- /dev/null
+++ b/changelogs/unreleased/feat-x509-update-signatures-rake-task.yml
@@ -0,0 +1,5 @@
+---
+title: Add rake task to update x509 signatures
+merge_request: 28406
+author: Roger Meier
+type: added
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index acdc61d008f..0ae7a10a290 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -147,7 +147,12 @@ released.
#### TLS enabled
NOTE: **Note**
-This requires GitLab Runner 11.11 or higher.
+Requires GitLab Runner 11.11 or later, but is not supported if GitLab
+Runner is installed using the [Helm
+chart](https://docs.gitlab.com/runner/install/kubernetes.html). See the
+[related
+issue](https://gitlab.com/gitlab-org/charts/gitlab-runner/issues/83) for
+details.
The Docker daemon supports connection over TLS and it's done by default
for Docker 19.03.8 or higher. This is the **suggested** way to use the
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index e5d619ea00c..e79d44cb057 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1264,7 +1264,9 @@ osx job:
`allow_failure` allows a job to fail without impacting the rest of the CI
suite.
-The default value is `false`, except for [manual](#whenmanual) jobs.
+The default value is `false`, except for [manual](#whenmanual) jobs using the
+`when: manual` syntax, unless using [`rules:`](#rules) syntax, where all jobs
+default to false, *including* `when: manual` jobs.
When enabled and the job fails, the job will show an orange warning in the UI.
However, the logical flow of the pipeline will consider the job a
@@ -1379,14 +1381,17 @@ manual action by clicking a _play_ button.
When a pipeline is blocked, it will not be merged if Merge When Pipeline Succeeds
is set. Blocked pipelines also do have a special status, called _manual_.
-Manual actions are non-blocking by default. If you want to make manual action
-blocking, it is necessary to add `allow_failure: false` to the job's definition
-in `.gitlab-ci.yml`.
+When the `when:manual` syntax is used, manual actions are non-blocking by
+default. If you want to make manual action blocking, it is necessary to add
+`allow_failure: false` to the job's definition in `.gitlab-ci.yml`.
Optional manual actions have `allow_failure: true` set by default and their
Statuses do not contribute to the overall pipeline status. So, if a manual
action fails, the pipeline will eventually succeed.
+NOTE: **Note:**
+When using [`rules:`](#rules), `allow_failure` defaults to `false`, including for manual jobs.
+
Manual actions are considered to be write actions, so permissions for
[protected branches](../../user/project/protected_branches.md) are used when
a user wants to trigger an action. In other words, in order to trigger a manual
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 8faace58599..1670849016c 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -36,3 +36,4 @@ The following are available Rake tasks:
| [Uploads sanitize](../administration/raketasks/uploads/sanitize.md) | Remove EXIF data from images uploaded to earlier versions of GitLab. |
| [User management](user_management.md) | Perform user management tasks. |
| [Webhooks administration](web_hooks.md) | Maintain project Webhooks. |
+| [X509 signatures](x509_signatures.md) | Update x509 commit signatures, useful if certificate store has changed. |
diff --git a/doc/raketasks/x509_signatures.md b/doc/raketasks/x509_signatures.md
new file mode 100644
index 00000000000..dd518997f7b
--- /dev/null
+++ b/doc/raketasks/x509_signatures.md
@@ -0,0 +1,22 @@
+# X509 signatures
+
+When [signing commits with x509](../user/project/repository/x509_signed_commits/index.md)
+the trust anchor might change and the signatures stored within the database have
+to be updated.
+
+## Update all x509 signatures
+
+This task loops through all X509 signed commits and updates their verification
+based on current certificate store.
+
+**Omnibus Installation**
+
+```shell
+sudo gitlab-rake gitlab:x509:update_signatures
+```
+
+**Source Installation**
+
+```shell
+sudo -u git -H bundle exec rake gitlab:x509:update_signatures RAILS_ENV=production
+```
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 12d5aa1e29f..63a10fdf4be 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -8,8 +8,8 @@ GitLab offers tiers of features. Your subscription determines which tier you hav
GitLab provides special subscriptions to participants in the [GitLab Education Program](https://about.gitlab.com/solutions/education/) and [GitLab Open Source Program](https://about.gitlab.com/solutions/open-source/). For details on obtaining and renewing these subscriptions, see:
-- [GitLab Education Program subscriptions](#gitlab-education-program-subscriptions)
-- [GitLab Open Source Program subscriptions](#gitlab-open-source-program-subscriptions)
+- [GitLab for Education subscriptions](#gitlab-for-education-subscriptions)
+- [GitLab for Open Source subscriptions](#gitlab-for-open-source-subscriptions)
## Choosing a GitLab subscription
@@ -493,9 +493,9 @@ Learn more about:
- The tiers of [GitLab Support](https://about.gitlab.com/support/).
- [Submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new).
-## GitLab Education Program subscriptions
+## GitLab for Education subscriptions
-To renew a [GitLab Education Program](https://about.gitlab.com/solutions/education/) subscription, send an email to `education@gitlab.com` with the following information:
+To renew a [GitLab for Education](https://about.gitlab.com/solutions/education/) subscription, send an email to `education@gitlab.com` with the following information:
1. The number of seats for the renewal. You can add seats if needed.
1. The use case for the license. Specifically, we need verification that the use meets the conditions of the [End User License Agreement](https://about.gitlab.com/terms/#edu-oss). Note that university infrastructure operations and information technology operations don't fall within the stated terms of the Education Program. For details, see the [Education FAQ](https://about.gitlab.com/solutions/education/#FAQ).
@@ -503,9 +503,9 @@ To renew a [GitLab Education Program](https://about.gitlab.com/solutions/educati
After we receive the above information, we will process the request and return a renewal quote for signature. Please allow a minimum of 2 business days for return. Email us at `education@gitlab.com` with any questions.
-## GitLab Open Source Program subscriptions
+## GitLab for Open Source subscriptions
-All requests for our GitLab Open Source program, including subscription renewals, must be made by using the [Open Source Program](https://about.gitlab.com/solutions/open-source/program/) application process. If you have any questions, send an email to `opensource@gitlab.com` for assistance.
+All [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/program/) requests, including subscription renewals, must be made by using the application process. If you have any questions, send an email to `opensource@gitlab.com` for assistance.
<!-- ## Troubleshooting
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index ce1e1a31bbe..a7e712a4c0a 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -53,8 +53,10 @@ also requires the GitLab Runner 11.5 or later. For earlier versions, use the
This example shows how to run Code Quality on your code by using GitLab CI/CD and Docker.
-First, you need GitLab Runner with
-[docker-in-docker executor](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
+First, you need GitLab Runner with:
+
+- The [docker-in-docker executor](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
+- Enough disk space to handle generated Code Quality files. For example on the [GitLab project](https://gitlab.com/gitlab-org/gitlab) the files are approximately 7 GB.
Once you set up the Runner, include the CodeQuality template in your CI config:
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index aad46937c32..3c0dbba64bf 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -2,8 +2,6 @@
module Gitlab
class GitAccessWiki < GitAccess
- prepend_if_ee('EE::Gitlab::GitAccessWiki') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
ERROR_MESSAGES = {
read_only: "You can't push code to a read-only GitLab instance.",
write_to_wiki: "You are not allowed to write to this project's wiki."
@@ -33,10 +31,8 @@ module Gitlab
ERROR_MESSAGES[:read_only]
end
- private
-
- def repository
- project.wiki.repository
+ def container
+ project.wiki
end
end
end
diff --git a/lib/tasks/gitlab/x509/update.rake b/lib/tasks/gitlab/x509/update.rake
new file mode 100644
index 00000000000..eaba9196acf
--- /dev/null
+++ b/lib/tasks/gitlab/x509/update.rake
@@ -0,0 +1,27 @@
+require 'logger'
+
+desc "GitLab | X509 | Update signatures when certificate store has changed"
+namespace :gitlab do
+ namespace :x509 do
+ task update_signatures: :environment do
+ update_certificates
+ end
+
+ def update_certificates
+ logger = Logger.new(STDOUT)
+
+ unless X509CommitSignature.exists?
+ logger.info("Unable to find any x509 commit signatures. Exiting.")
+ return
+ end
+
+ logger.info("Start to update x509 commit signatures")
+
+ X509CommitSignature.find_each do |sig|
+ sig.x509_commit&.update_signature!(sig)
+ end
+
+ logger.info("End update x509 commit signatures")
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 58d2b4b9586..a267e65a925 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1289,6 +1289,9 @@ msgstr ""
msgid "Added at"
msgstr ""
+msgid "Added for this merge request"
+msgstr ""
+
msgid "Added in this version"
msgstr ""
@@ -1855,6 +1858,9 @@ msgstr ""
msgid "An error occurred fetching the dropdown data."
msgstr ""
+msgid "An error occurred fetching the project authors."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -2173,6 +2179,9 @@ msgstr ""
msgid "Any"
msgstr ""
+msgid "Any Author"
+msgstr ""
+
msgid "Any Label"
msgstr ""
@@ -14195,6 +14204,9 @@ msgstr ""
msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
msgstr ""
+msgid "Overridden"
+msgstr ""
+
msgid "Overview"
msgstr ""
@@ -17691,6 +17703,9 @@ msgstr ""
msgid "Search branches and tags"
msgstr ""
+msgid "Search by author"
+msgstr ""
+
msgid "Search files"
msgstr ""
@@ -17851,6 +17866,9 @@ msgid_plural "SearchResults|wiki results"
msgstr[0] ""
msgstr[1] ""
+msgid "Searching by both author and message is currently not supported."
+msgstr ""
+
msgid "Seat Link"
msgstr ""
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index c9f2b110147..84dd0b70e71 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -213,23 +213,23 @@ export const deploymentData = [
export const annotationsData = [
{
id: 'gid://gitlab/Metrics::Dashboard::Annotation/1',
- from: '2020-04-01T12:51:58.373Z',
- to: null,
+ starting_at: '2020-04-01T12:51:58.373Z',
+ ending_at: null,
panelId: null,
description: 'This is a test annotation',
},
{
id: 'gid://gitlab/Metrics::Dashboard::Annotation/2',
description: 'test annotation 2',
- from: '2020-04-02T12:51:58.373Z',
- to: null,
+ starting_at: '2020-04-02T12:51:58.373Z',
+ ending_at: null,
panelId: null,
},
{
id: 'gid://gitlab/Metrics::Dashboard::Annotation/3',
description: 'test annotation 3',
- from: '2020-04-04T12:51:58.373Z',
- to: null,
+ starting_at: '2020-04-04T12:51:58.373Z',
+ ending_at: null,
panelId: null,
},
];
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
new file mode 100644
index 00000000000..dab91d8b37c
--- /dev/null
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -0,0 +1,216 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import * as urlUtility from '~/lib/utils/url_utility';
+import AuthorSelect from '~/projects/commits/components/author_select.vue';
+import { createStore } from '~/projects/commits/store';
+import {
+ GlNewDropdown,
+ GlNewDropdownHeader,
+ GlSearchBoxByType,
+ GlNewDropdownItem,
+} from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const commitsPath = 'author/search/url';
+const currentAuthor = 'lorem';
+const authors = [
+ {
+ id: 1,
+ name: currentAuthor,
+ username: 'ipsum',
+ avatar_url: 'some/url',
+ },
+ {
+ id: 2,
+ name: 'lorem2',
+ username: 'ipsum2',
+ avatar_url: 'some/url/2',
+ },
+];
+
+describe('Author Select', () => {
+ let store;
+ let wrapper;
+
+ const createComponent = () => {
+ setFixtures(`
+ <div class="js-project-commits-show">
+ <input id="commits-search" type="text" />
+ <div id="commits-list"></div>
+ </div>
+ `);
+
+ wrapper = shallowMount(AuthorSelect, {
+ localVue,
+ store: new Vuex.Store(store),
+ propsData: {
+ projectCommitsEl: document.querySelector('.js-project-commits-show'),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ store.actions.fetchAuthors = jest.fn();
+
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDropdownContainer = () => wrapper.find({ ref: 'dropdownContainer' });
+ const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findDropdownHeader = () => wrapper.find(GlNewDropdownHeader);
+ const findSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
+
+ describe('user is searching via "filter by commit message"', () => {
+ it('disables dropdown container', () => {
+ wrapper.setData({ hasSearchParam: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDropdownContainer().attributes('disabled')).toBeFalsy();
+ });
+ });
+
+ it('has correct tooltip message', () => {
+ wrapper.setData({ hasSearchParam: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDropdownContainer().attributes('title')).toBe(
+ 'Searching by both author and message is currently not supported.',
+ );
+ });
+ });
+
+ it('disables dropdown', () => {
+ wrapper.setData({ hasSearchParam: false });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDropdown().attributes('disabled')).toBeFalsy();
+ });
+ });
+
+ it('hasSearchParam if user types a truthy string', () => {
+ wrapper.vm.setSearchParam('false');
+
+ expect(wrapper.vm.hasSearchParam).toBeTruthy();
+ });
+ });
+
+ describe('dropdown', () => {
+ it('displays correct default text', () => {
+ expect(findDropdown().attributes('text')).toBe('Author');
+ });
+
+ it('displays the current selected author', () => {
+ wrapper.setData({ currentAuthor });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDropdown().attributes('text')).toBe(currentAuthor);
+ });
+ });
+
+ it('displays correct header text', () => {
+ expect(findDropdownHeader().text()).toBe('Search by author');
+ });
+
+ it('does not have popover text by default', () => {
+ expect(wrapper.attributes('title')).not.toExist();
+ });
+ });
+
+ describe('dropdown search box', () => {
+ it('has correct placeholder', () => {
+ expect(findSearchBox().attributes('placeholder')).toBe('Search');
+ });
+
+ it('fetch authors on input change', () => {
+ const authorName = 'lorem';
+ findSearchBox().vm.$emit('input', authorName);
+
+ expect(store.actions.fetchAuthors).toHaveBeenCalledWith(
+ expect.anything(),
+ authorName,
+ undefined,
+ );
+ });
+ });
+
+ describe('dropdown list', () => {
+ beforeEach(() => {
+ store.state.commitsAuthors = authors;
+ store.state.commitsPath = commitsPath;
+ });
+
+ it('has a "Any Author" as the first list item', () => {
+ expect(
+ findDropdownItems()
+ .at(0)
+ .text(),
+ ).toBe('Any Author');
+ });
+
+ it('displays the project authors', () => {
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDropdownItems()).toHaveLength(authors.length + 1);
+ });
+ });
+
+ it('has the correct props', () => {
+ const [{ avatar_url, username }] = authors;
+ const result = {
+ avatarUrl: avatar_url,
+ secondaryText: username,
+ isChecked: true,
+ };
+
+ wrapper.setData({ currentAuthor });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(
+ findDropdownItems()
+ .at(1)
+ .props(),
+ ).toEqual(expect.objectContaining(result));
+ });
+ });
+
+ it("display the author's name", () => {
+ return wrapper.vm.$nextTick().then(() => {
+ expect(
+ findDropdownItems()
+ .at(1)
+ .text(),
+ ).toBe(currentAuthor);
+ });
+ });
+
+ it('passes selected author to redirectPath', () => {
+ const redirectToUrl = `${commitsPath}?author=${currentAuthor}`;
+ const spy = jest.spyOn(urlUtility, 'redirectTo');
+ spy.mockImplementation(() => 'mock');
+
+ findDropdownItems()
+ .at(1)
+ .vm.$emit('click');
+
+ expect(spy).toHaveBeenCalledWith(redirectToUrl);
+ });
+
+ it('does not pass any author to redirectPath', () => {
+ const redirectToUrl = commitsPath;
+ const spy = jest.spyOn(urlUtility, 'redirectTo');
+ spy.mockImplementation();
+
+ findDropdownItems()
+ .at(0)
+ .vm.$emit('click');
+ expect(spy).toHaveBeenCalledWith(redirectToUrl);
+ });
+ });
+});
diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js
new file mode 100644
index 00000000000..c9945e1cc27
--- /dev/null
+++ b/spec/frontend/projects/commits/store/actions_spec.js
@@ -0,0 +1,69 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import * as types from '~/projects/commits/store/mutation_types';
+import testAction from 'helpers/vuex_action_helper';
+import actions from '~/projects/commits/store/actions';
+import createState from '~/projects/commits/store/state';
+import createFlash from '~/flash';
+
+jest.mock('~/flash');
+
+describe('Project commits actions', () => {
+ let state;
+ let mock;
+
+ beforeEach(() => {
+ state = createState();
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('setInitialData', () => {
+ it(`commits ${types.SET_INITIAL_DATA}`, () =>
+ testAction(actions.setInitialData, undefined, state, [{ type: types.SET_INITIAL_DATA }]));
+ });
+
+ describe('receiveAuthorsSuccess', () => {
+ it(`commits ${types.COMMITS_AUTHORS}`, () =>
+ testAction(actions.receiveAuthorsSuccess, undefined, state, [
+ { type: types.COMMITS_AUTHORS },
+ ]));
+ });
+
+ describe('shows a flash message when there is an error', () => {
+ it('creates a flash', () => {
+ const mockDispatchContext = { dispatch: () => {}, commit: () => {}, state };
+ actions.receiveAuthorsError(mockDispatchContext);
+
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith('An error occurred fetching the project authors.');
+ });
+ });
+
+ describe('fetchAuthors', () => {
+ it('dispatches request/receive', () => {
+ const path = '/autocomplete/users.json';
+ state.projectId = '8';
+ const data = [{ id: 1 }];
+
+ mock.onGet(path).replyOnce(200, data);
+ testAction(
+ actions.fetchAuthors,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAuthorsSuccess', payload: data }],
+ );
+ });
+
+ it('dispatches request/receive on error', () => {
+ const path = '/autocomplete/users.json';
+ mock.onGet(path).replyOnce(500);
+
+ testAction(actions.fetchAuthors, null, state, [], [{ type: 'receiveAuthorsError' }]);
+ });
+ });
+});
diff --git a/spec/frontend/projects/commits/store/mutations_spec.js b/spec/frontend/projects/commits/store/mutations_spec.js
new file mode 100644
index 00000000000..4fc346beb33
--- /dev/null
+++ b/spec/frontend/projects/commits/store/mutations_spec.js
@@ -0,0 +1,43 @@
+import * as types from '~/projects/commits/store/mutation_types';
+import mutations from '~/projects/commits/store/mutations';
+import createState from '~/projects/commits/store/state';
+
+describe('Project commits mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ afterEach(() => {
+ state = null;
+ });
+
+ describe(`${types.SET_INITIAL_DATA}`, () => {
+ it('sets initial data', () => {
+ state.commitsPath = null;
+ state.projectId = null;
+ state.commitsAuthors = [];
+
+ const data = {
+ commitsPath: 'some/path',
+ projectId: '8',
+ };
+
+ mutations[types.SET_INITIAL_DATA](state, data);
+
+ expect(state).toEqual(expect.objectContaining(data));
+ });
+ });
+
+ describe(`${types.COMMITS_AUTHORS}`, () => {
+ it('sets commitsAuthors', () => {
+ const authors = [{ id: 1 }, { id: 2 }];
+ state.commitsAuthors = [];
+
+ mutations[types.COMMITS_AUTHORS](state, authors);
+
+ expect(state.commitsAuthors).toEqual(authors);
+ });
+ });
+});
diff --git a/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
index 85e3bc14cdc..40b784fdb87 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::ImportExport::JSON::NdjsonReader do
describe '#exist?' do
subject { ndjson_reader.exist? }
- context 'given valid dir_path', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213843' do
+ context 'given valid dir_path' do
let(:dir_path) { fixture }
it { is_expected.to be true }
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index 16762f32d8b..4e287f36648 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -15,15 +15,17 @@ module ImportExport
export_path = [prefix, 'spec', 'fixtures', 'lib', 'gitlab', 'import_export', name].compact
export_path = File.join(*export_path)
- extract_archive(export_path, 'tree.tar.gz')
+ if File.exist?(File.join(export_path, 'tree.tar.gz'))
+ extract_archive(export_path, 'tree.tar.gz')
+ end
allow_any_instance_of(Gitlab::ImportExport).to receive(:export_path) { export_path }
end
def extract_archive(path, archive)
- if File.exist?(File.join(path, archive))
- system("cd #{path}; tar xzvf #{archive} &> /dev/null")
- end
+ output, exit_status = Gitlab::Popen.popen(["cd #{path}; tar xzf #{archive}"])
+
+ raise "Failed to extract archive. Output: #{output}" unless exit_status.zero?
end
def cleanup_artifacts_from_extract_archive(name, prefix = nil)
@@ -31,7 +33,7 @@ module ImportExport
export_path = File.join(*export_path)
if File.exist?(File.join(export_path, 'tree.tar.gz'))
- system("cd #{export_path}; rm -fr tree &> /dev/null")
+ system("cd #{export_path}; rm -fr tree")
end
end
diff --git a/spec/tasks/gitlab/x509/update_rake_spec.rb b/spec/tasks/gitlab/x509/update_rake_spec.rb
new file mode 100644
index 00000000000..8c62c6c1728
--- /dev/null
+++ b/spec/tasks/gitlab/x509/update_rake_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe 'gitlab:x509 namespace rake task' do
+ before :all do
+ Rake.application.rake_require 'tasks/gitlab/x509/update'
+ end
+
+ describe 'update_signatures' do
+ subject { run_rake_task('gitlab:x509:update_signatures') }
+
+ let(:project) { create :project, :repository, path: X509Helpers::User1.path }
+ let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') }
+ let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature }
+
+ it 'changes from unverified to verified if the certificate store contains the root certificate' do
+ x509_commit
+
+ store = OpenSSL::X509::Store.new
+ certificate = OpenSSL::X509::Certificate.new X509Helpers::User1.trust_cert
+ store.add_cert(certificate)
+ allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
+
+ expect(x509_commit.verification_status).to eq('unverified')
+ expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original
+
+ subject
+
+ x509_commit.reload
+ expect(x509_commit.verification_status).to eq('verified')
+ end
+
+ it 'returns if no signature is available' do
+ expect_any_instance_of(Gitlab::X509::Commit) do |x509_commit|
+ expect(x509_commit).not_to receive(:update_signature!)
+
+ subject
+ end
+ end
+ end
+end