diff options
96 files changed, 1299 insertions, 770 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc71ae9245a..d49b620f22c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -44,3 +44,4 @@ include: - local: .gitlab/ci/review.gitlab-ci.yml - local: .gitlab/ci/setup.gitlab-ci.yml - local: .gitlab/ci/test-metadata.gitlab-ci.yml + - local: .gitlab/ci/yaml.gitlab-ci.yml diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 326bc5981c1..fd5733593ef 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -38,11 +38,11 @@ gitlab:assets:compile: - retry bundle exec rake gitlab:assets:compile - time scripts/build_assets_image - scripts/clean-old-cached-assets - - rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here + - rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here # Play dependent manual jobs - install_api_client_dependencies_with_apt - - play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played - - play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played + - play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played + - play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played artifacts: name: webpack-report expire_in: 31d @@ -141,8 +141,8 @@ jest: extends: .dedicated-no-docs-and-no-qa-pull-cache-job <<: *use-pg dependencies: - - compile-assets - - setup-test-env + - compile-assets + - setup-test-env script: - scripts/gitaly-test-spawn - date @@ -154,8 +154,8 @@ jest: expire_in: 31d when: always paths: - - coverage-frontend/ - - junit_jest.xml + - coverage-frontend/ + - junit_jest.xml reports: junit: junit_jest.xml cache: @@ -220,7 +220,7 @@ lint:javascript:report: before_script: [] script: - date - - yarn run eslint-report || true # ignore exit code + - yarn run eslint-report || true # ignore exit code artifacts: name: eslint-report expire_in: 31d @@ -235,7 +235,7 @@ jsdoc: before_script: [] script: - date - - yarn run jsdoc || true # ignore exit code + - yarn run jsdoc || true # ignore exit code artifacts: name: jsdoc expire_in: 31d diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index cf87f5eb39c..aa25bad00e8 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -1,6 +1,6 @@ .dedicated-runner: retry: - max: 2 # This is confusing but this means "3 runs at max". + max: 2 # This is confusing but this means "3 runs at max". when: - unknown_failure - api_failure diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml index 7d9136b8213..f7b18b809b4 100644 --- a/.gitlab/ci/pages.gitlab-ci.yml +++ b/.gitlab/ci/pages.gitlab-ci.yml @@ -1,4 +1,3 @@ - pages: extends: .dedicated-no-docs-no-db-pull-cache-job before_script: [] diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index 6b23af952e6..ee9e467886a 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -1,6 +1,6 @@ package-and-qa: image: ruby:2.6-alpine - stage: review # So even if review-deploy failed we can still run this + stage: review # So even if review-deploy failed we can still run this when: manual before_script: [] dependencies: [] diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 3f0c899d704..33e7b00784c 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -136,7 +136,7 @@ setup-test-env: stage: prepare script: - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - - scripts/gitaly-test-build # Do not use 'bundle exec' here + - scripts/gitaly-test-build # Do not use 'bundle exec' here artifacts: expire_in: 7d paths: @@ -342,8 +342,8 @@ coverage: name: coverage expire_in: 31d paths: - - coverage/index.html - - coverage/assets/ + - coverage/index.html + - coverage/assets/ except: - /(^docs[\/-].*|.*-docs$)/ - /(^qa[\/-].*|.*-qa$)/ diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml new file mode 100644 index 00000000000..401318d2df2 --- /dev/null +++ b/.gitlab/ci/yaml.gitlab-ci.yml @@ -0,0 +1,9 @@ +# Yamllint of *.yml for .gitlab-ci.yml. +# This uses rules from project root `.yamllint`. +lint-ci-gitlab: + extends: .dedicated-runner + before_script: [] + dependencies: [] + image: sdesbure/yamllint:latest + script: + - yamllint .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates diff --git a/.gitlab/route-map.yml b/.gitlab/route-map.yml index 0b37dc68f8b..cc0c2856d49 100644 --- a/.gitlab/route-map.yml +++ b/.gitlab/route-map.yml @@ -1,3 +1,3 @@ # Documentation -- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md - public: '\1.html' # doc/administration/build_artifacts.html +- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md + public: '\1.html' # doc/administration/build_artifacts.html diff --git a/.yamllint b/.yamllint new file mode 100644 index 00000000000..df7cdf404bc --- /dev/null +++ b/.yamllint @@ -0,0 +1,7 @@ +--- + +extends: default + +rules: + line-length: disable + document-start: disable diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 18fa6265108..05f34391323 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -565,6 +565,11 @@ GitLabDropdown = (function() { !$target.data('isLink') ) { e.stopPropagation(); + + // This prevents automatic scrolling to the top + if ($target.is('a')) { + return false; + } } return true; diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index cccde1bb278..d2198bcccfe 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -134,6 +134,7 @@ export default { :current-path="path" :path="entry.flatPath" :type="entry.type" + :url="entry.webUrl" /> </template> </tbody> diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 9a264bef87e..764882a7936 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -21,6 +21,11 @@ export default { type: String, required: true, }, + url: { + type: String, + required: false, + default: null, + }, }, computed: { routerLinkTo() { @@ -59,7 +64,7 @@ export default { <tr v-once :class="`file_${id}`" class="tree-item" @click="openRow"> <td class="tree-item-file-name"> <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> - <component :is="linkComponent" :to="routerLinkTo" class="str-truncated"> + <component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> {{ fullPath }} </component> <template v-if="isSubmodule"> diff --git a/app/assets/javascripts/repository/queries/getFiles.graphql b/app/assets/javascripts/repository/queries/getFiles.graphql index a9b61d28560..7d92bc46455 100644 --- a/app/assets/javascripts/repository/queries/getFiles.graphql +++ b/app/assets/javascripts/repository/queries/getFiles.graphql @@ -23,6 +23,7 @@ query getFiles( edges { node { ...TreeEntry + webUrl } } pageInfo { @@ -43,6 +44,7 @@ query getFiles( edges { node { ...TreeEntry + webUrl } } pageInfo { diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb index 230624201b0..f2b7d5df2b2 100644 --- a/app/graphql/types/tree/blob_type.rb +++ b/app/graphql/types/tree/blob_type.rb @@ -4,7 +4,11 @@ module Types class BlobType < BaseObject implements Types::Tree::EntryType + present_using BlobPresenter + graphql_name 'Blob' + + field :web_url, GraphQL::STRING_TYPE, null: true end end end diff --git a/app/graphql/types/tree/tree_entry_type.rb b/app/graphql/types/tree/tree_entry_type.rb index d5cfb898aea..23ec2ef0ec2 100644 --- a/app/graphql/types/tree/tree_entry_type.rb +++ b/app/graphql/types/tree/tree_entry_type.rb @@ -4,8 +4,12 @@ module Types class TreeEntryType < BaseObject implements Types::Tree::EntryType + present_using TreeEntryPresenter + graphql_name 'TreeEntry' description 'Represents a directory' + + field :web_url, GraphQL::STRING_TYPE, null: true end end end diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb index 1eb6c43972e..1ee93ed9542 100644 --- a/app/graphql/types/tree/tree_type.rb +++ b/app/graphql/types/tree/tree_type.rb @@ -4,9 +4,15 @@ module Types class TreeType < BaseObject graphql_name 'Tree' - field :trees, Types::Tree::TreeEntryType.connection_type, null: false + field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do + Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository) + end + field :submodules, Types::Tree::SubmoduleType.connection_type, null: false - field :blobs, Types::Tree::BlobType.connection_type, null: false + + field :blobs, Types::Tree::BlobType.connection_type, null: false, resolve: -> (obj, args, ctx) do + Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository) + end end end end diff --git a/app/helpers/tracking_helper.rb b/app/helpers/tracking_helper.rb new file mode 100644 index 00000000000..51ea79d1ddd --- /dev/null +++ b/app/helpers/tracking_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module TrackingHelper + def tracking_attrs(label, event, property) + {} # CE has no tracking features + end +end diff --git a/app/models/commit.rb b/app/models/commit.rb index f412d252e5c..fa0bf36ba49 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -13,6 +13,7 @@ class Commit include StaticModel include Presentable include ::Gitlab::Utils::StrongMemoize + include CacheMarkdownField attr_mentionable :safe_message, pipeline: :single_line @@ -37,13 +38,9 @@ class Commit # Used by GFM to match and present link extensions on node texts and hrefs. LINK_EXTENSION_PATTERN = /(patch)/.freeze - def banzai_render_context(field) - pipeline = field == :description ? :commit_description : :single_line - context = { pipeline: pipeline, project: self.project } - context[:author] = self.author if self.author - - context - end + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :full_title, pipeline: :single_line + cache_markdown_field :description, pipeline: :commit_description class << self def decorate(commits, project) diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index f90cd1ea690..42203a5f214 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -13,43 +13,9 @@ module CacheMarkdownField extend ActiveSupport::Concern - # Increment this number every time the renderer changes its output - CACHE_COMMONMARK_VERSION_START = 10 - CACHE_COMMONMARK_VERSION = 16 - # changes to these attributes cause the cache to be invalidates INVALIDATED_BY = %w[author project].freeze - # Knows about the relationship between markdown and html field names, and - # stores the rendering contexts for the latter - class FieldData - def initialize - @data = {} - end - - delegate :[], :[]=, to: :@data - - def markdown_fields - @data.keys - end - - def html_field(markdown_field) - "#{markdown_field}_html" - end - - def html_fields - markdown_fields.map { |field| html_field(field) } - end - - def html_fields_whitelisted - markdown_fields.each_with_object([]) do |field, fields| - if @data[field].fetch(:whitelisted, false) - fields << html_field(field) - end - end - end - end - def skip_project_check? false end @@ -85,24 +51,22 @@ module CacheMarkdownField end.to_h updates['cached_markdown_version'] = latest_cached_markdown_version - updates.each {|html_field, data| write_attribute(html_field, data) } + updates.each { |field, data| write_markdown_field(field, data) } end def refresh_markdown_cache! updates = refresh_markdown_cache - return unless persisted? && Gitlab::Database.read_write? - - update_columns(updates) + save_markdown(updates) end def cached_html_up_to_date?(markdown_field) - html_field = cached_markdown_fields.html_field(markdown_field) + return false if cached_html_for(markdown_field).nil? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend - return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend + html_field = cached_markdown_fields.html_field(markdown_field) - markdown_changed = attribute_changed?(markdown_field) || false - html_changed = attribute_changed?(html_field) || false + markdown_changed = markdown_field_changed?(markdown_field) + html_changed = markdown_field_changed?(html_field) latest_cached_markdown_version == cached_markdown_version && (html_changed || markdown_changed == html_changed) @@ -117,21 +81,21 @@ module CacheMarkdownField end def cached_html_for(markdown_field) - raise ArgumentError.new("Unknown field: #{field}") unless + raise ArgumentError.new("Unknown field: #{markdown_field}") unless cached_markdown_fields.markdown_fields.include?(markdown_field) __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend end def latest_cached_markdown_version - @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version + @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version end def local_version # because local_markdown_version is stored in application_settings which # uses cached_markdown_version too, we check explicitly to avoid # endless loop - return local_markdown_version if has_attribute?(:local_markdown_version) + return local_markdown_version if respond_to?(:has_attribute?) && has_attribute?(:local_markdown_version) settings = Gitlab::CurrentSettings.current_application_settings @@ -150,32 +114,14 @@ module CacheMarkdownField included do cattr_reader :cached_markdown_fields do - FieldData.new + Gitlab::MarkdownCache::FieldData.new end - # Always exclude _html fields from attributes (including serialization). - # They contain unredacted HTML, which would be a security issue - alias_method :attributes_before_markdown_cache, :attributes - def attributes - attrs = attributes_before_markdown_cache - html_fields = cached_markdown_fields.html_fields - whitelisted = cached_markdown_fields.html_fields_whitelisted - exclude_fields = html_fields - whitelisted - - exclude_fields.each do |field| - attrs.delete(field) - end - - if whitelisted.empty? - attrs.delete('cached_markdown_version') - end - - attrs + if self < ActiveRecord::Base + include Gitlab::MarkdownCache::ActiveRecord::Extension + else + prepend Gitlab::MarkdownCache::Redis::Extension end - - # Using before_update here conflicts with elasticsearch-model somehow - before_create :refresh_markdown_cache, if: :invalidated_markdown_cache? - before_update :refresh_markdown_cache, if: :invalidated_markdown_cache? end class_methods do @@ -193,10 +139,8 @@ module CacheMarkdownField # The HTML becomes invalid if any dependent fields change. For now, assume # author and project invalidate the cache in all circumstances. define_method(invalidation_method) do - changed_fields = changed_attributes.keys - invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY] - invalidations.delete(markdown_field.to_s) if changed_fields.include?("#{markdown_field}_html") - + invalidations = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY] + invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html") !invalidations.empty? || !cached_html_up_to_date?(markdown_field) end end diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index 6323c1b3389..c5675ef3ea3 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -13,4 +13,8 @@ class BlobPresenter < Gitlab::View::Presenter::Simple plain: plain ) end + + def web_url + Gitlab::Routing.url_helpers.project_blob_url(blob.repository.project, File.join(blob.commit_id, blob.path)) + end end diff --git a/app/presenters/tree_entry_presenter.rb b/app/presenters/tree_entry_presenter.rb new file mode 100644 index 00000000000..7bb10cd1455 --- /dev/null +++ b/app/presenters/tree_entry_presenter.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class TreeEntryPresenter < Gitlab::View::Presenter::Delegated + presents :tree + + def web_url + Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, File.join(tree.commit_id, tree.path)) + end +end diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index b5678b56ca6..28d4f8eb201 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -8,59 +8,58 @@ .import-buttons - if gitlab_project_import_enabled? .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitlab_export" } do + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do = icon('gitlab', text: 'GitLab export') - if github_import_enabled? %div - = link_to new_import_github_path, class: 'btn js-import-github', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "github" } do + = link_to new_import_github_path, class: 'btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do = icon('github', text: 'GitHub') - if bitbucket_import_enabled? %div = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}", - data: { track_label: "#{track_label}", track_event: "click_button", track_property: "bitbucket_cloud" } do + **tracking_attrs(track_label, 'click_button', 'bitbucket_cloud') do = icon('bitbucket', text: 'Bitbucket Cloud') - unless bitbucket_import_configured? = render 'bitbucket_import_modal' - if bitbucket_server_import_enabled? %div - = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket", - data: { track_label: "#{track_label}", track_event: "click_button", track_property: "bitbucket_server" } do + = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do = icon('bitbucket-square', text: 'Bitbucket Server') %div - if gitlab_import_enabled? %div = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}", - data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitlab_com" } do + **tracking_attrs(track_label, 'click_button', 'gitlab_com') do = icon('gitlab', text: 'GitLab.com') - unless gitlab_import_configured? = render 'gitlab_import_modal' - if google_code_import_enabled? %div - = link_to new_import_google_code_path, class: 'btn import_google_code', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "google_code" } do + = link_to new_import_google_code_path, class: 'btn import_google_code', **tracking_attrs(track_label, 'click_button', 'google_code') do = icon('google', text: 'Google Code') - if fogbugz_import_enabled? %div - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "fogbugz" } do + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do = icon('bug', text: 'Fogbugz') - if gitea_import_enabled? %div - = link_to new_import_gitea_path, class: 'btn import_gitea', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitea" } do + = link_to new_import_gitea_path, class: 'btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do = custom_icon('gitea_logo') Gitea - if git_import_enabled? %div - %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active', data: { toggle_open_class: 'active', track_label: "#{track_label}" , track_event: "click_button", track_property: "repo_url" } } } + %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') } = icon('git', text: 'Repo by URL') - if manifest_import_enabled? %div - = link_to new_import_manifest_path, class: 'btn import_manifest', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "manifest_file" } do + = link_to new_import_manifest_path, class: 'btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do = icon('file-text-o', text: 'Manifest file') - if phabricator_import_enabled? diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml index e37fd7624be..065fef606d5 100644 --- a/app/views/projects/tree/_tree_commit_column.html.haml +++ b/app/views/projects/tree/_tree_commit_column.html.haml @@ -1,2 +1,3 @@ +- full_title = markdown_field(commit, :full_title) %span.str-truncated - = link_to_html commit.redacted_full_title_html, project_commit_path(@project, commit.id), title: commit.redacted_full_title_html, class: 'tree-commit-link' + = link_to_html full_title, project_commit_path(@project, commit.id), title: full_title, class: 'tree-commit-link' diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml index d5327a2b4cc..dfcd1c6b19f 100644 --- a/app/views/repository_check_mailer/notify.html.haml +++ b/app/views/repository_check_mailer/notify.html.haml @@ -6,3 +6,5 @@ %p = _("You are receiving this message because you are a GitLab administrator for %{url}.") % { url: Gitlab.config.gitlab.url } + += render_if_exists 'repository_check_mailer/email_additional_text' diff --git a/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml new file mode 100644 index 00000000000..efda07380a4 --- /dev/null +++ b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml @@ -0,0 +1,5 @@ +--- +title: Use Redis for CacheMarkDownField on non AR models +merge_request: 29054 +author: +type: performance diff --git a/changelogs/unreleased/60819_yamllint_gitlabci.yml b/changelogs/unreleased/60819_yamllint_gitlabci.yml new file mode 100644 index 00000000000..aba3b206f7e --- /dev/null +++ b/changelogs/unreleased/60819_yamllint_gitlabci.yml @@ -0,0 +1,5 @@ +--- +title: Fix yaml linting for GitLab CI inside project (.gitlab/ci) *.yml files and CI template files +merge_request: 27576 +author: Will Hall +type: fixed diff --git a/changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml b/changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml new file mode 100644 index 00000000000..7fa15e1c0fd --- /dev/null +++ b/changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml @@ -0,0 +1,5 @@ +--- +title: Fix label click scrolling to top +merge_request: 29202 +author: +type: fixed diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb index f346151a3c1..2acc9d13f07 100644 --- a/lib/banzai/commit_renderer.rb +++ b/lib/banzai/commit_renderer.rb @@ -2,7 +2,7 @@ module Banzai module CommitRenderer - ATTRIBUTES = [:description, :title].freeze + ATTRIBUTES = [:description, :title, :full_title].freeze def self.render(commits, project, user = nil) obj_renderer = ObjectRenderer.new(user: user, default_project: project) diff --git a/lib/gitlab/ci/templates/.yamllint b/lib/gitlab/ci/templates/.yamllint new file mode 100644 index 00000000000..669c8646ff2 --- /dev/null +++ b/lib/gitlab/ci/templates/.yamllint @@ -0,0 +1,5 @@ +extends: default + +rules: + line-length: disable + document-start: disable diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml index 120272200c6..2ca6e73a803 100644 --- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml @@ -54,7 +54,7 @@ ensureContainer: - rm -f android-signing-keystore.jks || true artifacts: paths: - - app/build/outputs + - app/build/outputs buildDebug: extends: .build_job @@ -116,6 +116,6 @@ promoteProduction: # We only allow production promotion on `master` because # it has its own production scoped secret variables only: - - master + - master script: - bundle exec fastlane promote_beta_to_production diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml index 2be7f933462..b7194110002 100644 --- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml @@ -6,7 +6,7 @@ image: openjdk:8-jdk variables: ANDROID_COMPILE_SDK: "28" ANDROID_BUILD_TOOLS: "28.0.2" - ANDROID_SDK_TOOLS: "4333796" + ANDROID_SDK_TOOLS: "4333796" before_script: - apt-get --quiet update --yes @@ -35,7 +35,7 @@ assembleDebug: - ./gradlew assembleDebug artifacts: paths: - - app/build/outputs/ + - app/build/outputs/ debugTests: stage: test diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 32ab216dd55..65a6630365d 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -60,7 +60,7 @@ variables: stages: - build - test - - deploy # dummy stage to follow the template guidelines + - deploy # dummy stage to follow the template guidelines - review - dast - staging diff --git a/lib/gitlab/ci/templates/C++.gitlab-ci.yml b/lib/gitlab/ci/templates/C++.gitlab-ci.yml index 9a8fa9d7091..33a2a534508 100644 --- a/lib/gitlab/ci/templates/C++.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/C++.gitlab-ci.yml @@ -14,10 +14,10 @@ build: artifacts: paths: - mybinary - # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time - # cache: - # paths: - # - "*.o" + # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time + # cache: + # paths: + # - "*.o" # run tests using the binary built before test: diff --git a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml index 1e14aa8bea9..5f17c93b853 100644 --- a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml @@ -23,31 +23,31 @@ stages: foodcritic: stage: lint script: - - chef exec foodcritic . + - chef exec foodcritic . cookstyle: stage: lint script: - - chef exec cookstyle . + - chef exec cookstyle . chefspec: stage: test script: - - chef exec rspec spec + - chef exec rspec spec # Set up your test matrix here. Example: -#verify-centos-6: -# stage: functional -# before_script: -# - apt-get update -# - apt-get -y install rsync -# script: -# - kitchen verify default-centos-6 --destroy=always +# verify-centos-6: +# stage: functional +# before_script: +# - apt-get update +# - apt-get -y install rsync +# script: +# - kitchen verify default-centos-6 --destroy=always # -#verify-centos-7: -# stage: functional -# before_script: -# - apt-get update -# - apt-get -y install rsync -# script: -# - kitchen verify default-centos-7 --destroy=always +# verify-centos-7: +# stage: functional +# before_script: +# - apt-get update +# - apt-get -y install rsync +# script: +# - kitchen verify default-centos-7 --destroy=always diff --git a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml index 0610cb9ccc0..c3568c0d2c8 100644 --- a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml @@ -8,7 +8,7 @@ before_script: # If you need to install any external applications, like a # postgres client, you may want to uncomment the line below: # - #- apt-get update -y + # - apt-get update -y # # Retrieve project dependencies # Do this on before_script since it'll be shared between both test and @@ -17,6 +17,6 @@ before_script: test: script: - # If you need to run any migrations or configure the database, this - # would be the point to do it. - - lein test + # If you need to run any migrations or configure the database, this + # would be the point to do it. + - lein test diff --git a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml index 36386a19fdc..e9301a2638d 100644 --- a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml @@ -22,15 +22,15 @@ cache: # services such as redis or postgres before_script: - apt-get update -qq && apt-get install -y -qq libxml2-dev - - crystal -v # Print out Crystal version for debugging + - crystal -v # Print out Crystal version for debugging - shards # If you are using built-in Crystal Spec. spec: script: - - crystal spec + - crystal spec # If you are using minitest.cr minitest: script: - - crystal test/spec_test.cr # change to the file(s) you execute for tests + - crystal test/spec_test.cr # change to the file(s) you execute for tests diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml index 1d8be6f017e..d35fcb0f807 100644 --- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml @@ -16,19 +16,19 @@ variables: # http://docs.gitlab.com/ce/ci/yaml/README.html#cache cache: paths: - - ~/.cache/pip/ + - ~/.cache/pip/ # This is a basic example for a gem or script which doesn't use # services such as redis or postgres before_script: - - python -V # Print out python version for debugging + - python -V # Print out python version for debugging # Uncomment next line if your Django app needs a JS runtime: # - apt-get update -q && apt-get install nodejs -yqq - pip install -r requirements.txt # To get Django tests to work you may need to create a settings file using # the following DATABASES: -# +# # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.postgresql_psycopg2', @@ -46,4 +46,4 @@ test: variables: DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB" script: - - python manage.py test + - python manage.py test diff --git a/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml b/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml index cf9c731637c..4d4c6a64cd5 100644 --- a/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml @@ -15,4 +15,4 @@ before_script: mix: script: - - mix test + - mix test diff --git a/lib/gitlab/ci/templates/Go.gitlab-ci.yml b/lib/gitlab/ci/templates/Go.gitlab-ci.yml index 55fda1a4799..1b686bc6cc0 100644 --- a/lib/gitlab/ci/templates/Go.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Go.gitlab-ci.yml @@ -16,21 +16,21 @@ before_script: - cd $GOPATH/src/$REPO_NAME stages: - - test - - build - - deploy + - test + - build + - deploy format: - stage: test - script: - - go fmt $(go list ./... | grep -v /vendor/) - - go vet $(go list ./... | grep -v /vendor/) - - go test -race $(go list ./... | grep -v /vendor/) + stage: test + script: + - go fmt $(go list ./... | grep -v /vendor/) + - go vet $(go list ./... | grep -v /vendor/) + - go test -race $(go list ./... | grep -v /vendor/) compile: - stage: build - script: - - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary - artifacts: - paths: - - mybinary + stage: build + script: + - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary + artifacts: + paths: + - mybinary diff --git a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml index dbc868238f8..efcd1d3ddc0 100644 --- a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml @@ -16,25 +16,25 @@ variables: # We use SDKMan as tool for managing versions before_script: - - apt-get update -qq && apt-get install -y -qq unzip - - curl -sSL https://get.sdkman.io | bash - - echo sdkman_auto_answer=true > /root/.sdkman/etc/config - - source /root/.sdkman/bin/sdkman-init.sh - - sdk install gradle $GRADLE_VERSION < /dev/null - - sdk use gradle $GRADLE_VERSION -# As it's not a good idea to version gradle.properties feel free to add your -# environments variable here - - echo grailsVersion=$GRAILS_VERSION > gradle.properties - - echo gradleWrapperVersion=2.14 >> gradle.properties -# refresh dependencies from your project - - ./gradlew --refresh-dependencies -# Be aware that if you are using Angular profile, -# Bower cannot be run as root if you don't allow it before. -# Feel free to remove next line if you are not using Bower - - echo {\"allow_root\":true} > /root/.bowerrc + - apt-get update -qq && apt-get install -y -qq unzip + - curl -sSL https://get.sdkman.io | bash + - echo sdkman_auto_answer=true > /root/.sdkman/etc/config + - source /root/.sdkman/bin/sdkman-init.sh + - sdk install gradle $GRADLE_VERSION < /dev/null + - sdk use gradle $GRADLE_VERSION + # As it's not a good idea to version gradle.properties feel free to add your + # environments variable here + - echo grailsVersion=$GRAILS_VERSION > gradle.properties + - echo gradleWrapperVersion=2.14 >> gradle.properties + # refresh dependencies from your project + - ./gradlew --refresh-dependencies + # Be aware that if you are using Angular profile, + # Bower cannot be run as root if you don't allow it before. + # Feel free to remove next line if you are not using Bower + - echo {\"allow_root\":true} > /root/.bowerrc # This build job does the full grails pipeline # (compile, test, integrationTest, war, assemble). build: - script: - - ./gradlew build + script: + - ./gradlew build diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml index 546c4affb4e..a09217e8cf0 100644 --- a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml @@ -26,8 +26,8 @@ performance: - mv sitespeed-results/data/performance.json performance.json artifacts: paths: - - performance.json - - sitespeed-results/ + - performance.json + - sitespeed-results/ only: refs: - branches diff --git a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml index 2c4683fbfbb..32d4e07d398 100644 --- a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml @@ -20,7 +20,7 @@ # want coverage results. - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage"); Pkg.test("MyPackage"; coverage = true)' # Comment out below if you do not want coverage results. - - julia -e 'using Pkg; Pkg.add("Coverage"); + - julia -e 'using Pkg; Pkg.add("Coverage"); import MyPackage; cd(joinpath(dirname(pathof(MyPackage)), "..")); using Coverage; cl, tl = get_summary(process_folder()); println("(", cl/tl*100, "%) covered")' @@ -55,17 +55,16 @@ pages: image: julia:0.7 stage: deploy script: - - apt-get update -qq && apt-get install -y git # needed by Documenter - - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage");' # rebuild Julia (can be put somewhere else I'm sure - - julia -e 'using Pkg; import MyPackage; Pkg.add("Documenter")' # install Documenter - - julia --color=yes docs/make.jl # make documentation - - mv docs/build public # move to the directory picked up by Gitlab pages + - apt-get update -qq && apt-get install -y git # needed by Documenter + - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage");' # rebuild Julia (can be put somewhere else I'm sure + - julia -e 'using Pkg; import MyPackage; Pkg.add("Documenter")' # install Documenter + - julia --color=yes docs/make.jl # make documentation + - mv docs/build public # move to the directory picked up by Gitlab pages artifacts: paths: - public only: - - master - + - master # WARNING: This template is using the `julia` images from [Docker # Hub][3]. One can use custom Julia images and/or the official ones found diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml index e1cd29ecc94..9bde04dff19 100644 --- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml @@ -16,8 +16,8 @@ variables: # http://docs.gitlab.com/ce/ci/yaml/README.html#cache cache: paths: - - vendor/ - - node_modules/ + - vendor/ + - node_modules/ # This is a basic example for a gem or script which doesn't use # services such as redis or postgres @@ -37,7 +37,7 @@ before_script: - docker-php-ext-enable xdebug # Install Composer and project dependencies. - curl -sS https://getcomposer.org/installer | php - - php composer.phar install + - php composer.phar install # Install Node dependencies. # comment this out if you don't have a node dependency - npm install @@ -63,10 +63,10 @@ before_script: test: script: - # run laravel tests - - php vendor/bin/phpunit --coverage-text --colors=never - # run frontend tests - # if you have any task for testing frontend - # set it in your package.json script - # comment this out if you don't have a frontend test - - npm test + # run laravel tests + - php vendor/bin/phpunit --coverage-text --colors=never + # run frontend tests + # if you have any task for testing frontend + # set it in your package.json script + # comment this out if you don't have a frontend test + - npm test diff --git a/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml b/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml index 41de1458582..b87178141a1 100644 --- a/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml @@ -14,14 +14,14 @@ services: # http://docs.gitlab.com/ce/ci/yaml/README.html#cache cache: paths: - - node_modules/ + - node_modules/ test_async: script: - - npm install - - node ./specs/start.js ./specs/async.spec.js + - npm install + - node ./specs/start.js ./specs/async.spec.js test_db: script: - - npm install - - node ./specs/start.js ./specs/db-postgres.spec.js + - npm install + - node ./specs/start.js ./specs/db-postgres.spec.js diff --git a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml index 61a925e0d2d..65abee1f5eb 100644 --- a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml @@ -1,9 +1,9 @@ image: ayufan/openshift-cli stages: - - build # dummy stage to follow the template guidelines + - build # dummy stage to follow the template guidelines - test - - deploy # dummy stage to follow the template guidelines + - deploy # dummy stage to follow the template guidelines - review - staging - production diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml index 33f44ee9222..b9fee2d5731 100644 --- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml @@ -4,19 +4,19 @@ image: php:7.1.1 # Select what we should cache between builds cache: paths: - - vendor/ + - vendor/ before_script: -- apt-get update -yqq -- apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -# Install PHP extensions -- docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache -# Install & enable Xdebug for code coverage reports -- pecl install xdebug -- docker-php-ext-enable xdebug -# Install and run Composer -- curl -sS https://getcomposer.org/installer | php -- php composer.phar install + - apt-get update -yqq + - apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev + # Install PHP extensions + - docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache + # Install & enable Xdebug for code coverage reports + - pecl install xdebug + - docker-php-ext-enable xdebug + # Install and run Composer + - curl -sS https://getcomposer.org/installer | php + - php composer.phar install # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # See http://docs.gitlab.com/ce/ci/services/README.html for examples. @@ -33,4 +33,4 @@ variables: # If Xdebug was installed you can generate a coverage report and see code coverage metrics. test: script: - - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
\ No newline at end of file + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never diff --git a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml index d6de8cab5d1..d2dd3fbfb75 100644 --- a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml @@ -4,12 +4,12 @@ image: node:4.2.2 pages: cache: paths: - - node_modules/ + - node_modules/ script: - - npm install -g brunch - - brunch build --production + - npm install -g brunch + - brunch build --production artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml index 791afdd23f1..ba422c08614 100644 --- a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml @@ -3,11 +3,11 @@ image: alpine pages: script: - - apk update && apk add doxygen - - doxygen doxygen/Doxyfile - - mv doxygen/documentation/html/ public/ + - apk update && apk add doxygen + - doxygen doxygen/Doxyfile + - mv doxygen/documentation/html/ public/ artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml index 9df2a4797b2..a683561a455 100644 --- a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml @@ -4,14 +4,14 @@ image: node:latest # http://docs.gitlab.com/ce/ci/yaml/README.html#cache cache: paths: - - node_modules/ + - node_modules/ pages: script: - - yarn install - - ./node_modules/.bin/gatsby build --prefix-paths + - yarn install + - ./node_modules/.bin/gatsby build --prefix-paths artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml index 249a168aa33..92f25280c6e 100644 --- a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml @@ -2,11 +2,11 @@ pages: stage: deploy script: - - mkdir .public - - cp -r * .public - - mv .public public + - mkdir .public + - cp -r * .public + - mv .public public artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml index 4b58003ee10..0e206423fa5 100644 --- a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml @@ -4,12 +4,12 @@ image: node:4.2.2 pages: cache: paths: - - node_modules + - node_modules script: - - npm install -g harp - - harp compile ./ public + - npm install -g harp + - harp compile ./ public artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml index a9e195370f7..d91a8d7421f 100644 --- a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml @@ -3,15 +3,15 @@ image: node:10.15.3 pages: script: - - npm install hexo-cli -g - - test -e package.json && npm install - - hexo generate + - npm install hexo-cli -g + - test -e package.json && npm install + - hexo generate artifacts: paths: - - public + - public cache: paths: - node_modules key: project only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml index f9ddcc6fb0a..9a3ecd1c34f 100644 --- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml @@ -3,15 +3,15 @@ image: dettmering/hugo-build pages: script: - - hugo + - hugo artifacts: paths: - - public + - public only: - - master + - master test: script: - - hugo + - hugo except: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml index f5b40f2b9f1..7a441a2f70f 100644 --- a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml @@ -3,7 +3,7 @@ image: python:2.7 cache: paths: - - vendor/ + - vendor/ test: stage: test @@ -20,6 +20,6 @@ pages: - hyde gen -d public artifacts: paths: - - public + - public only: - master diff --git a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml index 7a485f8d135..886b6c36249 100644 --- a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml @@ -4,28 +4,28 @@ # JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers # # This yml works with jBake 2.5.1 -# Feel free to change JBAKE_VERSION version +# Feel free to change JBAKE_VERSION version # # HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/ image: java:8 variables: - JBAKE_VERSION: 2.5.1 + JBAKE_VERSION: 2.5.1 # We use SDKMan as tool for managing versions before_script: - - apt-get update -qq && apt-get install -y -qq unzip zip - - curl -sSL https://get.sdkman.io | bash - - echo sdkman_auto_answer=true > /root/.sdkman/etc/config - - source /root/.sdkman/bin/sdkman-init.sh - - sdk install jbake $JBAKE_VERSION < /dev/null - - sdk use jbake $JBAKE_VERSION + - apt-get update -qq && apt-get install -y -qq unzip zip + - curl -sSL https://get.sdkman.io | bash + - echo sdkman_auto_answer=true > /root/.sdkman/etc/config + - source /root/.sdkman/bin/sdkman-init.sh + - sdk install jbake $JBAKE_VERSION < /dev/null + - sdk use jbake $JBAKE_VERSION # This build job produced the output directory of your site pages: - script: - - jbake . public - artifacts: - paths: - - public + script: + - jbake . public + artifacts: + paths: + - public diff --git a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml index 37f50554036..0d742aa282d 100644 --- a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml @@ -6,25 +6,24 @@ variables: JEKYLL_ENV: production before_script: -- bundle install + - bundle install test: stage: test script: - - bundle exec jekyll build -d test + - bundle exec jekyll build -d test artifacts: paths: - - test + - test except: - - master + - master pages: stage: deploy script: - - bundle exec jekyll build -d public + - bundle exec jekyll build -d public artifacts: paths: - - public + - public only: - - master - + - master diff --git a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml index 5ca4619e200..2d26b86a328 100644 --- a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml @@ -7,8 +7,8 @@ image: php:7.2 # These folders are cached between builds cache: paths: - - vendor/ - - node_modules/ + - vendor/ + - node_modules/ before_script: # Update packages @@ -28,10 +28,10 @@ before_script: pages: script: - - npm run production - - mv build_production public + - npm run production + - mv build_production public artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml index c5c44a5d86c..93ab8e0be0d 100644 --- a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml @@ -3,10 +3,10 @@ image: python:2.7 pages: script: - - pip install lektor - - lektor build --output-path public + - pip install lektor + - lektor build --output-path public artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml index c6ded272150..6524405133a 100644 --- a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml @@ -4,13 +4,13 @@ image: node:4.2.2 pages: cache: paths: - - node_modules/ + - node_modules/ script: - - npm install -g metalsmith - - npm install - - make build + - npm install -g metalsmith + - npm install + - make build artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml index 9f4cc0574d6..57ac323dfdf 100644 --- a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml @@ -3,25 +3,25 @@ image: ruby:2.3 cache: paths: - - vendor + - vendor test: script: - - apt-get update -yqqq - - apt-get install -y nodejs - - bundle install --path vendor - - bundle exec middleman build + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build except: - master pages: script: - - apt-get update -yqqq - - apt-get install -y nodejs - - bundle install --path vendor - - bundle exec middleman build + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml index b469b316ba5..7f037b5f5cf 100644 --- a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml @@ -3,10 +3,10 @@ image: ruby:2.3 pages: script: - - bundle install -j4 - - nanoc + - bundle install -j4 + - nanoc artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml index 4762ec9acfd..6d912a89bc1 100644 --- a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml @@ -3,13 +3,13 @@ image: ruby:2.3 pages: script: - - apt-get update -qq && apt-get install -qq nodejs - - bundle install -j4 - - bundle exec rake generate - - mv public .public - - mv .public/octopress public + - apt-get update -qq && apt-get install -qq nodejs + - bundle install -j4 + - bundle exec rake generate + - mv public .public + - mv .public/octopress public artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml index c5f3154f587..09c6649fc13 100644 --- a/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml @@ -3,8 +3,8 @@ image: python:2.7-alpine pages: script: - - pip install -r requirements.txt - - pelican -s publishconf.py + - pip install -r requirements.txt + - pelican -s publishconf.py artifacts: paths: - - public/ + - public/ diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml index 3eaed4e91cd..00b8b94b574 100644 --- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml @@ -18,34 +18,34 @@ cache: - venv/ before_script: - - python -V # Print out python version for debugging + - python -V # Print out python version for debugging - pip install virtualenv - virtualenv venv - source venv/bin/activate test: script: - - python setup.py test - - pip install tox flake8 # you can also use tox - - tox -e py36,flake8 + - python setup.py test + - pip install tox flake8 # you can also use tox + - tox -e py36,flake8 run: script: - - python setup.py bdist_wheel - # an alternative approach is to install and run: - - pip install dist/* - # run the command here + - python setup.py bdist_wheel + # an alternative approach is to install and run: + - pip install dist/* + # run the command here artifacts: paths: - - dist/*.whl + - dist/*.whl pages: script: - - pip install sphinx sphinx-rtd-theme - - cd doc ; make html - - mv build/html/ ../public/ + - pip install sphinx sphinx-rtd-theme + - cd doc ; make html + - mv build/html/ ../public/ artifacts: paths: - - public + - public only: - - master + - master diff --git a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml index 93196dbd475..b3cad8b858a 100644 --- a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml @@ -21,7 +21,7 @@ cache: # This is a basic example for a gem or script which doesn't use # services such as redis or postgres before_script: - - ruby -v # Print out ruby version for debugging + - ruby -v # Print out ruby version for debugging # Uncomment next line if your rails app needs a JS runtime: # - apt-get update -q && apt-get install nodejs -yqq - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby @@ -29,19 +29,19 @@ before_script: # Optional - Delete if not using `rubocop` rubocop: script: - - rubocop + - rubocop rspec: script: - - rspec spec + - rspec spec rails: variables: DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB" script: - - rails db:migrate - - rails db:seed - - rails test + - rails db:migrate + - rails db:seed + - rails test # This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk # are supported too: https://github.com/travis-ci/dpl @@ -49,5 +49,5 @@ deploy: type: deploy environment: production script: - - gem install dpl - - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY + - gem install dpl + - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml index cab087c48c7..a25dc38e4e7 100644 --- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml @@ -5,19 +5,19 @@ image: "rust:latest" # Optional: Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. # Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service -#services: -# - mysql:latest -# - redis:latest -# - postgres:latest +# services: +# - mysql:latest +# - redis:latest +# - postgres:latest # Optional: Install a C compiler, cmake and git into the container. # You will often need this when you (or any of your dependencies) depends on C code. -#before_script: -#- apt-get update -yqq -#- apt-get install -yqq --no-install-recommends build-essential +# before_script: +# - apt-get update -yqq +# - apt-get install -yqq --no-install-recommends build-essential # Use cargo to test the project test:cargo: script: - - rustc --version && cargo --version # Print version info for debugging - - cargo test --all --verbose + - rustc --version && cargo --version # Print version info for debugging + - cargo test --all --verbose diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml index 27a498b2daf..4b55ffd3771 100644 --- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml @@ -15,22 +15,22 @@ dast: image: name: "registry.gitlab.com/gitlab-org/security-products/dast:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable" variables: - # URL to scan: - # DAST_WEBSITE: https://example.com/ - # - # Time limit for target availability (scan is attempted even when timeout): - # DAST_TARGET_AVAILABILITY_TIMEOUT: 60 - # - # Set these variables to scan with an authenticated user: - # DAST_AUTH_URL: https://example.com/sign-in - # DAST_USERNAME: john.doe@example.com - # DAST_PASSWORD: john-doe-password - # DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form - # DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form - # DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional: URLs to skip during the authenticated scan; comma-separated, no spaces in between - # - # Perform ZAP Full Scan, which includes both passive and active scanning: - # DAST_FULL_SCAN_ENABLED: "true" + # URL to scan: + # DAST_WEBSITE: https://example.com/ + # + # Time limit for target availability (scan is attempted even when timeout): + # DAST_TARGET_AVAILABILITY_TIMEOUT: 60 + # + # Set these variables to scan with an authenticated user: + # DAST_AUTH_URL: https://example.com/sign-in + # DAST_USERNAME: john.doe@example.com + # DAST_PASSWORD: john-doe-password + # DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form + # DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form + # DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional: URLs to skip during the authenticated scan; comma-separated, no spaces in between + # + # Perform ZAP Full Scan, which includes both passive and active scanning: + # DAST_FULL_SCAN_ENABLED: "true" allow_failure: true script: - export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)} diff --git a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml index 0208beb35b8..b6555e627a1 100644 --- a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml @@ -1,7 +1,7 @@ # Read more about this feature here: https://docs.gitlab.com/ee/user/project/merge_requests/license_management.html variables: - LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager. + LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager. license_management: stage: test diff --git a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml index 9aa4abd4791..ffed7a0fec2 100644 --- a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml @@ -25,7 +25,7 @@ archive_project: - master artifacts: paths: - - build/ProjectName.ipa + - build/ProjectName.ipa tags: - ios_11-3 - xcode_9-3 diff --git a/lib/gitlab/ci/templates/dotNET-Core.yml b/lib/gitlab/ci/templates/dotNET-Core.yml index 708b75f83e8..40ca296d7bd 100644 --- a/lib/gitlab/ci/templates/dotNET-Core.yml +++ b/lib/gitlab/ci/templates/dotNET-Core.yml @@ -1,4 +1,5 @@ -# This is a simple example illustrating how to build and test .NET Core project +--- +# This is a simple example illustrating how to build and test .NET Core project # with GitLab Continuous Integration / Continuous Delivery. # ### Specify the Docker image @@ -17,21 +18,21 @@ image: microsoft/dotnet:latest # ### Define variables # variables: - # 1) Name of directory where restore and build objects are stored. - OBJECTS_DIRECTORY: 'obj' - # 2) Name of directory used for keeping restored dependencies. - NUGET_PACKAGES_DIRECTORY: '.nuget' - # 3) A relative path to the source code from project repository root. - # NOTE: Please edit this path so it matches the structure of your project! - SOURCE_CODE_PATH: '*/*/' + # 1) Name of directory where restore and build objects are stored. + OBJECTS_DIRECTORY: 'obj' + # 2) Name of directory used for keeping restored dependencies. + NUGET_PACKAGES_DIRECTORY: '.nuget' + # 3) A relative path to the source code from project repository root. + # NOTE: Please edit this path so it matches the structure of your project! + SOURCE_CODE_PATH: '*/*/' # ### Define stage list # # In this example there are only two stages. # Initially, the project will be built and then tested. stages: - - build - - test + - build + - test # ### Define global cache rule # @@ -50,31 +51,31 @@ stages: # With global cache rule, cached dependencies will be downloaded before every job # and then unpacked to the paths as specified below. cache: - # Per-stage and per-branch caching. - key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" - paths: - # Specify three paths that should be cached: + # Per-stage and per-branch caching. + key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" + paths: + # Specify three paths that should be cached: + # + # 1) Main JSON file holding information about package dependency tree, packages versions, + # frameworks etc. It also holds information where to the dependencies were restored. + - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json' + # 2) Other NuGet and MSBuild related files. Also needed. + - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*' + # 3) Path to the directory where restored dependencies are kept. + - '$NUGET_PACKAGES_DIRECTORY' # - # 1) Main JSON file holding information about package dependency tree, packages versions, - # frameworks etc. It also holds information where to the dependencies were restored. - - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json' - # 2) Other NuGet and MSBuild related files. Also needed. - - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*' - # 3) Path to the directory where restored dependencies are kept. - - '$NUGET_PACKAGES_DIRECTORY' - # - # 'pull-push' policy means that latest cache will be downloaded (if it exists) - # before executing the job, and a newer version will be uploaded afterwards. - # Such a setting saves time when there are no changes in referenced third-party - # packages. - # - # For example, if you run a pipeline with changes in your code, - # but with no changes within third-party packages which your project is using, - # then project restore will happen quickly as all required dependencies - # will already be there — unzipped from cache. + # 'pull-push' policy means that latest cache will be downloaded (if it exists) + # before executing the job, and a newer version will be uploaded afterwards. + # Such a setting saves time when there are no changes in referenced third-party + # packages. + # + # For example, if you run a pipeline with changes in your code, + # but with no changes within third-party packages which your project is using, + # then project restore will happen quickly as all required dependencies + # will already be there — unzipped from cache. - # 'pull-push' policy is the default cache policy, you do not have to specify it explicitly. - policy: pull-push + # 'pull-push' policy is the default cache policy, you do not have to specify it explicitly. + policy: pull-push # ### Restore project dependencies # @@ -87,30 +88,30 @@ cache: # # Learn more about GitLab cache: https://docs.gitlab.com/ee/ci/caching/index.html before_script: - - 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY' + - 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY' build: - stage: build - # ### Build all projects discovered from solution file. - # - # Note: this will fail if you have any projects in your solution that are not - # .NET Core-based projects (e.g. WCF service), which is based on .NET Framework, - # not .NET Core. In this scenario, you will need to build every .NET Core-based - # project by explicitly specifying a relative path to the directory - # where it is located (e.g. 'dotnet build ./src/ConsoleApp'). - # Only one project path can be passed as a parameter to 'dotnet build' command. - script: - - 'dotnet build --no-restore' + stage: build + # ### Build all projects discovered from solution file. + # + # Note: this will fail if you have any projects in your solution that are not + # .NET Core-based projects (e.g. WCF service), which is based on .NET Framework, + # not .NET Core. In this scenario, you will need to build every .NET Core-based + # project by explicitly specifying a relative path to the directory + # where it is located (e.g. 'dotnet build ./src/ConsoleApp'). + # Only one project path can be passed as a parameter to 'dotnet build' command. + script: + - 'dotnet build --no-restore' tests: - stage: test - # ### Run the tests - # - # You can either run tests for all test projects that are defined in your solution - # with 'dotnet test' or run tests only for specific project by specifying - # a relative path to the directory where it is located (e.g. 'dotnet test ./test/UnitTests'). - # - # You may want to define separate testing jobs for different types of testing - # (e.g. integration tests, unit tests etc). - script: - - 'dotnet test --no-restore' + stage: test + # ### Run the tests + # + # You can either run tests for all test projects that are defined in your solution + # with 'dotnet test' or run tests only for specific project by specifying + # a relative path to the directory where it is located (e.g. 'dotnet test ./test/UnitTests'). + # + # You may want to define separate testing jobs for different types of testing + # (e.g. integration tests, unit tests etc). + script: + - 'dotnet test --no-restore' diff --git a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml index 5e128b793d0..b29f45323f5 100644 --- a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml @@ -3,12 +3,12 @@ # (located in gitlab-runner.exe directory): # shell = "powershell" # -# The script is composed of 3 stages: build, test and deploy. +# The script is composed of 3 stages: build, test and deploy. # # The build stage restores NuGet packages and uses msbuild to build the exe and msi # One major issue you'll find is that you can't build msi projects from command line # if you use vdproj. There are workarounds building msi via devenv, but they rarely work -# The best solution is migrating your vdproj projects to WiX, as it can be build directly +# The best solution is migrating your vdproj projects to WiX, as it can be build directly # by msbuild. # # The test stage runs nunit from command line against Test project inside your solution @@ -38,28 +38,28 @@ stages: build_job: stage: build only: - - tags # the build process will only be started by git tag commits + - tags # the build process will only be started by git tag commits script: - - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies - - '& "$env:MSBUILD_PATH" /p:Configuration=Release' # build the project + - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies + - '& "$env:MSBUILD_PATH" /p:Configuration=Release' # build the project artifacts: - expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on + expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on paths: - - '$env:EXE_RELEASE_FOLDER\YourApp.exe' # saving exe to copy to deploy folder - - '$env:MSI_RELEASE_FOLDER\YourApp Setup.msi' # saving msi to copy to deploy folder - - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests + - '$env:EXE_RELEASE_FOLDER\YourApp.exe' # saving exe to copy to deploy folder + - '$env:MSI_RELEASE_FOLDER\YourApp Setup.msi' # saving msi to copy to deploy folder + - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests test_job: stage: test only: - tags script: - - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests + - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests artifacts: - when: always # save test results even when the task fails - expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on + when: always # save test results even when the task fails + expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on paths: - - '.\TestResult.xml' # saving NUnit results to copy to deploy folder + - '.\TestResult.xml' # saving NUnit results to copy to deploy folder dependencies: - build_job @@ -79,7 +79,6 @@ deploy_job: - 'xcopy /y ".\$env:EXE_RELEASE_FOLDER\YourApp.exe" "$deployFolder"' - 'xcopy /y ".\$env:MSI_RELEASE_FOLDER\YourApp Setup.msi" "$deployFolder"' - 'xcopy /y ".\TestResult.xml" "$deployFolder"' - dependencies: - build_job - test_job diff --git a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml index a4a9e96c1d2..87aea8527d1 100644 --- a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml @@ -1,13 +1,13 @@ # This is a very simple template that mainly relies on FastLane to build and distribute your app. # Read more about how to use this template on the blog post https://about.gitlab.com/2019/03/06/ios-publishing-with-gitlab-and-fastlane/ -# You will also need fastlane and signing configuration for this to work, along with a MacOS runner. +# You will also need fastlane and signing configuration for this to work, along with a MacOS runner. # These details are provided in the blog post. # Note that when you're using the shell executor for MacOS builds, the # build and tests run as the identity of the runner logged in user, directly on # the build host. This is less secure than using container executors, so please -# take a look at our security implications documentation at -# https://docs.gitlab.com/runner/security/#usage-of-shell-executor for additional +# take a look at our security implications documentation at +# https://docs.gitlab.com/runner/security/#usage-of-shell-executor for additional # detail on what to keep in mind in this scenario. stages: @@ -27,4 +27,4 @@ build: - bundle exec fastlane build artifacts: paths: - - ./*.ipa + - ./*.ipa diff --git a/lib/gitlab/graphql/representation/tree_entry.rb b/lib/gitlab/graphql/representation/tree_entry.rb new file mode 100644 index 00000000000..7ea83db5876 --- /dev/null +++ b/lib/gitlab/graphql/representation/tree_entry.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + module Representation + class TreeEntry < SimpleDelegator + class << self + def decorate(entries, repository) + return if entries.nil? + + entries.map do |entry| + if entry.is_a?(TreeEntry) + entry + else + self.new(entry, repository) + end + end + end + end + + attr_accessor :repository + + def initialize(raw_entry, repository) + @repository = repository + + super(raw_entry) + end + end + end + end +end diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb new file mode 100644 index 00000000000..0354c710a3f --- /dev/null +++ b/lib/gitlab/markdown_cache.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Gitlab + module MarkdownCache + # Increment this number every time the renderer changes its output + CACHE_COMMONMARK_VERSION_START = 10 + CACHE_COMMONMARK_VERSION = 16 + + BaseError = Class.new(StandardError) + UnsupportedClassError = Class.new(BaseError) + end +end diff --git a/lib/gitlab/markdown_cache/active_record/extension.rb b/lib/gitlab/markdown_cache/active_record/extension.rb new file mode 100644 index 00000000000..f3abe631779 --- /dev/null +++ b/lib/gitlab/markdown_cache/active_record/extension.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Gitlab + module MarkdownCache + module ActiveRecord + module Extension + extend ActiveSupport::Concern + + included do + # Using before_update here conflicts with elasticsearch-model somehow + before_create :refresh_markdown_cache, if: :invalidated_markdown_cache? + before_update :refresh_markdown_cache, if: :invalidated_markdown_cache? + end + + # Always exclude _html fields from attributes (including serialization). + # They contain unredacted HTML, which would be a security issue + def attributes + attrs = super + html_fields = cached_markdown_fields.html_fields + whitelisted = cached_markdown_fields.html_fields_whitelisted + exclude_fields = html_fields - whitelisted + + attrs.except!(*exclude_fields) + attrs.delete('cached_markdown_version') if whitelisted.empty? + + attrs + end + + def changed_markdown_fields + changed_attributes.keys.map(&:to_s) & cached_markdown_fields.markdown_fields.map(&:to_s) + end + + def write_markdown_field(field_name, value) + write_attribute(field_name, value) + end + + def markdown_field_changed?(field_name) + attribute_changed?(field_name) + end + + def save_markdown(updates) + return unless persisted? && Gitlab::Database.read_write? + + update_columns(updates) + end + end + end + end +end diff --git a/lib/gitlab/markdown_cache/field_data.rb b/lib/gitlab/markdown_cache/field_data.rb new file mode 100644 index 00000000000..14622c0f186 --- /dev/null +++ b/lib/gitlab/markdown_cache/field_data.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Gitlab + module MarkdownCache + # Knows about the relationship between markdown and html field names, and + # stores the rendering contexts for the latter + class FieldData + def initialize + @data = {} + end + + delegate :[], :[]=, to: :@data + + def markdown_fields + @data.keys + end + + def html_field(markdown_field) + "#{markdown_field}_html" + end + + def html_fields + @html_fields ||= markdown_fields.map { |field| html_field(field) } + end + + def html_fields_whitelisted + markdown_fields.each_with_object([]) do |field, fields| + if @data[field].fetch(:whitelisted, false) + fields << html_field(field) + end + end + end + end + end +end diff --git a/lib/gitlab/markdown_cache/redis/extension.rb b/lib/gitlab/markdown_cache/redis/extension.rb new file mode 100644 index 00000000000..97fc23343b4 --- /dev/null +++ b/lib/gitlab/markdown_cache/redis/extension.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Gitlab + module MarkdownCache + module Redis + module Extension + extend ActiveSupport::Concern + + attr_reader :cached_markdown_version + + class_methods do + def cache_markdown_field(markdown_field, context = {}) + super + + # define the `[field]_html` accessor + html_field = cached_markdown_fields.html_field(markdown_field) + define_method(html_field) do + load_cached_markdown unless markdown_data_loaded? + + instance_variable_get("@#{html_field}") + end + end + end + + private + + def save_markdown(updates) + markdown_store.save(updates) + end + + def write_markdown_field(field_name, value) + instance_variable_set("@#{field_name}", value) + end + + def markdown_field_changed?(field_name) + false + end + + def changed_markdown_fields + [] + end + + def cached_markdown + @cached_data ||= markdown_store.read + end + + def load_cached_markdown + cached_markdown.each do |field_name, value| + write_markdown_field(field_name, value) + end + end + + def markdown_data_loaded? + cached_markdown_version.present? || markdown_store.loaded? + end + + def markdown_store + @store ||= Gitlab::MarkdownCache::Redis::Store.new(self) + end + end + end + end +end diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb new file mode 100644 index 00000000000..0f954404808 --- /dev/null +++ b/lib/gitlab/markdown_cache/redis/store.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Gitlab + module MarkdownCache + module Redis + class Store + EXPIRES_IN = 1.day + + def initialize(subject) + @subject = subject + @loaded = false + end + + def save(updates) + @loaded = false + + Gitlab::Redis::Cache.with do |r| + r.mapped_hmset(markdown_cache_key, updates) + r.expire(markdown_cache_key, EXPIRES_IN) + end + end + + def read + @loaded = true + + results = Gitlab::Redis::Cache.with do |r| + r.mapped_hmget(markdown_cache_key, *fields) + end + # The value read from redis is a string, so we're converting it back + # to an int. + results[:cached_markdown_version] = results[:cached_markdown_version].to_i + results + end + + def loaded? + @loaded + end + + private + + def fields + @fields ||= @subject.cached_markdown_fields.html_fields + [:cached_markdown_version] + end + + def markdown_cache_key + unless @subject.respond_to?(:cache_key) + raise Gitlab::MarkdownCache::UnsupportedClassError, + "This class has no cache_key to use for caching" + end + + "markdown_cache:#{@subject.cache_key}" + end + end + end + end +end diff --git a/spec/features/markdown/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb index 6997ca48427..8fda3c7193e 100644 --- a/spec/features/markdown/gitlab_flavored_markdown_spec.rb +++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb @@ -20,8 +20,7 @@ describe "GitLab Flavored Markdown" do let(:commit) { project.commit } before do - allow_any_instance_of(Commit).to receive(:title) - .and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details") + create_commit("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details", project, user, 'master') end it "renders title in commits#index" do diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 6b4508c418e..a70dc7bb866 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -86,4 +86,16 @@ describe('Repository table row component', () => { expect(vm.find('.commit-sha').text()).toContain('1'); }); + + it('renders link with href', () => { + factory({ + id: '1', + path: 'test', + type: 'blob', + url: 'https://test.com', + currentPath: '/', + }); + + expect(vm.find('a').attributes('href')).toEqual('https://test.com'); + }); }); diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb index fa29bb5fff7..b12e214ca84 100644 --- a/spec/graphql/types/tree/blob_type_spec.rb +++ b/spec/graphql/types/tree/blob_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Types::Tree::BlobType do it { expect(described_class.graphql_name).to eq('Blob') } - it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) } end diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb index 397cabde8e5..ea1b6426034 100644 --- a/spec/graphql/types/tree/tree_entry_type_spec.rb +++ b/spec/graphql/types/tree/tree_entry_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Types::Tree::TreeEntryType do it { expect(described_class.graphql_name).to eq('TreeEntry') } - it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) } + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) } end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index c3956ba08fd..597c8f836a9 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -78,7 +78,8 @@ describe MarkupHelper do let(:link) { '/commits/0a1b2c3d' } let(:issues) { create_list(:issue, 2, project: project) } - it 'handles references nested in links with all the text' do + # Clean the cache to make sure the title is re-rendered from the stubbed one + it 'handles references nested in links with all the text', :clean_gitlab_redis_cache do allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real") actual = helper.link_to_markdown_field(commit, :title, link) diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb new file mode 100644 index 00000000000..71505e8ea69 --- /dev/null +++ b/spec/helpers/tracking_helper_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe TrackingHelper do + describe '#tracking_attrs' do + it 'returns an empty hash' do + expect(helper.tracking_attrs('a', 'b', 'c')).to eq({}) + end + end +end diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb index 1f53657c59c..316dbf052c3 100644 --- a/spec/lib/banzai/commit_renderer_spec.rb +++ b/spec/lib/banzai/commit_renderer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Banzai::CommitRenderer do - describe '.render' do + describe '.render', :clean_gitlab_redis_cache do it 'renders a commit description and title' do user = build(:user) project = create(:project, :repository) @@ -13,7 +13,7 @@ describe Banzai::CommitRenderer do described_class::ATTRIBUTES.each do |attr| expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original - expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, {}) + expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, { skip_project_check: false }).and_call_original end described_class.render([project.commit], project, user) diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 3b52f6666d0..7b855251a74 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -11,7 +11,7 @@ describe Banzai::ObjectRenderer do ) end - let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) } + let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) } describe '#render' do context 'with cache' do @@ -60,24 +60,38 @@ describe Banzai::ObjectRenderer do end context 'without cache' do - let(:commit) { project.commit } + let(:cacheless_class) do + Class.new do + attr_accessor :title, :redacted_title_html, :project + + def banzai_render_context(field) + { project: project, pipeline: :single_line } + end + end + end + let(:cacheless_thing) do + cacheless_class.new.tap do |thing| + thing.title = "Merge branch 'branch-merged' into 'master'" + thing.project = project + end + end it 'renders and redacts an Array of objects' do - renderer.render([commit], :title) + renderer.render([cacheless_thing], :title) - expect(commit.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'") + expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'") end it 'calls Banzai::Redactor to perform redaction' do expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original - renderer.render([commit], :title) + renderer.render([cacheless_thing], :title) end it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do - expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title, {}).and_call_original + expect(Banzai::Renderer).to receive(:cacheless_render_field).with(cacheless_thing, :title, {}).and_call_original - renderer.render([commit], :title) + renderer.render([cacheless_thing], :title) end end end diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb index 650cecfc778..aa828e2f0e9 100644 --- a/spec/lib/banzai/renderer_spec.rb +++ b/spec/lib/banzai/renderer_spec.rb @@ -11,16 +11,24 @@ describe Banzai::Renderer do object end + def fake_cacheless_object + object = double('cacheless object') + + allow(object).to receive(:respond_to?).with(:cached_markdown_fields).and_return(false) + + object + end + describe '#render_field' do let(:renderer) { described_class } context 'without cache' do - let(:commit) { create(:project, :repository).commit } + let(:commit) { fake_cacheless_object } it 'returns cacheless render field' do - expect(renderer).to receive(:cacheless_render_field).with(commit, :title, {}) + expect(renderer).to receive(:cacheless_render_field).with(commit, :field, {}) - renderer.render_field(commit, :title) + renderer.render_field(commit, :field) end end diff --git a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb new file mode 100644 index 00000000000..d45e690160c --- /dev/null +++ b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Representation::TreeEntry do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + + describe '.decorate' do + it 'returns NilClass when given nil' do + expect(described_class.decorate(nil, repository)).to be_nil + end + + it 'returns array of TreeEntry' do + entries = described_class.decorate(repository.tree.blobs, repository) + + expect(entries.first).to be_a(described_class) + end + end +end diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb new file mode 100644 index 00000000000..18052b1991c --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb @@ -0,0 +1,179 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::MarkdownCache::ActiveRecord::Extension do + let(:klass) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + include CacheMarkdownField + cache_markdown_field :title, whitelisted: true + cache_markdown_field :description, pipeline: :single_line + + attr_accessor :author, :project + end + end + + let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + + let(:markdown) { '`Foo`' } + let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' } + + let(:updated_markdown) { '`Bar`' } + let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' } + + context 'an unchanged markdown field' do + let(:thing) { klass.new(title: markdown) } + + before do + thing.title = thing.title + thing.save + end + + it { expect(thing.title).to eq(markdown) } + it { expect(thing.title_html).to eq(html) } + it { expect(thing.title_html_changed?).not_to be_truthy } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'a changed markdown field' do + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + + before do + thing.title = updated_markdown + thing.save + end + + it { expect(thing.title_html).to eq(updated_html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'when a markdown field is set repeatedly to an empty string' do + it do + expect(thing).to receive(:refresh_markdown_cache).once + thing.title = '' + thing.save + thing.title = '' + thing.save + end + end + + context 'when a markdown field is set repeatedly to a string which renders as empty html' do + it do + expect(thing).to receive(:refresh_markdown_cache).once + thing.title = '[//]: # (This is also a comment.)' + thing.save + thing.title = '[//]: # (This is also a comment.)' + thing.save + end + end + + context 'a non-markdown field changed' do + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + + before do + thing.state = 'closed' + thing.save + end + + it { expect(thing.state).to eq('closed') } + it { expect(thing.title).to eq(markdown) } + it { expect(thing.title_html).to eq(html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'version is out of date' do + let(:thing) { klass.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + + before do + thing.save + end + + it { expect(thing.title_html).to eq(updated_html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } + end + + context 'when an invalidating field is changed' do + it 'invalidates the cache when project changes' do + thing.project = :new_project + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + thing.save + + expect(thing.title_html).to eq(updated_html) + expect(thing.description_html).to eq(updated_html) + expect(thing.cached_markdown_version).to eq(cache_version) + end + + it 'invalidates the cache when author changes' do + thing.author = :new_author + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + thing.save + + expect(thing.title_html).to eq(updated_html) + expect(thing.description_html).to eq(updated_html) + expect(thing.cached_markdown_version).to eq(cache_version) + end + end + + describe '.attributes' do + it 'excludes cache attributes that is blacklisted by default' do + expect(thing.attributes.keys.sort).not_to include(%w[description_html]) + end + end + + describe '#cached_html_up_to_date?' do + let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + subject { thing.cached_html_up_to_date?(:title) } + + it 'returns false if markdown has been changed but html has not' do + thing.title = "changed!" + + is_expected.to be_falsy + end + + it 'returns true if markdown has not been changed but html has' do + thing.title_html = updated_html + + is_expected.to be_truthy + end + + it 'returns true if markdown and html have both been changed' do + thing.title = updated_markdown + thing.title_html = updated_html + + is_expected.to be_truthy + end + + it 'returns false if the markdown field is set but the html is not' do + thing.title_html = nil + + is_expected.to be_falsy + end + end + + describe '#refresh_markdown_cache!' do + before do + thing.title = updated_markdown + end + + it 'skips saving if not persisted' do + expect(thing).to receive(:persisted?).and_return(false) + expect(thing).not_to receive(:update_columns) + + thing.refresh_markdown_cache! + end + + it 'saves the changes' do + expect(thing).to receive(:persisted?).and_return(true) + + expect(thing).to receive(:update_columns) + .with("title_html" => updated_html, + "description_html" => "", + "cached_markdown_version" => cache_version) + + thing.refresh_markdown_cache! + end + end +end diff --git a/spec/lib/gitlab/markdown_cache/field_data_spec.rb b/spec/lib/gitlab/markdown_cache/field_data_spec.rb new file mode 100644 index 00000000000..393bf85aa43 --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/field_data_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::MarkdownCache::FieldData do + subject(:field_data) { described_class.new } + + before do + field_data[:description] = { project: double('project in context') } + end + + it 'translates a markdown field name into a html field name' do + expect(field_data.html_field(:description)).to eq("description_html") + end +end diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb new file mode 100644 index 00000000000..b6a781de426 --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do + let(:klass) do + Class.new do + include CacheMarkdownField + + def initialize(title: nil, description: nil) + @title, @description = title, description + end + + attr_reader :title, :description + + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description + + def cache_key + "cache-key" + end + end + end + + let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } + let(:thing) { klass.new(title: "`Hello`", description: "`World`") } + let(:expected_cache_key) { "markdown_cache:cache-key" } + + it 'defines the html attributes' do + expect(thing).to respond_to(:title_html, :description_html, :cached_markdown_version) + end + + it 'loads the markdown from the cache only once' do + expect(thing).to receive(:load_cached_markdown).once.and_call_original + + thing.title_html + thing.description_html + end + + it 'correctly loads the markdown if it was stored in redis' do + Gitlab::Redis::Cache.with do |r| + r.mapped_hmset(expected_cache_key, + title_html: 'hello', + description_html: 'world', + cached_markdown_version: cache_version) + end + + expect(thing.title_html).to eq('hello') + expect(thing.description_html).to eq('world') + expect(thing.cached_markdown_version).to eq(cache_version) + end + + describe "#refresh_markdown_cache!" do + it "stores the value in redis" do + expected_results = { "title_html" => "`Hello`", + "description_html" => "<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>", + "cached_markdown_version" => cache_version.to_s } + + thing.refresh_markdown_cache! + + results = Gitlab::Redis::Cache.with do |r| + r.mapped_hmget(expected_cache_key, + "title_html", "description_html", "cached_markdown_version") + end + + expect(results).to eq(expected_results) + end + + it "assigns the values" do + thing.refresh_markdown_cache! + + expect(thing.title_html).to eq('`Hello`') + expect(thing.description_html).to eq("<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>") + expect(thing.cached_markdown_version).to eq(cache_version) + end + end +end diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb new file mode 100644 index 00000000000..95c68e7d491 --- /dev/null +++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do + let(:storable_class) do + Class.new do + cattr_reader :cached_markdown_fields do + Gitlab::MarkdownCache::FieldData.new.tap do |field_data| + field_data[:field_1] = {} + field_data[:field_2] = {} + end + end + + attr_accessor :field_1, :field_2, :field_1_html, :field_2_html, :cached_markdown_version + + def cache_key + "cache-key" + end + end + end + let(:storable) { storable_class.new } + let(:cache_key) { "markdown_cache:#{storable.cache_key}" } + + subject(:store) { described_class.new(storable) } + + def read_values + Gitlab::Redis::Cache.with do |r| + r.mapped_hmget(cache_key, + :field_1_html, :field_2_html, :cached_markdown_version) + end + end + + def store_values(values) + Gitlab::Redis::Cache.with do |r| + r.mapped_hmset(cache_key, + values) + end + end + + describe '#save' do + it 'stores updates to html fields and version' do + values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 } + + store.save(values_to_store) + + expect(read_values) + .to eq({ field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1" }) + end + end + + describe '#read' do + it 'reads the html fields and version from redis if they were stored' do + stored_values = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 } + + store_values(stored_values) + + expect(store.read.symbolize_keys).to eq(stored_values) + end + + it 'is mared loaded after reading' do + expect(store).not_to be_loaded + + store.read + + expect(store).to be_loaded + end + end +end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 78637ff10c6..0e5fb2b5153 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -2,383 +2,213 @@ require 'spec_helper' -describe CacheMarkdownField do - # The minimum necessary ActiveModel to test this concern - class ThingWithMarkdownFields - include ActiveModel::Model - include ActiveModel::Dirty - - include ActiveModel::Serialization - - class_attribute :attribute_names - self.attribute_names = [] - - def attributes - attribute_names.each_with_object({}) do |name, hsh| - hsh[name.to_s] = send(name) - end +describe CacheMarkdownField, :clean_gitlab_redis_cache do + let(:ar_class) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + include CacheMarkdownField + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description end + end - extend ActiveModel::Callbacks - define_model_callbacks :create, :update - - include CacheMarkdownField - cache_markdown_field :foo - cache_markdown_field :baz, pipeline: :single_line - cache_markdown_field :zoo, whitelisted: true + let(:other_class) do + Class.new do + include CacheMarkdownField - def self.add_attr(name) - self.attribute_names += [name] - define_attribute_methods(name) - attr_reader(name) - define_method("#{name}=") do |value| - write_attribute(name, value) + def initialize(args = {}) + @title, @description, @cached_markdown_version = args[:title], args[:description], args[:cached_markdown_version] + @title_html, @description_html = args[:title_html], args[:description_html] + @author, @project = args[:author], args[:project] end - end - add_attr :cached_markdown_version + attr_accessor :title, :description, :cached_markdown_version - [:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name| - add_attr(name) - end - - def initialize(*) - super - - # Pretend new is load - clear_changes_information - end - - def read_attribute(name) - instance_variable_get("@#{name}") - end - - def write_attribute(name, value) - send("#{name}_will_change!") unless value == read_attribute(name) - instance_variable_set("@#{name}", value) - end + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description - def save - run_callbacks :update do - changes_applied + def cache_key + "cache-key" end end - - def has_attribute?(attr_name) - attribute_names.include?(attr_name) - end - end - - def thing_subclass(new_attr) - Class.new(ThingWithMarkdownFields) { add_attr(new_attr) } end let(:markdown) { '`Foo`' } - let(:html) { '<p dir="auto"><code>Foo</code></p>' } + let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' } let(:updated_markdown) { '`Bar`' } - let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' } - - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } - - before do - stub_commonmark_sourcepos_disabled - end + let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' } - describe '.attributes' do - it 'excludes cache attributes that is blacklisted by default' do - expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html]) - end - end - - context 'an unchanged markdown field' do - before do - thing.foo = thing.foo - thing.save - end + let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } - it { expect(thing.foo).to eq(markdown) } - it { expect(thing.foo_html).to eq(html) } - it { expect(thing.foo_html_changed?).not_to be_truthy } - it { expect(thing.cached_markdown_version).to eq(cache_version) } + def thing_subclass(klass, extra_attribute) + Class.new(klass) { attr_accessor(extra_attribute) } end - context 'a changed markdown field' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } + shared_examples 'a class with cached markdown fields' do + describe '#cached_html_up_to_date?' do + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - thing.save - end + subject { thing.cached_html_up_to_date?(:title) } - it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } - end + it 'returns false when the version is absent' do + thing.cached_markdown_version = nil - context 'when a markdown field is set repeatedly to an empty string' do - it do - expect(thing).to receive(:refresh_markdown_cache).once - thing.foo = '' - thing.save - thing.foo = '' - thing.save - end - end - - context 'when a markdown field is set repeatedly to a string which renders as empty html' do - it do - expect(thing).to receive(:refresh_markdown_cache).once - thing.foo = '[//]: # (This is also a comment.)' - thing.save - thing.foo = '[//]: # (This is also a comment.)' - thing.save - end - end - - context 'when a markdown field and html field are both changed' do - it do - expect(thing).not_to receive(:refresh_markdown_cache) - thing.foo = '_look over there!_' - thing.foo_html = '<em>look over there!</em>' - thing.save - end - end - - context 'a non-markdown field changed' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } - - before do - thing.bar = 'OK' - thing.save - end - - it { expect(thing.bar).to eq('OK') } - it { expect(thing.foo).to eq(markdown) } - it { expect(thing.foo_html).to eq(html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } - end - - context 'version is out of date' do - let(:thing) { ThingWithMarkdownFields.new(foo: updated_markdown, foo_html: html, cached_markdown_version: nil) } - - before do - thing.save - end - - it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } - end - - describe '#cached_html_up_to_date?' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - - subject { thing.cached_html_up_to_date?(:foo) } - - it 'returns false when the version is absent' do - thing.cached_markdown_version = nil - - is_expected.to be_falsy - end - - it 'returns false when the cached version is too old' do - thing.cached_markdown_version = cache_version - 1 - - is_expected.to be_falsy - end - - it 'returns false when the cached version is in future' do - thing.cached_markdown_version = cache_version + 1 - - is_expected.to be_falsy - end - - it 'returns false when the local version was bumped' do - allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) - thing.cached_markdown_version = cache_version - - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true when the local version is default' do - thing.cached_markdown_version = cache_version + it 'returns false when the version is too early' do + thing.cached_markdown_version -= 1 - is_expected.to be_truthy - end + is_expected.to be_falsy + end - it 'returns true when the cached version is just right' do - allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) - thing.cached_markdown_version = cache_version + 2 + it 'returns false when the version is too late' do + thing.cached_markdown_version += 1 - is_expected.to be_truthy - end + is_expected.to be_falsy + end - it 'returns false if markdown has been changed but html has not' do - thing.foo = updated_html + it 'returns false when the local version was bumped' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true if markdown has not been changed but html has' do - thing.foo_html = updated_html + it 'returns true when the local version is default' do + thing.cached_markdown_version = cache_version - is_expected.to be_truthy - end + is_expected.to be_truthy + end - it 'returns true if markdown and html have both been changed' do - thing.foo = updated_markdown - thing.foo_html = updated_html + it 'returns true when the cached version is just right' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version + 2 - is_expected.to be_truthy + is_expected.to be_truthy + end end - it 'returns false if the markdown field is set but the html is not' do - thing.foo_html = nil + describe '#latest_cached_markdown_version' do + let(:thing) { klass.new } + subject { thing.latest_cached_markdown_version } - is_expected.to be_falsy + it 'returns default version' do + thing.cached_markdown_version = nil + is_expected.to eq(cache_version) + end end - end - - describe '#latest_cached_markdown_version' do - subject { thing.latest_cached_markdown_version } - it 'returns default version' do - thing.cached_markdown_version = nil - is_expected.to eq(cache_version) - end - end + describe '#refresh_markdown_cache' do + let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) } - describe '#refresh_markdown_cache' do - before do - thing.foo = updated_markdown - end + before do + thing.description = updated_markdown + end - it 'fills all html fields' do - thing.refresh_markdown_cache + it 'fills all html fields' do + thing.refresh_markdown_cache - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + expect(thing.description_html).to eq(updated_html) + end - it 'does not save the result' do - expect(thing).not_to receive(:update_columns) + it 'does not save the result' do + expect(thing).not_to receive(:save_markdown) - thing.refresh_markdown_cache - end + thing.refresh_markdown_cache + end - it 'updates the markdown cache version' do - thing.cached_markdown_version = nil - thing.refresh_markdown_cache + it 'updates the markdown cache version' do + thing.cached_markdown_version = nil + thing.refresh_markdown_cache - expect(thing.cached_markdown_version).to eq(cache_version) + expect(thing.cached_markdown_version).to eq(cache_version) + end end - end - - describe '#refresh_markdown_cache!' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - end + describe '#refresh_markdown_cache!' do + let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) } - it 'fills all html fields' do - thing.refresh_markdown_cache! + before do + thing.description = updated_markdown + end - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + it 'fills all html fields' do + thing.refresh_markdown_cache! - it 'skips saving if not persisted' do - expect(thing).to receive(:persisted?).and_return(false) - expect(thing).not_to receive(:update_columns) + expect(thing.description_html).to eq(updated_html) + end - thing.refresh_markdown_cache! - end + it 'saves the changes' do + expect(thing) + .to receive(:save_markdown) + .with("description_html" => updated_html, "title_html" => "", "cached_markdown_version" => cache_version) - it 'saves the changes using #update_columns' do - expect(thing).to receive(:persisted?).and_return(true) - expect(thing).to receive(:update_columns) - .with( - "foo_html" => updated_html, - "baz_html" => "", - "zoo_html" => "", - "cached_markdown_version" => cache_version - ) - - thing.refresh_markdown_cache! + thing.refresh_markdown_cache! + end end - end - describe '#banzai_render_context' do - subject(:context) { thing.banzai_render_context(:foo) } + describe '#banzai_render_context' do + let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } + subject(:context) { thing.banzai_render_context(:title) } - it 'sets project to nil if the object lacks a project' do - is_expected.to have_key(:project) - expect(context[:project]).to be_nil - end + it 'sets project to nil if the object lacks a project' do + is_expected.to have_key(:project) + expect(context[:project]).to be_nil + end - it 'excludes author if the object lacks an author' do - is_expected.not_to have_key(:author) - end + it 'excludes author if the object lacks an author' do + is_expected.not_to have_key(:author) + end - it 'raises if the context for an unrecognised field is requested' do - expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError) - end + it 'raises if the context for an unrecognised field is requested' do + expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError) + end - it 'includes the pipeline' do - baz = thing.banzai_render_context(:baz) + it 'includes the pipeline' do + title_context = thing.banzai_render_context(:title) - expect(baz[:pipeline]).to eq(:single_line) - end + expect(title_context[:pipeline]).to eq(:single_line) + end - it 'returns copies of the context template' do - template = thing.cached_markdown_fields[:baz] - copy = thing.banzai_render_context(:baz) + it 'returns copies of the context template' do + template = thing.cached_markdown_fields[:description] + copy = thing.banzai_render_context(:description) - expect(copy).not_to be(template) - end + expect(copy).not_to be(template) + end - context 'with a project' do - let(:project) { create(:project, group: create(:group)) } - let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) } + context 'with a project' do + let(:project) { build(:project, group: create(:group)) } + let(:thing) { thing_subclass(klass, :project).new(title: markdown, title_html: html, project: project) } - it 'sets the project in the context' do - is_expected.to have_key(:project) - expect(context[:project]).to eq(project) + it 'sets the project in the context' do + is_expected.to have_key(:project) + expect(context[:project]).to eq(project) + end end - it 'invalidates the cache when project changes' do - thing.project = :new_project - allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) - - thing.save + context 'with an author' do + let(:thing) { thing_subclass(klass, :author).new(title: markdown, title_html: html, author: :author_value) } - expect(thing.foo_html).to eq(updated_html) - expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(cache_version) + it 'sets the author in the context' do + is_expected.to have_key(:author) + expect(context[:author]).to eq(:author_value) + end end end + end - context 'with an author' do - let(:thing) { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author_value) } - - it 'sets the author in the context' do - is_expected.to have_key(:author) - expect(context[:author]).to eq(:author_value) - end + context 'for Active record classes' do + let(:klass) { ar_class } - it 'invalidates the cache when author changes' do - thing.author = :new_author - allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + it_behaves_like 'a class with cached markdown fields' + end - thing.save + context 'for other classes' do + let(:klass) { other_class } - expect(thing.foo_html).to eq(updated_html) - expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(cache_version) - end - end + it_behaves_like 'a class with cached markdown fields' end end diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index 7eeb2fae57d..cb52f154299 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -82,13 +82,13 @@ RSpec.describe ResourceLabelEvent, type: :model do end it 'returns true if markdown is outdated' do - subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 } + subject.attributes = { cached_markdown_version: ((Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 } expect(subject.outdated_markdown?).to be true end it 'returns false if label and reference are set' do - subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } + subject.attributes = { reference: 'whatever', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } expect(subject.outdated_markdown?).to be false end diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index bb1db9a3d51..eacf383be7d 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -14,6 +14,16 @@ describe BlobPresenter, :seed_helper do end let(:blob) { Blob.new(git_blob) } + describe '.web_url' do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) } + + subject { described_class.new(blob) } + + it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/blob/#{blob.commit_id}/#{blob.path}") } + end + describe '#highlight' do subject { described_class.new(blob) } diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb new file mode 100644 index 00000000000..d74ee5dc28f --- /dev/null +++ b/spec/presenters/tree_entry_presenter_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe TreeEntryPresenter do + include Gitlab::Routing.url_helpers + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:tree) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.trees.first, repository) } + let(:presenter) { described_class.new(tree) } + + describe '.web_url' do + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/tree/#{tree.commit_id}/#{tree.path}") } + end +end |