summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/projects/protected_branches_controller.rb12
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/models/concerns/checksummable.rb11
-rw-r--r--app/models/lfs_object.rb3
-rw-r--r--app/models/protected_branch.rb5
-rw-r--r--app/models/upload.rb7
-rw-r--r--app/services/projects/create_from_template_service.rb9
-rw-r--r--app/services/projects/create_service.rb6
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml19
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml19
-rw-r--r--app/views/projects/protected_branches/shared/_protected_branch.html.haml2
-rw-r--r--changelogs/unreleased/12764-refactor-checksum-code.yml5
-rw-r--r--changelogs/unreleased/sh-use-template-project-id-backend.yml5
-rw-r--r--db/post_migrate/20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb46
-rw-r--r--doc/administration/auth/ldap-ee.md2
-rw-r--r--doc/api/protected_branches.md18
-rw-r--r--doc/user/permissions.md95
-rw-r--r--doc/user/project/img/protected_branches_list_v12_3.pngbin8774 -> 32351 bytes
-rw-r--r--doc/user/project/img/protected_branches_page_v12_3.pngbin9445 -> 50657 bytes
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md4
-rw-r--r--doc/user/project/protected_branches.md14
-rw-r--r--lib/api/protected_branches.rb4
-rw-r--r--lib/gitlab/ci/trace.rb3
-rw-r--r--locale/gitlab.pot48
-rw-r--r--spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb63
-rw-r--r--spec/models/concerns/checksummable_spec.rb19
-rw-r--r--spec/models/lfs_object_spec.rb11
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb6
29 files changed, 358 insertions, 81 deletions
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index c5454883060..d4f7d0bc521 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -19,9 +19,13 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
[:merge_access_levels, :push_access_levels]
end
- def protected_ref_params
- params.require(:protected_branch).permit(:name,
- merge_access_levels_attributes: access_level_attributes,
- push_access_levels_attributes: access_level_attributes)
+ def protected_ref_params(*attrs)
+ attrs = ([:name,
+ merge_access_levels_attributes: access_level_attributes,
+ push_access_levels_attributes: access_level_attributes] + attrs).uniq
+
+ params.require(:protected_branch).permit(attrs)
end
end
+
+Projects::ProtectedBranchesController.prepend_if_ee('EE::Projects::ProtectedBranchesController')
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index b8beecf823c..abd19df9a3d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -376,6 +376,7 @@ class ProjectsController < Projects::ApplicationController
:tag_list,
:visibility_level,
:template_name,
+ :template_project_id,
:merge_method,
:initialize_with_readme,
diff --git a/app/models/concerns/checksummable.rb b/app/models/concerns/checksummable.rb
new file mode 100644
index 00000000000..1f76eb87aa5
--- /dev/null
+++ b/app/models/concerns/checksummable.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Checksummable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def hexdigest(path)
+ Digest::SHA256.file(path).hexdigest
+ end
+ end
+end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index d31d6226559..535c3cf2ba1 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -2,6 +2,7 @@
class LfsObject < ApplicationRecord
include AfterCommitQueue
+ include Checksummable
include EachBatch
include ObjectStorage::BackgroundMove
@@ -46,7 +47,7 @@ class LfsObject < ApplicationRecord
# rubocop: enable DestroyAll
def self.calculate_oid(path)
- Digest::SHA256.file(path).hexdigest
+ self.hexdigest(path)
end
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 8769d3eb916..1857a59e01c 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -40,6 +40,11 @@ class ProtectedBranch < ApplicationRecord
def self.protected_refs(project)
project.protected_branches.select(:name)
end
+
+ def self.branch_requires_code_owner_approval?(project, branch_name)
+ # NOOP
+ #
+ end
end
ProtectedBranch.prepend_if_ee('EE::ProtectedBranch')
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 384949ddb86..df8f9c56fa8 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Upload < ApplicationRecord
+ include Checksummable
# Upper limit for foreground checksum processing
CHECKSUM_THRESHOLD = 100.megabytes
@@ -21,10 +22,6 @@ class Upload < ApplicationRecord
# hooks are not executed and the file will not be deleted
after_destroy :delete_file!, if: -> { uploader_class <= FileUploader }
- def self.hexdigest(path)
- Digest::SHA256.file(path).hexdigest
- end
-
class << self
##
# FastDestroyAll concerns
@@ -55,7 +52,7 @@ class Upload < ApplicationRecord
self.checksum = nil
return unless needs_checksum?
- self.checksum = Digest::SHA256.file(absolute_path).hexdigest
+ self.checksum = self.class.hexdigest(absolute_path)
end
# Initialize the associated Uploader class with current model
diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb
index 91ece024e13..a207fd2c574 100644
--- a/app/services/projects/create_from_template_service.rb
+++ b/app/services/projects/create_from_template_service.rb
@@ -4,8 +4,11 @@ module Projects
class CreateFromTemplateService < BaseService
include Gitlab::Utils::StrongMemoize
+ attr_reader :template_name
+
def initialize(user, params)
@current_user, @params = user, params.to_h.dup
+ @template_name = @params.delete(:template_name).presence
end
def execute
@@ -21,12 +24,6 @@ module Projects
file&.close
end
- def template_name
- strong_memoize(:template_name) do
- params.delete(:template_name).presence
- end
- end
-
private
def validate_template!
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 728eb039b54..ef06545b27d 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -13,7 +13,7 @@ module Projects
end
def execute
- if @params[:template_name].present?
+ if create_from_template?
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
@@ -184,6 +184,10 @@ module Projects
private
+ def create_from_template?
+ @params[:template_name].present? || @params[:template_project_id].present?
+ end
+
def import_schedule
if @project.errors.empty?
@project.import_state.schedule if @project.import? && !@project.bare_repository_import?
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index 1913d06a6f8..ff8dae08ad0 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,9 +1,9 @@
.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty?
.card-header.bg-white
- Protected branch (#{@protected_branches_count})
+ = s_("ProtectedBranch|Protected branch (%{protected_branches_count})") % { protected_branches_count: @protected_branches_count }
%p.settings-message.text-center
- There are currently no protected branches, protect a branch with the form above.
+ = s_("ProtectedBranch|There are currently no protected branches, protect a branch with the form above.")
- else
%table.table.table-bordered
%colgroup
@@ -15,10 +15,17 @@
%col
%thead
%tr
- %th Protected branch (#{@protected_branches_count})
- %th Last commit
- %th Allowed to merge
- %th Allowed to push
+ %th
+ = s_("ProtectedBranch|Protected branch (%{protected_branches_count})") % { protected_branches_count: @protected_branches_count }
+ %th
+ = s_("ProtectedBranch|Last commit")
+ %th
+ = s_("ProtectedBranch|Allowed to merge")
+ %th
+ = s_("ProtectedBranch|Allowed to push")
+
+ = render_if_exists 'projects/protected_branches/ee/code_owner_approval_table_head'
+
- if can_admin_project
%th
%tbody
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index bba4949277d..f84c7b39733 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -2,7 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' }
.card
.card-header
- Protect a branch
+ = s_("ProtectedBranch|Protect a branch")
.card-body
= form_errors(@protected_branch)
.form-group.row
@@ -11,22 +11,19 @@
.col-md-10
= render partial: "projects/protected_branches/shared/dropdown", locals: { f: f }
.form-text.text-muted
- = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
- such as
- %code *-stable
- or
- %code production/*
- are supported
+ - wildcards_url = help_page_url('user/project/protected_branches', anchor: 'wildcard-protected-branches')
+ - wildcards_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: wildcards_url }
+ = (s_("ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported") % { wildcards_link_start: wildcards_link_start, wildcards_link_end: '</a>', code_tag_start: '<code>', code_tag_end: '</code>' }).html_safe
.form-group.row
%label.col-md-2.text-right{ for: 'merge_access_levels_attributes' }
- Allowed to merge:
+ = s_("ProtectedBranch|Allowed to merge:")
.col-md-10
= yield :merge_access_levels
.form-group.row
%label.col-md-2.text-right{ for: 'push_access_levels_attributes' }
- Allowed to push:
+ = s_("ProtectedBranch|Allowed to push:")
.col-md-10
= yield :push_access_levels
-
+ = render_if_exists 'projects/protected_branches/ee/code_owner_approval_form'
.card-footer
- = f.submit 'Protect', class: 'btn-success btn', disabled: true, data: { qa_selector: 'protect_button' }
+ = f.submit s_('ProtectedBranch|Protect'), class: 'btn-success btn', disabled: true, data: { qa_selector: 'protect_button' }
diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
index 81dcab1d1ab..2768e4ac5a5 100644
--- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -19,6 +19,8 @@
= yield
+ = render_if_exists 'projects/protected_branches/ee/code_owner_approval_table', protected_branch: protected_branch
+
- if can_admin_project
%td
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning"
diff --git a/changelogs/unreleased/12764-refactor-checksum-code.yml b/changelogs/unreleased/12764-refactor-checksum-code.yml
new file mode 100644
index 00000000000..b29d7bad5be
--- /dev/null
+++ b/changelogs/unreleased/12764-refactor-checksum-code.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor checksum code in uploads
+merge_request: 18065
+author: briankabiro
+type: other
diff --git a/changelogs/unreleased/sh-use-template-project-id-backend.yml b/changelogs/unreleased/sh-use-template-project-id-backend.yml
new file mode 100644
index 00000000000..00be1dcbd42
--- /dev/null
+++ b/changelogs/unreleased/sh-use-template-project-id-backend.yml
@@ -0,0 +1,5 @@
+---
+title: Add backend support for selecting custom templates by ID
+merge_request: 18178
+author:
+type: fixed
diff --git a/db/post_migrate/20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb b/db/post_migrate/20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb
new file mode 100644
index 00000000000..b109f582909
--- /dev/null
+++ b/db/post_migrate/20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class MigrateCodeOwnerApprovalStatusToProtectedBranchesInBatches < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ BATCH_SIZE = 200
+
+ class Project < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'projects'
+ self.inheritance_column = :_type_disabled
+
+ has_many :protected_branches
+ end
+
+ class ProtectedBranch < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'protected_branches'
+ self.inheritance_column = :_type_disabled
+
+ belongs_to :project
+ end
+
+ def up
+ add_concurrent_index :projects, :id, name: "temp_active_projects_with_prot_branches", where: 'archived = false and pending_delete = false and merge_requests_require_code_owner_approval = true'
+
+ ProtectedBranch
+ .joins(:project)
+ .where(projects: { archived: false, pending_delete: false, merge_requests_require_code_owner_approval: true })
+ .each_batch(of: BATCH_SIZE) do |batch|
+ batch.update_all(code_owner_approval_required: true)
+ end
+
+ remove_concurrent_index_by_name(:projects, "temp_active_projects_with_prot_branches")
+ end
+
+ def down
+ # noop
+ #
+ end
+end
diff --git a/doc/administration/auth/ldap-ee.md b/doc/administration/auth/ldap-ee.md
index 924cc153b0c..e2894318fe5 100644
--- a/doc/administration/auth/ldap-ee.md
+++ b/doc/administration/auth/ldap-ee.md
@@ -281,7 +281,7 @@ sync to run once every 2 hours at the top of the hour.
> Introduced in GitLab Enterprise Edition Starter 8.9.
Using the `external_groups` setting will allow you to mark all users belonging
-to these groups as [external users](../../user/permissions.md#external-users-permissions).
+to these groups as [external users](../../user/permissions.md#external-users-core-only).
Group membership is checked periodically through the `LdapGroupSync` background
task.
diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md
index debf1b264f9..4a750b42f65 100644
--- a/doc/api/protected_branches.md
+++ b/doc/api/protected_branches.md
@@ -296,3 +296,21 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" 'https://git
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the branch |
+
+## Require code owner approvals for a single branch
+
+Update the "code owner approval required" option for the given protected branch protected branch.
+
+```
+PATCH /projects/:id/protected_branches/:name
+```
+
+```bash
+curl --request PATCH --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/feature-branch'
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | yes | The name of the branch |
+| `code_owner_approval_required` | boolean | no | **(PREMIUM)** Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). (defaults: false)|
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 196f2afcfe3..2bf7f24bbab 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -241,58 +241,83 @@ nested groups if you have membership in one of its parents.
To learn more, read through the documentation on
[subgroups memberships](group/subgroups/index.md#membership).
-## Free Guest users **(ULTIMATE)**
-
-When a user is given `Guest` permissions on a project and/or group, and holds no
-higher permission level on any other project or group on the instance, the user
-is considered a guest user by GitLab and will not consume a license seat.
-There is no other specific "guest" designation for newly created users.
-
-If the user is assigned a higher role on any projects or groups, the user will
-take a license seat. If a user creates a project, the user becomes a `Maintainer`
-on the project, resulting in the use of a license seat.
-
-To prevent a guest user from creating projects, you can edit the user profile to mark the user as
-[External](#external-users-permissions).
-
-## External users permissions
+## External users **(CORE ONLY)**
In cases where it is desired that a user has access only to some internal or
private projects, there is the option of creating **External Users**. This
feature may be useful when for example a contractor is working on a given
project and should only have access to that project.
-External users can only access projects to which they are explicitly granted
-access, thus hiding all other internal or private ones from them. Access can be
-granted by adding the user as member to the project or group.
+External users:
+
+- Cannot create groups or projects.
+- Can only access projects to which they are explicitly granted access,
+ thus hiding all other internal or private ones from them (like being
+ logged out).
+Access can be granted by adding the user as member to the project or group.
They will, like usual users, receive a role in the project or group with all
-the abilities that are mentioned in the table above. They cannot however create
-groups or projects, and they have the same access as logged out users in all
-other cases.
+the abilities that are mentioned in the [permissions table above](#project-members-permissions).
+For example, if an external user is added as Guest, and your project is
+private, they will not have access to the code; you would need to grant the external
+user access at the Reporter level or above if you want them to have access to the code. You should
+always take into account the
+[project's visibility and permissions settings](project/settings/index.md#sharing-and-permissions)
+as well as the permission level of the user.
-An administrator can flag a user as external [through the API](../api/users.md)
-or by checking the checkbox on the admin panel. As an administrator, navigate
-to **Admin > Users** to create a new user or edit an existing one. There, you
-will find the option to flag the user as external.
+NOTE: **Note:**
+External users still count towards a license seat.
+
+An administrator can flag a user as external by either of the following methods:
-By default new users are not set as external users. This behavior can be changed
-by an administrator under **Admin > Application Settings**.
+- Either [through the API](../api/users.md#user-modification).
+- Or by navigating to the **Admin area > Overview > Users** to create a new user
+ or edit an existing one. There, you will find the option to flag the user as
+ external.
-### Default internal users
+### Setting new users to external
-The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
+By default, new users are not set as external users. This behavior can be changed
+by an administrator under the **Admin Area > Settings > General > Account and limit** page.
-New users whose email address matches the regex pattern will be set to internal by default rather than an external collaborator.
+If you change the default behavior of creating new users as external, you will
+have the option to narrow it down by defining a set of internal users.
+The **Internal users** field allows specifying an email address regex pattern to
+identify default internal users. New users whose email address matches the regex
+pattern will be set to internal by default rather than an external collaborator.
-The regex pattern format is Ruby, but it needs to be convertible to JavaScript, and the ignore case flag will be set, e.g. "/regex pattern/i".
+The regex pattern format is Ruby, but it needs to be convertible to JavaScript,
+and the ignore case flag will be set (`/regex pattern/i`). Here are some examples:
-Here are some examples:
+- Use `\.internal@domain\.com$` to mark email addresses ending with
+ `.internal@domain.com` as internal.
+- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses
+ NOT including `.ext@domain.com` as internal.
-- Use `\.internal@domain\.com$` to mark email addresses ending with ".internal@domain.com" internal.
-- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
+CAUTION: **Warning:**
+Be aware that this regex could lead to a
+[regular expression denial of service (ReDoS) attack](https://en.wikipedia.org/wiki/ReDoS).
+
+## Free Guest users **(ULTIMATE)**
-Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
+When a user is given Guest permissions on a project, group, or both, and holds no
+higher permission level on any other project or group on the GitLab instance,
+the user is considered a guest user by GitLab and will not consume a license seat.
+There is no other specific "guest" designation for newly created users.
+
+If the user is assigned a higher role on any projects or groups, the user will
+take a license seat. If a user creates a project, the user becomes a Maintainer
+on the project, resulting in the use of a license seat. Also, note that if your
+project is internal or private, Guest users will have all the abilities that are
+mentioned in the [permissions table above](#project-members-permissions) (they
+will not be able to browse the project's repository for example).
+
+TIP: **Tip:**
+To prevent a guest user from creating projects, as an admin, you can edit the
+user's profile to mark the user as [external](#external-users-core-only).
+Beware though that even if a user is external, if they already have Reporter or
+higher permissions in any project or group, they will **not** be counted as a
+free guest user.
## Auditor users **(PREMIUM ONLY)**
diff --git a/doc/user/project/img/protected_branches_list_v12_3.png b/doc/user/project/img/protected_branches_list_v12_3.png
index 365d8d99e5a..2353ddd23be 100644
--- a/doc/user/project/img/protected_branches_list_v12_3.png
+++ b/doc/user/project/img/protected_branches_list_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_page_v12_3.png b/doc/user/project/img/protected_branches_page_v12_3.png
index 17f19642552..9a194c85c41 100644
--- a/doc/user/project/img/protected_branches_page_v12_3.png
+++ b/doc/user/project/img/protected_branches_page_v12_3.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 8f5b7c3c421..6d2a0563ec1 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -21,7 +21,7 @@ Here's how the integration responds when you take the following actions in GitLa
- GitLab hyperlinks to the Jira issue.
- The Jira issue adds an issue link to the commit/MR in GitLab.
- The Jira issue adds a comment reflecting the comment made in GitLab, the comment author, and a link to the commit/MR in GitLab.
-- **Mention that a commit or MR 'closes', 'resolves', or 'fixes' a Jira issue ID**. When the commit is made on master or the change is merged to master:
+- **Mention that a commit or MR 'closes', 'resolves', or 'fixes' a Jira issue ID**. When the commit is made on the project's default branch (usually master) or the change is merged to the default branch:
- GitLab's merge request page displays a note that it "Closed" the Jira issue, with a link to the issue. (Note: Before the merge, an MR will display that it "Closes" the Jira issue.)
- The Jira issue shows the activity and the Jira issue is closed, or otherwise transitioned.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 0e60c4eca75..5f3bb83df70 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -47,7 +47,7 @@ It is important to note that we have a few types of users:
Administrator will have to be a member of it in order to have access to it
via another project's job.
-- **External users**: CI jobs created by [external users](../permissions.md#external-users-permissions) will have
+- **External users**: CI jobs created by [external users](../permissions.md#external-users-core-only) will have
access only to projects to which user has at least reporter access. This
rules out accessing all internal projects by default.
@@ -58,7 +58,7 @@ Let's consider the following scenario:
hosted in private repositories and you have multiple CI jobs that make use
of these repositories.
-1. You invite a new [external user](../permissions.md#external-users-permissions). CI jobs created by that user do not
+1. You invite a new [external user](../permissions.md#external-users-core-only). CI jobs created by that user do not
have access to internal repositories, because the user also doesn't have the
access from within GitLab. You as an employee have to grant explicit access
for this user. This allows us to prevent from accidental data leakage.
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 9a6cd3dc362..1bd272bdd0c 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -86,6 +86,20 @@ Click **Protect** and the branch will appear in the "Protected branch" list.
![Roles and users list](img/protected_branches_select_roles_and_users_list.png)
+## Code Owners approvals **(PREMIUM)**
+
+It is possible to require at least one approval for each entry in the
+[`CODEOWNERS` file](code_owners.md) that matches a file changed in
+the merge request. To enable this feature:
+
+1. Toggle the **Require approval from code owners** slider.
+
+1. Click **Protect**.
+
+When this feature is enabled, all merge requests need approval
+from one code owner per matched rule before they can be merged. Additionally,
+pushes to the protected branch are denied if a rule is matched.
+
## Wildcard protected branches
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/4665) in GitLab 8.10.
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index ca75ee906ce..c7665c20234 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -42,7 +42,7 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
- desc 'Protect a single branch or wildcard' do
+ desc 'Protect a single branch' do
success Entities::ProtectedBranch
end
params do
@@ -93,3 +93,5 @@ module API
end
end
end
+
+API::ProtectedBranches.prepend_if_ee('EE::API::ProtectedBranches')
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index 5b8c2d2f7c7..941f7178dac 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -4,6 +4,7 @@ module Gitlab
module Ci
class Trace
include ::Gitlab::ExclusiveLeaseHelpers
+ include Checksummable
LOCK_TTL = 10.minutes
LOCK_RETRIES = 2
@@ -193,7 +194,7 @@ module Gitlab
project: job.project,
file_type: :trace,
file: stream,
- file_sha256: Digest::SHA256.file(path).hexdigest)
+ file_sha256: self.class.hexdigest(path))
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c61c27033c5..8c322b57d8b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -358,6 +358,9 @@ msgstr[1] ""
msgid "%{tabname} changed"
msgstr ""
+msgid "%{template_project_id} is unknown or invalid"
+msgstr ""
+
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
msgstr[0] ""
@@ -12812,6 +12815,48 @@ msgstr ""
msgid "Protected branches"
msgstr ""
+msgid "ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to merge:"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push"
+msgstr ""
+
+msgid "ProtectedBranch|Allowed to push:"
+msgstr ""
+
+msgid "ProtectedBranch|Code owner approval"
+msgstr ""
+
+msgid "ProtectedBranch|Last commit"
+msgstr ""
+
+msgid "ProtectedBranch|Protect"
+msgstr ""
+
+msgid "ProtectedBranch|Protect a branch"
+msgstr ""
+
+msgid "ProtectedBranch|Protected branch (%{protected_branches_count})"
+msgstr ""
+
+msgid "ProtectedBranch|Pushes that change filenames matched by the CODEOWNERS file will be rejected"
+msgstr ""
+
+msgid "ProtectedBranch|Require approval from code owners:"
+msgstr ""
+
+msgid "ProtectedBranch|There are currently no protected branches, protect a branch with the form above."
+msgstr ""
+
+msgid "ProtectedBranch|Toggle code owner approval"
+msgstr ""
+
msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
msgstr ""
@@ -13468,9 +13513,6 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
-msgid "Require approval from code owners"
-msgstr ""
-
msgid "Require user password to approve"
msgstr ""
diff --git a/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb b/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
new file mode 100644
index 00000000000..67ac40d4d39
--- /dev/null
+++ b/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb')
+
+describe MigrateCodeOwnerApprovalStatusToProtectedBranchesInBatches, :migration do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:protected_branches) { table(:protected_branches) }
+
+ let(:namespace) do
+ namespaces.create!(
+ path: 'gitlab-instance-administrators',
+ name: 'GitLab Instance Administrators'
+ )
+ end
+
+ let(:project) do
+ projects.create!(
+ namespace_id: namespace.id,
+ name: 'GitLab Instance Administration'
+ )
+ end
+
+ let!(:protected_branch_1) do
+ protected_branches.create!(
+ name: "branch name",
+ project_id: project.id
+ )
+ end
+
+ describe '#up' do
+ context "when there's no projects needing approval" do
+ it "doesn't change any protected branch records" do
+ expect { migrate! }
+ .not_to change { ProtectedBranch.where(code_owner_approval_required: true).count }
+ end
+ end
+
+ context "when there's a project needing approval" do
+ let!(:project_needing_approval) do
+ projects.create!(
+ namespace_id: namespace.id,
+ name: 'GitLab Instance Administration',
+ merge_requests_require_code_owner_approval: true
+ )
+ end
+
+ let!(:protected_branch_2) do
+ protected_branches.create!(
+ name: "branch name",
+ project_id: project_needing_approval.id
+ )
+ end
+
+ it "changes N protected branch records" do
+ expect { migrate! }
+ .to change { ProtectedBranch.where(code_owner_approval_required: true).count }
+ .by(1)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/checksummable_spec.rb b/spec/models/concerns/checksummable_spec.rb
new file mode 100644
index 00000000000..017077bd297
--- /dev/null
+++ b/spec/models/concerns/checksummable_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Checksummable do
+ describe ".hexdigest" do
+ let(:fake_class) do
+ Class.new do
+ include Checksummable
+ end
+ end
+
+ it 'returns the SHA256 sum of the file' do
+ expected = Digest::SHA256.file(__FILE__).hexdigest
+
+ expect(fake_class.hexdigest(__FILE__)).to eq(expected)
+ end
+ end
+end
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
index 5b22f9e3e81..47cae5cf197 100644
--- a/spec/models/lfs_object_spec.rb
+++ b/spec/models/lfs_object_spec.rb
@@ -156,4 +156,15 @@ describe LfsObject do
end
end
end
+
+ describe ".calculate_oid" do
+ let(:lfs_object) { create(:lfs_object, :with_file) }
+
+ it 'returns SHA256 sum of the file' do
+ path = lfs_object.file.path
+ expected = Digest::SHA256.file(path).hexdigest
+
+ expect(described_class.calculate_oid(path)).to eq expected
+ end
+ end
end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index e2b4b50d41d..441d3f4ccb9 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -423,7 +423,7 @@ shared_examples_for 'trace with disabled live trace feature' do
expect(build.job_artifacts_trace.file.filename).to eq('job.log')
expect(File.exist?(src_path)).to be_falsy
expect(src_checksum)
- .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+ .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path))
expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
end
end
@@ -449,7 +449,7 @@ shared_examples_for 'trace with disabled live trace feature' do
expect(build.job_artifacts_trace.file.filename).to eq('job.log')
expect(build.old_trace).to be_nil
expect(src_checksum)
- .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+ .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path))
expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
end
end
@@ -787,7 +787,7 @@ shared_examples_for 'trace with enabled live trace feature' do
expect(build.job_artifacts_trace.file.filename).to eq('job.log')
expect(Ci::BuildTraceChunk.where(build: build)).not_to be_exist
expect(src_checksum)
- .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+ .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path))
expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
end
end