diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-11 21:08:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-11 21:08:28 +0300 |
commit | 0cc7fe66f5f614339b6584e66371ef3cd2c9fd97 (patch) | |
tree | 6c25bb4ae1f2747e1236bc5b1b4609e7507002b8 | |
parent | 018431c70e62358b959d23e7881bf1513e68f165 (diff) |
Add latest changes from gitlab-org/gitlab@master
27 files changed, 589 insertions, 159 deletions
diff --git a/app/assets/javascripts/releases/components/release_block_assets.vue b/app/assets/javascripts/releases/components/release_block_assets.vue index 21911f9d3cd..4e1d6fd1936 100644 --- a/app/assets/javascripts/releases/components/release_block_assets.vue +++ b/app/assets/javascripts/releases/components/release_block_assets.vue @@ -1,65 +1,190 @@ <script> -import { GlTooltipDirective, GlLink } from '@gitlab/ui'; +import { GlTooltipDirective, GlLink, GlButton, GlCollapse, GlIcon, GlBadge } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { ASSET_LINK_TYPE } from '../constants'; +import { __, s__, sprintf } from '~/locale'; +import { difference } from 'lodash'; export default { name: 'ReleaseBlockAssets', components: { GlLink, + GlButton, + GlCollapse, + GlIcon, Icon, + GlBadge, }, directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], props: { assets: { type: Object, required: true, }, }, + data() { + return { + isAssetsExpanded: true, + }; + }, computed: { hasAssets() { return Boolean(this.assets.count); }, + imageLinks() { + return this.linksForType(ASSET_LINK_TYPE.IMAGE); + }, + packageLinks() { + return this.linksForType(ASSET_LINK_TYPE.PACKAGE); + }, + runbookLinks() { + return this.linksForType(ASSET_LINK_TYPE.RUNBOOK); + }, + otherLinks() { + return difference(this.assets.links, [ + ...this.imageLinks, + ...this.packageLinks, + ...this.runbookLinks, + ]); + }, + sections() { + return [ + { + links: this.assets.sources.map(s => ({ + url: s.url, + name: sprintf(__('Source code (%{fileExtension})'), { fileExtension: s.format }), + })), + iconName: 'doc-code', + }, + { + title: s__('ReleaseAssetLinkType|Images'), + links: this.imageLinks, + iconName: 'container-image', + }, + { + title: s__('ReleaseAssetLinkType|Packages'), + links: this.packageLinks, + iconName: 'package', + }, + { + title: s__('ReleaseAssetLinkType|Runbooks'), + links: this.runbookLinks, + iconName: 'book', + }, + { + title: s__('ReleaseAssetLinkType|Other'), + links: this.otherLinks, + iconName: 'link', + }, + ].filter(section => section.links.length > 0); + }, + }, + methods: { + toggleAssetsExpansion() { + this.isAssetsExpanded = !this.isAssetsExpanded; + }, + linksForType(type) { + return this.assets.links.filter(l => l.linkType === type); + }, }, + externalLinkTooltipText: __('This link points to external content'), }; </script> <template> <div class="card-text prepend-top-default"> - <b> - {{ __('Assets') }} - <span class="js-assets-count badge badge-pill">{{ assets.count }}</span> - </b> - - <ul v-if="assets.links.length" class="pl-0 mb-0 gl-mt-3 list-unstyled js-assets-list"> - <li v-for="link in assets.links" :key="link.name" class="append-bottom-8"> - <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.directAssetUrl"> - <icon name="package" class="align-middle append-right-4 align-text-bottom" /> - {{ link.name }} - <span v-if="link.external">{{ __('(external source)') }}</span> - </gl-link> - </li> - </ul> - - <div v-if="hasAssets" class="dropdown"> - <button - type="button" - class="btn btn-link" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false" + <template v-if="glFeatures.releaseAssetLinkType"> + <gl-button + data-testid="accordion-button" + variant="link" + class="gl-font-weight-bold" + @click="toggleAssetsExpansion" > - <icon name="doc-code" class="align-top append-right-4" /> - {{ __('Source code') }} - <icon name="chevron-down" /> - </button> + <gl-icon + name="chevron-right" + class="gl-transition-medium" + :class="{ 'gl-rotate-90': isAssetsExpanded }" + /> + {{ __('Assets') }} + <gl-badge size="sm" variant="neutral" class="gl-display-inline-block">{{ + assets.count + }}</gl-badge> + </gl-button> + <gl-collapse v-model="isAssetsExpanded"> + <div class="gl-pl-6 gl-pt-3 js-assets-list"> + <template v-for="(section, index) in sections"> + <h5 v-if="section.title" :key="`section-header-${index}`" class="gl-mb-2"> + {{ section.title }} + </h5> + <ul :key="`section-body-${index}`" class="list-unstyled gl-m-0"> + <li v-for="link in section.links" :key="link.url"> + <gl-link + :href="link.directAssetUrl || link.url" + class="gl-display-flex gl-align-items-center gl-line-height-24" + > + <gl-icon + :name="section.iconName" + class="gl-mr-2 gl-flex-shrink-0 gl-flex-grow-0" + /> + {{ link.name }} + <gl-icon + v-if="link.external" + v-gl-tooltip + name="external-link" + :aria-label="$options.externalLinkTooltipText" + :title="$options.externalLinkTooltipText" + data-testid="external-link-indicator" + class="gl-ml-2 gl-flex-shrink-0 gl-flex-grow-0 gl-text-gray-600" + /> + </gl-link> + </li> + </ul> + </template> + </div> + </gl-collapse> + </template> - <div class="js-sources-dropdown dropdown-menu"> - <li v-for="asset in assets.sources" :key="asset.url"> - <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link> + <template v-else> + <b> + {{ __('Assets') }} + <span class="js-assets-count badge badge-pill">{{ assets.count }}</span> + </b> + + <ul v-if="assets.links.length" class="pl-0 mb-0 gl-mt-3 list-unstyled js-assets-list"> + <li v-for="link in assets.links" :key="link.name" class="append-bottom-8"> + <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.directAssetUrl"> + <icon name="package" class="align-middle append-right-4 align-text-bottom" /> + {{ link.name }} + <span v-if="link.external" data-testid="external-link-indicator">{{ + __('(external source)') + }}</span> + </gl-link> </li> + </ul> + + <div v-if="hasAssets" class="dropdown"> + <button + type="button" + class="btn btn-link" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + > + <icon name="doc-code" class="align-top append-right-4" /> + {{ __('Source code') }} + <icon name="chevron-down" /> + </button> + + <div class="js-sources-dropdown dropdown-menu"> + <li v-for="asset in assets.sources" :key="asset.url"> + <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link> + </li> + </div> </div> - </div> + </template> </div> </template> diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 5db3e8d5fa5..b4b4fd84c37 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -78,7 +78,7 @@ module WikiActions # rubocop:disable Gitlab/ModuleWithInstanceVariables def update - return render('empty') unless can?(current_user, :create_wiki, container) + return render('shared/wikis/empty') unless can?(current_user, :create_wiki, container) @page = WikiPages::UpdateService.new(container: container, current_user: current_user, params: wiki_params).execute(page) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index d35498260c6..abc1d58cbf1 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -94,7 +94,6 @@ class Projects::BlobController < Projects::ApplicationController def destroy create_commit(Files::DeleteService, success_notice: _("The file has been successfully deleted."), success_path: -> { after_delete_path }, - failure_view: :show, failure_path: project_blob_path(@project, @id)) end diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index ffd050dae87..d3285b64dab 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,7 +10,7 @@ class Projects::ReleasesController < Projects::ApplicationController push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true) push_frontend_feature_flag(:release_show_page, project, default_enabled: true) push_frontend_feature_flag(:release_asset_link_editing, project, default_enabled: true) - push_frontend_feature_flag(:release_asset_link_type, project, default_enabled: false) + push_frontend_feature_flag(:release_asset_link_type, project, default_enabled: true) end before_action :authorize_update_release!, only: %i[edit update] diff --git a/changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml b/changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml new file mode 100644 index 00000000000..d2ea7fe1be1 --- /dev/null +++ b/changelogs/unreleased/fj-backfill-imported-snippet-repositories.yml @@ -0,0 +1,5 @@ +--- +title: Backfill failed imported snippet repositories +merge_request: 34052 +author: +type: other diff --git a/changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml b/changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml new file mode 100644 index 00000000000..dc24c002774 --- /dev/null +++ b/changelogs/unreleased/nfriend-enable-release_asset_link_type-f.yml @@ -0,0 +1,5 @@ +--- +title: Allow Release asset links to be associated with a type +merge_request: 33998 +author: +type: added diff --git a/changelogs/unreleased/sh-fix-delete-blob-failure.yml b/changelogs/unreleased/sh-fix-delete-blob-failure.yml new file mode 100644 index 00000000000..ec3a48a09f2 --- /dev/null +++ b/changelogs/unreleased/sh-fix-delete-blob-failure.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 error in BlobController#delete +merge_request: 34367 +author: +type: fixed diff --git a/config/initializers/database_config.rb b/config/initializers/database_config.rb index 52897ad549d..ce732677c74 100644 --- a/config/initializers/database_config.rb +++ b/config/initializers/database_config.rb @@ -30,7 +30,7 @@ if Gitlab::Runtime.multi_threaded? Rails.application.config.database_configuration[Rails.env] previous_db_pool_size = db_config['pool'] - db_config['pool'] = [db_config['pool'].to_i, max_threads].max + db_config['pool'] = [db_config['pool'].to_i, max_threads].max + ENV["DB_POOL_HEADROOM"].to_i ActiveRecord::Base.establish_connection(db_config) diff --git a/db/post_migrate/20200608072931_backfill_imported_snippet_repositories.rb b/db/post_migrate/20200608072931_backfill_imported_snippet_repositories.rb new file mode 100644 index 00000000000..0566524fa90 --- /dev/null +++ b/db/post_migrate/20200608072931_backfill_imported_snippet_repositories.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class BackfillImportedSnippetRepositories < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + DELAY_INTERVAL = 2.minutes.to_i + BATCH_SIZE = 200 + MIGRATION = 'BackfillSnippetRepositories' + + disable_ddl_transaction! + + class Snippet < ActiveRecord::Base + include EachBatch + + self.table_name = 'snippets' + self.inheritance_column = :_type_disabled + end + + class SnippetRepository < ActiveRecord::Base + self.table_name = 'snippet_repositories' + end + + def up + index = 1 + + Snippet.select(:id).where.not(id: SnippetRepository.select(:snippet_id)).each_batch(of: BATCH_SIZE, column: 'id') do |batch| + split_in_consecutive_batches(batch).each do |ids_batch| + migrate_in(index * DELAY_INTERVAL, MIGRATION, [ids_batch.first, ids_batch.last]) + + index += 1 + end + end + end + + def down + # no-op + end + + private + + def split_in_consecutive_batches(relation) + ids = relation.pluck(:id) + + (ids.first..ids.last).to_a.split {|i| !ids.include?(i) }.select(&:present?) + end +end diff --git a/db/structure.sql b/db/structure.sql index 14d1ec21c4b..bb90a6c09e3 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -13886,6 +13886,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200604145731 20200604174544 20200604174558 +20200608072931 20200608075553 20200609002841 \. diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml index 859ce4417c3..277a7a229ec 100644 --- a/doc/.vale/gitlab/Acronyms.yml +++ b/doc/.vale/gitlab/Acronyms.yml @@ -15,6 +15,7 @@ second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)' # ... with the exception of these: exceptions: - API + - ARN - ASCII - AWS - CNAME @@ -30,6 +31,7 @@ exceptions: - HTML - HTTP - HTTPS + - IAM - IDE - JSON - LDAP diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md index 388256e16b1..22e1f8bf769 100644 --- a/doc/development/integrations/secure_partner_integration.md +++ b/doc/development/integrations/secure_partner_integration.md @@ -99,5 +99,9 @@ and complete an integration with the Secure stage. doing an [Unfiltered blog post](https://about.gitlab.com/handbook/marketing/blog/unfiltered/), doing a co-branded webinar, or producing a co-branded whitepaper. +We have a [video playlist](https://www.youtube.com/playlist?list=PL05JrBw4t0KpMqYxJiOLz-uBIr5w-yP4A) +that may be helpful as part of this process. This covers various topics related to integrating your +tool. + If you have any issues while working through your integration or the steps above, please create an issue to discuss with us further. diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md index 441ef545141..253d5e56463 100644 --- a/doc/topics/autodevops/customize.md +++ b/doc/topics/autodevops/customize.md @@ -72,25 +72,20 @@ Avoid passing secrets as Docker build arguments if possible, as they may be persisted in your image. See [this discussion of best practices with secrets](https://github.com/moby/moby/issues/13490) for details. -## Passing secrets to `docker build` +## Forward CI variables to the build environment > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25514) in GitLab 12.3, but available in versions 11.9 and above. -CI environment variables can be passed as -[build secrets](https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information) to the `docker build` command by listing them -by name, comma-separated, in the `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` -variable. For example, to forward the variables `CI_COMMIT_SHA` and `CI_ENVIRONMENT_NAME`, -set `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` to `CI_COMMIT_SHA,CI_ENVIRONMENT_NAME`. +CI variables can be forwarded into the build environment using the +`AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` CI variable. +The forwarded variables should be specified by name in a comma-separated +list. For example, to forward the variables `CI_COMMIT_SHA` and +`CI_ENVIRONMENT_NAME`, set `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` +to `CI_COMMIT_SHA,CI_ENVIRONMENT_NAME`. -CAUTION: **Caution:** -Unlike build arguments, these variables are not persisted by Docker in the final image, -though you can still persist them yourself. - -In projects: - -- Without a `Dockerfile`, these are available automatically as environment - variables. -- With a `Dockerfile`, the following is required: +- When using Buildpacks, the forwarded variables are available automatically + as environment variables. +- When using a `Dockerfile`, the following additional steps are required: 1. Activate the experimental `Dockerfile` syntax by adding the following code to the top of the file: @@ -304,7 +299,7 @@ applications. | `AUTO_DEVOPS_BUILD_IMAGE_CNB_ENABLED` | When set to a non-empty value and no `Dockerfile` is present, Auto Build builds your application using Cloud Native Buildpacks instead of Herokuish. [More details](stages.md#auto-build-using-cloud-native-buildpacks-beta). | | `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER` | The builder used when building with Cloud Native Buildpacks. The default builder is `heroku/buildpacks:18`. [More details](stages.md#auto-build-using-cloud-native-buildpacks-beta). | | `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` | Extra arguments to be passed to the `docker build` command. Note that using quotes won't prevent word splitting. [More details](#passing-arguments-to-docker-build). | -| `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` | A [comma-separated list of CI variable names](#passing-secrets-to-docker-build) to be passed to the `docker build` command as secrets. | +| `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` | A [comma-separated list of CI variable names](#forward-ci-variables-to-the-build-environment) to be forwarded to the build environment (the buildpack builder or `docker build`). | | `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). | | `AUTO_DEVOPS_CHART_REPOSITORY` | Helm Chart repository used to search for charts. Defaults to `https://charts.gitlab.io`. | | `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From GitLab 11.11, used to set the name of the Helm repository. Defaults to `gitlab`. | diff --git a/doc/user/project/clusters/add_eks_clusters.md b/doc/user/project/clusters/add_eks_clusters.md index 4d9604bf467..b11483a7446 100644 --- a/doc/user/project/clusters/add_eks_clusters.md +++ b/doc/user/project/clusters/add_eks_clusters.md @@ -59,7 +59,6 @@ To create and add a new Kubernetes cluster to your project, group, or instance: - Project's **{cloud-gear}** **Operations > Kubernetes** page, for a project-level cluster. - Group's **{cloud-gear}** **Kubernetes** page, for a group-level cluster. - **{admin}** **Admin Area >** **{cloud-gear}** **Kubernetes**, for an instance-level cluster. -<br><br/> 1. Click **Add Kubernetes cluster**. 1. Under the **Create new cluster** tab, click **Amazon EKS**. You will be provided with an `Account ID` and `External ID` to use in the next step. @@ -155,7 +154,6 @@ To create and add a new Kubernetes cluster to your project, group, or instance: - **Node count** - The number of worker nodes. - **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](index.md#gitlab-managed-clusters) for more information. -<br><br/> 1. Finally, click the **Create Kubernetes cluster** button. After about 10 minutes, your cluster will be ready to go. You can now proceed diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md index 8750f1a4cc6..d0cba729e35 100644 --- a/doc/user/project/clusters/add_remove_clusters.md +++ b/doc/user/project/clusters/add_remove_clusters.md @@ -161,7 +161,6 @@ To add a Kubernetes cluster to your project, group, or instance: 1. Project's **{cloud-gear}** **Operations > Kubernetes** page, for a project-level cluster. 1. Group's **{cloud-gear}** **Kubernetes** page, for a group-level cluster. 1. **{admin}** **Admin Area >** **{cloud-gear}** **Kubernetes** page, for an instance-level cluster. -<br></br> 1. Click **Add Kubernetes cluster**. 1. Click the **Add existing cluster** tab and fill in the details: 1. **Kubernetes cluster name** (required) - The name you wish to give the cluster. @@ -184,9 +183,7 @@ To add a Kubernetes cluster to your project, group, or instance: 1. Get the certificate by running this command: ```shell - kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode - ``` NOTE: **Note:** @@ -199,7 +196,6 @@ To add a Kubernetes cluster to your project, group, or instance: **The token used should belong to a service account with [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) privileges.** To create this service account: - 1. Create a file called `gitlab-admin-service-account.yaml` with contents: ```yaml @@ -273,16 +269,15 @@ To add a Kubernetes cluster to your project, group, or instance: token: <authentication_token> ``` - NOTE: **Note:** - For GKE clusters, you will need the - `container.clusterRoleBindings.create` permission to create a cluster - role binding. You can follow the [Google Cloud - documentation](https://cloud.google.com/iam/docs/granting-changing-revoking-access) - to grant access. + NOTE: **Note:** + For GKE clusters, you will need the + `container.clusterRoleBindings.create` permission to create a cluster + role binding. You can follow the [Google Cloud + documentation](https://cloud.google.com/iam/docs/granting-changing-revoking-access) + to grant access. 1. **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](index.md#gitlab-managed-clusters) for more information. - 1. **Project namespace** (optional) - You don't have to fill it in; by leaving it blank, GitLab will create one for you. Also: - Each project should have a unique namespace. @@ -292,7 +287,7 @@ To add a Kubernetes cluster to your project, group, or instance: - If you or someone created a secret specifically for the project, usually with limited permissions, the secret's namespace and project namespace may be the same. -<br></br> + 1. Finally, click the **Create Kubernetes cluster** button. After a couple of minutes, your cluster will be ready to go. You can now proceed diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md index db8f24fc1e1..7b21c590c8a 100644 --- a/doc/user/project/integrations/bamboo.md +++ b/doc/user/project/integrations/bamboo.md @@ -22,7 +22,7 @@ need to be configured in a Bamboo build plan before GitLab can integrate. 1. Choose 'Repository triggers the build when changes are committed' 1. Check one or more repositories checkboxes 1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a - whitelist of IP addresses that are allowed to trigger Bamboo builds. + list of IP addresses that are allowed to trigger Bamboo builds. 1. Save the trigger. 1. In the left pane, select a build stage. If you have multiple build stages you want to select the last stage that contains the Git checkout task. diff --git a/doc/user/project/repository/repository_mirroring.md b/doc/user/project/repository/repository_mirroring.md index 03a23c71465..bc643301ad9 100644 --- a/doc/user/project/repository/repository_mirroring.md +++ b/doc/user/project/repository/repository_mirroring.md @@ -406,13 +406,13 @@ proxy_push() REFNAME="$3" # --- Pattern of branches to proxy pushes - whitelisted=$(expr "$branch" : "\(master\)") + allowlist=$(expr "$branch" : "\(master\)") case "$refname" in refs/heads/*) branch=$(expr "$refname" : "refs/heads/\(.*\)") - if [ "$whitelisted" = "$branch" ]; then + if [ "$allowlist" = "$branch" ]; then unset GIT_QUARANTINE_PATH # handle https://git-scm.com/docs/git-receive-pack#_quarantine_environment error="$(git push --quiet $TARGET_REPO $NEWREV:$REFNAME 2>&1)" fail=$? @@ -453,7 +453,7 @@ Note that this sample has a few limitations: - This example may not work verbatim for your use case and might need modification. - It does not regard different types of authentication mechanisms for the mirror. - It does not work with forced updates (rewriting history). - - Only branches that match the `whitelisted` patterns will be proxy pushed. + - Only branches that match the `allowlist` patterns will be proxy pushed. - The script circumvents the Git hook quarantine environment because the update of `$TARGET_REPO` is seen as a ref update and Git will complain about it. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index a35f8f01fbb..0798c39fff5 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -192,11 +192,9 @@ to transfer a project. You can transfer an existing project into a [group](../../group/index.md) if: -1. You have at least **Maintainer** [permissions](../../permissions.md#project-members-permissions) to that group. -1. The project is in a subgroup you own. -1. You're at least a **Maintainer** of the project under your personal namespace. - Similarly, if you're an owner of a group, you can transfer any of its projects - under your own user. +- You have at least **Maintainer** [permissions](../../permissions.md#project-members-permissions) to that group. +- You're at least an **Owner** of the project to be transferred. +- The group to which the project is being transferred to must allow creation of new projects. To transfer a project: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f366815e7c1..642475c0e86 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18341,15 +18341,24 @@ msgstr "" msgid "ReleaseAssetLinkType|Image" msgstr "" +msgid "ReleaseAssetLinkType|Images" +msgstr "" + msgid "ReleaseAssetLinkType|Other" msgstr "" msgid "ReleaseAssetLinkType|Package" msgstr "" +msgid "ReleaseAssetLinkType|Packages" +msgstr "" + msgid "ReleaseAssetLinkType|Runbook" msgstr "" +msgid "ReleaseAssetLinkType|Runbooks" +msgstr "" + msgid "Releases" msgstr "" @@ -20992,6 +21001,9 @@ msgstr "" msgid "Source code" msgstr "" +msgid "Source code (%{fileExtension})" +msgstr "" + msgid "Source is not available" msgstr "" @@ -22894,6 +22906,9 @@ msgstr "" msgid "This license has already expired." msgstr "" +msgid "This link points to external content" +msgstr "" + msgid "This may expose confidential information as the selected fork is in another namespace that can have other members." msgstr "" diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 33faf54361a..9fee97f938c 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -378,6 +378,22 @@ RSpec.describe Projects::BlobController do expect(response).to redirect_to(after_delete_path) end + + context 'when a validation failure occurs' do + let(:failure_path) { project_blob_path(project, default_params[:id]) } + + render_views + + it 'redirects to a valid page' do + expect_next_instance_of(Files::DeleteService) do |instance| + expect(instance).to receive(:validate!).and_raise(Commits::CreateService::ValidationError, "validation error") + end + + delete :destroy, params: default_params + + expect(response).to redirect_to(failure_path) + end + end end context 'if deleted file is the last one in a subdirectory' do diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb index defcb252ce4..962d5551631 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -26,47 +26,65 @@ RSpec.describe 'User views releases', :js do expect(page).not_to have_content('Upcoming Release') end - context 'when there is a link as an asset' do - let!(:release_link) { create(:release_link, release: release, url: url ) } - let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } - let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath } - - it 'sees the link' do - visit project_releases_path(project) - - page.within('.js-assets-list') do - expect(page).to have_link release_link.name, href: direct_asset_link - expect(page).not_to have_content('(external source)') - end - end - - context 'when there is a link redirect' do - let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) } + shared_examples 'asset link tests' do + context 'when there is a link as an asset' do + let!(:release_link) { create(:release_link, release: release, url: url ) } let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } + let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release) << release_link.filepath } it 'sees the link' do visit project_releases_path(project) page.within('.js-assets-list') do expect(page).to have_link release_link.name, href: direct_asset_link - expect(page).not_to have_content('(external source)') + expect(page).not_to have_css('[data-testid="external-link-indicator"]') end end - end - context 'when url points to external resource' do - let(:url) { 'http://google.com/download' } + context 'when there is a link redirect' do + let!(:release_link) { create(:release_link, release: release, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) } + let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" } - it 'sees that the link is external resource' do - visit project_releases_path(project) + it 'sees the link' do + visit project_releases_path(project) - page.within('.js-assets-list') do - expect(page).to have_content('(external source)') + page.within('.js-assets-list') do + expect(page).to have_link release_link.name, href: direct_asset_link + expect(page).not_to have_css('[data-testid="external-link-indicator"]') + end + end + end + + context 'when url points to external resource' do + let(:url) { 'http://google.com/download' } + + it 'sees that the link is external resource' do + visit project_releases_path(project) + + page.within('.js-assets-list') do + expect(page).to have_css('[data-testid="external-link-indicator"]') + end end end end end + context 'when the release_asset_link_type feature flag is enabled' do + before do + stub_feature_flags(release_asset_link_type: true) + end + + it_behaves_like 'asset link tests' + end + + context 'when the release_asset_link_type feature flag is disabled' do + before do + stub_feature_flags(release_asset_link_type: false) + end + + it_behaves_like 'asset link tests' + end + context 'with an upcoming release' do let(:tomorrow) { Time.zone.now + 1.day } let!(:release) { create(:release, project: project, released_at: tomorrow ) } diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js new file mode 100644 index 00000000000..44b190b0d19 --- /dev/null +++ b/spec/frontend/releases/components/release_block_assets_spec.js @@ -0,0 +1,137 @@ +import { mount } from '@vue/test-utils'; +import { GlCollapse } from '@gitlab/ui'; +import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue'; +import { ASSET_LINK_TYPE } from '~/releases/constants'; +import { trimText } from 'helpers/text_helper'; +import { assets } from '../mock_data'; + +describe('Release block assets', () => { + let wrapper; + let defaultProps; + + // A map of types to the expected section heading text + const sections = { + [ASSET_LINK_TYPE.IMAGE]: 'Images', + [ASSET_LINK_TYPE.PACKAGE]: 'Packages', + [ASSET_LINK_TYPE.RUNBOOK]: 'Runbooks', + [ASSET_LINK_TYPE.OTHER]: 'Other', + }; + + const createComponent = (propsData = defaultProps) => { + wrapper = mount(ReleaseBlockAssets, { + provide: { + glFeatures: { releaseAssetLinkType: true }, + }, + propsData, + }); + }; + + const findSectionHeading = type => + wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]); + + beforeEach(() => { + defaultProps = { assets }; + }); + + describe('with default props', () => { + beforeEach(() => createComponent()); + + const findAccordionButton = () => wrapper.find('[data-testid="accordion-button"]'); + + it('renders an "Assets" accordion with the asset count', () => { + const accordionButton = findAccordionButton(); + + expect(accordionButton.exists()).toBe(true); + expect(trimText(accordionButton.text())).toBe('Assets 5'); + }); + + it('renders the accordion as expanded by default', () => { + const accordion = wrapper.find(GlCollapse); + + expect(accordion.exists()).toBe(true); + expect(accordion.isVisible()).toBe(true); + }); + + it('renders sources with the expected text and URL', () => { + defaultProps.assets.sources.forEach(s => { + const sourceLink = wrapper.find(`li>a[href="${s.url}"]`); + + expect(sourceLink.exists()).toBe(true); + expect(sourceLink.text()).toBe(`Source code (${s.format})`); + }); + }); + + it('renders a heading for each assets type (except sources)', () => { + Object.keys(sections).forEach(type => { + const sectionHeadings = findSectionHeading(type); + + expect(sectionHeadings).toHaveLength(1); + }); + }); + + it('renders asset links with the expected text and URL', () => { + defaultProps.assets.links.forEach(l => { + const sourceLink = wrapper.find(`li>a[href="${l.directAssetUrl}"]`); + + expect(sourceLink.exists()).toBe(true); + expect(sourceLink.text()).toBe(l.name); + }); + }); + }); + + describe("when a release doesn't have a link with a certain asset type", () => { + const typeToExclude = ASSET_LINK_TYPE.IMAGE; + + beforeEach(() => { + defaultProps.assets.links = defaultProps.assets.links.filter( + l => l.linkType !== typeToExclude, + ); + createComponent(defaultProps); + }); + + it('does not render a section heading if there are no links of that type', () => { + const sectionHeadings = findSectionHeading(typeToExclude); + + expect(sectionHeadings).toHaveLength(0); + }); + }); + + describe('external vs internal links', () => { + const containsExternalSourceIndicator = () => + wrapper.contains('[data-testid="external-link-indicator"]'); + + describe('when a link is external', () => { + beforeEach(() => { + defaultProps.assets.sources = []; + defaultProps.assets.links = [ + { + ...defaultProps.assets.links[0], + external: true, + }, + ]; + createComponent(defaultProps); + }); + + it('renders the link with an "external source" indicator', () => { + expect(containsExternalSourceIndicator()).toBe(true); + }); + }); + + describe('when a link is internal', () => { + beforeEach(() => { + defaultProps.assets.sources = []; + defaultProps.assets.links = [ + { + ...defaultProps.assets.links[0], + external: false, + }, + ]; + createComponent(defaultProps); + }); + + it('renders the link without the "external source" indicator', () => { + expect(containsExternalSourceIndicator()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js index 5924a78d911..b97385154bd 100644 --- a/spec/frontend/releases/mock_data.js +++ b/spec/frontend/releases/mock_data.js @@ -1,3 +1,5 @@ +import { ASSET_LINK_TYPE } from '~/releases/constants'; + export const milestones = [ { id: 50, @@ -150,6 +152,42 @@ export const pageInfoHeadersWithPagination = { 'X-TOTAL-PAGES': '2', }; +export const assets = { + count: 5, + sources: [ + { + format: 'zip', + url: 'https://example.gitlab.com/path/to/zip', + }, + ], + links: [ + { + linkType: ASSET_LINK_TYPE.IMAGE, + url: 'https://example.gitlab.com/path/to/image', + directAssetUrl: 'https://example.gitlab.com/path/to/image', + name: 'Example image link', + }, + { + linkType: ASSET_LINK_TYPE.PACKAGE, + url: 'https://example.gitlab.com/path/to/package', + directAssetUrl: 'https://example.gitlab.com/path/to/package', + name: 'Example package link', + }, + { + linkType: ASSET_LINK_TYPE.RUNBOOK, + url: 'https://example.gitlab.com/path/to/runbook', + directAssetUrl: 'https://example.gitlab.com/path/to/runbook', + name: 'Example runbook link', + }, + { + linkType: ASSET_LINK_TYPE.OTHER, + url: 'https://example.gitlab.com/path/to/link', + directAssetUrl: 'https://example.gitlab.com/path/to/link', + name: 'Example link', + }, + ], +}; + export const release2 = { name: 'Bionic Beaver', tag_name: '18.04', @@ -180,42 +218,7 @@ export const release2 = { committer_email: 'jack@example.com', committed_date: '2012-05-28T04:42:42-07:00', }, - assets: { - count: 6, - sources: [ - { - format: 'zip', - url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip', - }, - { - format: 'tar.gz', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', - }, - { - format: 'tar.bz2', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', - }, - { - format: 'tar', - url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', - }, - ], - links: [ - { - name: 'release-18.04.dmg', - url: 'https://my-external-hosting.example.com/scrambled-url/', - external: true, - }, - { - name: 'binary-linux-amd64', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', - external: false, - }, - ], - }, + assets, }; export const releases = [release, release2]; diff --git a/spec/initializers/database_config_spec.rb b/spec/initializers/database_config_spec.rb index 85577ce007a..7c0b280fdaf 100644 --- a/spec/initializers/database_config_spec.rb +++ b/spec/initializers/database_config_spec.rb @@ -48,6 +48,21 @@ describe 'Database config initializer' do expect { subject }.not_to change { Gitlab::Database.config['pool'] } end end + + context "when specifying headroom through an ENV variable" do + let(:headroom) { 10 } + + before do + stub_database_config(pool_size: 1) + stub_env("DB_POOL_HEADROOM", headroom) + end + + it "adds headroom on top of the calculated size" do + expect { subject }.to change { Gitlab::Database.config['pool'] } + .from(1) + .to(max_threads + headroom) + end + end end context "when using single-threaded runtime" do diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index aa18a1a843c..35f79042df0 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -4,34 +4,17 @@ require 'spec_helper' describe Gitlab::ProjectTemplate do describe '.all' do - it 'returns a all templates' do - expected = [ - described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'), - described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'), - described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'), - described_class.new('iosswift', 'iOS (Swift)', 'A ready-to-go template for use with iOS Swift apps.', 'https://gitlab.com/gitlab-org/project-templates/iosswift'), - described_class.new('dotnetcore', '.NET Core', 'A .NET Core console application template, customizable for any .NET Core project', 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'), - described_class.new('android', 'Android', 'A ready-to-go template for use with Android apps.', 'https://gitlab.com/gitlab-org/project-templates/android'), - described_class.new('gomicro', 'Go Micro', 'Go Micro is a framework for micro service development.', 'https://gitlab.com/gitlab-org/project-templates/go-micro'), - described_class.new('gatsby', 'Pages/Gatsby', 'Everything you need to get started using a Gatsby site.', 'https://gitlab.com/pages/gatsby'), - described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'), - described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'), - described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'), - described_class.new('gitbook', 'Pages/GitBook', 'Everything you need to get started using a GitBook Pages site.', 'https://gitlab.com/pages/gitbook'), - described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a Hexo Pages site.', 'https://gitlab.com/pages/hexo'), - described_class.new('sse_middleman', 'Static Site Editor/Middleman', _('Middleman project with Static Site Editor support'), 'https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman'), - described_class.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo'), - described_class.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll'), - described_class.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html'), - described_class.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook'), - described_class.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo'), - described_class.new('salesforcedx', 'SalesforceDX', _('A project boilerplate for Salesforce App development with Salesforce Developer tools.'), 'https://gitlab.com/gitlab-org/project-templates/salesforcedx'), - described_class.new('serverless_framework', 'Serverless Framework/JS', _('A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages'), 'https://gitlab.com/gitlab-org/project-templates/serverless-framework', 'illustrations/logos/serverless_framework.svg'), - described_class.new('cluster_management', 'GitLab Cluster Management', _('An example project for managing Kubernetes clusters integrated with GitLab.'), 'https://gitlab.com/gitlab-org/project-templates/cluster-management') + it 'returns all templates' do + expected = %w[ + rails spring express iosswift dotnetcore android + gomicro gatsby hugo jekyll plainhtml gitbook + hexo sse_middleman nfhugo nfjekyll nfplainhtml + nfgitbook nfhexo salesforcedx serverless_framework + cluster_management ] expect(described_class.all).to be_an(Array) - expect(described_class.all).to eq(expected) + expect(described_class.all.map(&:name)).to match_array(expected) end end diff --git a/spec/migrations/backfill_imported_snippet_repositories_spec.rb b/spec/migrations/backfill_imported_snippet_repositories_spec.rb new file mode 100644 index 00000000000..c77978b23e4 --- /dev/null +++ b/spec/migrations/backfill_imported_snippet_repositories_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200608072931_backfill_imported_snippet_repositories.rb') + +describe BackfillImportedSnippetRepositories do + let(:users) { table(:users) } + let(:snippets) { table(:snippets) } + let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') } + + def create_snippet(id) + params = { + id: id, + type: 'PersonalSnippet', + author_id: user.id, + file_name: 'foo', + content: 'bar' + } + + snippets.create!(params) + end + + it 'correctly schedules background migrations' do + create_snippet(1) + create_snippet(2) + create_snippet(3) + create_snippet(5) + create_snippet(7) + create_snippet(8) + create_snippet(10) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(2.minutes, 1, 3) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(4.minutes, 5, 5) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(6.minutes, 7, 8) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(8.minutes, 10, 10) + + expect(BackgroundMigrationWorker.jobs.size).to eq(4) + end + end + end +end diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index db6af403112..c128bbe5e02 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -282,6 +282,18 @@ RSpec.shared_examples 'wiki controller actions' do expect(wiki_page.content).to eq new_content end end + + context 'when user does not have edit permissions' do + before do + sign_out(:user) + end + + it 'renders the empty state' do + subject + + expect(response).to render_template('shared/wikis/empty') + end + end end def redirect_to_wiki(wiki, page) |