diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 30f1e843e7b0a259d73eb22484cc9281ee93498e..8fd377938b41e0d4622854d420b6eeb7587c44df 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -64,6 +64,7 @@ export default {
           this.groupId,
           term,
           {
+            search_namespaces: true,
             with_issues_enabled: true,
             with_shared: false,
             include_subgroups: true,
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 66ce1ab5659ff0de4d8dee8545ff3045e744ee63..15c7c09366c4816e599045402aeaa2a5c9ce7fbf 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -54,6 +54,7 @@ const projectSelect = () => {
             this.groupId,
             query.term,
             {
+              search_namespaces: true,
               with_issues_enabled: this.withIssuesEnabled,
               with_merge_requests_enabled: this.withMergeRequestsEnabled,
               with_shared: this.withShared,
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 03766c4877ec205ba5f46763b79eac84ea8b6147..6c58f48dc74e291e45f18f14e3fb972a537a2cb8 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -134,7 +134,9 @@ export default {
           },
           {
             attrs: {
-              href: `${this.newBlobPath}/${this.currentPath ? escape(this.currentPath) : ''}`,
+              href: `${this.newBlobPath}/${
+                this.currentPath ? encodeURIComponent(this.currentPath) : ''
+              }`,
               class: 'qa-new-file-option',
             },
             text: __('New file'),
diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue
index a5c6c9822fb64a85c132585774cc0334601b4d74..f9fcbc356e842cf27209f6f6d9846b64f0c17864 100644
--- a/app/assets/javascripts/repository/components/table/parent_row.vue
+++ b/app/assets/javascripts/repository/components/table/parent_row.vue
@@ -25,10 +25,10 @@ export default {
       const splitArray = this.path.split('/');
       splitArray.pop();
 
-      return splitArray.join('/');
+      return splitArray.map(p => encodeURIComponent(p)).join('/');
     },
     parentRoute() {
-      return { path: `/-/tree/${escape(this.commitRef)}/${escape(this.parentPath)}` };
+      return { path: `/-/tree/${escape(this.commitRef)}/${this.parentPath}` };
     },
   },
   methods: {
diff --git a/app/finders/autocomplete/move_to_project_finder.rb b/app/finders/autocomplete/move_to_project_finder.rb
index 491cce2232ec1b20a35dc80ed340ea0071a32743..af6defc1fc6cd4d895f5f9c6b03b843d0b209e40 100644
--- a/app/finders/autocomplete/move_to_project_finder.rb
+++ b/app/finders/autocomplete/move_to_project_finder.rb
@@ -25,7 +25,7 @@ module Autocomplete
     def execute
       current_user
         .projects_where_can_admin_issues
-        .optionally_search(search)
+        .optionally_search(search, include_namespace: true)
         .excluding_project(project_id)
         .eager_load_namespace_and_owner
         .sorted_by_name_asc_limited(LIMIT)
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index c319d2fed874d1f6afe3d4b7c7638f3a7f9f7ce8..961694bd91fd4d1200ccecd214b1075baa3ce61e 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -17,6 +17,7 @@
 #     tags: string[]
 #     personal: boolean
 #     search: string
+#     search_namespaces: boolean
 #     non_archived: boolean
 #     archived: 'only' or boolean
 #     min_access_level: integer
@@ -171,7 +172,7 @@ class ProjectsFinder < UnionFinder
 
   def by_search(items)
     params[:search] ||= params[:name]
-    params[:search].present? ? items.search(params[:search]) : items
+    items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
   end
 
   def by_deleted_status(items)
diff --git a/app/models/concerns/alert_event_lifecycle.rb b/app/models/concerns/alert_event_lifecycle.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4d2b717ead28ec8b1f7b1f5a74a218de6f0dc72d
--- /dev/null
+++ b/app/models/concerns/alert_event_lifecycle.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module AlertEventLifecycle
+  extend ActiveSupport::Concern
+
+  included do
+    validates :started_at, presence: true
+    validates :status, presence: true
+
+    state_machine :status, initial: :none do
+      state :none, value: nil
+
+      state :firing, value: 0 do
+        validates :payload_key, presence: true
+        validates :ended_at, absence: true
+      end
+
+      state :resolved, value: 1 do
+        validates :ended_at, presence: true
+      end
+
+      event :fire do
+        transition none: :firing
+      end
+
+      event :resolve do
+        transition firing: :resolved
+      end
+
+      before_transition to: :firing do |alert_event, transition|
+        started_at = transition.args.first
+        alert_event.started_at = started_at
+      end
+
+      before_transition to: :resolved do |alert_event, transition|
+        ended_at = transition.args.first
+        alert_event.ended_at = ended_at || Time.current
+      end
+    end
+
+    scope :firing, -> { where(status: status_value_for(:firing)) }
+    scope :resolved, -> { where(status: status_value_for(:resolved)) }
+
+    scope :count_by_project_id, -> { group(:project_id).count }
+
+    def self.status_value_for(name)
+      state_machines[:status].states[name].value
+    end
+  end
+end
diff --git a/app/models/concerns/optionally_search.rb b/app/models/concerns/optionally_search.rb
index 4093429e372a4b8e89e1b5c95205dd265607718a..06f8c3dc1cb6b88fc9d3a8a5d97d677bd8397b80 100644
--- a/app/models/concerns/optionally_search.rb
+++ b/app/models/concerns/optionally_search.rb
@@ -12,8 +12,8 @@ module OptionallySearch
     end
 
     # Optionally limits a result set to those matching the given search query.
-    def optionally_search(query = nil)
-      query.present? ? search(query) : all
+    def optionally_search(query = nil, **options)
+      query.present? ? search(query, **options) : all
     end
   end
 end
diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb
index 01cb5a14762e39bc74858b2e9bd621d1ec37f35f..7373f006d64bee941bafa72c3f733aea326c140c 100644
--- a/app/models/concerns/protected_ref_access.rb
+++ b/app/models/concerns/protected_ref_access.rb
@@ -19,7 +19,6 @@ module ProtectedRefAccess
   end
 
   included do
-    scope :master, -> { maintainer } # @deprecated
     scope :maintainer, -> { where(access_level: Gitlab::Access::MAINTAINER) }
     scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) }
     scope :by_user, -> (user) { where(user_id: user ) }
diff --git a/app/models/concerns/select_for_project_authorization.rb b/app/models/concerns/select_for_project_authorization.rb
index 333c9118aa5c56af26caa41cf775c1cf3fc6fb0b..4fae36f7b8d3c6f068ea796738792cd45a866648 100644
--- a/app/models/concerns/select_for_project_authorization.rb
+++ b/app/models/concerns/select_for_project_authorization.rb
@@ -11,8 +11,5 @@ module SelectForProjectAuthorization
     def select_as_maintainer_for_project_authorization
       select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"])
     end
-
-    # @deprecated
-    alias_method :select_as_master_for_project_authorization, :select_as_maintainer_for_project_authorization
   end
 end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 3f9247b1544d293100c12c9880d2a88e3bc04bba..e9b1c55726dea2cd51ffe4b4c606ec9862345744 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -14,6 +14,7 @@ class Environment < ApplicationRecord
   has_many :successful_deployments, -> { success }, class_name: 'Deployment'
   has_many :active_deployments, -> { active }, class_name: 'Deployment'
   has_many :prometheus_alerts, inverse_of: :environment
+  has_many :self_managed_prometheus_alert_events, inverse_of: :environment
 
   has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
   has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus'
diff --git a/app/models/group.rb b/app/models/group.rb
index e9b3e3c3369f9fb580285d55de6f053cea136f4e..da69f7cc11e047bed77dac5f7a731691bdd49082 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -245,9 +245,6 @@ class Group < Namespace
     add_user(user, :maintainer, current_user: current_user)
   end
 
-  # @deprecated
-  alias_method :add_master, :add_maintainer
-
   def add_owner(user, current_user = nil)
     add_user(user, :owner, current_user: current_user)
   end
@@ -274,9 +271,6 @@ class Group < Namespace
     ::ContainerRepository.for_group_and_its_subgroups(self).exists?
   end
 
-  # @deprecated
-  alias_method :has_master?, :has_maintainer?
-
   # Check if user is a last owner of the group.
   def last_owner?(user)
     has_owner?(user) && members_with_parents.owners.size == 1
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 68ef84223c5089fafba8d68ac2f1f52bc2588bfb..e1966eda2771fd2c514cc54259eb8875eada83fd 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class LfsObjectsProject < ApplicationRecord
+  include ::EachBatch
+
   belongs_to :project
   belongs_to :lfs_object
 
diff --git a/app/models/member.rb b/app/models/member.rb
index 99dee67346e9aa4a79042f11f5ab2ef3cb7e3a7d..089efcb81dd6663de6d72ee996a9ae6dad078016 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -76,10 +76,8 @@ class Member < ApplicationRecord
   scope :developers, -> { active.where(access_level: DEVELOPER) }
   scope :maintainers, -> { active.where(access_level: MAINTAINER) }
   scope :non_guests, -> { where('members.access_level > ?', GUEST) }
-  scope :masters, -> { maintainers } # @deprecated
-  scope :owners,  -> { active.where(access_level: OWNER) }
+  scope :owners, -> { active.where(access_level: OWNER) }
   scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
-  scope :owners_and_masters,  -> { owners_and_maintainers } # @deprecated
   scope :with_user, -> (user) { where(user: user) }
 
   scope :with_source_id, ->(source_id) { where(source_id: source_id) }
diff --git a/app/models/project.rb b/app/models/project.rb
index ffdd13b72d56515d1cd96a8369c3ceabd9f97349..34c9c7320be447a7e7a32ebfe5931cce70985ca0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -255,6 +255,8 @@ class Project < ApplicationRecord
 
   has_many :prometheus_metrics
   has_many :prometheus_alerts, inverse_of: :project
+  has_many :prometheus_alert_events, inverse_of: :project
+  has_many :self_managed_prometheus_alert_events, inverse_of: :project
 
   # Container repositories need to remove data from the container registry,
   # which is not managed by the DB. Hence we're still using dependent: :destroy
@@ -349,7 +351,6 @@ class Project < ApplicationRecord
   delegate :members, to: :team, prefix: true
   delegate :add_user, :add_users, to: :team
   delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team
-  delegate :add_master, to: :team # @deprecated
   delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings
   delegate :root_ancestor, to: :namespace, allow_nil: true
   delegate :last_pipeline, to: :commit, allow_nil: true
@@ -591,9 +592,9 @@ class Project < ApplicationRecord
     # case-insensitive.
     #
     # query - The search query as a String.
-    def search(query)
-      if Feature.enabled?(:project_search_by_full_path, default_enabled: true)
-        joins(:route).fuzzy_search(query, [Route.arel_table[:path], :name, :description])
+    def search(query, include_namespace: false)
+      if include_namespace && Feature.enabled?(:project_search_by_full_path, default_enabled: true)
+        joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
       else
         fuzzy_search(query, [:path, :name, :description])
       end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index bc16a34612a6af0396a5e8ac50a56a8c5ee56329..b4071c6d4a6855d9a104076e66a6c2cc9baff885 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -7,7 +7,6 @@ class ProjectGroupLink < ApplicationRecord
   REPORTER  = 20
   DEVELOPER = 30
   MAINTAINER = 40
-  MASTER = MAINTAINER # @deprecated
 
   belongs_to :project
   belongs_to :group
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index de1fc55ba933599413d58e641eb5df74fd0aedb2..072d281e5f8b4582104d153137f2fc96d6e39989 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -25,9 +25,6 @@ class ProjectTeam
     add_user(user, :maintainer, current_user: current_user)
   end
 
-  # @deprecated
-  alias_method :add_master, :add_maintainer
-
   def add_role(user, role, current_user: nil)
     public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
   end
@@ -98,9 +95,6 @@ class ProjectTeam
     @maintainers ||= fetch_members(Gitlab::Access::MAINTAINER)
   end
 
-  # @deprecated
-  alias_method :masters, :maintainers
-
   def owners
     @owners ||=
       if group
@@ -156,9 +150,6 @@ class ProjectTeam
     max_member_access(user.id) == Gitlab::Access::MAINTAINER
   end
 
-  # @deprecated
-  alias_method :master?, :maintainer?
-
   # Checks if `user` is authorized for this project, with at least the
   # `min_access_level` (if given).
   def member?(user, min_access_level = Gitlab::Access::GUEST)
diff --git a/app/models/prometheus_alert_event.rb b/app/models/prometheus_alert_event.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e61f6d5e3cd233b6a32a347fc88c52ec5b13b10
--- /dev/null
+++ b/app/models/prometheus_alert_event.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class PrometheusAlertEvent < ApplicationRecord
+  include AlertEventLifecycle
+
+  belongs_to :project, optional: false, validate: true, inverse_of: :prometheus_alert_events
+  belongs_to :prometheus_alert, optional: false, validate: true, inverse_of: :prometheus_alert_events
+  has_and_belongs_to_many :related_issues, class_name: 'Issue', join_table: :issues_prometheus_alert_events # rubocop:disable Rails/HasAndBelongsToMany
+
+  validates :payload_key, uniqueness: { scope: :prometheus_alert_id }
+  validates :started_at, presence: true
+
+  delegate :title, :prometheus_metric_id, to: :prometheus_alert
+
+  scope :for_environment, -> (environment) do
+    joins(:prometheus_alert).where(prometheus_alerts: { environment_id: environment })
+  end
+
+  scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
+
+  def self.last_by_project_id
+    ids = select(arel_table[:id].maximum.as('id')).group(:project_id).map(&:id)
+    with_prometheus_alert.find(ids)
+  end
+
+  def self.find_or_initialize_by_payload_key(project, alert, payload_key)
+    find_or_initialize_by(project: project, prometheus_alert: alert, payload_key: payload_key)
+  end
+
+  def self.find_by_payload_key(payload_key)
+    find_by(payload_key: payload_key)
+  end
+
+  def self.status_value_for(name)
+    state_machines[:status].states[name].value
+  end
+
+  def self.payload_key_for(gitlab_alert_id, started_at)
+    plain = [gitlab_alert_id, started_at].join('/')
+
+    Digest::SHA1.hexdigest(plain)
+  end
+end
diff --git a/app/models/self_managed_prometheus_alert_event.rb b/app/models/self_managed_prometheus_alert_event.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d2d4a5c37d401af645748aa2c995c80f03f7d768
--- /dev/null
+++ b/app/models/self_managed_prometheus_alert_event.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class SelfManagedPrometheusAlertEvent < ApplicationRecord
+  include AlertEventLifecycle
+
+  belongs_to :project, validate: true, inverse_of: :self_managed_prometheus_alert_events
+  belongs_to :environment, validate: true, inverse_of: :self_managed_prometheus_alert_events
+  has_and_belongs_to_many :related_issues, class_name: 'Issue', join_table: :issues_self_managed_prometheus_alert_events # rubocop:disable Rails/HasAndBelongsToMany
+
+  validates :started_at, presence: true
+  validates :payload_key, uniqueness: { scope: :project_id }
+
+  def self.find_or_initialize_by_payload_key(project, payload_key)
+    find_or_initialize_by(project: project, payload_key: payload_key) do |event|
+      yield event if block_given?
+    end
+  end
+
+  def self.payload_key_for(started_at, alert_name, query_expression)
+    plain = [started_at, alert_name, query_expression].join('/')
+
+    Digest::SHA1.hexdigest(plain)
+  end
+end
diff --git a/app/models/snippet_repository.rb b/app/models/snippet_repository.rb
index f879f58b5a384d4770d7693af644b887b4f088b1..e60dbb4d14154addf8e31786d563ad1f13a0ec38 100644
--- a/app/models/snippet_repository.rb
+++ b/app/models/snippet_repository.rb
@@ -48,15 +48,27 @@ class SnippetRepository < ApplicationRecord
     next_index = get_last_empty_file_index + 1
 
     files.each do |file_entry|
+      file_entry[:file_path] = file_path_for(file_entry, next_index) { next_index += 1 }
       file_entry[:action] = infer_action(file_entry) unless file_entry[:action]
-
-      if file_entry[:file_path].blank?
-        file_entry[:file_path] = build_empty_file_name(next_index)
-        next_index += 1
-      end
     end
   end
 
+  def file_path_for(file_entry, next_index)
+    return file_entry[:file_path] if file_entry[:file_path].present?
+    return file_entry[:previous_path] if reuse_previous_path?(file_entry)
+
+    build_empty_file_name(next_index).tap { yield }
+  end
+
+  # If the user removed the file_path and the previous_path
+  # matches the EMPTY_FILE_PATTERN, we don't need to
+  # rename the file and build a new empty file name,
+  # we can just reuse the existing file name
+  def reuse_previous_path?(file_entry)
+    file_entry[:file_path].blank? &&
+      EMPTY_FILE_PATTERN.match?(file_entry[:previous_path])
+  end
+
   def infer_action(file_entry)
     return :create if file_entry[:previous_path].blank?
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 48438d0e7e2bc17f8266842a95023a0ba6e369b1..7789326e8fa40332bd2702b98dfa2b9d7f3865cc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1693,9 +1693,6 @@ class User < ApplicationRecord
     end
   end
 
-  # @deprecated
-  alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
-
   protected
 
   # override, from Devise::Validatable
diff --git a/app/serializers/prometheus_alert_entity.rb b/app/serializers/prometheus_alert_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..413be5119034ac1407391d7a13dcd333d40aa2a6
--- /dev/null
+++ b/app/serializers/prometheus_alert_entity.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class PrometheusAlertEntity < Grape::Entity
+  include RequestAwareEntity
+
+  expose :id
+  expose :title
+  expose :query
+  expose :threshold
+
+  expose :operator do |prometheus_alert|
+    prometheus_alert.computed_operator
+  end
+
+  expose :alert_path do |prometheus_alert|
+    project_prometheus_alert_path(prometheus_alert.project, prometheus_alert.prometheus_metric_id, environment_id: prometheus_alert.environment.id, format: :json)
+  end
+
+  private
+
+  alias_method :prometheus_alert, :object
+
+  def can_read_prometheus_alerts?
+    can?(request.current_user, :read_prometheus_alerts, prometheus_alert.project)
+  end
+end
diff --git a/app/serializers/prometheus_alert_serializer.rb b/app/serializers/prometheus_alert_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4dafb7216db616249f9f2fc7b527168b04c56ba9
--- /dev/null
+++ b/app/serializers/prometheus_alert_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class PrometheusAlertSerializer < BaseSerializer
+  entity PrometheusAlertEntity
+end
diff --git a/app/services/metrics/dashboard/update_dashboard_service.rb b/app/services/metrics/dashboard/update_dashboard_service.rb
index 65e6e195f79bbc2617167ef0d60c92ff880dd76d..25a727ad44c6a93def090446bde320e4a1e341b9 100644
--- a/app/services/metrics/dashboard/update_dashboard_service.rb
+++ b/app/services/metrics/dashboard/update_dashboard_service.rb
@@ -12,7 +12,8 @@ module Metrics
       steps :check_push_authorized,
         :check_branch_name,
         :check_file_type,
-        :update_file
+        :update_file,
+        :create_merge_request
 
       def execute
         execute_steps
@@ -49,6 +50,23 @@ module Metrics
         end
       end
 
+      def create_merge_request(result)
+        return success(result) if project.default_branch == branch
+
+        merge_request_params = {
+          source_branch: branch,
+          target_branch: project.default_branch,
+          title: params[:commit_message]
+        }
+        merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
+
+        if merge_request.persisted?
+          success(result.merge(merge_request: Gitlab::UrlBuilder.build(merge_request)))
+        else
+          error(merge_request.errors.full_messages.join(','), :bad_request)
+        end
+      end
+
       def push_authorized?
         Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch)
       end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 4a0d85038eec1935c56ccc9f939f6f9ffacd8ced..80bc44859887c7c548891163248063850a45883e 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -17,57 +17,72 @@ module Notes
       # We execute commands (extracted from `params[:note]`) on the noteable
       # **before** we save the note because if the note consists of commands
       # only, there is no need be create a note!
-      quick_actions_service = QuickActionsService.new(project, current_user)
 
-      if quick_actions_service.supported?(note)
-        content, update_params, message = quick_actions_service.execute(note, quick_action_options)
+      execute_quick_actions(note) do |only_commands|
+        note.run_after_commit do
+          # Finish the harder work in the background
+          NewNoteWorker.perform_async(note.id)
+        end
 
-        only_commands = content.empty?
+        note_saved = note.with_transaction_returning_status do
+          !only_commands && note.save
+        end
 
-        note.note = content
+        when_saved(note) if note_saved
       end
 
-      note.run_after_commit do
-        # Finish the harder work in the background
-        NewNoteWorker.perform_async(note.id)
-      end
+      note
+    end
 
-      note_saved = note.with_transaction_returning_status do
-        !only_commands && note.save
-      end
+    private
 
-      if note_saved
-        if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
-          note.discussion.convert_to_discussion!(save: true)
-        end
+    def execute_quick_actions(note)
+      return yield(false) unless quick_actions_service.supported?(note)
 
-        todo_service.new_note(note, current_user)
-        clear_noteable_diffs_cache(note)
-        Suggestions::CreateService.new(note).execute
-        increment_usage_counter(note)
+      content, update_params, message = quick_actions_service.execute(note, quick_action_options)
+      only_commands = content.empty?
+      note.note = content
 
-        if Feature.enabled?(:notes_create_service_tracking, project)
-          Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
-        end
-      end
+      yield(only_commands)
 
-      if quick_actions_service.commands_executed_count.to_i > 0
-        if update_params.present?
-          quick_actions_service.apply_updates(update_params, note)
-          note.commands_changes = update_params
-        end
+      do_commands(note, update_params, message, only_commands)
+    end
 
-        # We must add the error after we call #save because errors are reset
-        # when #save is called
-        if only_commands
-          note.errors.add(:commands_only, message.presence || _('Failed to apply commands.'))
-        end
+    def quick_actions_service
+      @quick_actions_service ||= QuickActionsService.new(project, current_user)
+    end
+
+    def when_saved(note)
+      if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
+        note.discussion.convert_to_discussion!(save: true)
       end
 
-      note
+      todo_service.new_note(note, current_user)
+      clear_noteable_diffs_cache(note)
+      Suggestions::CreateService.new(note).execute
+      increment_usage_counter(note)
+
+      if Feature.enabled?(:notes_create_service_tracking, project)
+        Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
+      end
     end
 
-    private
+    def do_commands(note, update_params, message, only_commands)
+      return if quick_actions_service.commands_executed_count.to_i.zero?
+
+      if update_params.present?
+        quick_actions_service.apply_updates(update_params, note)
+        note.commands_changes = update_params
+      end
+
+      # We must add the error after we call #save because errors are reset
+      # when #save is called
+      if only_commands
+        note.errors.add(:commands_only, message.presence || _('Failed to apply commands.'))
+        # Allow consumers to detect problems applying commands
+        note.errors.add(:commands, _('Failed to apply commands.')) unless message.present?
+      end
+    end
 
     # EE::Notes::CreateService would override this method
     def quick_action_options
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 3070e7b0e53bf31c921ae0f72bb889044f9e037e..ed08f693901ad6c7c99bb2204708c0397192bfe7 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -55,6 +55,8 @@ module Notes
       # We must add the error after we call #save because errors are reset
       # when #save is called
       note.errors.add(:commands_only, message.presence || _('Commands did not apply'))
+      # Allow consumers to detect problems applying commands
+      note.errors.add(:commands, _('Commands did not apply')) unless message.present?
 
       Notes::DestroyService.new(project, current_user).execute(note)
     end
diff --git a/app/services/projects/prometheus/alerts/create_events_service.rb b/app/services/projects/prometheus/alerts/create_events_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a29240947ffa08a2930a72b507a85c17588c0505
--- /dev/null
+++ b/app/services/projects/prometheus/alerts/create_events_service.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Projects
+  module Prometheus
+    module Alerts
+      # Persists a series of Prometheus alert events as list of PrometheusAlertEvent.
+      class CreateEventsService < BaseService
+        def execute
+          create_events_from(alerts)
+        end
+
+        private
+
+        def create_events_from(alerts)
+          Array.wrap(alerts).map { |alert| create_event(alert) }.compact
+        end
+
+        def create_event(payload)
+          parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: payload)
+
+          return unless parsed_alert.valid?
+
+          if parsed_alert.gitlab_managed?
+            create_managed_prometheus_alert_event(parsed_alert)
+          else
+            create_self_managed_prometheus_alert_event(parsed_alert)
+          end
+        end
+
+        def alerts
+          params['alerts']
+        end
+
+        def find_alert(metric)
+          Projects::Prometheus::AlertsFinder
+            .new(project: project, metric: metric)
+            .execute
+            .first
+        end
+
+        def create_managed_prometheus_alert_event(parsed_alert)
+          alert = find_alert(parsed_alert.metric_id)
+          payload_key = PrometheusAlertEvent.payload_key_for(parsed_alert.metric_id, parsed_alert.starts_at_raw)
+
+          event = PrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, alert, payload_key)
+
+          set_status(parsed_alert, event)
+        end
+
+        def create_self_managed_prometheus_alert_event(parsed_alert)
+          payload_key = SelfManagedPrometheusAlertEvent.payload_key_for(parsed_alert.starts_at_raw, parsed_alert.title, parsed_alert.full_query)
+
+          event = SelfManagedPrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, payload_key) do |event|
+            event.environment      = parsed_alert.environment
+            event.title            = parsed_alert.title
+            event.query_expression = parsed_alert.full_query
+          end
+
+          set_status(parsed_alert, event)
+        end
+
+        def set_status(parsed_alert, event)
+          persisted = case parsed_alert.status
+                      when 'firing'
+                        event.fire(parsed_alert.starts_at)
+                      when 'resolved'
+                        event.resolve(parsed_alert.ends_at)
+                      end
+
+          event if persisted
+        end
+      end
+    end
+  end
+end
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index 2998208f50b0acdc8355630e77aee36e10dddd89..9178b929656b69bed48adaa02c1768a69f01cc63 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -38,19 +38,16 @@ module Snippets
     private
 
     def save_and_commit(snippet)
-      result = snippet.with_transaction_returning_status do
-        (snippet.save && snippet.store_mentions!).tap do |saved|
-          break false unless saved
-
-          if Feature.enabled?(:version_snippets, current_user)
-            create_repository_for(snippet)
-          end
-        end
+      snippet_saved = snippet.with_transaction_returning_status do
+        snippet.save && snippet.store_mentions!
       end
 
-      create_commit(snippet) if result && snippet.repository_exists?
+      if snippet_saved && Feature.enabled?(:version_snippets, current_user)
+        create_repository_for(snippet)
+        create_commit(snippet)
+      end
 
-      result
+      snippet_saved
     rescue => e # Rescuing all because we can receive Creation exceptions, GRPC exceptions, Git exceptions, ...
       snippet.errors.add(:base, e.message)
 
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index dd0eeaa93590cffd220f09520e2b5a4e40768f7c..19158e7173c8617032ca5b078ad9746c799b2432 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -528,6 +528,13 @@
   :resource_boundary: :unknown
   :weight: 2
   :idempotent: 
+- :name: incident_management:incident_management_process_prometheus_alert
+  :feature_category: :incident_management
+  :has_external_dependencies: 
+  :urgency: :low
+  :resource_boundary: :cpu
+  :weight: 2
+  :idempotent: 
 - :name: jira_importer:jira_import_advance_stage
   :feature_category: :importers
   :has_external_dependencies: 
diff --git a/app/workers/incident_management/process_prometheus_alert_worker.rb b/app/workers/incident_management/process_prometheus_alert_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..768e049c60e2df487f92772ab46186a07b7a0489
--- /dev/null
+++ b/app/workers/incident_management/process_prometheus_alert_worker.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+  class ProcessPrometheusAlertWorker # rubocop:disable Scalability/IdempotentWorker
+    include ApplicationWorker
+
+    queue_namespace :incident_management
+    feature_category :incident_management
+    worker_resource_boundary :cpu
+
+    def perform(project_id, alert_hash)
+      project = find_project(project_id)
+      return unless project
+
+      parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: alert_hash)
+      event = find_prometheus_alert_event(parsed_alert)
+
+      if event&.resolved?
+        issue = event.related_issues.order_created_at_desc.detect(&:opened?)
+
+        close_issue(project, issue)
+      else
+        issue = create_issue(project, alert_hash)
+
+        relate_issue_to_event(event, issue)
+      end
+    end
+
+    private
+
+    def find_project(project_id)
+      Project.find_by_id(project_id)
+    end
+
+    def find_prometheus_alert_event(alert)
+      if alert.gitlab_managed?
+        find_gitlab_managed_event(alert)
+      else
+        find_self_managed_event(alert)
+      end
+    end
+
+    def find_gitlab_managed_event(alert)
+      payload_key = payload_key_for_alert(alert)
+
+      PrometheusAlertEvent.find_by_payload_key(payload_key)
+    end
+
+    def find_self_managed_event(alert)
+      payload_key = payload_key_for_alert(alert)
+
+      SelfManagedPrometheusAlertEvent.find_by_payload_key(payload_key)
+    end
+
+    def payload_key_for_alert(alert)
+      if alert.gitlab_managed?
+        PrometheusAlertEvent.payload_key_for(alert.metric_id, alert.starts_at_raw)
+      else
+        SelfManagedPrometheusAlertEvent.payload_key_for(alert.starts_at_raw, alert.title, alert.full_query)
+      end
+    end
+
+    def create_issue(project, alert)
+      IncidentManagement::CreateIssueService
+        .new(project, alert)
+        .execute
+        .dig(:issue)
+    end
+
+    def close_issue(project, issue)
+      return if issue.blank? || issue.closed?
+
+      processed_issue = Issues::CloseService
+                      .new(project, User.alert_bot)
+                      .execute(issue, system_note: false)
+
+      SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if processed_issue.reset.closed?
+    end
+
+    def relate_issue_to_event(event, issue)
+      return unless event && issue
+
+      if event.related_issues.exclude?(issue)
+        event.related_issues << issue
+      end
+    end
+  end
+end
diff --git a/bin/secpick b/bin/secpick
index fd3de2756ec1ef8247d2dd3247cf395cb295dd4c..603c6d2c05d1ba61bbf17a1b346c8da81dc72a04 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -159,7 +159,11 @@ module Secpick
         options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
         options[:remote] ||= DEFAULT_REMOTE
 
-        abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.value?(nil)
+        nil_options = options.select {|_, v| v.nil? }
+        unless nil_options.empty?
+          abort("Missing: #{nil_options.keys.join(', ')}. Use #{$0} --help to see the list of options available".red)
+        end
+
         abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
       end
     end
diff --git a/changelogs/unreleased/20444-limit-full-path-search.yml b/changelogs/unreleased/20444-limit-full-path-search.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e4a4444118dae807b6d216aaf7c6331e52fbac2d
--- /dev/null
+++ b/changelogs/unreleased/20444-limit-full-path-search.yml
@@ -0,0 +1,5 @@
+---
+title: Only enable searching of projects by full path / name on certain dropdowns
+merge_request: 21910
+author:
+type: changed
diff --git a/changelogs/unreleased/208174-create-merge-request.yml b/changelogs/unreleased/208174-create-merge-request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a2007b46dbdcec2868895734ede0b80256f34472
--- /dev/null
+++ b/changelogs/unreleased/208174-create-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Start merge request for custom dashboard if new branch is provided
+merge_request: 27189
+author:
+type: added
diff --git a/changelogs/unreleased/210596-fix_smartcard_config_initializer.yml b/changelogs/unreleased/210596-fix_smartcard_config_initializer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5c3d7654955003e261355af6ea029d696f7c23a
--- /dev/null
+++ b/changelogs/unreleased/210596-fix_smartcard_config_initializer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix smartcard config initialization
+merge_request: 27560
+author:
+type: fixed
diff --git a/changelogs/unreleased/35627-api-response-for-adding-a-note-returns-http-400-for-command-only-no.yml b/changelogs/unreleased/35627-api-response-for-adding-a-note-returns-http-400-for-command-only-no.yml
new file mode 100644
index 0000000000000000000000000000000000000000..359832986da4c78981ad4058929cb23f946800ff
--- /dev/null
+++ b/changelogs/unreleased/35627-api-response-for-adding-a-note-returns-http-400-for-command-only-no.yml
@@ -0,0 +1,5 @@
+---
+title: Return 202 for command only notes in REST API
+merge_request: 19624
+author:
+type: fixed
diff --git a/changelogs/unreleased/36628-create-a-rake-task-to-cleanup-unused-lfs-files.yml b/changelogs/unreleased/36628-create-a-rake-task-to-cleanup-unused-lfs-files.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c03bebfe40d56ab3e9ba6e0ef03e119343d6a603
--- /dev/null
+++ b/changelogs/unreleased/36628-create-a-rake-task-to-cleanup-unused-lfs-files.yml
@@ -0,0 +1,5 @@
+---
+title: Create a rake task to cleanup unused LFS files
+merge_request: 21747
+author:
+type: added
diff --git a/changelogs/unreleased/fj-reuse-default-snippet-name.yml b/changelogs/unreleased/fj-reuse-default-snippet-name.yml
new file mode 100644
index 0000000000000000000000000000000000000000..554ad0809f9ad4aa5904692f6f0f3e90913b79c5
--- /dev/null
+++ b/changelogs/unreleased/fj-reuse-default-snippet-name.yml
@@ -0,0 +1,5 @@
+---
+title: Reuse default generated snippet file name in repository
+merge_request: 27673
+author:
+type: fixed
diff --git a/changelogs/unreleased/ph-encodeUriComponentNewDirectoryPath.yml b/changelogs/unreleased/ph-encodeUriComponentNewDirectoryPath.yml
new file mode 100644
index 0000000000000000000000000000000000000000..27384e54e914e95e47671ad7a196062a8be61344
--- /dev/null
+++ b/changelogs/unreleased/ph-encodeUriComponentNewDirectoryPath.yml
@@ -0,0 +1,5 @@
+---
+title: Fix new file not being created in non-ascii character folders
+merge_request: 26165
+author:
+type: fixed
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 81de0ac6818548cf80af4a4de894d811e75115ed..79bfcfd79e137821037d88fd8452dd74f94bbf01 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -74,15 +74,6 @@ if Settings.ldap['enabled'] || Rails.env.test?
   end
 end
 
-Gitlab.ee do
-  Settings['smartcard'] ||= Settingslogic.new({})
-  Settings.smartcard['enabled'] = false if Settings.smartcard['enabled'].nil?
-  Settings.smartcard['client_certificate_required_host'] = Settings.gitlab['host'] if Settings.smartcard['client_certificate_required_host'].nil?
-  Settings.smartcard['client_certificate_required_port'] = 3444 if Settings.smartcard['client_certificate_required_port'].nil?
-  Settings.smartcard['required_for_git_access'] = false if Settings.smartcard['required_for_git_access'].nil?
-  Settings.smartcard['san_extensions'] = false if Settings.smartcard['san_extensions'].nil?
-end
-
 Settings['omniauth'] ||= Settingslogic.new({})
 Settings.omniauth['enabled'] = true if Settings.omniauth['enabled'].nil?
 Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
@@ -671,6 +662,18 @@ Gitlab.ee do
   end
 end
 
+#
+# Smartcard
+#
+Gitlab.ee do
+  Settings['smartcard'] ||= Settingslogic.new({})
+  Settings.smartcard['enabled'] = false if Settings.smartcard['enabled'].nil?
+  Settings.smartcard['client_certificate_required_host'] = Settings.gitlab.host if Settings.smartcard['client_certificate_required_host'].nil?
+  Settings.smartcard['client_certificate_required_port'] = 3444 if Settings.smartcard['client_certificate_required_port'].nil?
+  Settings.smartcard['required_for_git_access'] = false if Settings.smartcard['required_for_git_access'].nil?
+  Settings.smartcard['san_extensions'] = false if Settings.smartcard['san_extensions'].nil?
+end
+
 #
 # Extra customization
 #
diff --git a/config/routes/project.rb b/config/routes/project.rb
index c37b5528f71a74e5ca919b6202def61f97cd13fa..b86fd48e22263652ee59c2475a7734f8e8acd44a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -339,6 +339,13 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
       end
 
       namespace :prometheus do
+        resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do
+          post :notify, on: :collection
+          member do
+            get :metrics_dashboard
+          end
+        end
+
         resources :metrics, constraints: { id: %r{[^\/]+} }, only: [:index, :new, :create, :edit, :update, :destroy] do
           get :active_common, on: :collection
         end
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 2d78174d669cac839ae4b1edcbda75c249cbb7da..ea11a2039212a8eceb46bfdd572122110417e738 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -5046,6 +5046,7 @@ type Mutation {
   will be destroyed during the update, and no Note will be returned
   """
   updateNote(input: UpdateNoteInput!): UpdateNotePayload
+  updateRequirement(input: UpdateRequirementInput!): UpdateRequirementPayload
   updateSnippet(input: UpdateSnippetInput!): UpdateSnippetPayload
 }
 
@@ -8498,6 +8499,56 @@ type UpdateNotePayload {
   note: Note
 }
 
+"""
+Autogenerated input type of UpdateRequirement
+"""
+input UpdateRequirementInput {
+  """
+  A unique identifier for the client performing the mutation.
+  """
+  clientMutationId: String
+
+  """
+  The iid of the requirement to update
+  """
+  iid: String!
+
+  """
+  The project full path the requirement is associated with
+  """
+  projectPath: ID!
+
+  """
+  State of the requirement
+  """
+  state: RequirementState
+
+  """
+  Title of the requirement
+  """
+  title: String
+}
+
+"""
+Autogenerated return type of UpdateRequirement
+"""
+type UpdateRequirementPayload {
+  """
+  A unique identifier for the client performing the mutation.
+  """
+  clientMutationId: String
+
+  """
+  Reasons why the mutation failed.
+  """
+  errors: [String!]!
+
+  """
+  The requirement after mutation
+  """
+  requirement: Requirement
+}
+
 """
 Autogenerated input type of UpdateSnippet
 """
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 2be4573cafea149b3aa29ca6ddec024c183751c2..9e3460c0b03fd7dcf2b329dba0022c7e8731c44b 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -15331,6 +15331,33 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "updateRequirement",
+              "description": null,
+              "args": [
+                {
+                  "name": "input",
+                  "description": null,
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "INPUT_OBJECT",
+                      "name": "UpdateRequirementInput",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "UpdateRequirementPayload",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "updateSnippet",
               "description": null,
@@ -25662,6 +25689,142 @@
           "enumValues": null,
           "possibleTypes": null
         },
+        {
+          "kind": "INPUT_OBJECT",
+          "name": "UpdateRequirementInput",
+          "description": "Autogenerated input type of UpdateRequirement",
+          "fields": null,
+          "inputFields": [
+            {
+              "name": "title",
+              "description": "Title of the requirement",
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "state",
+              "description": "State of the requirement",
+              "type": {
+                "kind": "ENUM",
+                "name": "RequirementState",
+                "ofType": null
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "iid",
+              "description": "The iid of the requirement to update",
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "String",
+                  "ofType": null
+                }
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "projectPath",
+              "description": "The project full path the requirement is associated with",
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "ID",
+                  "ofType": null
+                }
+              },
+              "defaultValue": null
+            },
+            {
+              "name": "clientMutationId",
+              "description": "A unique identifier for the client performing the mutation.",
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "defaultValue": null
+            }
+          ],
+          "interfaces": null,
+          "enumValues": null,
+          "possibleTypes": null
+        },
+        {
+          "kind": "OBJECT",
+          "name": "UpdateRequirementPayload",
+          "description": "Autogenerated return type of UpdateRequirement",
+          "fields": [
+            {
+              "name": "clientMutationId",
+              "description": "A unique identifier for the client performing the mutation.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "errors",
+              "description": "Reasons why the mutation failed.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "LIST",
+                  "name": null,
+                  "ofType": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "SCALAR",
+                      "name": "String",
+                      "ofType": null
+                    }
+                  }
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "requirement",
+              "description": "The requirement after mutation",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "Requirement",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
         {
           "kind": "INPUT_OBJECT",
           "name": "UpdateSnippetInput",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6a79a407dacdbc1bfe6ac57d40ad827ddd9b1da1..dfbd08be8989b232a79502da44106fc698a9166e 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1372,6 +1372,16 @@ Autogenerated return type of UpdateNote
 | `errors` | String! => Array | Reasons why the mutation failed. |
 | `note` | Note | The note after mutation |
 
+## UpdateRequirementPayload
+
+Autogenerated return type of UpdateRequirement
+
+| Name  | Type  | Description |
+| ---   |  ---- | ----------  |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `requirement` | Requirement | The requirement after mutation |
+
 ## UpdateSnippetPayload
 
 Autogenerated return type of UpdateSnippet
diff --git a/doc/api/projects.md b/doc/api/projects.md
index ae9f7de427ddc5bc0cd5756a0ad44bc5f6902325..04775b0339d97533ac7b20f100dbcd744685374d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -46,6 +46,7 @@ GET /projects
 | `order_by`                    | string  | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
 | `sort`                        | string  | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
 | `search`                      | string  | no | Return list of projects matching the search criteria |
+| `search_namespaces`           | boolean | no | Include ancestor namespaces when matching search criteria. Default is `false` |
 | `simple`                      | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. |
 | `owned`                       | boolean | no | Limit by projects explicitly owned by the current user |
 | `membership`                  | boolean | no | Limit by projects that the current user is a member of |
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 460bb6d25dfd44f82d0bebb1e4cd5e826cd5bacc..030cced8c2b7a08cb8b196fc9006f50b8f0ac455 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -77,6 +77,7 @@ request is as follows:
    1. The merge request author resolves only the threads they have fully addressed.
       If there's an open reply or thread, a suggestion, a question, or anything else,
       the thread should be left to be resolved by the reviewer.
+   1. It should not be assumed that all feedback requires their recommended changes to be incorporated into the MR before it is merged. It is a judgment call by the MR author and the reviewer as to if this is required, or if a follow-up issue should be created to address the feedback in the future after the MR in question is merged.
 1. If your MR touches code that executes shell commands, reads or opens files, or
    handles paths to files on disk, make sure it adheres to the
    [shell command guidelines](../shell_commands.md)
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index bbae713676d620f764d2c64bb48f164b56a40425..bcce6a8a096f87ed83f457d70e9df2bc9d7409f6 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -1,5 +1,61 @@
 # Cleanup
 
+## Remove unreferenced LFS files from filesystem
+
+DANGER: **Danger:**
+Do not run this within 12 hours of a GitLab upgrade. This is to ensure that all background migrations have finished, which otherwise may lead to data loss.
+
+When you remove LFS files from a repository's history, they become orphaned and continue to consume disk space. With this rake task, you can remove invalid references from the database, which
+will allow garbage collection of LFS files.
+
+For example:
+
+```shell
+# omnibus-gitlab
+sudo gitlab-rake gitlab:cleanup:orphan_lfs_file_references PROJECT_PATH="gitlab-org/gitlab-foss"
+
+# installation from source
+bundle exec rake gitlab:cleanup:orphan_lfs_file_references RAILS_ENV=production PROJECT_PATH="gitlab-org/gitlab-foss"
+```
+
+You can also specify the project with `PROJECT_ID` instead of `PROJECT_PATH`.
+
+For example:
+
+```shell
+$ sudo gitlab-rake gitlab:cleanup:orphan_lfs_file_references PROJECT_PATH="gitlab-org/gitlab-foss"
+I, [2019-12-13T16:35:31.764962 #82356]  INFO -- :  Looking for orphan LFS files for project GitLab Org / GitLab Foss
+I, [2019-12-13T16:35:31.923659 #82356]  INFO -- :  Removed invalid references: 12
+```
+
+By default, this task does not delete anything but shows how many file references it can
+delete. Run the command with `DRY_RUN=false` if you actually want to
+delete the references. You can also use `LIMIT={number}` parameter to limit the number of deleted references.
+
+Note that this rake task only removes the references to LFS files. Unreferenced LFS files will be garbage-collected
+later (once a day). If you need to garbage collect them immediately, run
+`rake gitlab:cleanup:orphan_lfs_files` described below.
+
+## Remove unreferenced LFS files
+
+Unreferenced LFS files are removed on a daily basis but you can remove them immediately if
+you need to. For example:
+
+```shell
+# omnibus-gitlab
+sudo gitlab-rake gitlab:cleanup:orphan_lfs_files
+
+# installation from source
+bundle exec rake gitlab:cleanup:orphan_lfs_files
+```
+
+Example output:
+
+```shell
+$ sudo gitlab-rake gitlab:cleanup:orphan_lfs_files
+I, [2020-01-08T20:51:17.148765 #43765]  INFO -- : Removed unreferenced LFS files: 12
+```
+
 ## Remove garbage from filesystem
 
 Clean up local project upload files if they don't exist in GitLab database. The
diff --git a/doc/topics/airgap/index.md b/doc/topics/airgap/index.md
index abeb3a4b1d30f8ce41726f51955d6e229815c8a0..fc92dd5519d9209816db7b2cc266bbcc5d5108fd 100644
--- a/doc/topics/airgap/index.md
+++ b/doc/topics/airgap/index.md
@@ -3,6 +3,11 @@
 Computers in an air-gapped network are isolated from the public internet as a security measure.
 This page lists all the information available for running GitLab in an air-gapped environment.
 
+## Quick start
+
+If you plan to deploy a GitLab instance on a physically-isolated and offline network, see the
+[quick start guide](quick_start_guide.md) for configuration steps.
+
 ## Features
 
 Follow these best practices to use GitLab's features in an offline environment:
diff --git a/doc/topics/airgap/quick_start_guide.md b/doc/topics/airgap/quick_start_guide.md
new file mode 100644
index 0000000000000000000000000000000000000000..c4c02af3c85695d21390d4fafe34610b1ae45fb2
--- /dev/null
+++ b/doc/topics/airgap/quick_start_guide.md
@@ -0,0 +1,157 @@
+# Getting started with an air-gapped GitLab Installation
+
+This is a step-by-step guide that helps you install, configure, and use a self-managed GitLab
+instance entirely offline.
+
+## Installation
+
+NOTE: **Note:**
+This guide assumes the server is Ubuntu 18.04. Instructions for other servers may vary.
+
+NOTE: **Note:**
+This guide assumes the server host resolves as `my-host`, which you should replace with your
+server's name.
+
+Follow the installation instructions [as outlined in the omnibus install
+guide](https://about.gitlab.com/install/#ubuntu), but make sure to specify an `http`
+URL for the `EXTERNAL_URL` installation step. Once installed, we will manually
+configure the SSL ourselves.
+
+It is strongly recommended to setup a domain for IP resolution rather than bind
+to the server's IP address. This better ensures a stable target for our certs' CN
+and will make long-term resolution simpler.
+
+```shell
+sudo EXTERNAL_URL="http://my-host.internal" install gitlab-ee
+```
+
+## Enabling SSL
+
+Follow these steps to enable SSL for your fresh instance. Note that these steps reflect those for
+[manually configuring SSL in Omnibus's NGINX configuration](https://docs.gitlab.com/omnibus/settings/nginx.html#manually-configuring-https):
+
+1. Make the following changes to `/etc/gitlab/gitlab.rb`:
+
+   ```ruby
+   # Update external_url from "http" to "https"
+   external_url "https://example.gitlab.com"
+
+   # Set Let's Encrypt to false
+   letsencrypt['enable'] = false
+   ```
+
+1. Create the following directories with the appropriate permissions for generating self-signed
+   certificates:
+
+   ```shell
+   sudo mkdir -p /etc/gitlab/ssl
+   sudo chmod 755 /etc/gitlab/ssl
+   sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/gitlab/ssl/my-host.internal.key -out /etc/gitlab/ssl/my-host.internal.crt
+   ```
+
+1. Reconfigure your instance to apply the changes:
+
+   ```shell
+   sudo gitlab-ctl reconfigure
+   ```
+
+## Enabling the GitLab Container Registry
+
+Follow these steps to enable the container registry. Note that these steps reflect those for
+[configuring the container registry under an existing domain](../../administration/packages/container_registry.md#configure-container-registry-under-an-existing-gitlab-domain):
+
+1. Make the following changes to `/etc/gitlab/gitlab.rb`:
+
+   ```ruby
+   # Change external_registry_url to match external_url, but append the port 4567
+   external_url "https://example.gitlab.com"
+   registry_external_url "https://example.gitlab.com:4567"
+   ```
+
+1. Reconfigure your instance to apply the changes:
+
+   ```shell
+   sudo gitlab-ctl reconfigure
+   ```
+
+## Allow the docker daemon to trust the registry and GitLab Runner
+
+Provide your Docker daemon with your certs by
+[following the steps for using trusted certificates with your registry](../../administration/packages/container_registry.md#using-self-signed-certificates-with-container-registry):
+
+```shell
+sudo mkdir -p /etc/docker/certs.d/my-host.internal:5000
+
+sudo cp /etc/gitlab/ssl/my-host.internal.crt /etc/docker/certs.d/my-host.internal:5000/ca.crt
+```
+
+Provide your GitLab Runner (to be installed next) with your certs by
+[following the steps for using trusted certificates with your Runner](https://docs.gitlab.com/runner/install/docker.html#installing-trusted-ssl-server-certificates):
+
+```shell
+sudo mkdir -p /etc/gitlab-runner/certs
+
+sudo cp /etc/gitlab/ssl/my-host.internal.crt /etc/gitlab-runner/certs/ca.crt
+```
+
+## Enabling GitLab Runner
+
+[Following a similar process to the steps for installing our GitLab Runner as a
+Docker service](https://docs.gitlab.com/runner/install/docker.html#docker-image-installation), we must first register our Runner:
+
+```shell
+$ sudo docker run --rm -it -v /etc/gitlab-runner:/etc/gitlab-runner gitlab/gitlab-runner register
+Updating CA certificates...
+Runtime platform                                    arch=amd64 os=linux pid=7 revision=1b659122 version=12.8.0
+Running in system-mode.
+
+Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
+https://my-host.internal
+Please enter the gitlab-ci token for this runner:
+XXXXXXXXXXX
+Please enter the gitlab-ci description for this runner:
+[eb18856e13c0]:
+Please enter the gitlab-ci tags for this runner (comma separated):
+
+Registering runner... succeeded                     runner=FSMwkvLZ
+Please enter the executor: custom, docker, virtualbox, kubernetes, docker+machine, docker-ssh+machine, docker-ssh, parallels, shell, ssh:
+docker
+Please enter the default Docker image (e.g. ruby:2.6):
+ruby:2.6
+Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
+```
+
+Now we must add some additional configuration to our runner:
+
+Make the following changes to `/etc/gitlab-runner/config.toml`:
+
+- Add docker socket to volumes `volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]`
+- Add `pull_policy = "if-not-present"` to the executor configuration
+
+Now we can start our Runner:
+
+```shell
+sudo docker run -d --restart always --name gitlab-runner -v /etc/gitlab-runner:/etc/gitlab-runner -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:latest
+90646b6587127906a4ee3f2e51454c6e1f10f26fc7a0b03d9928d8d0d5897b64
+```
+
+### Authenticating the registry against the host OS
+
+As noted in [Docker's registry authentication documentation](https://docs.docker.com/registry/insecure/#docker-still-complains-about-the-certificate-when-using-authentication),
+certain versions of Docker require trusting the certificate chain at the OS level.
+
+In the case of Ubuntu, this involves using `update-ca-certificates`:
+
+```shell
+sudo cp /etc/docker/certs.d/my-host.internal\:5000/ca.crt /usr/local/share/ca-certificates/my-host.internal.crt
+
+sudo update-ca-certificates
+```
+
+If all goes well, this is what you should see:
+
+```
+1 added, 0 removed; done.
+Running hooks in /etc/ca-certificates/update.d...
+done.
+```
diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md
index 2679dc85f42c2e44e7d36f6a4660a185087ac22b..dd2bb75b1df7f6ddbb83e8d6d9d163994eb04b91 100644
--- a/doc/user/compliance/license_compliance/index.md
+++ b/doc/user/compliance/license_compliance/index.md
@@ -53,7 +53,7 @@ The following languages and package managers are supported.
 | Go         | [Godep](https://github.com/tools/godep), go get ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), gvt ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), glide ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), dep ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), trash ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) and govendor ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), [go mod](https://github.com/golang/go/wiki/Modules) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))   |[License Finder](https://github.com/pivotal/LicenseFinder)|
 | Java       | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
 | .NET      | [Nuget](https://www.nuget.org/) (.NET Framework is supported via the [mono project](https://www.mono-project.com/). Windows specific dependencies are not supported at this time.)  |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Python     | [pip](https://pip.pypa.io/en/stable/)                             |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Python     | [pip](https://pip.pypa.io/en/stable/) (Python is supported through [requirements.txt](https://pip.readthedocs.io/en/1.1/requirements.html) and [Pipfile.lock](https://github.com/pypa/pipfile#pipfilelock).) |[License Finder](https://github.com/pivotal/LicenseFinder)|
 | Ruby       | [gem](https://rubygems.org/)                                      |[License Finder](https://github.com/pivotal/LicenseFinder)|
 | Erlang     | [rebar](https://www.rebar3.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
 | Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) , [CocoaPods v0.39 and below](https://cocoapods.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))  |[License Finder](https://github.com/pivotal/LicenseFinder)|
diff --git a/lib/api/entities/note.rb b/lib/api/entities/note.rb
index dcfb9a6d670dde6daf7eaae86815bbee79473e71..4d140454ff0748021575f2b0eed8d0a47fa7bbcc 100644
--- a/lib/api/entities/note.rb
+++ b/lib/api/entities/note.rb
@@ -25,6 +25,14 @@ module API
 
       # Avoid N+1 queries as much as possible
       expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) }
+
+      expose(:commands_changes) { |note| note.commands_changes || {} }
+    end
+
+    # To be returned if the note was command-only
+    class NoteCommands < Grape::Entity
+      expose(:commands_changes) { |note| note.commands_changes || {} }
+      expose(:summary) { |note| note.errors[:commands_only] }
     end
   end
 end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index c3b5654e217320cf5b8ca077e0cc9bdd026bf7a7..47784dc771ec3ff54c80f1c1e28951a0d67e191b 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -505,6 +505,7 @@ module API
       finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
       finder_params[:archived] = archived_param unless params[:archived].nil?
       finder_params[:search] = params[:search] if params[:search]
+      finder_params[:search_namespaces] = true if params[:search_namespaces].present?
       finder_params[:user] = params.delete(:user) if params[:user]
       finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
       finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index bed0345a608664b412515d83546d323332f3b316..c85a38fc18b8973716fdc6a486ad89db5c2ca303 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -113,6 +113,7 @@ module API
       end
 
       def create_note(noteable, opts)
+        whitelist_query_limiting
         authorize!(:create_note, noteable)
 
         parent = noteable_parent(noteable)
@@ -139,6 +140,10 @@ module API
 
         present discussion, with: Entities::Discussion
       end
+
+      def whitelist_query_limiting
+        Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/211538')
+      end
     end
   end
 end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 7237fa24bab95257e55777ffcdac61b1b24a21df..3eafc1ead77bd1ca2c9e27b8661c8edc91ac05b2 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -82,9 +82,13 @@ module API
 
           note = create_note(noteable, opts)
 
-          if note.valid?
+          if note.errors.keys == [:commands_only]
+            status 202
+            present note, with: Entities::NoteCommands
+          elsif note.valid?
             present note, with: Entities.const_get(note.class.name, false)
           else
+            note.errors.delete(:commands_only) if note.errors.has_key?(:commands)
             bad_request!("Note #{note.errors.messages}")
           end
         end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 3717e25d997b9ca8ac2d55d6fbe819df097c36b3..a33418c3336b2c3bc1be9738f28f84b839eef8db 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -63,6 +63,7 @@ module API
         optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
                               desc: 'Limit by visibility'
         optional :search, type: String, desc: 'Return list of projects matching the search criteria'
+        optional :search_namespaces, type: Boolean, desc: "Include ancestor namespaces when matching search criteria"
         optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
         optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
         optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 6492ccc286a381ce0abc91a18febcf7f8ad718e2..bf4438fb5182fa51177e188cf5d6238f7f0e07d2 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -14,8 +14,6 @@ module Gitlab
     REPORTER   = 20
     DEVELOPER  = 30
     MAINTAINER = 40
-    # @deprecated
-    MASTER     = MAINTAINER
     OWNER      = 50
 
     # Branch protection settings
diff --git a/lib/gitlab/cleanup/orphan_lfs_file_references.rb b/lib/gitlab/cleanup/orphan_lfs_file_references.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5789fe4f92d912380d41f22e322e02fd3af8ba86
--- /dev/null
+++ b/lib/gitlab/cleanup/orphan_lfs_file_references.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Cleanup
+    class OrphanLfsFileReferences
+      include Gitlab::Utils::StrongMemoize
+
+      attr_reader :project, :dry_run, :logger, :limit
+
+      DEFAULT_REMOVAL_LIMIT = 1000
+
+      def initialize(project, dry_run: true, logger: nil, limit: nil)
+        @project = project
+        @dry_run = dry_run
+        @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
+        @limit = limit
+      end
+
+      def run!
+        log_info("Looking for orphan LFS files for project #{project.name_with_namespace}")
+
+        remove_orphan_references
+      end
+
+      private
+
+      def remove_orphan_references
+        invalid_references = project.lfs_objects_projects.where(lfs_object: orphan_objects) # rubocop:disable CodeReuse/ActiveRecord
+
+        if dry_run
+          log_info("Found invalid references: #{invalid_references.count}")
+        else
+          count = 0
+          invalid_references.each_batch(of: limit || DEFAULT_REMOVAL_LIMIT) do |relation|
+            count += relation.delete_all
+          end
+
+          log_info("Removed invalid references: #{count}")
+        end
+      end
+
+      def lfs_oids_from_repository
+        project.repository.gitaly_blob_client.get_all_lfs_pointers(nil).map(&:lfs_oid)
+      end
+
+      def orphan_oids
+        lfs_oids_from_database - lfs_oids_from_repository
+      end
+
+      def lfs_oids_from_database
+        oids = []
+
+        project.lfs_objects.each_batch do |relation|
+          oids += relation.pluck(:oid) # rubocop:disable CodeReuse/ActiveRecord
+        end
+
+        oids
+      end
+
+      def orphan_objects
+        LfsObject.where(oid: orphan_oids) # rubocop:disable CodeReuse/ActiveRecord
+      end
+
+      def log_info(msg)
+        logger.info("#{'[DRY RUN] ' if dry_run}#{msg}")
+      end
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index c26aa848d5aef95b2a19f157b38556e3ad5333a0..a56a043567307ad0502f048b9fe721e14da7d88e 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -64,6 +64,40 @@ namespace :gitlab do
       end
     end
 
+    desc 'GitLab | Cleanup | Clean orphan LFS file references'
+    task orphan_lfs_file_references: :gitlab_environment do
+      warn_user_is_not_gitlab
+
+      project = find_project
+
+      unless project
+        logger.info "Specify the project with PROJECT_ID={number} or PROJECT_PATH={namespace/project-name}".color(:red)
+        exit
+      end
+
+      cleaner = Gitlab::Cleanup::OrphanLfsFileReferences.new(
+        project,
+        dry_run: dry_run?,
+        logger: logger,
+        limit: limit
+      )
+
+      cleaner.run!
+
+      if dry_run?
+        logger.info "To clean up these files run this command with DRY_RUN=false".color(:yellow)
+      end
+    end
+
+    desc 'GitLab | Cleanup | Clean orphan LFS files'
+    task orphan_lfs_files: :gitlab_environment do
+      warn_user_is_not_gitlab
+
+      removed_files = RemoveUnreferencedLfsObjectsWorker.new.perform
+
+      logger.info "Removed unreferenced LFS files: #{removed_files.count}".color(:green)
+    end
+
     namespace :sessions do
       desc "GitLab | Cleanup | Sessions | Clean ActiveSession lookup keys"
       task active_sessions_lookup_keys: :gitlab_environment do
@@ -136,6 +170,14 @@ namespace :gitlab do
       ENV['NICENESS'].presence
     end
 
+    def find_project
+      if ENV['PROJECT_ID']
+        Project.find_by_id(ENV['PROJECT_ID']&.to_i)
+      elsif ENV['PROJECT_PATH']
+        Project.find_by_full_path(ENV['PROJECT_PATH'])
+      end
+    end
+
     # rubocop:disable Gitlab/RailsLogger
     def logger
       return @logger if defined?(@logger)
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
index bf0e98da1392ed9ed9330204c9bc6c7187f39655..45cdef2ba86d5cfd46669c9b4d772bf7f85abd94 100644
--- a/scripts/frontend/prettier.js
+++ b/scripts/frontend/prettier.js
@@ -78,10 +78,15 @@ const checkFileWithOptions = (filePath, options) =>
         passedCount += 1;
       } else {
         if (!didWarn) {
-          console.log(warningMessage);
+          // \x1b[31m  make text red
+          // \x1b[1m   make text bold
+          // %s        warningMessage
+          // \x1b[0m   reset text color (so logs after aren't red)
+          const redBoldText = '\x1b[1m\x1b[31;1m%s\x1b[0m';
+          console.log(redBoldText, warningMessage);
           didWarn = true;
         }
-        console.log(`Prettify Manually : ${filePath}`);
+        console.log(`yarn prettier --write ${filePath}`);
         failedCount += 1;
       }
     }
diff --git a/spec/factories/prometheus_alert_event.rb b/spec/factories/prometheus_alert_event.rb
new file mode 100644
index 0000000000000000000000000000000000000000..281fbacabe2b6e45171ae1ef4bb636e09d955755
--- /dev/null
+++ b/spec/factories/prometheus_alert_event.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :prometheus_alert_event do
+    project { prometheus_alert.project }
+    prometheus_alert
+    sequence(:payload_key) { |n| "hash payload key #{n}" }
+    status { PrometheusAlertEvent.status_value_for(:firing) }
+    started_at { Time.now }
+
+    trait :resolved do
+      status { PrometheusAlertEvent.status_value_for(:resolved) }
+      ended_at { Time.now }
+      payload_key { nil }
+    end
+
+    trait :none do
+      status { nil }
+      started_at { nil }
+    end
+  end
+end
diff --git a/spec/factories/self_managed_prometheus_alert_event.rb b/spec/factories/self_managed_prometheus_alert_event.rb
new file mode 100644
index 0000000000000000000000000000000000000000..238942e2c46c77e062ab9b4bc47d6c40705d2ed3
--- /dev/null
+++ b/spec/factories/self_managed_prometheus_alert_event.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :self_managed_prometheus_alert_event do
+    project
+    sequence(:payload_key) { |n| "hash payload key #{n}" }
+    status { SelfManagedPrometheusAlertEvent.status_value_for(:firing) }
+    title { 'alert' }
+    query_expression { 'vector(2)' }
+    started_at { Time.now }
+
+    trait :resolved do
+      status { SelfManagedPrometheusAlertEvent.status_value_for(:resolved) }
+      ended_at { Time.now }
+      payload_key { nil }
+    end
+
+    trait :none do
+      status { nil }
+      started_at { nil }
+    end
+  end
+end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 5b2e98804b0c8e6d63a6fe51b16f43f2e1482dc3..e03d7b6d1f7d7956ce628526b1a95f7e8b947352 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -100,6 +100,8 @@ describe 'Group issues page' do
         find('.empty-state .js-lazy-loaded')
         find('.new-project-item-link').click
 
+        find('.select2-input').set(group.name)
+
         page.within('.select2-results') do
           expect(page).to have_content(project.full_name)
           expect(page).not_to have_content(project_with_issues_disabled.full_name)
diff --git a/spec/finders/autocomplete/move_to_project_finder_spec.rb b/spec/finders/autocomplete/move_to_project_finder_spec.rb
index f997dd32c4002553753dd900cfe8ead9981ddedc..9129a3b65bead7defab030bbf8d50dec011100e0 100644
--- a/spec/finders/autocomplete/move_to_project_finder_spec.rb
+++ b/spec/finders/autocomplete/move_to_project_finder_spec.rb
@@ -3,8 +3,8 @@
 require 'spec_helper'
 
 describe Autocomplete::MoveToProjectFinder do
-  let(:user) { create(:user) }
-  let(:project) { create(:project) }
+  let_it_be(:user) { create(:user) }
+  let_it_be(:project) { create(:project) }
 
   let(:no_access_project) { create(:project) }
   let(:guest_project) { create(:project) }
@@ -92,6 +92,15 @@ describe Autocomplete::MoveToProjectFinder do
         expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a)
           .to eq([wadus_project])
       end
+
+      it 'allows searching by parent namespace' do
+        group = create(:group)
+        other_project = create(:project, group: group)
+        other_project.add_maintainer(user)
+
+        expect(described_class.new(user, project_id: project.id, search: group.name).execute.to_a)
+          .to contain_exactly(other_project)
+      end
     end
   end
 end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index ee8606e474e7e1562f3b5b833874b566b9a4f251..8d3564ca3c0e922412c64688cff6ddda84faa699 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -123,7 +123,7 @@ describe GroupDescendantsFinder do
       project = create(:project, namespace: group)
       other_project = create(:project)
       other_project.project_group_links.create(group: group,
-                                               group_access: ProjectGroupLink::MASTER)
+                                               group_access: ProjectGroupLink::MAINTAINER)
 
       expect(finder.execute).to contain_exactly(project)
     end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 6a04ca0eb671056e87ab789bd1fa48e0b47b3bba..eb3e28d166820526600bb8505d62637a80f6f423 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -6,22 +6,22 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
   include AdminModeHelper
 
   describe '#execute' do
-    let(:user) { create(:user) }
-    let(:group) { create(:group, :public) }
+    let_it_be(:user) { create(:user) }
+    let_it_be(:group) { create(:group, :public) }
 
-    let!(:private_project) do
+    let_it_be(:private_project) do
       create(:project, :private, name: 'A', path: 'A')
     end
 
-    let!(:internal_project) do
+    let_it_be(:internal_project) do
       create(:project, :internal, group: group, name: 'B', path: 'B')
     end
 
-    let!(:public_project) do
+    let_it_be(:public_project) do
       create(:project, :public, group: group, name: 'C', path: 'C')
     end
 
-    let!(:shared_project) do
+    let_it_be(:shared_project) do
       create(:project, :private, name: 'D', path: 'D')
     end
 
@@ -139,6 +139,12 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
         it { is_expected.to eq([public_project]) }
       end
 
+      describe 'filter by group name' do
+        let(:params) { { name: group.name, search_namespaces: true } }
+
+        it { is_expected.to eq([public_project, internal_project]) }
+      end
+
       describe 'filter by archived' do
         let!(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') }
 
diff --git a/spec/fixtures/api/schemas/entities/discussion.json b/spec/fixtures/api/schemas/entities/discussion.json
index bcc1db79e83b4f0368fc9cb5e86afcccedec268e..16622ef688764bb19f00ca5665452916de35c582 100644
--- a/spec/fixtures/api/schemas/entities/discussion.json
+++ b/spec/fixtures/api/schemas/entities/discussion.json
@@ -54,7 +54,8 @@
           "cached_markdown_version": { "type": "integer" },
           "human_access": { "type": ["string", "null"] },
           "toggle_award_path": { "type": "string" },
-          "path": { "type": "string" }
+          "path": { "type": "string" },
+          "commands_changes": { "type": "object", "additionalProperties": true }
         },
         "required": [
           "id", "attachment", "author", "created_at", "updated_at",
diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json
index 0f9c221fd6df4a235adfa3242102abdac1d330b3..9668327adc4f0fae5c56ae5a7785625e52f9b4b9 100644
--- a/spec/fixtures/api/schemas/public_api/v4/notes.json
+++ b/spec/fixtures/api/schemas/public_api/v4/notes.json
@@ -19,6 +19,7 @@
         },
         "additionalProperties": false
       },
+      "commands_changes": { "type": "object", "additionalProperties": true },
       "created_at": { "type": "date" },
       "updated_at": { "type": "date" },
       "system": { "type": "boolean" },
diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js
index 904798e0b83fe94d54980f39dda4eb9c77973639..b4800112fee3576b6783d95b3a4b03184c81fef3 100644
--- a/spec/frontend/repository/components/table/parent_row_spec.js
+++ b/spec/frontend/repository/components/table/parent_row_spec.js
@@ -31,10 +31,11 @@ describe('Repository parent row component', () => {
   });
 
   it.each`
-    path                  | to
-    ${'app'}              | ${'/-/tree/master/'}
-    ${'app/assets'}       | ${'/-/tree/master/app'}
-    ${'app/assets#/test'} | ${'/-/tree/master/app/assets%23'}
+    path                        | to
+    ${'app'}                    | ${'/-/tree/master/'}
+    ${'app/assets'}             | ${'/-/tree/master/app'}
+    ${'app/assets#/test'}       | ${'/-/tree/master/app/assets%23'}
+    ${'app/assets#/test/world'} | ${'/-/tree/master/app/assets%23/test'}
   `('renders link in $path to $to', ({ path, to }) => {
     factory(path);
 
diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb
index ff4212ddf18c5263305c27e114dc198bc521141c..71cf536db8960dfcbfad86e6d28ea8850c675fde 100644
--- a/spec/models/concerns/optionally_search_spec.rb
+++ b/spec/models/concerns/optionally_search_spec.rb
@@ -22,12 +22,22 @@ describe OptionallySearch do
       it 'delegates to the search method' do
         expect(model)
           .to receive(:search)
-          .with('foo')
+          .with('foo', {})
 
         model.optionally_search('foo')
       end
     end
 
+    context 'when an option is provided' do
+      it 'delegates to the search method' do
+        expect(model)
+          .to receive(:search)
+          .with('foo', some_option: true)
+
+        model.optionally_search('foo', some_option: true)
+      end
+    end
+
     context 'when no query is given' do
       it 'returns the current relation' do
         expect(model.optionally_search).to be_a_kind_of(ActiveRecord::Relation)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 44be49854390877effb897d0f7332080725d4c04..ceb6382eb6cb65dc04194e0789c8742773b41f88 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -108,6 +108,8 @@ describe Project do
     it { is_expected.to have_many(:external_pull_requests) }
     it { is_expected.to have_many(:sourced_pipelines) }
     it { is_expected.to have_many(:source_pipelines) }
+    it { is_expected.to have_many(:prometheus_alert_events) }
+    it { is_expected.to have_many(:self_managed_prometheus_alert_events) }
 
     it_behaves_like 'model with repository' do
       let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
@@ -1757,7 +1759,7 @@ describe Project do
       expect(described_class.search(project.path.upcase)).to eq([project])
     end
 
-    context 'by full path' do
+    context 'when include_namespace is true' do
       let_it_be(:group) { create(:group) }
       let_it_be(:project) { create(:project, group: group) }
 
@@ -1767,11 +1769,11 @@ describe Project do
         end
 
         it 'returns projects that match the group path' do
-          expect(described_class.search(group.path)).to eq([project])
+          expect(described_class.search(group.path, include_namespace: true)).to eq([project])
         end
 
         it 'returns projects that match the full path' do
-          expect(described_class.search(project.full_path)).to eq([project])
+          expect(described_class.search(project.full_path, include_namespace: true)).to eq([project])
         end
       end
 
@@ -1781,11 +1783,11 @@ describe Project do
         end
 
         it 'returns no results when searching by group path' do
-          expect(described_class.search(group.path)).to be_empty
+          expect(described_class.search(group.path, include_namespace: true)).to be_empty
         end
 
         it 'returns no results when searching by full path' do
-          expect(described_class.search(project.full_path)).to be_empty
+          expect(described_class.search(project.full_path, include_namespace: true)).to be_empty
         end
       end
     end
diff --git a/spec/models/prometheus_alert_event_spec.rb b/spec/models/prometheus_alert_event_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..040113643ddb9f9ce376888dc31e0bcfea7ca43b
--- /dev/null
+++ b/spec/models/prometheus_alert_event_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PrometheusAlertEvent do
+  subject { build(:prometheus_alert_event) }
+
+  let(:alert) { subject.prometheus_alert }
+
+  describe 'associations' do
+    it { is_expected.to belong_to(:prometheus_alert).required }
+  end
+
+  describe 'validations' do
+    it { is_expected.to be_valid }
+
+    it { is_expected.to validate_presence_of(:prometheus_alert).with_message(:required) }
+    it { is_expected.to validate_uniqueness_of(:payload_key).scoped_to(:prometheus_alert_id) }
+    it { is_expected.to validate_presence_of(:started_at) }
+
+    describe 'payload_key & ended_at' do
+      context 'absent if firing?' do
+        subject { build(:prometheus_alert_event) }
+
+        it { is_expected.to validate_presence_of(:payload_key) }
+        it { is_expected.not_to validate_presence_of(:ended_at) }
+      end
+
+      context 'present if resolved?' do
+        subject { build(:prometheus_alert_event, :resolved) }
+
+        it { is_expected.not_to validate_presence_of(:payload_key) }
+        it { is_expected.to validate_presence_of(:ended_at) }
+      end
+    end
+  end
+
+  describe '#title' do
+    it 'delegates to alert' do
+      expect(subject.title).to eq(alert.title)
+    end
+  end
+
+  describe 'prometheus_metric_id' do
+    it 'delegates to alert' do
+      expect(subject.prometheus_metric_id).to eq(alert.prometheus_metric_id)
+    end
+  end
+
+  describe 'transaction' do
+    describe 'fire' do
+      let(:started_at) { Time.now }
+
+      context 'when status is none' do
+        subject { build(:prometheus_alert_event, :none) }
+
+        it 'fires an event' do
+          result = subject.fire(started_at)
+
+          expect(result).to eq(true)
+          expect(subject).to be_firing
+          expect(subject.started_at).to be_like_time(started_at)
+        end
+      end
+
+      context 'when firing' do
+        subject { build(:prometheus_alert_event) }
+
+        it 'cannot fire again' do
+          result = subject.fire(started_at)
+
+          expect(result).to eq(false)
+        end
+      end
+    end
+
+    describe 'resolve' do
+      let(:ended_at) { Time.now }
+
+      context 'when firing' do
+        subject { build(:prometheus_alert_event) }
+
+        it 'resolves an event' do
+          result = subject.resolve!(ended_at)
+
+          expect(result).to eq(true)
+          expect(subject).to be_resolved
+          expect(subject.ended_at).to be_like_time(ended_at)
+        end
+      end
+
+      context 'when resolved' do
+        subject { build(:prometheus_alert_event, :resolved) }
+
+        it 'cannot resolve again' do
+          result = subject.resolve(ended_at)
+
+          expect(result).to eq(false)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb
index 6861e03282aca689bf3d987ba009eec3c83768f7..c31fe1923676562387a3d4841cd96c0da454e5d8 100644
--- a/spec/models/snippet_repository_spec.rb
+++ b/spec/models/snippet_repository_spec.rb
@@ -140,6 +140,41 @@ describe SnippetRepository do
     let_it_be(:named_snippet) { { file_path: 'fee.txt', content: 'bar', action: :create } }
     let_it_be(:unnamed_snippet) { { file_path: '', content: 'dummy', action: :create } }
 
+    context 'when existing file has a default name' do
+      let(:default_name) { 'snippetfile1.txt' }
+      let(:new_file) { { file_path: '', content: 'bar' } }
+      let(:existing_file) { { previous_path: default_name, file_path: '', content: 'new_content' } }
+
+      before do
+        expect(blob_at(snippet, default_name)).to be_nil
+
+        snippet_repository.multi_files_action(user, [new_file], commit_opts)
+
+        expect(blob_at(snippet, default_name)).to be
+      end
+
+      it 'reuses the existing file name' do
+        snippet_repository.multi_files_action(user, [existing_file], commit_opts)
+
+        blob = blob_at(snippet, default_name)
+        expect(blob.data).to eq existing_file[:content]
+      end
+    end
+
+    context 'when file name consists of one or several whitespaces' do
+      let(:default_name) { 'snippetfile1.txt' }
+      let(:new_file) { { file_path: ' ', content: 'bar' } }
+
+      it 'assigns a new name to the file' do
+        expect(blob_at(snippet, default_name)).to be_nil
+
+        snippet_repository.multi_files_action(user, [new_file], commit_opts)
+
+        blob = blob_at(snippet, default_name)
+        expect(blob.data).to eq new_file[:content]
+      end
+    end
+
     context 'when some files are not named' do
       let(:data) { [named_snippet] + Array.new(2) { unnamed_snippet.clone } }
 
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 54bb2e670da1f2c519180e92e3e5d81ebb660e95..cec4995c6201e5799595f0ce41a3606e6dbefefa 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -302,7 +302,7 @@ describe API::Groups do
 
       before do
         group1.add_developer(user2)
-        group3.add_master(user2)
+        group3.add_maintainer(user2)
       end
 
       it 'returns an array of groups the user has at least master access' do
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 6cf978e717eb6f8003388632a564bd3108c786f9..3fb14eb9d5aa9e1183524046d43b36e5616bfe73 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,8 +3,8 @@
 require 'spec_helper'
 
 describe API::Notes do
-  let(:user) { create(:user) }
-  let!(:project) { create(:project, :public, namespace: user.namespace) }
+  let!(:user) { create(:user) }
+  let!(:project) { create(:project, :public) }
   let(:private_user) { create(:user) }
 
   before do
@@ -226,14 +226,56 @@ describe API::Notes do
       let(:note) { merge_request_note }
     end
 
+    let(:request_body) { 'Hi!' }
+    let(:request_path) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes" }
+
+    subject { post api(request_path, user), params: { body: request_body } }
+
+    context 'a command only note' do
+      let(:assignee) { create(:user) }
+      let(:request_body) { "/assign #{assignee.to_reference}" }
+
+      before do
+        project.add_developer(assignee)
+        project.add_developer(user)
+      end
+
+      it 'returns 202 Accepted status' do
+        subject
+
+        expect(response).to have_gitlab_http_status(:accepted)
+      end
+
+      it 'does not actually create a new note' do
+        expect { subject }.not_to change { Note.where(system: false).count }
+      end
+
+      it 'does however create a system note about the change' do
+        expect { subject }.to change { Note.system.count }.by(1)
+      end
+
+      it 'applies the commands' do
+        expect { subject }.to change { merge_request.reset.assignees }
+      end
+
+      it 'reports the changes' do
+        subject
+
+        expect(json_response).to include(
+          'commands_changes' => include(
+            'assignee_ids' => [Integer]
+          ),
+          'summary' => include("Assigned #{assignee.to_reference}.")
+        )
+      end
+    end
+
     context 'when the merge request discussion is locked' do
       before do
         merge_request.update_attribute(:discussion_locked, true)
       end
 
       context 'when a user is a team member' do
-        subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), params: { body: 'Hi!' } }
-
         it 'returns 200 status' do
           subject
 
diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb
index 91139b987dfd94b133605074fb0064b3a5ef325e..ee55d1c54b7a7765d18c937b9719813d4b4e9230 100644
--- a/spec/requests/api/pages/internal_access_spec.rb
+++ b/spec/requests/api/pages/internal_access_spec.rb
@@ -19,7 +19,7 @@ describe "Internal Project Pages Access" do
   before do
     allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
     group.add_owner(owner)
-    project.add_master(master)
+    project.add_maintainer(master)
     project.add_developer(developer)
     project.add_reporter(reporter)
     project.add_guest(guest)
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
index 7c592ccfd43feed6e9293ff0a6c7a2cbd550094b..146c6a389f3dc830fa86112dfafda53bc0b228eb 100644
--- a/spec/requests/api/pages/private_access_spec.rb
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -19,7 +19,7 @@ describe "Private Project Pages Access" do
   before do
     allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
     group.add_owner(owner)
-    project.add_master(master)
+    project.add_maintainer(master)
     project.add_developer(developer)
     project.add_reporter(reporter)
     project.add_guest(guest)
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
index f2fe64434c65407d4503c0d9d39b3a9728071ac7..7d929e2a287f4e47892bfc7cfa5fa627fd5d585c 100644
--- a/spec/requests/api/pages/public_access_spec.rb
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -19,7 +19,7 @@ describe "Public Project Pages Access" do
   before do
     allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
     group.add_owner(owner)
-    project.add_master(master)
+    project.add_maintainer(master)
     project.add_developer(developer)
     project.add_reporter(reporter)
     project.add_guest(guest)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 8706b941e4f4352af4913c0a179f5e64823f3f1c..c4f4801e372327cb0ea312b6405504f2d3c15c2e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -362,6 +362,21 @@ describe API::Projects do
         end
       end
 
+      context 'and using search and search_namespaces is true' do
+        let(:group) { create(:group) }
+        let!(:project_in_group) { create(:project, group: group) }
+
+        before do
+          group.add_guest(user)
+        end
+
+        it_behaves_like 'projects response' do
+          let(:filter) { { search: group.name, search_namespaces: true } }
+          let(:current_user) { user }
+          let(:projects) { [project_in_group] }
+        end
+      end
+
       context 'and using id_after' do
         it_behaves_like 'projects response' do
           let(:filter) { { id_after: project2.id } }
diff --git a/spec/serializers/prometheus_alert_entity_spec.rb b/spec/serializers/prometheus_alert_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5121c62a0e03b5abb438062afe30824246b04188
--- /dev/null
+++ b/spec/serializers/prometheus_alert_entity_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PrometheusAlertEntity do
+  let(:user) { create(:user) }
+  let(:prometheus_alert) { create(:prometheus_alert) }
+  let(:request) { double('prometheus_alert', current_user: user) }
+  let(:entity) { described_class.new(prometheus_alert, request: request) }
+
+  subject { entity.as_json }
+
+  context 'when user can read prometheus alerts' do
+    before do
+      prometheus_alert.project.add_maintainer(user)
+      stub_licensed_features(prometheus_alerts: true)
+    end
+
+    it 'exposes prometheus_alert attributes' do
+      expect(subject).to include(:id, :title, :query, :operator, :threshold)
+    end
+
+    it 'exposes alert_path' do
+      expect(subject).to include(:alert_path)
+    end
+  end
+end
diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
index 227041344d71a71eb37b473a1b6c9343184a1211..6ba4b4035e4a011289e4fb74196536aad7fc6b18 100644
--- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
@@ -92,6 +92,8 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto
       end
 
       context 'Files::UpdateService success' do
+        let(:merge_request) { project.merge_requests.last }
+
         before do
           allow(::Files::UpdateService).to receive(:new).and_return(double(execute: { status: :success }))
         end
@@ -107,6 +109,31 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto
           expect(service_call[:status]).to be :success
           expect(service_call[:http_status]).to be :created
           expect(service_call[:dashboard]).to match dashboard_details
+          expect(service_call[:merge_request]).to eq(Gitlab::UrlBuilder.build(merge_request))
+        end
+
+        context 'when the merge request does not succeed' do
+          let(:error_message) { 'There was an error' }
+
+          let(:merge_request) do
+            build(:merge_request, target_project: project, source_project: project, author: user)
+          end
+
+          before do
+            merge_request.errors.add(:base, error_message)
+            allow_next_instance_of(::MergeRequests::CreateService) do |mr|
+              allow(mr).to receive(:execute).and_return(merge_request)
+            end
+          end
+
+          it 'returns an appropriate message and status code', :aggregate_failures do
+            result = service_call
+
+            expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step)
+            expect(result[:status]).to eq(:error)
+            expect(result[:http_status]).to eq(:bad_request)
+            expect(result[:message]).to eq(error_message)
+          end
         end
 
         context 'with escaped characters in file name' do
@@ -125,6 +152,25 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto
             expect(service_call[:dashboard]).to match dashboard_details
           end
         end
+
+        context 'when pushing to the default branch' do
+          let(:branch) { 'master' }
+
+          it 'does not create a merge request', :aggregate_failures do
+            dashboard_details = {
+              path: '.gitlab/dashboards/custom_dashboard.yml',
+              display_name: 'custom_dashboard.yml',
+              default: false,
+              system_dashboard: false
+            }
+
+            expect(::MergeRequests::CreateService).not_to receive(:new)
+            expect(service_call.keys).to contain_exactly(:dashboard, :http_status, :status)
+            expect(service_call[:status]).to be :success
+            expect(service_call[:http_status]).to be :created
+            expect(service_call[:dashboard]).to match dashboard_details
+          end
+        end
       end
 
       context 'Files::UpdateService fails' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 8b43844eb96a66b3d76dc466a9c525f1350780a3..9fa8f8073301b4f5a27e35173cadd3d2e744c781 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -2790,7 +2790,7 @@ describe NotificationService, :mailer do
     let!(:developer) { create(:user) }
 
     before do
-      project.add_master(master)
+      project.add_maintainer(master)
     end
 
     it 'sends the email to owners and masters' do
diff --git a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1d726db6ce32a40235af4a3d635e136654b30dc4
--- /dev/null
+++ b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
@@ -0,0 +1,312 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Prometheus::Alerts::CreateEventsService do
+  let(:user) { create(:user) }
+  let_it_be(:project) { create(:project) }
+  let(:metric) { create(:prometheus_metric, project: project) }
+  let(:service) { described_class.new(project, user, alerts_payload) }
+
+  shared_examples 'events persisted' do |expected_count|
+    subject { service.execute }
+
+    it 'returns proper amount of created events' do
+      expect(subject.size).to eq(expected_count)
+    end
+
+    it 'increments event count' do
+      expect { subject }.to change { PrometheusAlertEvent.count }.to(expected_count)
+    end
+  end
+
+  shared_examples 'no events persisted' do
+    subject { service.execute }
+
+    it 'returns no created events' do
+      expect(subject).to be_empty
+    end
+
+    it 'does not change event count' do
+      expect { subject }.not_to change { PrometheusAlertEvent.count }
+    end
+  end
+
+  shared_examples 'self managed events persisted' do
+    subject { service.execute }
+
+    it 'returns created events' do
+      expect(subject).not_to be_empty
+    end
+
+    it 'does change self managed event count' do
+      expect { subject }.to change { SelfManagedPrometheusAlertEvent.count }
+    end
+  end
+
+  context 'with valid alerts_payload' do
+    let!(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
+
+    let(:events) { service.execute }
+
+    context 'with a firing payload' do
+      let(:started_at) { truncate_to_second(Time.now) }
+      let(:firing_event) { alert_payload(status: 'firing', started_at: started_at) }
+      let(:alerts_payload) { { 'alerts' => [firing_event] } }
+
+      it_behaves_like 'events persisted', 1
+
+      it 'returns created event' do
+        event = events.first
+
+        expect(event).to be_firing
+        expect(event.started_at).to eq(started_at)
+        expect(event.ended_at).to be_nil
+      end
+
+      context 'with 2 different firing events' do
+        let(:another_firing_event) { alert_payload(status: 'firing', started_at: started_at + 1) }
+        let(:alerts_payload) { { 'alerts' => [firing_event, another_firing_event] } }
+
+        it_behaves_like 'events persisted', 2
+      end
+
+      context 'with already persisted firing event' do
+        before do
+          service.execute
+        end
+
+        it_behaves_like 'no events persisted'
+      end
+
+      context 'with duplicate payload' do
+        let(:alerts_payload) { { 'alerts' => [firing_event, firing_event] } }
+
+        it_behaves_like 'events persisted', 1
+      end
+    end
+
+    context 'with a resolved payload' do
+      let(:started_at) { truncate_to_second(Time.now) }
+      let(:ended_at) { started_at + 1 }
+      let(:payload_key) { PrometheusAlertEvent.payload_key_for(alert.prometheus_metric_id, utc_rfc3339(started_at)) }
+      let(:resolved_event) { alert_payload(status: 'resolved', started_at: started_at, ended_at: ended_at) }
+      let(:alerts_payload) { { 'alerts' => [resolved_event] } }
+
+      context 'with a matching firing event' do
+        before do
+          create(:prometheus_alert_event,
+                 prometheus_alert: alert,
+                 payload_key: payload_key,
+                 started_at: started_at)
+        end
+
+        it 'does not create an additional event' do
+          expect { service.execute }.not_to change { PrometheusAlertEvent.count }
+        end
+
+        it 'marks firing event as `resolved`' do
+          expect(events.size).to eq(1)
+
+          event = events.first
+          expect(event).to be_resolved
+          expect(event.started_at).to eq(started_at)
+          expect(event.ended_at).to eq(ended_at)
+        end
+
+        context 'with duplicate payload' do
+          let(:alerts_payload) { { 'alerts' => [resolved_event, resolved_event] } }
+
+          it 'does not create an additional event' do
+            expect { service.execute }.not_to change { PrometheusAlertEvent.count }
+          end
+
+          it 'marks firing event as `resolved` only once' do
+            expect(events.size).to eq(1)
+          end
+        end
+      end
+
+      context 'without a matching firing event' do
+        context 'due to payload_key' do
+          let(:payload_key) { 'some other payload_key' }
+
+          before do
+            create(:prometheus_alert_event,
+                   prometheus_alert: alert,
+                   payload_key: payload_key,
+                   started_at: started_at)
+          end
+
+          it_behaves_like 'no events persisted'
+        end
+
+        context 'due to status' do
+          before do
+            create(:prometheus_alert_event, :resolved,
+                   prometheus_alert: alert,
+                   started_at: started_at)
+          end
+
+          it_behaves_like 'no events persisted'
+        end
+      end
+
+      context 'with already resolved event' do
+        before do
+          service.execute
+        end
+
+        it_behaves_like 'no events persisted'
+      end
+    end
+
+    context 'with a metric from another project' do
+      let(:another_project) { create(:project) }
+      let(:metric) { create(:prometheus_metric, project: another_project) }
+      let(:alerts_payload) { { 'alerts' => [alert_payload] } }
+
+      let!(:alert) do
+        create(:prometheus_alert,
+               prometheus_metric: metric,
+               project: another_project)
+      end
+
+      it_behaves_like 'no events persisted'
+    end
+  end
+
+  context 'with invalid payload' do
+    let(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
+
+    describe '`alerts` key' do
+      context 'is missing' do
+        let(:alerts_payload) { {} }
+
+        it_behaves_like 'no events persisted'
+      end
+
+      context 'is nil' do
+        let(:alerts_payload) { { 'alerts' => nil } }
+
+        it_behaves_like 'no events persisted'
+      end
+
+      context 'is empty' do
+        let(:alerts_payload) { { 'alerts' => [] } }
+
+        it_behaves_like 'no events persisted'
+      end
+
+      context 'is not a Hash' do
+        let(:alerts_payload) { { 'alerts' => [:not_a_hash] } }
+
+        it_behaves_like 'no events persisted'
+      end
+
+      describe '`status`' do
+        context 'is missing' do
+          let(:alerts_payload) { { 'alerts' => [alert_payload(status: nil)] } }
+
+          it_behaves_like 'no events persisted'
+        end
+
+        context 'is invalid' do
+          let(:alerts_payload) { { 'alerts' => [alert_payload(status: 'invalid')] } }
+
+          it_behaves_like 'no events persisted'
+        end
+      end
+
+      describe '`started_at`' do
+        context 'is missing' do
+          let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: nil)] } }
+
+          it_behaves_like 'no events persisted'
+        end
+
+        context 'is invalid' do
+          let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: 'invalid date')] } }
+
+          it_behaves_like 'no events persisted'
+        end
+      end
+
+      describe '`ended_at`' do
+        context 'is missing and status is resolved' do
+          let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: nil, status: 'resolved')] } }
+
+          it_behaves_like 'no events persisted'
+        end
+
+        context 'is invalid and status is resolved' do
+          let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: 'invalid date', status: 'resolved')] } }
+
+          it_behaves_like 'no events persisted'
+        end
+      end
+
+      describe '`labels`' do
+        describe '`gitlab_alert_id`' do
+          context 'is missing' do
+            let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil)] } }
+
+            it_behaves_like 'no events persisted'
+          end
+
+          context 'is missing but title is given' do
+            let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert')] } }
+
+            it_behaves_like 'self managed events persisted'
+          end
+
+          context 'is missing and environment name is given' do
+            let(:environment) { create(:environment, project: project) }
+            let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert', environment: environment.name)] } }
+
+            it_behaves_like 'self managed events persisted'
+
+            it 'associates the environment to the alert event' do
+              service.execute
+
+              expect(SelfManagedPrometheusAlertEvent.last.environment).to eq environment
+            end
+          end
+
+          context 'is invalid' do
+            let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: '-1')] } }
+
+            it_behaves_like 'no events persisted'
+          end
+        end
+      end
+    end
+  end
+
+  private
+
+  def alert_payload(status: 'firing', started_at: Time.now, ended_at: Time.now, gitlab_alert_id: alert.prometheus_metric_id, title: nil, environment: nil)
+    payload = {}
+
+    payload['status'] = status if status
+    payload['startsAt'] = utc_rfc3339(started_at) if started_at
+    payload['endsAt'] = utc_rfc3339(ended_at) if ended_at
+    payload['labels'] = {}
+    payload['labels']['gitlab_alert_id'] = gitlab_alert_id.to_s if gitlab_alert_id
+    payload['labels']['alertname'] = title if title
+    payload['labels']['gitlab_environment_name'] = environment if environment
+
+    payload
+  end
+
+  # Example: 2018-09-27T18:25:31.079079416Z
+  def utc_rfc3339(date)
+    date.utc.rfc3339
+  rescue
+    date
+  end
+
+  def truncate_to_second(date)
+    date.change(usec: 0)
+  end
+end
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index ffad3c8b8e502fb0e9afc929d84f0abb8a6ebca6..26c80ee05b3036e114e9b34fa64890b9c42b4859 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -193,6 +193,12 @@ describe Snippets::CreateService do
           subject
         end
 
+        it 'destroys the snippet_repository' do
+          subject
+
+          expect(SnippetRepository.count).to be_zero
+        end
+
         it 'returns the error' do
           response = subject
 
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 0c52af43465ce03ebd50d0c4ce964e6bf2571de5..72e8b92019235baf37b78f2f6b72f30e4644b325 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -172,6 +172,8 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
 
       if parent_type == 'projects'
         context 'by a project owner' do
+          let(:user) { project.owner }
+
           it 'sets the creation time on the new note' do
             post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
 
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 92ccc195a9a65a81d3b9ccb00668d8ac810e57ad..8db18895c24547212756d260d5793ce1212a9946 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -120,6 +120,71 @@ describe 'gitlab:cleanup rake tasks' do
     end
   end
 
+  describe 'gitlab:cleanup:orphan_lfs_file_references' do
+    subject(:rake_task) { run_rake_task('gitlab:cleanup:orphan_lfs_file_references') }
+
+    let(:project) { create(:project, :repository) }
+
+    before do
+      stub_env('PROJECT_ID', project.id)
+    end
+
+    it 'runs the task without errors' do
+      expect(Gitlab::Cleanup::OrphanLfsFileReferences)
+        .to receive(:new).and_call_original
+
+      expect { rake_task }.not_to raise_error
+    end
+
+    context 'with DRY_RUN set to false' do
+      before do
+        stub_env('DRY_RUN', 'false')
+      end
+
+      it 'passes dry_run correctly' do
+        expect(Gitlab::Cleanup::OrphanLfsFileReferences)
+          .to receive(:new)
+          .with(project,
+                limit: anything,
+                dry_run: false,
+                logger: anything)
+          .and_call_original
+
+        rake_task
+      end
+    end
+
+    context 'with LIMIT set to 100' do
+      before do
+        stub_env('LIMIT', '100')
+      end
+
+      it 'passes limit as integer' do
+        expect(Gitlab::Cleanup::OrphanLfsFileReferences)
+          .to receive(:new)
+          .with(project,
+                limit: 100,
+                dry_run: true,
+                logger: anything)
+          .and_call_original
+
+        rake_task
+      end
+    end
+  end
+
+  describe 'gitlab:cleanup:orphan_lfs_files' do
+    subject(:rake_task) { run_rake_task('gitlab:cleanup:orphan_lfs_files') }
+
+    it 'runs RemoveUnreferencedLfsObjectsWorker' do
+      expect_any_instance_of(RemoveUnreferencedLfsObjectsWorker)
+        .to receive(:perform)
+        .and_call_original
+
+      rake_task
+    end
+  end
+
   context 'sessions' do
     describe 'gitlab:cleanup:sessions:active_sessions_lookup_keys', :clean_gitlab_redis_shared_state do
       subject(:rake_task) { run_rake_task('gitlab:cleanup:sessions:active_sessions_lookup_keys') }
diff --git a/spec/views/projects/artifacts/_artifact.html.haml_spec.rb b/spec/views/projects/artifacts/_artifact.html.haml_spec.rb
index 460b63efa2f0ca1620dcee7411cd632927f86b8a..b3bf54e143a5599576ad7e0fc018d82cb8bcc959 100644
--- a/spec/views/projects/artifacts/_artifact.html.haml_spec.rb
+++ b/spec/views/projects/artifacts/_artifact.html.haml_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe "projects/artifacts/_artifact.html.haml" do
       let(:user) { create(:user) }
 
       it 'has a delete button' do
-        allow_any_instance_of(ProjectTeam).to receive(:max_member_access).and_return(Gitlab::Access::MASTER)
+        allow_any_instance_of(ProjectTeam).to receive(:max_member_access).and_return(Gitlab::Access::MAINTAINER)
         render_partial
 
         expect(rendered).to have_link('Delete artifacts', href: project_artifact_path(project, project.job_artifacts.first))
diff --git a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19ef26358822b04422353a4a28903cfacf4503be
--- /dev/null
+++ b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IncidentManagement::ProcessPrometheusAlertWorker do
+  describe '#perform' do
+    let_it_be(:project) { create(:project) }
+    let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
+    let_it_be(:payload_key) { PrometheusAlertEvent.payload_key_for(prometheus_alert.prometheus_metric_id, prometheus_alert.created_at.rfc3339) }
+    let!(:prometheus_alert_event) { create(:prometheus_alert_event, prometheus_alert: prometheus_alert, payload_key: payload_key) }
+
+    let(:alert_params) do
+      {
+        startsAt: prometheus_alert.created_at.rfc3339,
+        labels: {
+          gitlab_alert_id: prometheus_alert.prometheus_metric_id
+        }
+      }.with_indifferent_access
+    end
+
+    it 'creates an issue' do
+      expect { subject.perform(project.id, alert_params) }
+        .to change(Issue, :count)
+        .by(1)
+    end
+
+    it 'relates issue to an event' do
+      expect { subject.perform(project.id, alert_params) }
+        .to change(prometheus_alert.related_issues, :count)
+        .from(0)
+        .to(1)
+    end
+
+    context 'resolved event' do
+      let(:issue) { create(:issue, project: project) }
+
+      before do
+        prometheus_alert_event.related_issues << issue
+        prometheus_alert_event.resolve
+      end
+
+      it 'does not create an issue' do
+        expect { subject.perform(project.id, alert_params) }
+          .not_to change(Issue, :count)
+      end
+
+      it 'closes the existing issue' do
+        expect { subject.perform(project.id, alert_params) }
+          .to change { issue.reload.state }
+          .from('opened')
+          .to('closed')
+      end
+
+      it 'leaves a system note on the issue' do
+        expect(SystemNoteService)
+          .to receive(:auto_resolve_prometheus_alert)
+
+        subject.perform(project.id, alert_params)
+      end
+    end
+
+    context 'when project could not be found' do
+      let(:non_existing_project_id) { (Project.maximum(:id) || 0) + 1 }
+
+      it 'does not create an issue' do
+        expect { subject.perform(non_existing_project_id, alert_params) }
+          .not_to change(Issue, :count)
+      end
+
+      it 'does not relate issue to an event' do
+        expect { subject.perform(non_existing_project_id, alert_params) }
+          .not_to change(prometheus_alert.related_issues, :count)
+      end
+    end
+
+    context 'when event could not be found' do
+      before do
+        alert_params[:labels][:gitlab_alert_id] = (PrometheusAlertEvent.maximum(:id) || 0) + 1
+      end
+
+      it 'does not create an issue' do
+        expect { subject.perform(project.id, alert_params) }
+          .not_to change(Issue, :count)
+      end
+
+      it 'does not relate issue to an event' do
+        expect { subject.perform(project.id, alert_params) }
+          .not_to change(prometheus_alert.related_issues, :count)
+      end
+    end
+
+    context 'when issue could not be created' do
+      before do
+        allow_next_instance_of(IncidentManagement::CreateIssueService) do |instance|
+          allow(instance).to receive(:execute).and_return( { error: true } )
+        end
+      end
+
+      it 'does not relate issue to an event' do
+        expect { subject.perform(project.id, alert_params) }
+          .not_to change(prometheus_alert.related_issues, :count)
+      end
+    end
+
+    context 'self-managed alert' do
+      let(:alert_name) { 'alert' }
+      let(:starts_at) { Time.now.rfc3339 }
+
+      let!(:prometheus_alert_event) do
+        payload_key = SelfManagedPrometheusAlertEvent.payload_key_for(starts_at, alert_name, 'vector(1)')
+        create(:self_managed_prometheus_alert_event, project: project, payload_key: payload_key)
+      end
+
+      let(:alert_params) do
+        {
+          startsAt: starts_at,
+          generatorURL: 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1',
+          labels: {
+            alertname: alert_name
+          }
+        }.with_indifferent_access
+      end
+
+      it 'creates an issue' do
+        expect { subject.perform(project.id, alert_params) }
+          .to change(Issue, :count)
+          .by(1)
+      end
+
+      it 'relates issue to an event' do
+        expect { subject.perform(project.id, alert_params) }
+          .to change(prometheus_alert_event.related_issues, :count)
+          .from(0)
+          .to(1)
+      end
+
+      context 'when event could not be found' do
+        before do
+          alert_params[:generatorURL] = 'http://somethingelse.com'
+        end
+
+        it 'creates an issue' do
+          expect { subject.perform(project.id, alert_params) }
+            .to change(Issue, :count)
+            .by(1)
+        end
+
+        it 'does not relate issue to an event' do
+          expect { subject.perform(project.id, alert_params) }
+            .not_to change(prometheus_alert.related_issues, :count)
+        end
+      end
+    end
+  end
+end