diff options
47 files changed, 877 insertions, 523 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 137c1281121..359ee08a7ce 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.85.0 +0.87.0 @@ -411,7 +411,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.85.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly' # Locked until https://github.com/google/protobuf/issues/4210 is closed gem 'google-protobuf', '= 3.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index 89b86ae0259..6918f92aa84 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.85.0) + gitaly-proto (0.87.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (5.3.3) @@ -1057,7 +1057,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.85.0) + gitaly-proto (~> 0.87.0) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 659dc9eaa1f..53b01cca1d3 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -36,8 +36,11 @@ import initBreadcrumbs from './breadcrumb'; import initDispatcher from './dispatcher'; -// eslint-disable-next-line global-require, import/no-commonjs -if (process.env.NODE_ENV !== 'production') require('./test_utils/'); +// inject test utilities if necessary +if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) { + $.fx.off = true; + import(/* webpackMode: "eager" */ './test_utils/'); +} svg4everybody(); diff --git a/app/assets/javascripts/two_factor_auth.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index e3414d9afff..5b2473e0989 100644 --- a/app/assets/javascripts/two_factor_auth.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -1,4 +1,4 @@ -import U2FRegister from './u2f/register'; +import U2FRegister from '~/u2f/register'; document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index 001128ead59..788d86d1192 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -4,10 +4,14 @@ import ProtectedTagCreate from '~/protected_tags/protected_tag_create'; import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list'; import initSettingsPanels from '~/settings_panels'; import initDeployKeys from '~/deploy_keys'; +import ProtectedBranchCreate from '~/protected_branches/protected_branch_create'; +import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list'; document.addEventListener('DOMContentLoaded', () => { new ProtectedTagCreate(); new ProtectedTagEditList(); initDeployKeys(); initSettingsPanels(); + new ProtectedBranchCreate(); // eslint-disable-line no-new + new ProtectedBranchEditList(); // eslint-disable-line no-new }); diff --git a/app/assets/javascripts/protected_branches/index.js b/app/assets/javascripts/protected_branches/index.js deleted file mode 100644 index c9e7af127d2..00000000000 --- a/app/assets/javascripts/protected_branches/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import ProtectedBranchCreate from './protected_branch_create'; -import ProtectedBranchEditList from './protected_branch_edit_list'; - -$(() => { - const protectedBranchCreate = new ProtectedBranchCreate(); - const protectedBranchEditList = new ProtectedBranchEditList(); -}); diff --git a/app/assets/javascripts/test.js b/app/assets/javascripts/test.js deleted file mode 100644 index c4c7918a68f..00000000000 --- a/app/assets/javascripts/test.js +++ /dev/null @@ -1 +0,0 @@ -$.fx.off = true; diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index f7ba305a59f..4114ca6bf7c 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -17,7 +17,7 @@ module IssuableCollections set_pagination return if redirect_out_of_range(@total_pages) - if params[:label_name].present? + if params[:label_name].present? && @project labels_params = { project_id: @project.id, title: params[:label_name] } @labels = LabelsFinder.new(current_user, labels_params).execute end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index f3a9e591c3e..ac1d97dc54a 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -14,7 +14,14 @@ class Groups::LabelsController < Groups::ApplicationController end format.json do - available_labels = LabelsFinder.new(current_user, group_id: @group.id).execute + available_labels = LabelsFinder.new( + current_user, + group_id: @group.id, + only_group_labels: params[:only_group_labels], + include_ancestor_groups: params[:include_ancestor_groups], + include_descendant_groups: params[:include_descendant_groups] + ).execute + render json: LabelSerializer.new.represent_appearance(available_labels) end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 1d910e461b1..7b7cb52d7ed 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -14,37 +14,31 @@ class Projects::CommitsController < Projects::ApplicationController @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened .find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref) - # https://gitlab.com/gitlab-org/gitaly/issues/931 - Gitlab::GitalyClient.allow_n_plus_1_calls do - respond_to do |format| - format.html - format.atom { render layout: 'xml.atom' } + respond_to do |format| + format.html + format.atom { render layout: 'xml.atom' } - format.json do - pager_json( - 'projects/commits/_commits', - @commits.size, - project: @project, - ref: @ref) - end + format.json do + pager_json( + 'projects/commits/_commits', + @commits.size, + project: @project, + ref: @ref) end end end def signatures - # https://gitlab.com/gitlab-org/gitaly/issues/931 - Gitlab::GitalyClient.allow_n_plus_1_calls do - respond_to do |format| - format.json do - render json: { - signatures: @commits.select(&:has_signature?).map do |commit| - { - commit_sha: commit.sha, - html: view_to_html_string('projects/commit/_signature', signature: commit.signature) - } - end - } - end + respond_to do |format| + format.json do + render json: { + signatures: @commits.select(&:has_signature?).map do |commit| + { + commit_sha: commit.sha, + html: view_to_html_string('projects/commit/_signature', signature: commit.signature) + } + end + } end end end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 5c9fce211ec..780c0fdb03e 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -61,12 +61,20 @@ class LabelsFinder < UnionFinder def group_ids strong_memoize(:group_ids) do - group = Group.find(params[:group_id]) - groups = params[:include_ancestor_groups].present? ? group.self_and_ancestors : [group] - groups_user_can_read_labels(groups).map(&:id) + groups_user_can_read_labels(groups_to_include).map(&:id) end end + def groups_to_include + group = Group.find(params[:group_id]) + groups = [group] + + groups += group.ancestors if params[:include_ancestor_groups].present? + groups += group.descendants if params[:include_descendant_groups].present? + + groups + end + def group? params[:group_id].present? end diff --git a/app/models/commit.rb b/app/models/commit.rb index add5fcf0e79..b9106309142 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -19,6 +19,7 @@ class Commit attr_accessor :project, :author attr_accessor :redacted_description_html attr_accessor :redacted_title_html + attr_reader :gpg_commit DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] @@ -110,6 +111,7 @@ class Commit @raw = raw_commit @project = project @statuses = {} + @gpg_commit = Gitlab::Gpg::Commit.new(self) if project end def id @@ -452,8 +454,4 @@ class Commit def merged_merge_request_no_cache(user) MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? end - - def gpg_commit - @gpg_commit ||= Gitlab::Gpg::Commit.new(self) - end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3469d5d795c..9fb5b7efec6 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base end def group_name - name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip + name.to_s.gsub(%r{\d+[\.\s:/\\]+\d+\s*}, '').strip end def failed_but_allowed? diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index 818cfb01b14..e2ffdf72201 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -99,7 +99,7 @@ class ChatNotificationService < Service def get_message(object_kind, data) case object_kind when "push", "tag_push" - ChatMessage::PushMessage.new(data) + ChatMessage::PushMessage.new(data) if notify_for_ref?(data) when "issue" ChatMessage::IssueMessage.new(data) unless update?(data) when "merge_request" @@ -145,10 +145,16 @@ class ChatNotificationService < Service end def notify_for_ref?(data) - return true if data[:object_attributes][:tag] + return true if data.dig(:object_attributes, :tag) return true unless notify_only_default_branch? - data[:object_attributes][:ref] == project.default_branch + ref = if data[:ref] + Gitlab::Git.ref_name(data[:ref]) + else + data.dig(:object_attributes, :ref) + end + + ref == project.default_branch end def notify_for_pipeline?(data) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index b89b7a9ff85..68788134b8e 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -666,15 +666,15 @@ .checkbox = f.label :usage_ping_enabled do = f.check_box :usage_ping_enabled, disabled: !can_be_configured - Usage ping enabled - = link_to icon('question-circle'), help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping") + Enable usage ping .help-block - if can_be_configured - Every week GitLab will report license usage back to GitLab, Inc. - Disable this option if you do not want this to occur. To see the - JSON payload that will be sent, visit the - = succeed '.' do - = link_to "Cohorts page", admin_cohorts_path(anchor: 'usage-ping') + To help improve GitLab and its user experience, GitLab will + periodically collect usage information. + = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping") + about what information is shared with GitLab Inc. Visit + = link_to 'Cohorts', admin_cohorts_path(anchor: 'usage-ping') + to see the JSON payload sent. - else The usage ping is disabled, and cannot be configured through this form. For more information, see the documentation on diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 0c979109b3f..b981b5fdafa 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -42,7 +42,6 @@ = webpack_bundle_tag "common" = webpack_bundle_tag "main" = webpack_bundle_tag "raven" if Gitlab::CurrentSettings.clientside_sentry_enabled - = webpack_bundle_tag "test" if Rails.env.test? - if content_for?(:page_specific_javascripts) = yield :page_specific_javascripts diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 329bf16895f..1bd10018b40 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -2,10 +2,6 @@ - add_to_breadcrumbs("Two-Factor Authentication", profile_account_path) - @content_class = "limit-container-width" unless fluid_layout - -- content_for :page_specific_javascripts do - = webpack_bundle_tag('two_factor_auth') - .js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path } .row.prepend-top-default .col-lg-4 diff --git a/app/views/projects/protected_branches/_index.html.haml b/app/views/projects/protected_branches/_index.html.haml index 127a338e413..2b0a502fe4d 100644 --- a/app/views/projects/protected_branches/_index.html.haml +++ b/app/views/projects/protected_branches/_index.html.haml @@ -1,6 +1,3 @@ -- content_for :page_specific_javascripts do - = webpack_bundle_tag('protected_branches') - - content_for :create_protected_branch do = render 'projects/protected_branches/create_protected_branch' diff --git a/changelogs/unreleased/33570-slack-notify-default-branch.yml b/changelogs/unreleased/33570-slack-notify-default-branch.yml new file mode 100644 index 00000000000..5c90ce47729 --- /dev/null +++ b/changelogs/unreleased/33570-slack-notify-default-branch.yml @@ -0,0 +1,5 @@ +--- +title: Fix Slack/Mattermost notifications not respecting `notify_only_default_branch` setting for pushes +merge_request: 17345 +author: +type: fixed diff --git a/changelogs/unreleased/41905_merge_request_and_issue_metrics.yml b/changelogs/unreleased/41905_merge_request_and_issue_metrics.yml new file mode 100644 index 00000000000..c9e23360e3b --- /dev/null +++ b/changelogs/unreleased/41905_merge_request_and_issue_metrics.yml @@ -0,0 +1,5 @@ +--- +title: expose more metrics in merge requests api +merge_request: 16589 +author: haseebeqx +type: added diff --git a/changelogs/unreleased/zj-version-string-grouping-ci.yml b/changelogs/unreleased/zj-version-string-grouping-ci.yml new file mode 100644 index 00000000000..04ef0f65b1e --- /dev/null +++ b/changelogs/unreleased/zj-version-string-grouping-ci.yml @@ -0,0 +1,5 @@ +--- +title: Allow CI/CD Jobs being grouped on version strings +merge_request: +author: +type: added diff --git a/config/webpack.config.js b/config/webpack.config.js index 6d672ff00ba..959cebe7487 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -45,9 +45,7 @@ function generateEntries() { const manualEntries = { monitoring: './monitoring/monitoring_bundle.js', mr_notes: './mr_notes/index.js', - protected_branches: './protected_branches', terminal: './terminal/terminal_bundle.js', - two_factor_auth: './two_factor_auth.js', common: './commons/index.js', common_vue: './vue_shared/vue_resource_interceptor.js', @@ -55,7 +53,6 @@ function generateEntries() { main: './main.js', ide: './ide/index.js', raven: './raven/index.js', - test: './test.js', webpack_runtime: './webpack.js', }; diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md new file mode 100644 index 00000000000..6c5a466ced5 --- /dev/null +++ b/doc/administration/incoming_email.md @@ -0,0 +1,331 @@ +# Incoming email + +GitLab has several features based on receiving incoming emails: + +- [Reply by Email](reply_by_email.md): allow GitLab users to comment on issues + and merge requests by replying to notification emails. +- [New issue by email](../user/project/issues/create_new_issue.md#new-issue-via-email): + allow GitLab users to create a new issue by sending an email to a + user-specific email address. +- [New merge request by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email): + allow GitLab users to create a new merge request by sending an email to a + user-specific email address. + +## Requirements + +Handling incoming emails requires an [IMAP]-enabled email account. GitLab +requires one of the following three strategies: + +- Email sub-addressing +- Dedicated email address +- Catch-all mailbox + +Let's walk through each of these options. + +**If your provider or server supports email sub-addressing, we recommend using it. +Most features (other than reply by email) only work with sub-addressing.** + +[IMAP]: https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol + +### Email sub-addressing + +[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is +a feature where any email to `user+some_arbitrary_tag@example.com` will end up +in the mailbox for `user@example.com`, and is supported by providers such as +Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the +[Postfix mail server] which you can run on-premises. + +[Postfix mail server]: reply_by_email_postfix_setup.md + +### Dedicated email address + +This solution is really simple to set up: you just have to create an email +address dedicated to receive your users' replies to GitLab notifications. + +### Catch-all mailbox + +A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will +"catch all" the emails addressed to the domain that do not exist in the mail +server. + +GitLab can be set up to allow users to comment on issues and merge requests by +replying to notification emails. + +## Set it up + +If you want to use Gmail / Google Apps for incoming emails, make sure you have +[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) +and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255) +or [turn-on 2-step validation](https://support.google.com/accounts/answer/185839) +and use [an application password](https://support.google.com/mail/answer/185833). + +To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the +[Postfix setup documentation](reply_by_email_postfix_setup.md). + +### Security Concerns + +**WARNING:** Be careful when choosing the domain used for receiving incoming +email. + +For the sake of example, suppose your top-level company domain is `hooli.com`. +All employees in your company have an email address at that domain via Google +Apps, and your company's private Slack instance requires a valid `@hooli.com` +email address in order to sign up. + +If you also host a public-facing GitLab instance at `hooli.com` and set your +incoming email domain to `hooli.com`, an attacker could abuse the "Create new +issue by email" or +"[Create new merge request by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email)" +features by using a project's unique address as the email when signing up for +Slack, which would send a confirmation email, which would create a new issue or +merge request on the project owned by the attacker, allowing them to click the +confirmation link and validate their account on your company's private Slack +instance. + +We recommend receiving incoming email on a subdomain, such as +`incoming.hooli.com`, and ensuring that you do not employ any services that +authenticate solely based on access to an email domain such as `*.hooli.com.` +Alternatively, use a dedicated domain for GitLab email communications such as +`hooli-gitlab.com`. + +See GitLab issue [#30366](https://gitlab.com/gitlab-org/gitlab-ce/issues/30366) +for a real-world example of this exploit. + +### Omnibus package installations + +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the + feature and fill in the details for your specific IMAP server and email account: + + Configuration for Postfix mail server, assumes mailbox + incoming@gitlab.example.com + + ```ruby + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "incoming" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "gitlab.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 143 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = false + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + # The IDLE command timeout. + gitlab_rails['incoming_email_idle_timeout'] = 60 + ``` + + Configuration for Gmail / Google Apps, assumes mailbox + gitlab-incoming@gmail.com + + ```ruby + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "imap.gmail.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + # The IDLE command timeout. + gitlab_rails['incoming_email_idle_timeout'] = 60 + ``` + + Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes + mailbox incoming@exchange.example.com + + ```ruby + gitlab_rails['incoming_email_enabled'] = true + + # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here + gitlab_rails['incoming_email_address'] = "incoming@exchange.example.com" + + # Email account username + # Typically this is the userPrincipalName (UPN) + gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "exchange.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + ``` + +1. Reconfigure GitLab for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo gitlab-rake gitlab:incoming_email:check + ``` + +1. Reply by email should now be working. + +### Installations from source + +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature + and fill in the details for your specific IMAP server and email account: + + ```sh + sudo editor config/gitlab.yml + ``` + + Configuration for Postfix mail server, assumes mailbox + incoming@gitlab.example.com + + ```yaml + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "incoming" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "gitlab.example.com" + # IMAP server port + port: 143 + # Whether the IMAP server uses SSL + ssl: false + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + # The IDLE command timeout. + idle_timeout: 60 + ``` + + Configuration for Gmail / Google Apps, assumes mailbox + gitlab-incoming@gmail.com + + ```yaml + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + # The IDLE command timeout. + idle_timeout: 60 + ``` + + Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes + mailbox incoming@exchange.example.com + + ```yaml + incoming_email: + enabled: true + + # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here + address: "incoming@exchange.example.com" + + # Email account username + # Typically this is the userPrincipalName (UPN) + user: "incoming@ad-domain.example.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "exchange.example.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + # The IDLE command timeout. + idle_timeout: 60 + ``` + +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: + + ```sh + sudo mkdir -p /etc/default + echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab + ``` + +1. Restart GitLab: + + ```sh + sudo service gitlab restart + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production + ``` + +1. Reply by email should now be working. diff --git a/doc/administration/index.md b/doc/administration/index.md index 51444651bdb..69efaf75140 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -79,11 +79,19 @@ created in snippets, wikis, and repos. - [Sign-up restrictions](../user/admin_area/settings/sign_up_restrictions.md): block email addresses of specific domains, or whitelist only specific domains. - [Access restrictions](../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab (SSH, HTTP, HTTPS). - [Authentication/Authorization](../topics/authentication/index.md#gitlab-administrators): Enforce 2FA, configure external authentication with LDAP, SAML, CAS and additional Omniauth providers. -- [Reply by email](reply_by_email.md): Allow users to comment on issues and merge requests by replying to notification emails. - - [Postfix for Reply by email](reply_by_email_postfix_setup.md): Set up a basic Postfix mail +- [Incoming email](incoming_email.md): Configure incoming emails to allow + users to [reply by email], create [issues by email] and + [merge requests by email], and to enable [Service Desk]. + - [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a + basic Postfix mail server with IMAP authentication on Ubuntu for incoming + emails. server with IMAP authentication on Ubuntu, to be used with Reply by email. - [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time. +[reply by email]: reply_by_email.md +[issues by email]: ../user/project/issues/create_new_issue.md#new-issue-via-email +[merge requests by email]: ../user/project/merge_requests/index.md#create-new-merge-requests-by-email + ## Project settings - [Container Registry](container_registry.md): Configure Container Registry with GitLab. diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md index 3a2cced37bf..426245c7aca 100644 --- a/doc/administration/reply_by_email.md +++ b/doc/administration/reply_by_email.md @@ -5,33 +5,7 @@ replying to notification emails. ## Requirement -Reply by email requires an IMAP-enabled email account. GitLab allows you to use -three strategies for this feature: -- using email sub-addressing -- using a dedicated email address -- using a catch-all mailbox - -### Email sub-addressing - -**If your provider or server supports email sub-addressing, we recommend using it. -Some features (e.g. create new issue via email) only work with sub-addressing.** - -[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is -a feature where any email to `user+some_arbitrary_tag@example.com` will end up -in the mailbox for `user@example.com`, and is supported by providers such as -Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix -mail server which you can run on-premises. - -### Dedicated email address - -This solution is really simple to set up: you just have to create an email -address dedicated to receive your users' replies to GitLab notifications. - -### Catch-all mailbox - -A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will -"catch all" the emails addressed to the domain that do not exist in the mail -server. +Make sure [incoming email](incoming_email.md) is setup. ## How it works? @@ -65,329 +39,3 @@ the entity the notification was about (issue, merge request, commit...). For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). - -## Set it up - -If you want to use Gmail / Google Apps with Reply by email, make sure you have -[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) -and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255) -or [turn-on 2-step validation](https://support.google.com/accounts/answer/185839) -and use [an application password](https://support.google.com/mail/answer/185833). - -To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the -[Postfix setup documentation](reply_by_email_postfix_setup.md). - -### Security Concerns - -**WARNING:** Be careful when choosing the domain used for receiving incoming -email. - -For the sake of example, suppose your top-level company domain is `hooli.com`. -All employees in your company have an email address at that domain via Google -Apps, and your company's private Slack instance requires a valid `@hooli.com` -email address in order to sign up. - -If you also host a public-facing GitLab instance at `hooli.com` and set your -incoming email domain to `hooli.com`, an attacker could abuse the "Create new -issue by email" or -"[Create new merge request by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email)" -features by using a project's unique address as the email when signing up for -Slack, which would send a confirmation email, which would create a new issue or -merge request on the project owned by the attacker, allowing them to click the -confirmation link and validate their account on your company's private Slack -instance. - -We recommend receiving incoming email on a subdomain, such as -`incoming.hooli.com`, and ensuring that you do not employ any services that -authenticate solely based on access to an email domain such as `*.hooli.com.` -Alternatively, use a dedicated domain for GitLab email communications such as -`hooli-gitlab.com`. - -See GitLab issue [#30366](https://gitlab.com/gitlab-org/gitlab-ce/issues/30366) -for a real-world example of this exploit. - -### Omnibus package installations - -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the - feature and fill in the details for your specific IMAP server and email account: - - ```ruby - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "incoming" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "gitlab.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 143 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = false - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - # The IDLE command timeout. - gitlab_rails['incoming_email_idle_timeout'] = 60 - ``` - - ```ruby - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "imap.gmail.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 993 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - # The IDLE command timeout. - gitlab_rails['incoming_email_idle_timeout'] = 60 - ``` - - ```ruby - # Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes mailbox incoming@exchange.example.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here - gitlab_rails['incoming_email_address'] = "incoming@exchange.example.com" - - # Email account username - # Typically this is the userPrincipalName (UPN) - gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "exchange.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 993 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - ``` - -1. Reconfigure GitLab for the changes to take effect: - - ```sh - sudo gitlab-ctl reconfigure - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo gitlab-rake gitlab:incoming_email:check - ``` - -1. Reply by email should now be working. - -### Installations from source - -1. Go to the GitLab installation directory: - - ```sh - cd /home/git/gitlab - ``` - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature - and fill in the details for your specific IMAP server and email account: - - ```sh - sudo editor config/gitlab.yml - ``` - - ```yaml - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "incoming" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "gitlab.example.com" - # IMAP server port - port: 143 - # Whether the IMAP server uses SSL - ssl: false - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` - - ```yaml - # Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes mailbox incoming@exchange.example.com - incoming_email: - enabled: true - - # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here - address: "incoming@exchange.example.com" - - # Email account username - # Typically this is the userPrincipalName (UPN) - user: "incoming@ad-domain.example.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "exchange.example.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` - -1. Enable `mail_room` in the init script at `/etc/default/gitlab`: - - ```sh - sudo mkdir -p /etc/default - echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab - ``` - -1. Restart GitLab: - - ```sh - sudo service gitlab restart - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production - ``` - -1. Reply by email should now be working. - -### Development - -1. Go to the GitLab installation directory. - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` - - As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. - -1. Uncomment the `mail_room` line in your `Procfile`: - - ```yaml - mail_room: bundle exec mail_room -q -c config/mail_room.yml - ``` - -1. Restart GitLab: - - ```sh - bundle exec foreman start - ``` - -1. Verify that everything is configured correctly: - - ```sh - bundle exec rake gitlab:incoming_email:check RAILS_ENV=development - ``` - -1. Reply by email should now be working. diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md index a1bb3851951..3e8b78e56d5 100644 --- a/doc/administration/reply_by_email_postfix_setup.md +++ b/doc/administration/reply_by_email_postfix_setup.md @@ -1,7 +1,7 @@ -# Set up Postfix for Reply by email +# Set up Postfix for incoming email This document will take you through the steps of setting up a basic Postfix mail -server with IMAP authentication on Ubuntu, to be used with [Reply by email]. +server with IMAP authentication on Ubuntu, to be used with [incoming email]. The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. @@ -177,12 +177,12 @@ Courier, which we will install later to add IMAP authentication, requires mailbo ```sh sudo apt-get install courier-imap ``` - + And start `imapd`: ```sh imapd start ``` - + 1. The courier-authdaemon isn't started after installation. Without it, imap authentication will fail: ```sh sudo service courier-authdaemon start @@ -329,10 +329,10 @@ Courier, which we will install later to add IMAP authentication, requires mailbo ## Done! -If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./reply_by_email.md) guide to configure GitLab. +If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [incoming email] guide to configure GitLab. --- _This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ -[reply by email]: reply_by_email.md +[incoming email]: incoming_email.md diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 6ce021cb4bf..cb9b0618767 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -261,20 +261,20 @@ Parameters: "upvotes": 0, "downvotes": 0, "author": { - "id": 1, - "username": "admin", - "email": "admin@example.com", - "name": "Administrator", - "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "state" : "active", + "web_url" : "https://gitlab.example.com/root", + "avatar_url" : null, + "username" : "root", + "id" : 1, + "name" : "Administrator" }, "assignee": { - "id": 1, - "username": "admin", - "email": "admin@example.com", - "name": "Administrator", - "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "state" : "active", + "web_url" : "https://gitlab.example.com/root", + "avatar_url" : null, + "username" : "root", + "id" : 1, + "name" : "Administrator" }, "source_project_id": 2, "target_project_id": 3, @@ -308,6 +308,26 @@ Parameters: "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null + }, + "closed_at": "2018-01-19T14:36:11.086Z", + "latest_build_started_at": null, + "latest_build_finished_at": null, + "first_deployed_to_production_at": null, + "pipeline": { + "id": 8, + "ref": "master", + "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0", + "status": "created" + }, + "merged_by": null, + "merged_at": null, + "closed_by": { + "state" : "active", + "web_url" : "https://gitlab.example.com/root", + "avatar_url" : null, + "username" : "root", + "id" : 1, + "name" : "Administrator" } } ``` diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ac4a9b0ed27..856d7f264e4 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -121,8 +121,9 @@ The basic requirements is that there are two numbers separated with one of the following (you can even use them interchangeably): - a space -- a backslash (`/`) +- a forward slash (`/`) - a colon (`:`) +- a dot (`.`) >**Note:** More specifically, [it uses][regexp] this regular expression: `\d+[\s:\/\\]+\d+\s*`. diff --git a/doc/development/emails.md b/doc/development/emails.md index 18f47f44cb5..677029b1295 100644 --- a/doc/development/emails.md +++ b/doc/development/emails.md @@ -18,6 +18,68 @@ See the [Rails guides] for more info. [previews]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/spec/mailers/previews [Rails guides]: http://guides.rubyonrails.org/action_mailer_basics.html#previewing-emails +## Incoming email + +1. Go to the GitLab installation directory. + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the + feature and fill in the details for your specific IMAP server and email + account: + + Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + + ```yaml + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + # The IDLE command timeout. + idle_timeout: 60 + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. + +1. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +1. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +1. Verify that everything is configured correctly: + + ```sh + bundle exec rake gitlab:incoming_email:check RAILS_ENV=development + ``` + +1. Reply by email should now be working. + --- [Return to Development documentation](README.md) diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md index 9af088374a1..1688edc1ee2 100644 --- a/doc/user/project/issues/create_new_issue.md +++ b/doc/user/project/issues/create_new_issue.md @@ -36,3 +36,25 @@ From an Issue Board, create a new issue by clicking on the plus sign (**+**) on It opens a new issue for that project labeled after its respective list. ![From the issue board](img/new_issue_from_issue_board.png) + +## New issue via email + +*This feature needs [incoming email](../../../administration/incoming_email.md) +to be configured by a GitLab administrator to be available for CE/EE users, and +it's available on GitLab.com.* + +At the bottom of a project's issue page, click +**Email a new issue to this project**, and you will find an email address +which belongs to you. You could add this address to your contact. + +This is a private email address, generated just for you. +**Keep it to yourself** as anyone who gets ahold of it can create issues or +merge requests as if they were you. You can add this address to your contact +list for easy access. + +Sending an email to this address will create a new issue on your behalf for +this project, where the email subject becomes the issue title, and the email +body becomes the issue description. [Markdown] and [quick actions] are +supported. + +![Bottom of a project issues page](img/new_issue_from_email.png) diff --git a/doc/user/project/issues/img/new_issue_from_email.png b/doc/user/project/issues/img/new_issue_from_email.png Binary files differnew file mode 100644 index 00000000000..775ea0cdffb --- /dev/null +++ b/doc/user/project/issues/img/new_issue_from_email.png diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 16027744164..d3220598933 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -134,6 +134,10 @@ those conflicts in the GitLab UI. ## Create new merge requests by email +*This feature needs [incoming email](../../../administration/incoming_email.md) +to be configured by a GitLab administrator to be available for CE/EE users, and +it's available on GitLab.com.* + You can create a new merge request by sending an email to a user-specific email address. The address can be obtained on the merge requests page by clicking on a **Email a new merge request to this project** button. The subject will be diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c88fcf9472e..0c8ec7dd5f5 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -481,6 +481,10 @@ module API expose :id end + class PipelineBasic < Grape::Entity + expose :id, :sha, :ref, :status + end + class MergeRequestSimple < ProjectEntity expose :title expose :web_url do |merge_request, options| @@ -546,6 +550,42 @@ module API expose :changes_count do |merge_request, _options| merge_request.merge_request_diff.real_size end + + expose :merged_by, using: Entities::UserBasic do |merge_request, _options| + merge_request.metrics&.merged_by + end + + expose :merged_at do |merge_request, _options| + merge_request.metrics&.merged_at + end + + expose :closed_by, using: Entities::UserBasic do |merge_request, _options| + merge_request.metrics&.latest_closed_by + end + + expose :closed_at do |merge_request, _options| + merge_request.metrics&.latest_closed_at + end + + expose :latest_build_started_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options| + merge_request.metrics&.latest_build_started_at + end + + expose :latest_build_finished_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options| + merge_request.metrics&.latest_build_finished_at + end + + expose :first_deployed_to_production_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options| + merge_request.metrics&.first_deployed_to_production_at + end + + expose :pipeline, using: Entities::PipelineBasic, if: -> (_, options) { build_available?(options) } do |merge_request, _options| + merge_request.metrics&.pipeline + end + + def build_available?(options) + options[:project]&.feature_available?(:builds, options[:current_user]) + end end class MergeRequestChanges < MergeRequest @@ -909,10 +949,6 @@ module API expose :filename, :size end - class PipelineBasic < Grape::Entity - expose :id, :sha, :ref, :status - end - class JobBasic < Grape::Entity expose :id, :status, :stage, :name, :ref, :tag, :coverage expose :created_at, :started_at, :finished_at diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4ffd4895c7e..16d0f005f21 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -222,7 +222,7 @@ module API get ':id/merge_requests/:merge_request_iid/changes' do merge_request = find_merge_request_with_access(params[:merge_request_iid]) - present merge_request, with: Entities::MergeRequestChanges, current_user: current_user + present merge_request, with: Entities::MergeRequestChanges, current_user: current_user, project: user_project end desc 'Get the merge request pipelines' do diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index ae27a138b7c..594b6a9cbc5 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -250,6 +250,45 @@ module Gitlab end end + def extract_signature_lazily(repository, commit_id) + BatchLoader.for({ repository: repository, commit_id: commit_id }).batch do |items, loader| + items_by_repo = items.group_by { |i| i[:repository] } + + items_by_repo.each do |repo, items| + commit_ids = items.map { |i| i[:commit_id] } + + signatures = batch_signature_extraction(repository, commit_ids) + + signatures.each do |commit_sha, signature_data| + loader.call({ repository: repository, commit_id: commit_sha }, signature_data) + end + end + end + end + + def batch_signature_extraction(repository, commit_ids) + repository.gitaly_migrate(:extract_commit_signature_in_batch) do |is_enabled| + if is_enabled + gitaly_batch_signature_extraction(repository, commit_ids) + else + rugged_batch_signature_extraction(repository, commit_ids) + end + end + end + + def gitaly_batch_signature_extraction(repository, commit_ids) + repository.gitaly_commit_client.get_commit_signatures(commit_ids) + end + + def rugged_batch_signature_extraction(repository, commit_ids) + commit_ids.each_with_object({}) do |commit_id, signatures| + signature_data = rugged_extract_signature(repository, commit_id) + next unless signature_data + + signatures[commit_id] = signature_data + end + end + def rugged_extract_signature(repository, commit_id) begin Rugged::Commit.extract_signature(repository.rugged, commit_id) diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index d60f57717b5..1ad0bf1d060 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -319,6 +319,23 @@ module Gitlab [signature, signed_text] end + def get_commit_signatures(commit_ids) + request = Gitaly::GetCommitSignaturesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids) + response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_signatures, request) + + signatures = Hash.new { |h, k| h[k] = [''.b, ''.b] } + current_commit_id = nil + + response.each do |message| + current_commit_id = message.commit_id if message.commit_id.present? + + signatures[current_commit_id].first << message.signature + signatures[current_commit_id].last << message.signed_text + end + + signatures + end + private def call_commit_diff(request_params, options = {}) diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb index 90dd569aaf8..6d2278d0876 100644 --- a/lib/gitlab/gpg/commit.rb +++ b/lib/gitlab/gpg/commit.rb @@ -1,15 +1,29 @@ module Gitlab module Gpg class Commit + include Gitlab::Utils::StrongMemoize + def initialize(commit) @commit = commit repo = commit.project.repository.raw_repository - @signature_text, @signed_text = Gitlab::Git::Commit.extract_signature(repo, commit.sha) + @signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id) + end + + def signature_text + strong_memoize(:signature_text) do + @signature_data&.itself && @signature_data[0] + end + end + + def signed_text + strong_memoize(:signed_text) do + @signature_data&.itself && @signature_data[1] + end end def has_signature? - !!(@signature_text && @signed_text) + !!(signature_text && signed_text) end def signature @@ -53,7 +67,7 @@ module Gitlab end def verified_signature - @verified_signature ||= GPGME::Crypto.new.verify(@signature_text, signed_text: @signed_text) do |verified_signature| + @verified_signature ||= GPGME::Crypto.new.verify(signature_text, signed_text: signed_text) do |verified_signature| break verified_signature end end diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index da54aa9054c..185b6b4ce57 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe Groups::LabelsController do - let(:group) { create(:group) } - let(:user) { create(:user) } + set(:group) { create(:group) } + set(:user) { create(:user) } + set(:project) { create(:project, namespace: group) } before do group.add_owner(user) @@ -10,6 +11,34 @@ describe Groups::LabelsController do sign_in(user) end + describe 'GET #index' do + set(:label_1) { create(:label, project: project, title: 'label_1') } + set(:group_label_1) { create(:group_label, group: group, title: 'group_label_1') } + + it 'returns group and project labels by default' do + get :index, group_id: group, format: :json + + label_ids = json_response.map {|label| label['title']} + expect(label_ids).to match_array([label_1.title, group_label_1.title]) + end + + context 'with ancestor group', :nested_groups do + set(:subgroup) { create(:group, parent: group) } + set(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') } + + before do + subgroup.add_owner(user) + end + + it 'returns ancestor group labels', :nested_groups do + get :index, group_id: subgroup, include_ancestor_groups: true, only_group_labels: true, format: :json + + label_ids = json_response.map {|label| label['title']} + expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) + end + end + end + describe 'POST #toggle_subscription' do it 'allows user to toggle subscription on group labels' do label = create(:group_label, group: group) diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index dc76efea35b..d434c501110 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -89,6 +89,25 @@ describe LabelsFinder do expect(finder.execute).to eq [private_subgroup_label_1] end end + + context 'when including labels from group descendants', :nested_groups do + it 'returns labels from group and its descendants' do + private_group_1.add_developer(user) + private_subgroup_1.add_developer(user) + + finder = described_class.new(user, group_id: private_group_1.id, only_group_labels: true, include_descendant_groups: true) + + expect(finder.execute).to eq [private_group_label_1, private_subgroup_label_1] + end + + it 'ignores labels from groups which user can not read' do + private_subgroup_1.add_developer(user) + + finder = described_class.new(user, group_id: private_group_1.id, only_group_labels: true, include_descendant_groups: true) + + expect(finder.execute).to eq [private_subgroup_label_1] + end + end end context 'filtering by project_id' do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 0b20a6349a2..a05feaac1ca 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -393,81 +393,111 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe '.extract_signature' do - subject { described_class.extract_signature(repository, commit_id) } - - shared_examples '.extract_signature' do - context 'when the commit is signed' do - let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } - - it 'returns signature and signed text' do - signature, signed_text = subject - - expected_signature = <<~SIGNATURE - -----BEGIN PGP SIGNATURE----- - Version: GnuPG/MacGPG2 v2.0.22 (Darwin) - Comment: GPGTools - https://gpgtools.org + shared_examples 'extracting commit signature' do + context 'when the commit is signed' do + let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + + it 'returns signature and signed text' do + signature, signed_text = subject + + expected_signature = <<~SIGNATURE + -----BEGIN PGP SIGNATURE----- + Version: GnuPG/MacGPG2 v2.0.22 (Darwin) + Comment: GPGTools - https://gpgtools.org + + iQEcBAABCgAGBQJTDvaZAAoJEGJ8X1ifRn8XfvYIAMuB0yrbTGo1BnOSoDfyrjb0 + Kw2EyUzvXYL72B63HMdJ+/0tlSDC6zONF3fc+bBD8z+WjQMTbwFNMRbSSy2rKEh+ + mdRybOP3xBIMGgEph0/kmWln39nmFQBsPRbZBWoU10VfI/ieJdEOgOphszgryRar + TyS73dLBGE9y9NIININVaNISet9D9QeXFqc761CGjh4YIghvPpi+YihMWapGka6v + hgKhX+hc5rj+7IEE0CXmlbYR8OYvAbAArc5vJD7UTxAY4Z7/l9d6Ydt9GQ25khfy + ANFgltYzlR6evLFmDjssiP/mx/ZMN91AL0ueJ9nNGv411Mu2CUW+tDCaQf35mdc= + =j51i + -----END PGP SIGNATURE----- + SIGNATURE + + expect(signature).to eq(expected_signature.chomp) + expect(signature).to be_a_binary_string + + expected_signed_text = <<~SIGNED_TEXT + tree 22bfa2fbd217df24731f43ff43a4a0f8db759dae + parent ae73cb07c9eeaf35924a10f713b364d32b2dd34f + author Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200 + committer Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200 + + Feature added + + Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> + SIGNED_TEXT + + expect(signed_text).to eq(expected_signed_text) + expect(signed_text).to be_a_binary_string + end + end - iQEcBAABCgAGBQJTDvaZAAoJEGJ8X1ifRn8XfvYIAMuB0yrbTGo1BnOSoDfyrjb0 - Kw2EyUzvXYL72B63HMdJ+/0tlSDC6zONF3fc+bBD8z+WjQMTbwFNMRbSSy2rKEh+ - mdRybOP3xBIMGgEph0/kmWln39nmFQBsPRbZBWoU10VfI/ieJdEOgOphszgryRar - TyS73dLBGE9y9NIININVaNISet9D9QeXFqc761CGjh4YIghvPpi+YihMWapGka6v - hgKhX+hc5rj+7IEE0CXmlbYR8OYvAbAArc5vJD7UTxAY4Z7/l9d6Ydt9GQ25khfy - ANFgltYzlR6evLFmDjssiP/mx/ZMN91AL0ueJ9nNGv411Mu2CUW+tDCaQf35mdc= - =j51i - -----END PGP SIGNATURE----- - SIGNATURE + context 'when the commit has no signature' do + let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } - expect(signature).to eq(expected_signature.chomp) - expect(signature).to be_a_binary_string + it 'returns nil' do + expect(subject).to be_nil + end + end - expected_signed_text = <<~SIGNED_TEXT - tree 22bfa2fbd217df24731f43ff43a4a0f8db759dae - parent ae73cb07c9eeaf35924a10f713b364d32b2dd34f - author Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200 - committer Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200 + context 'when the commit cannot be found' do + let(:commit_id) { Gitlab::Git::BLANK_SHA } - Feature added + it 'returns nil' do + expect(subject).to be_nil + end + end - Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> - SIGNED_TEXT + context 'when the commit ID is invalid' do + let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e' } - expect(signed_text).to eq(expected_signed_text) - expect(signed_text).to be_a_binary_string - end + it 'raises ArgumentError' do + expect { subject }.to raise_error(ArgumentError) end + end + end - context 'when the commit has no signature' do - let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } - - it 'returns nil' do - expect(subject).to be_nil + describe '.extract_signature_lazily' do + shared_examples 'loading signatures in batch once' do + it 'fetches signatures in batch once' do + commit_ids = %w[0b4bc9a49b562e85de7cc9e834518ea6828729b9 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6] + signatures = commit_ids.map do |commit_id| + described_class.extract_signature_lazily(repository, commit_id) end - end - context 'when the commit cannot be found' do - let(:commit_id) { Gitlab::Git::BLANK_SHA } + expect(described_class).to receive(:batch_signature_extraction) + .with(repository, commit_ids) + .once + .and_return({}) - it 'returns nil' do - expect(subject).to be_nil - end + 2.times { signatures.each(&:itself) } end + end - context 'when the commit ID is invalid' do - let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e' } + subject { described_class.extract_signature_lazily(repository, commit_id).itself } - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError) - end - end + context 'with Gitaly extract_commit_signature_in_batch feature enabled' do + it_behaves_like 'extracting commit signature' + it_behaves_like 'loading signatures in batch once' + end + + context 'with Gitaly extract_commit_signature_in_batch feature disabled', :disable_gitaly do + it_behaves_like 'extracting commit signature' + it_behaves_like 'loading signatures in batch once' end + end + + describe '.extract_signature' do + subject { described_class.extract_signature(repository, commit_id) } context 'with gitaly' do - it_behaves_like '.extract_signature' + it_behaves_like 'extracting commit signature' end - context 'without gitaly', :skip_gitaly_mock do - it_behaves_like '.extract_signature' + context 'without gitaly', :disable_gitaly do + it_behaves_like 'extracting commit signature' end end end diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 67c62458f0f..8c6d673391b 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::Gpg::Commit do end before do - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return( [ @@ -101,7 +101,7 @@ describe Gitlab::Gpg::Commit do end before do - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return( [ @@ -140,7 +140,7 @@ describe Gitlab::Gpg::Commit do end before do - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return( [ @@ -175,7 +175,7 @@ describe Gitlab::Gpg::Commit do end before do - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return( [ @@ -211,7 +211,7 @@ describe Gitlab::Gpg::Commit do end before do - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return( [ @@ -241,7 +241,7 @@ describe Gitlab::Gpg::Commit do let!(:commit) { create :commit, project: project, sha: commit_sha } before do - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return( [ diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb index c034eccf2a6..6fbffc38444 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do before do allow_any_instance_of(Project).to receive(:commit).and_return(commit) - allow(Gitlab::Git::Commit).to receive(:extract_signature) + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) .with(Gitlab::Git::Repository, commit_sha) .and_return(signature) end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index c536dab2681..b7ed8be69fc 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -368,7 +368,9 @@ describe CommitStatus do 'rspec:windows 0 : / 1' => 'rspec:windows', 'rspec:windows 0 : / 1 name' => 'rspec:windows name', '0 1 name ruby' => 'name ruby', - '0 :/ 1 name ruby' => 'name ruby' + '0 :/ 1 name ruby' => 'name ruby', + 'golang test 1.8' => 'golang test', + '1.9 golang test' => 'golang test' } tests.each do |name, group_name| diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 658cedd6b5f..e8eb01f6c32 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -9,6 +9,7 @@ describe API::MergeRequests do let(:non_member) { create(:user) } let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let(:pipeline) { create(:ci_empty_pipeline) } let(:milestone1) { create(:milestone, title: '0.9', project: project) } let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } @@ -500,6 +501,45 @@ describe API::MergeRequests do expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size) end + context 'merge_request_metrics' do + before do + merge_request.metrics.update!(merged_by: user, + latest_closed_by: user, + latest_closed_at: 1.hour.ago, + merged_at: 2.hours.ago, + pipeline: pipeline, + latest_build_started_at: 3.hours.ago, + latest_build_finished_at: 1.hour.ago, + first_deployed_to_production_at: 3.minutes.ago) + end + + it 'has fields from merge request metrics' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user) + + expect(json_response).to include('merged_by', + 'merged_at', + 'closed_by', + 'closed_at', + 'latest_build_started_at', + 'latest_build_finished_at', + 'first_deployed_to_production_at', + 'pipeline') + end + + it 'returns correct values' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.reload.iid}", user) + + expect(json_response['merged_by']['id']).to eq(merge_request.metrics.merged_by_id) + expect(Time.parse json_response['merged_at']).to be_like_time(merge_request.metrics.merged_at) + expect(json_response['closed_by']['id']).to eq(merge_request.metrics.latest_closed_by_id) + expect(Time.parse json_response['closed_at']).to be_like_time(merge_request.metrics.latest_closed_at) + expect(json_response['pipeline']['id']).to eq(merge_request.metrics.pipeline_id) + expect(Time.parse json_response['latest_build_started_at']).to be_like_time(merge_request.metrics.latest_build_started_at) + expect(Time.parse json_response['latest_build_finished_at']).to be_like_time(merge_request.metrics.latest_build_finished_at) + expect(Time.parse json_response['first_deployed_to_production_at']).to be_like_time(merge_request.metrics.first_deployed_to_production_at) + end + end + it "returns a 404 error if merge_request_iid not found" do get api("/projects/#{project.id}/merge_requests/999", user) expect(response).to have_gitlab_http_status(404) diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 3a935d98540..6aed481939e 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -15,8 +15,8 @@ describe MergeRequests::BuildService do let(:target_branch) { 'master' } let(:merge_request) { service.execute } let(:compare) { double(:compare, commits: commits) } - let(:commit_1) { double(:commit_1, safe_message: "Initial commit\n\nCreate the app") } - let(:commit_2) { double(:commit_2, safe_message: 'This is a bad commit message!') } + let(:commit_1) { double(:commit_1, sha: 'f00ba7', safe_message: "Initial commit\n\nCreate the app") } + let(:commit_2) { double(:commit_2, sha: 'f00ba7', safe_message: 'This is a bad commit message!') } let(:commits) { nil } let(:service) do diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index e827a8da0b7..5e1ce19eafb 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -337,6 +337,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do before do chat_service.notify_only_default_branch = true + WebMock.stub_request(:post, webhook_url) end it 'does not call the Slack/Mattermost API for pipeline events' do @@ -345,6 +346,23 @@ RSpec.shared_examples 'slack or mattermost notifications' do expect(result).to be_falsy end + + it 'does not notify push events if they are not for the default branch' do + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" + push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + + chat_service.execute(push_sample_data) + + expect(WebMock).not_to have_requested(:post, webhook_url) + end + + it 'notifies about push events for the default branch' do + push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) + + chat_service.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end end context 'when disabled' do |