diff options
49 files changed, 509 insertions, 118 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 60a2b5d5b5b..19540e4331e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -586,6 +586,7 @@ codequality: paths: [codeclimate.json] qa:internal: + <<: *except-docs stage: test variables: SETUP_DB: "false" diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 2f4c74eb2dd..46448c71b9d 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.56.0 +0.57.0 @@ -400,7 +400,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.58.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.59.0', require: 'gitaly' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index fdb81b101f5..48e46e3fab0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,7 +276,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.58.0) + gitaly-proto (0.59.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -1037,7 +1037,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.58.0) + gitaly-proto (~> 0.59.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index a21c92f24d6..6d2907e2164 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -298,18 +298,21 @@ import ProjectVariables from './project_variables'; break; case 'projects:snippets:show': initNotes(); + new ZenMode(); break; case 'projects:snippets:new': case 'projects:snippets:edit': case 'projects:snippets:create': case 'projects:snippets:update': new GLForm($('.snippet-form'), true); + new ZenMode(); break; case 'snippets:new': case 'snippets:edit': case 'snippets:create': case 'snippets:update': new GLForm($('.snippet-form'), false); + new ZenMode(); break; case 'projects:releases:edit': new ZenMode(); @@ -546,6 +549,7 @@ import ProjectVariables from './project_variables'; new LineHighlighter(); new BlobViewer(); initNotes(); + new ZenMode(); break; case 'import:fogbugz:new_user_map': new UsersSelect(); diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 30d5d7a653b..8d83554d813 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -608,7 +608,7 @@ } .dropdown-content { - max-height: 215px; + max-height: $dropdown-max-height; overflow-y: auto; } @@ -1030,3 +1030,28 @@ header.header-content .dropdown-menu.projects-dropdown-menu { } } } + +.dropdown-content-faded-mask { + position: relative; + + .dropdown-list { + max-height: $dropdown-max-height; + overflow-y: auto; + position: relative; + } + + &::after { + height: $dropdown-fade-mask-height; + width: 100%; + position: absolute; + bottom: 0; + background: linear-gradient(to top, $white-light 0, rgba($white-light, 0)); + transition: opacity $fade-mask-transition-duration $fade-mask-transition-curve; + content: ''; + pointer-events: none; + } + + &.fade-out::after { + opacity: 0; + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index cb2a237f574..6525b39d55c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -337,6 +337,7 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San * Dropdowns */ $dropdown-width: 300px; +$dropdown-max-height: 215px; $dropdown-vertical-offset: 4px; $dropdown-link-color: #555; $dropdown-link-hover-bg: $row-hover; @@ -353,6 +354,7 @@ $dropdown-loading-bg: rgba(#fff, .6); $dropdown-chevron-size: 10px; $dropdown-toggle-active-border-color: darken($border-color, 14%); $dropdown-item-hover-bg: $gray-darker; +$dropdown-fade-mask-height: 32px; /* * Filtered Search @@ -564,6 +566,8 @@ $label-border-radius: 100px; * Animation */ $fade-in-duration: 200ms; +$fade-mask-transition-duration: .1s; +$fade-mask-transition-curve: ease-in-out; /* * Lint diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb index 92df1c8dff0..dd0b38970bd 100644 --- a/app/controllers/admin/appearances_controller.rb +++ b/app/controllers/admin/appearances_controller.rb @@ -4,8 +4,8 @@ class Admin::AppearancesController < Admin::ApplicationController def show end - def preview - render 'preview', layout: 'devise' + def preview_sign_in + render 'preview_sign_in', layout: 'devise' end def create @@ -52,7 +52,7 @@ class Admin::AppearancesController < Admin::ApplicationController def appearance_params params.require(:appearance).permit( :title, :description, :logo, :logo_cache, :header_logo, :header_logo_cache, - :updated_by + :new_project_guidelines, :updated_by ) end end diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index df590cf47c8..c037de33c22 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -1,30 +1,26 @@ module AppearancesHelper def brand_title - if brand_item && brand_item.title - brand_item.title - else - 'GitLab Community Edition' - end + brand_item&.title.presence || 'GitLab Community Edition' end def brand_image - if brand_item.logo? - image_tag brand_item.logo - else - nil - end + image_tag(brand_item.logo) if brand_item&.logo? end def brand_text markdown_field(brand_item, :description) end + def brand_new_project_guidelines + markdown_field(brand_item, :new_project_guidelines) + end + def brand_item @appearance ||= Appearance.current end def brand_header_logo - if brand_item && brand_item.header_logo? + if brand_item&.header_logo? image_tag brand_item.header_logo else render 'shared/logo.svg' @@ -33,7 +29,7 @@ module AppearancesHelper # Skip the 'GitLab' type logo when custom brand logo is set def brand_header_logo_type - unless brand_item && brand_item.header_logo? + unless brand_item&.header_logo? render 'shared/logo_type.svg' end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index d06cf2de2c3..3605d6a3c95 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -84,7 +84,7 @@ module ButtonHelper button_content << content_tag(:span, description, class: 'dropdown-menu-inner-content') if description content_tag (href ? :a : :span), - button_content, + (href ? button_content : title), class: "#{title.downcase}-selector", href: (href if href) end diff --git a/app/models/appearance.rb b/app/models/appearance.rb index ff15689ecac..76cfe28742a 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -2,9 +2,8 @@ class Appearance < ActiveRecord::Base include CacheMarkdownField cache_markdown_field :description + cache_markdown_field :new_project_guidelines - validates :title, presence: true - validates :description, presence: true validates :logo, file_size: { maximum: 1.megabyte } validates :header_logo, file_size: { maximum: 1.megabyte } diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 8738e094510..d2402b55184 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -47,6 +47,25 @@ module Ci scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) } scope :ref_protected, -> { where(protected: true) } + scope :matches_tag_ids, -> (tag_ids) do + matcher = ::ActsAsTaggableOn::Tagging + .where(taggable_type: CommitStatus) + .where(context: 'tags') + .where('taggable_id = ci_builds.id') + .where.not(tag_id: tag_ids).select('1') + + where("NOT EXISTS (?)", matcher) + end + + scope :with_any_tags, -> do + matcher = ::ActsAsTaggableOn::Tagging + .where(taggable_type: CommitStatus) + .where(context: 'tags') + .where('taggable_id = ci_builds.id').select('1') + + where("EXISTS (?)", matcher) + end + mount_uploader :legacy_artifacts_file, LegacyArtifactUploader, mount_on: :artifacts_file mount_uploader :legacy_artifacts_metadata, LegacyArtifactUploader, mount_on: :artifacts_metadata diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index d39610a8995..dcbb397fb78 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -112,7 +112,7 @@ module Ci def can_pick?(build) return false if self.ref_protected? && !build.protected? - assignable_for?(build.project) && accepting_tags?(build) + assignable_for?(build.project_id) && accepting_tags?(build) end def only_for?(project) @@ -171,8 +171,8 @@ module Ci end end - def assignable_for?(project) - is_shared? || projects.exists?(id: project.id) + def assignable_for?(project_id) + is_shared? || projects.exists?(id: project_id) end def accepting_tags?(build) diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index b8db709211a..2ef76e03031 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -22,6 +22,16 @@ module Ci valid = true + if Feature.enabled?('ci_job_request_with_tags_matcher') + # pick builds that does not have other tags than runner's one + builds = builds.matches_tag_ids(runner.tags.ids) + + # pick builds that have at least one tag + unless runner.run_untagged? + builds = builds.with_any_tags + end + end + builds.find do |build| next unless runner.can_pick?(build) diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 4a2238fe277..15bda97c3b5 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -1,6 +1,23 @@ = form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| = form_errors(@appearance) + %fieldset.app_logo + %legend + Navigation bar: + .form-group + = f.label :header_logo, 'Header logo', class: 'control-label' + .col-sm-10 + - if @appearance.header_logo? + = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' + - if @appearance.persisted? + %br + = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo" + %hr + = f.hidden_field :header_logo_cache + = f.file_field :header_logo, class: "" + .hint + Maximum file size is 1MB. Pages are optimized for a 28px tall header logo + %fieldset.sign-in %legend Sign in/Sign up pages: @@ -28,27 +45,22 @@ .hint Maximum file size is 1MB. Pages are optimized for a 640x360 px logo. - %fieldset.app_logo + %fieldset %legend - Navigation bar: + New project pages: .form-group - = f.label :header_logo, 'Header logo', class: 'control-label' + = f.label :new_project_guidelines, class: 'control-label' .col-sm-10 - - if @appearance.header_logo? - = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' - - if @appearance.persisted? - %br - = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo" - %hr - = f.hidden_field :header_logo_cache - = f.file_field :header_logo, class: "" + = f.text_area :new_project_guidelines, class: "form-control", rows: 10 .hint - Maximum file size is 1MB. Pages are optimized for a 28px tall header logo + Guidelines parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}. .form-actions = f.submit 'Save', class: 'btn btn-save append-right-10' - if @appearance.persisted? - = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' + Preview last save: + = link_to 'Sign-in page', preview_sign_in_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' + = link_to 'New project page', new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' - if @appearance.updated_at %span.pull-right diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview_sign_in.html.haml index 1af7dd5bb67..1af7dd5bb67 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview_sign_in.html.haml diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 97016d28535..691d2528022 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -15,8 +15,8 @@ .col-sm-7.brand-holder.pull-left %h1 = brand_title - - if brand_item = brand_image + - if brand_item&.description? = brand_text - else %h3 Open source software to collaborate on code diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index a9431cc4956..2cd5d0c60ea 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -25,7 +25,7 @@ = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) - %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } + %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: "Go full screen", data: { container: "body" } } = sprite_icon("screen-full") .md-write-holder diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 0a7880ce4cd..cad7c2e83db 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -18,6 +18,7 @@ A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. %p All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings. + = brand_new_project_guidelines .col-lg-9.js-toggle-container %ul.nav-links.gitlab-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index fba08092351..1cba4fc6c41 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -3,7 +3,7 @@ .git-clone-holder.input-group .input-group-btn - if allowed_protocols_present? - .clone-dropdown-btn.btn.btn-static + .clone-dropdown-btn.btn %span = enabled_project_button(project, enabled_protocol) - else diff --git a/changelogs/unreleased/40508-snippets-zen-mode.yml b/changelogs/unreleased/40508-snippets-zen-mode.yml new file mode 100644 index 00000000000..c205218575b --- /dev/null +++ b/changelogs/unreleased/40508-snippets-zen-mode.yml @@ -0,0 +1,5 @@ +--- +title: Init zen mode in snippets pages +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/feature-custom-text-for-new-projects.yml b/changelogs/unreleased/feature-custom-text-for-new-projects.yml new file mode 100644 index 00000000000..905d76dab33 --- /dev/null +++ b/changelogs/unreleased/feature-custom-text-for-new-projects.yml @@ -0,0 +1,5 @@ +--- +title: Add custom brand text on new project pages +merge_request: 15541 +author: Markus Koller +type: changed diff --git a/changelogs/unreleased/perform-sql-matching-of-tags.yml b/changelogs/unreleased/perform-sql-matching-of-tags.yml new file mode 100644 index 00000000000..39f8a867a4d --- /dev/null +++ b/changelogs/unreleased/perform-sql-matching-of-tags.yml @@ -0,0 +1,5 @@ +--- +title: Perform SQL matching of Build&Runner tags to greatly speed-up job picking +merge_request: +author: +type: performance diff --git a/config/routes/admin.rb b/config/routes/admin.rb index c0748231813..e22fb440abc 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -97,7 +97,7 @@ namespace :admin do resource :appearances, only: [:show, :create, :update], path: 'appearance' do member do - get :preview + get :preview_sign_in delete :logo delete :header_logos end diff --git a/db/migrate/20171122131600_add_new_project_guidelines_to_appearances.rb b/db/migrate/20171122131600_add_new_project_guidelines_to_appearances.rb new file mode 100644 index 00000000000..f141c442d97 --- /dev/null +++ b/db/migrate/20171122131600_add_new_project_guidelines_to_appearances.rb @@ -0,0 +1,12 @@ +class AddNewProjectGuidelinesToAppearances < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + change_table :appearances do |t| + t.text :new_project_guidelines + t.text :new_project_guidelines_html + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 8e5bcc8b51d..0984ca6487f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -36,6 +36,8 @@ ActiveRecord::Schema.define(version: 20171124150326) do t.datetime_with_timezone "updated_at", null: false t.text "description_html" t.integer "cached_markdown_version" + t.text "new_project_guidelines" + t.text "new_project_guidelines_html" end create_table "application_settings", force: :cascade do |t| diff --git a/doc/README.md b/doc/README.md index d4119d35162..95cb9683a15 100644 --- a/doc/README.md +++ b/doc/README.md @@ -189,6 +189,7 @@ have access to GitLab administration tools and settings. - [Issue closing pattern](administration/issue_closing_pattern.md): Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md): Use Libravatar instead of Gravatar for user avatars. - [Welcome message](customization/welcome_message.md): Add a custom welcome message to the sign-in page. +- [New project page](customization/new_project_page.md): Customize the new project page. ### Admin tools diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 5b6ee354887..136192191f9 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -58,7 +58,9 @@ Runs the following rake tasks: It will check that each component was setup according to the installation guide and suggest fixes for issues found. -You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide). +You may also have a look at our Trouble Shooting Guides: +- [Trouble Shooting Guide (GitLab)](http://docs.gitlab.com/ee/README.html#troubleshooting) +- [Trouble Shooting Guide (Omnibus Gitlab)](http://docs.gitlab.com/omnibus/README.html#troubleshooting) **Omnibus Installation** diff --git a/doc/customization/new_project_page.md b/doc/customization/new_project_page.md new file mode 100644 index 00000000000..148bf9512c6 --- /dev/null +++ b/doc/customization/new_project_page.md @@ -0,0 +1,20 @@ +# Customizing the new project page + +It is possible to add a markdown-formatted message to your GitLab +new project page. + +By default, the new project page shows a sidebar with general information: + +![](new_project_page/default_new_project_page.png) + +## Changing the appearance of the new project page + +Navigate to the **Admin** area and go to the **Appearance** page. + +Fill in your project guidelines: + +![](new_project_page/appearance_settings.png) + +After saving the page, your new project page will show the guidelines in the sidebar, below the general information: + +![](new_project_page/custom_new_project_page.png) diff --git a/doc/customization/new_project_page/appearance_settings.png b/doc/customization/new_project_page/appearance_settings.png Binary files differnew file mode 100644 index 00000000000..08eea684e14 --- /dev/null +++ b/doc/customization/new_project_page/appearance_settings.png diff --git a/doc/customization/new_project_page/custom_new_project_page.png b/doc/customization/new_project_page/custom_new_project_page.png Binary files differnew file mode 100644 index 00000000000..662c715f193 --- /dev/null +++ b/doc/customization/new_project_page/custom_new_project_page.png diff --git a/doc/customization/new_project_page/default_new_project_page.png b/doc/customization/new_project_page/default_new_project_page.png Binary files differnew file mode 100644 index 00000000000..4a0bcf09903 --- /dev/null +++ b/doc/customization/new_project_page/default_new_project_page.png diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md index aa52b5415cf..36a8844e953 100644 --- a/doc/integration/slash_commands.md +++ b/doc/integration/slash_commands.md @@ -17,6 +17,9 @@ Taking the trigger term as `project-name`, the commands are: | `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` | | `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment | +Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for +your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage). + ## Issue commands It is possible to create new issue, display issue details and search up to 5 issues. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index b9532bf897f..4fa83388d0c 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -23,25 +23,26 @@ The following table depicts the various user permission levels in a project. |---------------------------------------|---------|------------|-------------|----------|--------| | Create new issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ | +| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ | | Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Lock discussions (issues and merge requests) | | | | ✓ | ✓ | | See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | -| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | +| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ | | Download project | [^1] | ✓ | ✓ | ✓ | ✓ | +| Assign issues and merge requests | | ✓ | ✓ | ✓ | ✓ | +| Label issues and merge requests | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ | | Manage issue tracker | | ✓ | ✓ | ✓ | ✓ | | Manage labels | | ✓ | ✓ | ✓ | ✓ | | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | | See environments | | ✓ | ✓ | ✓ | ✓ | +| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Create new environments | | | ✓ | ✓ | ✓ | -| Use environment terminals | | | | ✓ | ✓ | | Stop environments | | | ✓ | ✓ | ✓ | -| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | @@ -55,10 +56,11 @@ The following table depicts the various user permission levels in a project. | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | | Create/edit/delete project milestones | | | ✓ | ✓ | ✓ | +| Use environment terminals | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | | Enable/disable branch protection | | | | ✓ | ✓ | -| Turn on/off protected branch push for devs| | | | ✓ | ✓ | +| Turn on/off protected branch push for devs| | | | ✓ | ✓ | | Enable/disable tag protections | | | | ✓ | ✓ | | Rewrite/remove Git tags | | | | ✓ | ✓ | | Edit project | | | | ✓ | ✓ | @@ -69,14 +71,15 @@ The following table depicts the various user permission levels in a project. | Manage variables | | | | ✓ | ✓ | | Manage pages | | | | ✓ | ✓ | | Manage pages domains and certificates | | | | ✓ | ✓ | +| Manage clusters | | | | ✓ | ✓ | +| Edit comments (posted by any user) | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | | Delete issues | | | | | ✓ | +| Remove pages | | | | | ✓ | | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | -| Remove pages | | | | | ✓ | -| Manage clusters | | | | ✓ | ✓ | ## Project features permissions diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index cd7b4c043da..96922e1a62f 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -50,6 +50,10 @@ module Gitlab postgresql? && version.to_f >= 9.3 end + def self.replication_slots_supported? + postgresql? && version.to_f >= 9.4 + end + def self.nulls_last_order(field, direction = 'ASC') order = "#{field} #{direction}" diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index c85dcfa0475..8900e2d7afe 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -213,11 +213,17 @@ module Gitlab end def shas_with_signatures(repository, shas) - shas.select do |sha| - begin - Rugged::Commit.extract_signature(repository.rugged, sha) - rescue Rugged::OdbError - false + GitalyClient.migrate(:filter_shas_with_signatures) do |is_enabled| + if is_enabled + Gitlab::GitalyClient::CommitService.new(repository).filter_shas_with_signatures(shas) + else + shas.select do |sha| + begin + Rugged::Commit.extract_signature(repository.rugged, sha) + rescue Rugged::OdbError + false + end + end end end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index eab04bcac65..1468069a991 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -776,24 +776,21 @@ module Gitlab end def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - OperationService.new(user, self).with_branch( - branch_name, - start_branch_name: start_branch_name, - start_repository: start_repository - ) do |start_commit| - - Gitlab::Git.check_namespace!(commit, start_repository) - - revert_tree_id = check_revert_content(commit, start_commit.sha) - raise CreateTreeError unless revert_tree_id - - committer = user_to_committer(user) + gitaly_migrate(:revert) do |is_enabled| + args = { + user: user, + commit: commit, + branch_name: branch_name, + message: message, + start_branch_name: start_branch_name, + start_repository: start_repository + } - create_commit(message: message, - author: committer, - committer: committer, - tree: revert_tree_id, - parents: [start_commit.sha]) + if is_enabled + gitaly_operations_client.user_revert(args) + else + rugged_revert(args) + end end end @@ -1769,6 +1766,28 @@ module Gitlab end end + def rugged_revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) + OperationService.new(user, self).with_branch( + branch_name, + start_branch_name: start_branch_name, + start_repository: start_repository + ) do |start_commit| + + Gitlab::Git.check_namespace!(commit, start_repository) + + revert_tree_id = check_revert_content(commit, start_commit.sha) + raise CreateTreeError unless revert_tree_id + + committer = user_to_committer(user) + + create_commit(message: message, + author: committer, + committer: committer, + tree: revert_tree_id, + parents: [start_commit.sha]) + end + end + def gitaly_add_branch(branch_name, user, target) gitaly_operation_client.user_create_branch(branch_name, user, target) rescue GRPC::FailedPrecondition => ex diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 34807d280e5..7985f5b5457 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -250,6 +250,26 @@ module Gitlab consume_commits_response(response) end + def filter_shas_with_signatures(shas) + request = Gitaly::FilterShasWithSignaturesRequest.new(repository: @gitaly_repo) + + enum = Enumerator.new do |y| + shas.each_slice(20) do |revs| + request.shas = GitalyClient.encode_repeated(revs) + + y.yield request + + request = Gitaly::FilterShasWithSignaturesRequest.new + end + end + + response = GitalyClient.call(@repository.storage, :commit_service, :filter_shas_with_signatures, enum) + + response.flat_map do |msg| + msg.shas.map { |sha| EncodingHelper.encode!(sha) } + end + end + private def call_commit_diff(request_params, options = {}) diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 51de6a9a75d..400a4af363b 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -124,7 +124,31 @@ module Gitlab end def user_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - request = Gitaly::UserCherryPickRequest.new( + call_cherry_pick_or_revert(:cherry_pick, + user: user, + commit: commit, + branch_name: branch_name, + message: message, + start_branch_name: start_branch_name, + start_repository: start_repository) + end + + def user_revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) + call_cherry_pick_or_revert(:revert, + user: user, + commit: commit, + branch_name: branch_name, + message: message, + start_branch_name: start_branch_name, + start_repository: start_repository) + end + + private + + def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) + request_class = "Gitaly::User#{rpc.to_s.camelcase}Request".constantize + + request = request_class.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit: commit.to_gitaly_commit, @@ -137,11 +161,15 @@ module Gitlab response = GitalyClient.call( @repository.storage, :operation_service, - :user_cherry_pick, + :"user_#{rpc}", request, remote_storage: start_repository.storage ) + handle_cherry_pick_or_revert_response(response) + end + + def handle_cherry_pick_or_revert_response(response) if response.pre_receive_error.presence raise Gitlab::Git::HooksService::PreReceiveError, response.pre_receive_error elsif response.commit_error.presence diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb index cf2a2b76bcb..860973024c9 100644 --- a/spec/factories/appearances.rb +++ b/spec/factories/appearances.rb @@ -4,5 +4,6 @@ FactoryGirl.define do factory :appearance do title "MepMep" description "This is my Community Edition instance" + new_project_guidelines "Custom project guidelines" end end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 5f3a37c1dcc..d91dcf76191 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -9,6 +9,7 @@ feature 'Admin Appearance' do fill_in 'appearance_title', with: 'MyCompany' fill_in 'appearance_description', with: 'dev server' + fill_in 'appearance_new_project_guidelines', with: 'Custom project guidelines' click_button 'Save' expect(current_path).to eq admin_appearances_path @@ -16,21 +17,39 @@ feature 'Admin Appearance' do expect(page).to have_field('appearance_title', with: 'MyCompany') expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_field('appearance_new_project_guidelines', with: 'Custom project guidelines') expect(page).to have_content 'Last edit' end - scenario 'Preview appearance' do + scenario 'Preview sign-in page appearance' do sign_in(create(:admin)) visit admin_appearances_path - click_link "Preview" + click_link "Sign-in page" - expect_page_has_custom_appearance(appearance) + expect_custom_sign_in_appearance(appearance) + end + + scenario 'Preview new project page appearance' do + sign_in(create(:admin)) + + visit admin_appearances_path + click_link "New project page" + + expect_custom_new_project_appearance(appearance) end scenario 'Custom sign-in page' do visit new_user_session_path - expect_page_has_custom_appearance(appearance) + + expect_custom_sign_in_appearance(appearance) + end + + scenario 'Custom new project page' do + sign_in create(:user) + visit new_project_path + + expect_custom_new_project_appearance(appearance) end scenario 'Appearance logo' do @@ -57,11 +76,15 @@ feature 'Admin Appearance' do expect(page).not_to have_css(header_logo_selector) end - def expect_page_has_custom_appearance(appearance) + def expect_custom_sign_in_appearance(appearance) expect(page).to have_content appearance.title expect(page).to have_content appearance.description end + def expect_custom_new_project_appearance(appearance) + expect(page).to have_content appearance.new_project_guidelines + end + def logo_selector '//img[data-src^="/uploads/-/system/appearance/logo"]' end diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb index 1cfbbb4cb62..0fa7ca9afd4 100644 --- a/spec/features/projects/snippets_spec.rb +++ b/spec/features/projects/snippets_spec.rb @@ -39,6 +39,11 @@ describe 'Project snippets', :js do expect(page).to have_selector('.atwho-view') end + + it 'should have zen mode' do + find('.js-zen-enter').click() + expect(page).to have_selector('.fullscreen') + end end end end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index d2c7867febb..fee8df10129 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -92,6 +92,34 @@ describe ButtonHelper do end end + describe 'ssh and http clone buttons' do + let(:user) { create(:user) } + let(:project) { build_stubbed(:project) } + + def http_button_element + element = helper.http_clone_button(project, append_link: false) + + Nokogiri::HTML::DocumentFragment.parse(element).first_element_child + end + + def ssh_button_element + element = helper.ssh_clone_button(project, append_link: false) + + Nokogiri::HTML::DocumentFragment.parse(element).first_element_child + end + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + it 'only shows the title of any of the clone buttons when append_link is false' do + expect(http_button_element.text).to eq('HTTP') + expect(http_button_element.search('.dropdown-menu-inner-content').first).to eq(nil) + expect(ssh_button_element.text).to eq('SSH') + expect(ssh_button_element.search('.dropdown-menu-inner-content').first).to eq(nil) + end + end + describe 'clipboard_button' do let(:user) { create(:user) } let(:project) { build_stubbed(:project) } diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index fcddfad3f9f..a5657b81952 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -73,6 +73,28 @@ describe Gitlab::Database do end end + describe '.replication_slots_supported?' do + it 'returns false when using MySQL' do + allow(described_class).to receive(:postgresql?).and_return(false) + + expect(described_class.replication_slots_supported?).to eq(false) + end + + it 'returns false when using PostgreSQL 9.3' do + allow(described_class).to receive(:postgresql?).and_return(true) + allow(described_class).to receive(:version).and_return('9.3.1') + + expect(described_class.replication_slots_supported?).to eq(false) + end + + it 'returns true when using PostgreSQL 9.4.0 or newer' do + allow(described_class).to receive(:postgresql?).and_return(true) + allow(described_class).to receive(:version).and_return('9.4.0') + + expect(described_class.replication_slots_supported?).to eq(true) + end + end + describe '.nulls_last_order' do context 'when using PostgreSQL' do before do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 9f4e3c49adc..5ed639543e0 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -278,6 +278,35 @@ describe Gitlab::Git::Commit, seed_helper: true do it { is_expected.not_to include(SeedRepo::FirstCommit::ID) } end + shared_examples '.shas_with_signatures' do + let(:signed_shas) { %w[5937ac0a7beb003549fc5fd26fc247adbce4a52e 570e7b2abdd848b95f2f578043fc23bd6f6fd24d] } + let(:unsigned_shas) { %w[19e2e9b4ef76b422ce1154af39a91323ccc57434 c642fe9b8b9f28f9225d7ea953fe14e74748d53b] } + let(:first_signed_shas) { %w[5937ac0a7beb003549fc5fd26fc247adbce4a52e c642fe9b8b9f28f9225d7ea953fe14e74748d53b] } + + it 'has 2 signed shas' do + ret = described_class.shas_with_signatures(repository, signed_shas) + expect(ret).to eq(signed_shas) + end + + it 'has 0 signed shas' do + ret = described_class.shas_with_signatures(repository, unsigned_shas) + expect(ret).to eq([]) + end + + it 'has 1 signed sha' do + ret = described_class.shas_with_signatures(repository, first_signed_shas) + expect(ret).to contain_exactly(first_signed_shas.first) + end + end + + describe '.shas_with_signatures with gitaly on' do + it_should_behave_like '.shas_with_signatures' + end + + describe '.shas_with_signatures with gitaly disabled', :disable_gitaly do + it_should_behave_like '.shas_with_signatures' + end + describe '.find_all' do shared_examples 'finding all commits' do it 'should return a return a collection of commits' do diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 49f44525b29..56b5d616284 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -5,9 +5,6 @@ describe Appearance do it { is_expected.to be_valid } - it { is_expected.to validate_presence_of(:title) } - it { is_expected.to validate_presence_of(:description) } - it { is_expected.to have_many(:uploads).dependent(:destroy) } describe '.current', :use_clean_rails_memory_store_caching do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 9070692abfe..26d33663dad 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1921,4 +1921,77 @@ describe Ci::Build do end end end + + describe '.matches_tag_ids' do + set(:build) { create(:ci_build, project: project, user: user) } + let(:tag_ids) { ::ActsAsTaggableOn::Tag.named_any(tag_list).ids } + + subject { described_class.where(id: build).matches_tag_ids(tag_ids) } + + before do + build.update(tag_list: build_tag_list) + end + + context 'when have different tags' do + let(:build_tag_list) { %w(A B) } + let(:tag_list) { %w(C D) } + + it "does not match a build" do + is_expected.not_to contain_exactly(build) + end + end + + context 'when have a subset of tags' do + let(:build_tag_list) { %w(A B) } + let(:tag_list) { %w(A B C D) } + + it "does match a build" do + is_expected.to contain_exactly(build) + end + end + + context 'when build does not have tags' do + let(:build_tag_list) { [] } + let(:tag_list) { %w(C D) } + + it "does match a build" do + is_expected.to contain_exactly(build) + end + end + + context 'when does not have a subset of tags' do + let(:build_tag_list) { %w(A B C) } + let(:tag_list) { %w(C D) } + + it "does not match a build" do + is_expected.not_to contain_exactly(build) + end + end + end + + describe '.matches_tags' do + set(:build) { create(:ci_build, project: project, user: user) } + + subject { described_class.where(id: build).with_any_tags } + + before do + build.update(tag_list: tag_list) + end + + context 'when does have tags' do + let(:tag_list) { %w(A B) } + + it "does match a build" do + is_expected.to contain_exactly(build) + end + end + + context 'when does not have tags' do + let(:tag_list) { [] } + + it "does not match a build" do + is_expected.not_to contain_exactly(build) + end + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index af0c86abe86..d37e3d2c527 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1372,39 +1372,49 @@ describe Repository do end describe '#revert' do - let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') } - let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } - let(:message) { 'revert message' } + shared_examples 'reverting a commit' do + let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') } + let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + let(:message) { 'revert message' } - context 'when there is a conflict' do - it 'raises an error' do - expect { repository.revert(user, new_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError) + context 'when there is a conflict' do + it 'raises an error' do + expect { repository.revert(user, new_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError) + end end - end - context 'when commit was already reverted' do - it 'raises an error' do - repository.revert(user, update_image_commit, 'master', message) + context 'when commit was already reverted' do + it 'raises an error' do + repository.revert(user, update_image_commit, 'master', message) - expect { repository.revert(user, update_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError) + expect { repository.revert(user, update_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError) + end end - end - context 'when commit can be reverted' do - it 'reverts the changes' do - expect(repository.revert(user, update_image_commit, 'master', message)).to be_truthy + context 'when commit can be reverted' do + it 'reverts the changes' do + expect(repository.revert(user, update_image_commit, 'master', message)).to be_truthy + end end - end - context 'reverting a merge commit' do - it 'reverts the changes' do - merge_commit - expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present + context 'reverting a merge commit' do + it 'reverts the changes' do + merge_commit + expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present - repository.revert(user, merge_commit, 'master', message) - expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present + repository.revert(user, merge_commit, 'master', message) + expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present + end end end + + context 'when Gitaly revert feature is enabled' do + it_behaves_like 'reverting a commit' + end + + context 'when Gitaly revert feature is disabled', :disable_gitaly do + it_behaves_like 'reverting a commit' + end end describe '#cherry_pick' do diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 5ac30111ec9..decdd577226 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -15,16 +15,14 @@ module Ci describe '#execute' do context 'runner follow tag list' do it "picks build with the same tag" do - pending_job.tag_list = ["linux"] - pending_job.save - specific_runner.tag_list = ["linux"] + pending_job.update(tag_list: ["linux"]) + specific_runner.update(tag_list: ["linux"]) expect(execute(specific_runner)).to eq(pending_job) end it "does not pick build with different tag" do - pending_job.tag_list = ["linux"] - pending_job.save - specific_runner.tag_list = ["win32"] + pending_job.update(tag_list: ["linux"]) + specific_runner.update(tag_list: ["win32"]) expect(execute(specific_runner)).to be_falsey end @@ -33,13 +31,12 @@ module Ci end it "does not pick build with tag" do - pending_job.tag_list = ["linux"] - pending_job.save + pending_job.update(tag_list: ["linux"]) expect(execute(specific_runner)).to be_falsey end it "pick build without tag" do - specific_runner.tag_list = ["win32"] + specific_runner.update(tag_list: ["win32"]) expect(execute(specific_runner)).to eq(pending_job) end end @@ -172,7 +169,7 @@ module Ci context 'when first build is stalled' do before do - pending_job.lock_version = 10 + pending_job.update(lock_version: 0) end subject { described_class.new(specific_runner).execute } @@ -182,7 +179,7 @@ module Ci before do allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) - .and_return([pending_job, other_build]) + .and_return(Ci::Build.where(id: [pending_job, other_build])) end it "receives second build from the queue" do @@ -194,7 +191,7 @@ module Ci context 'when single build is in queue' do before do allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) - .and_return([pending_job]) + .and_return(Ci::Build.where(id: pending_job)) end it "does not receive any valid result" do @@ -205,7 +202,7 @@ module Ci context 'when there is no build in queue' do before do allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) - .and_return([]) + .and_return(Ci::Build.none) end it "does not receive builds but result is valid" do |