Commit 1b98b5ab authored by Valery Sizov's avatar Valery Sizov

Rename git hooks to push rules

parent 4d8cd1e8
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.10.0 (unreleased) v 8.10.0 (unreleased)
- Rename Git Hooks to Push Rules
v 8.9.4 v 8.9.4
- Improve how File Lock feature works with nested items. !497 - Improve how File Lock feature works with nested items. !497
......
class Admin::GitHooksController < Admin::ApplicationController class Admin::PushRulesController < Admin::ApplicationController
before_action :git_hook before_action :push_rule
respond_to :html respond_to :html
...@@ -7,10 +7,10 @@ class Admin::GitHooksController < Admin::ApplicationController ...@@ -7,10 +7,10 @@ class Admin::GitHooksController < Admin::ApplicationController
end end
def update def update
@git_hook.update_attributes(git_hook_params.merge(is_sample: true)) @push_rule.update_attributes(push_rule_params.merge(is_sample: true))
if @git_hook.valid? if @push_rule.valid?
redirect_to admin_git_hooks_path, notice: 'Git Hooks updated successfully.' redirect_to admin_push_rules_path, notice: 'Push Rules updated successfully.'
else else
render :index render :index
end end
...@@ -18,12 +18,12 @@ class Admin::GitHooksController < Admin::ApplicationController ...@@ -18,12 +18,12 @@ class Admin::GitHooksController < Admin::ApplicationController
private private
def git_hook_params def push_rule_params
params.require(:git_hook).permit(:deny_delete_tag, :delete_branch_regex, params.require(:push_rule).permit(:deny_delete_tag, :delete_branch_regex,
:commit_message_regex, :force_push_regex, :author_email_regex, :member_check, :file_name_regex, :max_file_size) :commit_message_regex, :force_push_regex, :author_email_regex, :member_check, :file_name_regex, :max_file_size)
end end
def git_hook def push_rule
@git_hook ||= GitHook.find_or_create_by(is_sample: true) @push_rule ||= PushRule.find_or_create_by(is_sample: true)
end end
end end
class Projects::GitHooksController < Projects::ApplicationController class Projects::PushRulesController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
...@@ -7,17 +7,17 @@ class Projects::GitHooksController < Projects::ApplicationController ...@@ -7,17 +7,17 @@ class Projects::GitHooksController < Projects::ApplicationController
layout "project_settings" layout "project_settings"
def index def index
project.create_git_hook unless project.git_hook project.create_push_rule unless project.push_rule
@git_hook = project.git_hook @push_rule = project.push_rule
end end
def update def update
@git_hook = project.git_hook @push_rule = project.push_rule
@git_hook.update_attributes(git_hook_params) @push_rule.update_attributes(push_rule_params)
if @git_hook.valid? if @push_rule.valid?
redirect_to namespace_project_git_hooks_path(@project.namespace, @project), notice: 'Git Hooks updated successfully.' redirect_to namespace_project_push_rules_path(@project.namespace, @project), notice: 'Push Rules updated successfully.'
else else
render :index render :index
end end
...@@ -26,8 +26,8 @@ class Projects::GitHooksController < Projects::ApplicationController ...@@ -26,8 +26,8 @@ class Projects::GitHooksController < Projects::ApplicationController
private private
# Only allow a trusted parameter "white list" through. # Only allow a trusted parameter "white list" through.
def git_hook_params def push_rule_params
params.require(:git_hook).permit(:deny_delete_tag, :delete_branch_regex, params.require(:push_rule).permit(:deny_delete_tag, :delete_branch_regex,
:commit_message_regex, :force_push_regex, :author_email_regex, :member_check, :file_name_regex, :max_file_size) :commit_message_regex, :force_push_regex, :author_email_regex, :member_check, :file_name_regex, :max_file_size)
end end
end end
...@@ -99,7 +99,7 @@ module TabHelper ...@@ -99,7 +99,7 @@ module TabHelper
return 'active' return 'active'
end end
if ['services', 'hooks', 'deploy_keys', 'protected_branches', 'git_hooks'].include? controller.controller_name if ['services', 'hooks', 'deploy_keys', 'protected_branches', 'push_rules'].include? controller.controller_name
"active" "active"
end end
end end
......
...@@ -57,7 +57,7 @@ class Project < ActiveRecord::Base ...@@ -57,7 +57,7 @@ class Project < ActiveRecord::Base
belongs_to :namespace belongs_to :namespace
belongs_to :mirror_user, foreign_key: 'mirror_user_id', class_name: 'User' belongs_to :mirror_user, foreign_key: 'mirror_user_id', class_name: 'User'
has_one :git_hook, dependent: :destroy has_one :push_rule, dependent: :destroy
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
# Project services # Project services
......
class GitHook < ActiveRecord::Base class PushRule < ActiveRecord::Base
belongs_to :project belongs_to :project
validates :project, presence: true, unless: "is_sample?" validates :project, presence: true, unless: "is_sample?"
......
...@@ -70,8 +70,8 @@ module Files ...@@ -70,8 +70,8 @@ module Files
end end
end end
def git_hook def push_rule
project.git_hook project.push_rule
end end
end end
end end
...@@ -31,16 +31,16 @@ module MergeRequests ...@@ -31,16 +31,16 @@ module MergeRequests
def hooks_validation_pass?(merge_request) def hooks_validation_pass?(merge_request)
return true if project.merge_requests_ff_only_enabled return true if project.merge_requests_ff_only_enabled
git_hook = merge_request.project.git_hook push_rule = merge_request.project.push_rule
return true unless git_hook return true unless push_rule
unless git_hook.commit_message_allowed?(params[:commit_message]) unless push_rule.commit_message_allowed?(params[:commit_message])
merge_request.update(merge_error: "Commit message does not follow the pattern '#{git_hook.commit_message_regex}'") merge_request.update(merge_error: "Commit message does not follow the pattern '#{push_rule.commit_message_regex}'")
return false return false
end end
unless git_hook.author_email_allowed?(current_user.email) unless push_rule.author_email_allowed?(current_user.email)
merge_request.update(merge_error: "Commit author's email '#{current_user.email}' does not follow the pattern '#{git_hook.author_email_regex}'") merge_request.update(merge_error: "Commit author's email '#{current_user.email}' does not follow the pattern '#{push_rule.author_email_regex}'")
return false return false
end end
......
...@@ -95,11 +95,11 @@ module Projects ...@@ -95,11 +95,11 @@ module Projects
@project.team << [current_user, :master, current_user] @project.team << [current_user, :master, current_user]
end end
predefined_git_hook = GitHook.find_by(is_sample: true) predefined_push_rule = PushRule.find_by(is_sample: true)
if predefined_git_hook if predefined_push_rule
git_hook = predefined_git_hook.dup.tap{ |gh| gh.is_sample = false } push_rule = predefined_push_rule.dup.tap{ |gh| gh.is_sample = false }
project.git_hook = git_hook project.push_rule = push_rule
end end
end end
......
- page_title "Git Hooks" - page_title "Push Rules"
%h3.page-title %h3.page-title
Pre-defined git hooks. Pre-defined push rules.
%p.light %p.light
Rules that define what git pushes are accepted for a project. All newly created projects will use this settings. Request new rules for free by creating an issue on the <a href="https://gitlab.com/gitlab-org/gitlab-ee/issues/">GitLab EE issue tracker</a> and labeling it 'Feature proposal'. Or if you can please contribute a tested merge request. Rules that define what git pushes are accepted for a project. All newly created projects will use this settings. Request new rules for free by creating an issue on the <a href="https://gitlab.com/gitlab-org/gitlab-ee/issues/">GitLab EE issue tracker</a> and labeling it 'Feature proposal'. Or if you can please contribute a tested merge request.
%hr.clearfix %hr.clearfix
= form_for [:admin, @git_hook] do |f| = form_for [:admin, @push_rule] do |f|
-if @git_hook.errors.any? -if @push_rule.errors.any?
.alert.alert-danger .alert.alert-danger
- @git_hook.errors.full_messages.each do |msg| - @push_rule.errors.full_messages.each do |msg|
%p= msg %p= msg
= render "shared/git_hooks_form", f: f = render "shared/push_rules_form", f: f
...@@ -46,10 +46,10 @@ ...@@ -46,10 +46,10 @@
%span %span
Spam Logs Spam Logs
= nav_link(controller: :git_hooks) do = nav_link(controller: :push_rules) do
= link_to admin_git_hooks_path, title: 'Git Hooks' do = link_to admin_push_rules_path, title: 'Push Rules' do
%span %span
Git Hooks Push Rules
= nav_link(controller: :geo_nodes) do = nav_link(controller: :geo_nodes) do
= link_to admin_geo_nodes_path, title: 'Geo Nodes' do = link_to admin_geo_nodes_path, title: 'Geo Nodes' do
......
...@@ -43,10 +43,10 @@ ...@@ -43,10 +43,10 @@
= link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
%span %span
Badges Badges
= nav_link(controller: :git_hooks) do = nav_link(controller: :push_rules) do
= link_to namespace_project_git_hooks_path(@project.namespace, @project), title: "Git Hooks" do = link_to namespace_project_push_rules_path(@project.namespace, @project), title: "Push Rules" do
%span %span
Git Hooks Push Rules
= nav_link(controller: :mirrors) do = nav_link(controller: :mirrors) do
= link_to namespace_project_mirror_path(@project.namespace, @project), title: 'Mirror Repository', data: {placement: 'right'} do = link_to namespace_project_mirror_path(@project.namespace, @project), title: 'Mirror Repository', data: {placement: 'right'} do
%span %span
......
- page_title "Git Hooks" - page_title "Push Rules"
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-3 .col-lg-3
%h4.prepend-top-0 %h4.prepend-top-0
= page_title = page_title
%p.light %p.light
Git Hooks outline what is accepted for this project. You can request new rules (for free) by creating an issue on our Push Rules outline what is accepted for this project. You can request new rules (for free) by creating an issue on our
= succeed '.' do = succeed '.' do
%a{ href: "https://gitlab.com/gitlab-org/gitlab-ee/issues/" }GitLab EE issue tracker %a{ href: "https://gitlab.com/gitlab-org/gitlab-ee/issues/" }GitLab EE issue tracker
Alternatively, submit a merge request to GitLab EE. Alternatively, submit a merge request to GitLab EE.
.col-lg-9 .col-lg-9
%h5.prepend-top-0 %h5.prepend-top-0
Add new web hook Add new push rule
= form_for [@project.namespace.becomes(Namespace), @project, @git_hook] do |f| = form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f|
= form_errors(@git_hook) = form_errors(@push_rule)
= render "shared/git_hooks_form", f: f = render "shared/push_rules_form", f: f
...@@ -49,4 +49,4 @@ ...@@ -49,4 +49,4 @@
Pushes that contain added or updated files that exceed this file size are rejected. Pushes that contain added or updated files that exceed this file size are rejected.
Set to 0 to allow files of any size. Set to 0 to allow files of any size.
= f.submit "Save Git hooks", class: "btn btn-create" = f.submit "Save Push Rules", class: "btn btn-create"
...@@ -262,7 +262,7 @@ Rails.application.routes.draw do ...@@ -262,7 +262,7 @@ Rails.application.routes.draw do
end end
end end
resources :git_hooks, only: [:index, :update] resources :push_rules, only: [:index, :update]
resource :impersonation, only: :destroy resource :impersonation, only: :destroy
...@@ -783,7 +783,7 @@ Rails.application.routes.draw do ...@@ -783,7 +783,7 @@ Rails.application.routes.draw do
post :update_now post :update_now
end end
end end
resources :git_hooks, constraints: { id: /\d+/ } resources :push_rules, constraints: { id: /\d+/ }
resources :pipelines, only: [:index, :new, :create, :show] do resources :pipelines, only: [:index, :new, :create, :show] do
member do member do
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RenameGitHooksToPushRules < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
rename_table :git_hooks, :push_rules
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160628085157) do ActiveRecord::Schema.define(version: 20160705111606) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -493,23 +493,6 @@ ActiveRecord::Schema.define(version: 20160628085157) do ...@@ -493,23 +493,6 @@ ActiveRecord::Schema.define(version: 20160628085157) do
add_index "geo_nodes", ["host"], name: "index_geo_nodes_on_host", using: :btree add_index "geo_nodes", ["host"], name: "index_geo_nodes_on_host", using: :btree
add_index "geo_nodes", ["primary"], name: "index_geo_nodes_on_primary", using: :btree add_index "geo_nodes", ["primary"], name: "index_geo_nodes_on_primary", using: :btree
create_table "git_hooks", force: :cascade do |t|
t.string "force_push_regex"
t.string "delete_branch_regex"
t.string "commit_message_regex"
t.boolean "deny_delete_tag"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "author_email_regex"
t.boolean "member_check", default: false, null: false
t.string "file_name_regex"
t.boolean "is_sample", default: false
t.integer "max_file_size", default: 0, null: false
end
add_index "git_hooks", ["project_id"], name: "index_git_hooks_on_project_id", using: :btree
create_table "historical_data", force: :cascade do |t| create_table "historical_data", force: :cascade do |t|
t.date "date", null: false t.date "date", null: false
t.integer "active_user_count" t.integer "active_user_count"
...@@ -918,43 +901,45 @@ ActiveRecord::Schema.define(version: 20160628085157) do ...@@ -918,43 +901,45 @@ ActiveRecord::Schema.define(version: 20160628085157) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "creator_id" t.integer "creator_id"
t.boolean "issues_enabled", default: true, null: false t.boolean "issues_enabled", default: true, null: false
t.boolean "merge_requests_enabled", default: true, null: false t.boolean "merge_requests_enabled", default: true, null: false
t.boolean "wiki_enabled", default: true, null: false t.boolean "wiki_enabled", default: true, null: false
t.integer "namespace_id" t.integer "namespace_id"
t.boolean "snippets_enabled", default: true, null: false t.string "issues_tracker", default: "gitlab", null: false
t.string "issues_tracker_id"
t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at" t.datetime "last_activity_at"
t.string "import_url" t.string "import_url"
t.integer "visibility_level", default: 0, null: false t.integer "visibility_level", default: 0, null: false
t.boolean "archived", default: false, null: false t.boolean "archived", default: false, null: false
t.string "avatar" t.string "avatar"
t.string "import_status" t.string "import_status"
t.float "repository_size", default: 0.0 t.float "repository_size", default: 0.0
t.text "merge_requests_template" t.text "merge_requests_template"
t.integer "star_count", default: 0, null: false t.integer "star_count", default: 0, null: false
t.boolean "merge_requests_rebase_enabled", default: false t.boolean "merge_requests_rebase_enabled", default: false
t.string "import_type" t.string "import_type"
t.string "import_source" t.string "import_source"
t.integer "approvals_before_merge", default: 0, null: false t.integer "approvals_before_merge", default: 0, null: false
t.boolean "reset_approvals_on_push", default: true t.boolean "reset_approvals_on_push", default: true
t.integer "commit_count", default: 0 t.integer "commit_count", default: 0
t.boolean "merge_requests_ff_only_enabled", default: false t.boolean "merge_requests_ff_only_enabled", default: false
t.text "issues_template" t.text "issues_template"
t.boolean "mirror", default: false, null: false t.boolean "mirror", default: false, null: false
t.datetime "mirror_last_update_at" t.datetime "mirror_last_update_at"
t.datetime "mirror_last_successful_update_at" t.datetime "mirror_last_successful_update_at"
t.integer "mirror_user_id" t.integer "mirror_user_id"
t.text "import_error" t.text "import_error"
t.integer "ci_id" t.integer "ci_id"
t.boolean "builds_enabled", default: true, null: false t.boolean "builds_enabled", default: true, null: false
t.boolean "shared_runners_enabled", default: true, null: false t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token" t.string "runners_token"
t.string "build_coverage_regex" t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false t.integer "build_timeout", default: 3600, null: false
t.boolean "mirror_trigger_builds", default: false, null: false t.boolean "mirror_trigger_builds", default: false, null: false
t.boolean "pending_delete", default: false t.boolean "pending_delete", default: false
t.boolean "public_builds", default: true, null: false t.boolean "public_builds", default: true, null: false
t.integer "pushes_since_gc", default: 0 t.integer "pushes_since_gc", default: 0
t.boolean "last_repository_check_failed" t.boolean "last_repository_check_failed"
t.datetime "last_repository_check_at" t.datetime "last_repository_check_at"
...@@ -991,6 +976,23 @@ ActiveRecord::Schema.define(version: 20160628085157) do ...@@ -991,6 +976,23 @@ ActiveRecord::Schema.define(version: 20160628085157) do
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
create_table "push_rules", force: :cascade do |t|
t.string "force_push_regex"
t.string "delete_branch_regex"
t.string "commit_message_regex"
t.boolean "deny_delete_tag"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "author_email_regex"
t.boolean "member_check", default: false, null: false
t.string "file_name_regex"
t.boolean "is_sample", default: false
t.integer "max_file_size", default: 0, null: false
end
add_index "push_rules", ["project_id"], name: "index_push_rules_on_project_id", using: :btree
create_table "releases", force: :cascade do |t| create_table "releases", force: :cascade do |t|
t.string "tag" t.string "tag"
t.text "description" t.text "description"
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
- [Changing the appearance of the login page](customization/branded_login_page.md) Make the login page branded for your GitLab instance. - [Changing the appearance of the login page](customization/branded_login_page.md) Make the login page branded for your GitLab instance.
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough.
- [Email](tools/email.md) Email GitLab users from GitLab - [Email](tools/email.md) Email GitLab users from GitLab
- [Git Hooks](git_hooks/git_hooks.md) Advanced push rules for your project. - [Push Rules](push_rules/push_rules.md) Advanced push rules for your project.
- [Help message](customization/help_message.md) Set information about administrators of your GitLab instance. - [Help message](customization/help_message.md) Set information about administrators of your GitLab instance.
- [Install](install/README.md) Requirements, directory structures and installation from source. - [Install](install/README.md) Requirements, directory structures and installation from source.
- [Installing your license](license/README.md) - [Installing your license](license/README.md)
......
...@@ -1177,14 +1177,14 @@ Parameters: ...@@ -1177,14 +1177,14 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields - `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
- `sort` (optional) - Return requests sorted in `asc` or `desc` order - `sort` (optional) - Return requests sorted in `asc` or `desc` order
## Git Hooks (EE only) ## Push Rules (EE only)
### Show project git hooks ### Show project push rules
Get a project git hook. Get a project push rule.
``` ```
GET /projects/:id/git_hook GET /projects/:id/push_rule
``` ```
Parameters: Parameters:
...@@ -1202,12 +1202,12 @@ Parameters: ...@@ -1202,12 +1202,12 @@ Parameters:
} }
``` ```
### Add project git hook ### Add project push rule
Adds a git hook to a specified project. Adds a push rule to a specified project.
``` ```
POST /projects/:id/git_hook POST /projects/:id/push_rule
``` ```
Parameters: Parameters:
...@@ -1216,12 +1216,12 @@ Parameters: ...@@ -1216,12 +1216,12 @@ Parameters:
- `deny_delete_tag` - Do not allow users to remove git tags with git push - `deny_delete_tag` - Do not allow users to remove git tags with git push
- `commit_message_regex` - Commit message regex - `commit_message_regex` - Commit message regex
### Edit project git hook ### Edit project push rule
Edits a git hook for a specified project. Edits a push rule for a specified project.
``` ```
PUT /projects/:id/git_hook PUT /projects/:id/push_rule
``` ```
Parameters: Parameters:
...@@ -1230,18 +1230,18 @@ Parameters: ...@@ -1230,18 +1230,18 @@ Parameters:
- `deny_delete_tag` - Do not allow users to remove git tags with git push - `deny_delete_tag` - Do not allow users to remove git tags with git push
- `commit_message_regex` - Commit message regex - `commit_message_regex` - Commit message regex
### Delete project git hook ### Delete project push rule
Removes a git hook from a project. This is an idempotent method and can be called multiple times. Removes a push rule from a project. This is an idempotent method and can be called multiple times.
Either the git hook is available or not. Either the push rule is available or not.
``` ```
DELETE /projects/:id/git_hook DELETE /projects/:id/push_rule
``` ```
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
Note the JSON response differs if the hook is available or not. If the project hook Note the JSON response differs if the push rule is available or not. If the project push rule
is available before it is returned in the JSON response or an empty response is returned. is available before it is returned in the JSON response or an empty response is returned.
# Git Hooks Git Hooks have been renamed to [Push Rules](../push_rules/push_rules.md)
Sometimes you need additional control over pushes to your repository.
GitLab already offers protected branches.
But there are cases when you need some specific rules like preventing git tag removal or enforcing a special format for commit messages.
GitLab Enterprise Edition offers a user-friendly interface for such cases.
Git hooks are defined per project so you can have different rules applied to different projects depends on your needs.
Git hooks settings can be found at Project settings -> Git Hooks page.
## New hooks
If you are a subscriber and need a hook that is not there yet we would be glad to add it for free, please contact support to request one.
## How to use
Let's assume you have the following requirements for your workflow:
* every commit should reference a reference JIRA issue. For example: `Refactored css. Fixes JIRA-123. `
* users should not be able to remove git tags with `git push`
All you need to do is write simple regular expression that requires mention of JIRA issue in a commit message.
It can be something like this `/JIRA\-\d+/`.
Just paste regular expression into commit message textfield(without start and ending slash) and save changes.
See the screenshot below:
![screenshot](git_hooks.png)
Now when a user tries to push a commit like `Bugfix` - their push will be declined.
And pushing commit with message like `Bugfix according to JIRA-123` will be accepted.
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab **Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks. server. Only GitLab server administrators will be able to complete these tasks.
Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html).** Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Push Rules interface, please see [GitLab Enterprise Edition Push Rules](http://docs.gitlab.com/ee/push_rules/push_rules.html).**
Git natively supports hooks that are executed on different actions. Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update. Examples of server-side git hooks include pre-receive, post-receive, and update.
......
# Push Rules
Sometimes you need additional control over pushes to your repository.
GitLab already offers protected branches.
But there are cases when you need some specific rules like preventing git tag removal or enforcing a special format for commit messages.
GitLab Enterprise Edition offers a user-friendly interface for such cases.
Push Rules are defined per project so you can have different rules applied to different projects depends on your needs.
Push Rules settings can be found at Project settings -> Push Rules page.
## New hooks
If you are a subscriber and need a hook that is not there yet we would be glad to add it for free, please contact support to request one.
## How to use
Let's assume you have the following requirements for your workflow:
* every commit should reference a reference JIRA issue. For example: `Refactored css. Fixes JIRA-123. `
* users should not be able to remove git tags with `git push`
All you need to do is write simple regular expression that requires mention of JIRA issue in a commit message.
It can be something like this `/JIRA\-\d+/`.
Just paste regular expression into commit message textfield(without start and ending slash) and save changes.
See the screenshot below:
![screenshot](push_rules.png)
Now when a user tries to push a commit like `Bugfix` - their push will be declined.
And pushing commit with message like `Bugfix according to JIRA-123` will be accepted.
@admin @admin
Feature: Admin git hooks sample Feature: Admin push rules sample
Background: Background:
Given I sign in as an admin Given I sign in as an admin
And I visit git hooks page And I visit push rules page
Scenario: I can create git hook sample Scenario: I can create push rule sample
When I fill in a form and submit When I fill in a form and submit
Then I see my git hook saved Then I see my push rule saved
\ No newline at end of file
Feature: Git Hooks Feature: Push Rules
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own project "Shop" And I own project "Shop"
Scenario: I should see git hook form Scenario: I should see push rule form
When I visit project git hooks page When I visit project push rules page
Then I should see git hook form Then I should see push rule form
\ No newline at end of file
...@@ -10,11 +10,11 @@ class Spinach::Features::AdminGitHooksSample < Spinach::FeatureSteps ...@@ -10,11 +10,11 @@ class Spinach::Features::AdminGitHooksSample < Spinach::FeatureSteps
step 'I fill in a form and submit' do step 'I fill in a form and submit' do
fill_in "Commit message", with: "my_string" fill_in "Commit message", with: "my_string"
click_button "Save Git hooks" click_button "Save Push Rules"
end end
step 'I see my git hook saved' do step 'I see my push rule saved' do
visit admin_git_hooks_path visit admin_push_rules_path
expect(page).to have_selector("input[value='my_string']") expect(page).to have_selector("input[value='my_string']")
end end
end end
require 'webmock' require 'webmock'
class Spinach::Features::GitHooks < Spinach::FeatureSteps class Spinach::Features::PushRules < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedProject include SharedProject
include SharedPaths include SharedPaths
...@@ -8,12 +8,9 @@ class Spinach::Features::GitHooks < Spinach::FeatureSteps ...@@ -8,12 +8,9 @@ class Spinach::Features::GitHooks < Spinach::FeatureSteps
include RSpec::Mocks::ExampleMethods include RSpec::Mocks::ExampleMethods
include WebMock::API include WebMock::API
step 'I should see push rule form' do
step 'I should see git hook form' do expect(page).to have_selector('input#push_rule_commit_message_regex')
expect(page).to have_selector('input#git_hook_commit_message_regex')
expect(page).to have_content "Commit message" expect(page).to have_content "Commit message"
expect(page).to have_content "Commit author's email" expect(page).to have_content "Commit author's email"
end end
end end
...@@ -223,8 +223,8 @@ module SharedPaths ...@@ -223,8 +223,8 @@ module SharedPaths
visit admin_applications_path visit admin_applications_path
end end
step 'I visit git hooks page' do step 'I visit push rules page' do
visit admin_git_hooks_path visit admin_push_rules_path
end end
step 'I visit admin license page' do step 'I visit admin license page' do
...@@ -295,8 +295,8 @@ module SharedPaths ...@@ -295,8 +295,8 @@ module SharedPaths
visit group_hooks_path(@group) visit group_hooks_path(@group)
end end
step 'I visit project git hooks page' do step 'I visit project push rules page' do
visit namespace_project_git_hooks_path(@project.namespace, @project) visit namespace_project_push_rules_path(@project.namespace, @project)
end end
step 'I visit project deploy keys page' do step 'I visit project deploy keys page' do
......
...@@ -49,7 +49,8 @@ module API ...@@ -49,7 +49,8 @@ module API
mount ::API::Namespaces mount ::API::Namespaces
mount ::API::Notes mount ::API::Notes
mount ::API::ProjectHooks mount ::API::ProjectHooks
mount ::API::ProjectGitHook mount ::API::ProjectGitHook # TODO: Should be removed after 8.11 is released
mount ::API::ProjectPushRule
mount ::API::ProjectMembers mount ::API::ProjectMembers
mount ::API::ProjectSnippets mount ::API::ProjectSnippets
mount ::API::Projects mount ::API::Projects
......
...@@ -52,7 +52,7 @@ module API ...@@ -52,7 +52,7 @@ module API
expose :enable_ssl_verification expose :enable_ssl_verification
end end
class ProjectGitHook < Grape::Entity class ProjectPushRule < Grape::Entity
expose :id, :project_id, :created_at expose :id, :project_id, :created_at
expose :commit_message_regex, :deny_delete_tag expose :commit_message_regex, :deny_delete_tag
end end
......
# TODO: These end-points are deprecated and replaced with push_rules
# and should be removed after GitLab 9.0 is released
module API module API
# Projects git hook API # Projects push rule API
class ProjectGitHook < Grape::API class ProjectGitHook < Grape::API
before { authenticate! } before { authenticate! }
before { authorize_admin_project } before { authorize_admin_project }
resource :projects do resource :projects do
# Get project git hook # Get project push rule
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# Example Request: # Example Request:
# GET /projects/:id/git_hook # GET /projects/:id/push_rule
get ":id/git_hook" do get ":id/git_hook" do
@git_hooks = user_project.git_hook @push_rule = user_project.push_rule
present @git_hooks, with: Entities::ProjectGitHook present @push_rule, with: Entities::ProjectPushRule
end end
# Add git hook to project # Add push rule to project
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# Example Request: # Example Request:
# POST /projects/:id/git_hook # POST /projects/:id/push_rule
post ":id/git_hook" do post ":id/git_hook" do
attrs = attributes_for_keys [ attrs = attributes_for_keys [
:commit_message_regex, :commit_message_regex,
:deny_delete_tag :deny_delete_tag
] ]
if user_project.git_hook if user_project.push_rule
error!("Project git hook exists", 422) error!("Project push rule exists", 422)
else else
@git_hook = user_project.create_git_hook(attrs) @push_rule = user_project.create_push_rule(attrs)
present @git_hook, with: Entities::ProjectGitHook present @push_rule, with: Entities::ProjectPushRule
end end
end end
# Update an existing project git hook # Update an existing project push rule
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# Example Request: # Example Request:
# PUT /projects/:id/git_hook # PUT /projects/:id/push_rule
put ":id/git_hook" do put ":id/git_hook" do
@git_hook = user_project.git_hook @push_rule = user_project.push_rule
attrs = attributes_for_keys [ attrs = attributes_for_keys [
:commit_message_regex, :commit_message_regex,
:deny_delete_tag :deny_delete_tag
] ]
if @git_hook && @git_hook.update_attributes(attrs) if @push_rule && @push_rule.update_attributes(attrs)
present @git_hook, with: Entities::ProjectGitHook present @push_rule, with: Entities::ProjectPushRule
else else
not_found! not_found!
end end
end end
# Deletes project git hook. This is an idempotent function. # Deletes project push rule. This is an idempotent function.
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# Example Request: # Example Request:
# DELETE /projects/:id/git_hook # DELETE /projects/:id/push_rule
delete ":id/git_hook" do delete ":id/git_hook" do
@git_hook = user_project.git_hook @push_rule = user_project.push_rule
if @git_hook if @push_rule
@git_hook.destroy @push_rule.destroy
else else
not_found! not_found!
end end
......
module API
# Projects push rule API
class ProjectPushRule < Grape::API
before { authenticate! }
before { authorize_admin_project }
resource :projects do
# Get project push rule
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/push_rule
get ":id/push_rule" do
@push_rule = user_project.push_rule
present @push_rule, with: Entities::ProjectPushRule
end
# Add push rule to project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# POST /projects/:id/push_rule
post ":id/push_rule" do
attrs = attributes_for_keys [
:commit_message_regex,
:deny_delete_tag
]
if user_project.push_rule
error!("Project push rule exists", 422)
else
@push_rule = user_project.create_push_rule(attrs)
present @push_rule, with: Entities::ProjectPushRule
end
end
# Update an existing project push rule
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# PUT /projects/:id/push_rule
put ":id/push_rule" do
@push_rule = user_project.push_rule
attrs = attributes_for_keys [
:commit_message_regex,
:deny_delete_tag
]
if @push_rule && @push_rule.update_attributes(attrs)
present @push_rule, with: Entities::ProjectPushRule
else
not_found!
end
end
# Deletes project push rule. This is an idempotent function.
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id/push_rule
delete ":id/push_rule" do
@push_rule = user_project.push_rule
if @push_rule
@push_rule.destroy
else
not_found!
end
end
end
end
end
...@@ -185,9 +185,9 @@ module Gitlab ...@@ -185,9 +185,9 @@ module Gitlab
return status return status
end end
# Return build_status_object(true) if all git hook checks passed successfully # Return build_status_object(true) if all push rule checks passed successfully
# or build_status_object(false) if any hook fails # or build_status_object(false) if any hook fails
result = git_hook_check(user, project, ref, oldrev, newrev) result = push_rule_check(user, project, ref, oldrev, newrev)
if result.status && license_allows_file_locks? if result.status && license_allows_file_locks?
result = path_locks_check(user, project, ref, oldrev, newrev) result = path_locks_check(user, project, ref, oldrev, newrev)
...@@ -227,21 +227,21 @@ module Gitlab ...@@ -227,21 +227,21 @@ module Gitlab
build_status_object(true) build_status_object(true)
end end
def git_hook_check(user, project, ref, oldrev, newrev) def push_rule_check(user, project, ref, oldrev, newrev)
unless project.git_hook && newrev && oldrev unless project.push_rule && newrev && oldrev
return build_status_object(true) return build_status_object(true)
end end
git_hook = project.git_hook push_rule = project.push_rule
# Prevent tag removal # Prevent tag removal
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
if git_hook.deny_delete_tag && protected_tag?(tag_name(ref)) && Gitlab::Git.blank_ref?(newrev) if push_rule.deny_delete_tag && protected_tag?(tag_name(ref)) && Gitlab::Git.blank_ref?(newrev)
return build_status_object(false, "You can not delete tag") return build_status_object(false, "You can not delete tag")
end end
else else
# if newrev is blank, the branch was deleted # if newrev is blank, the branch was deleted
if Gitlab::Git.blank_ref?(newrev) || !git_hook.commit_validation? if Gitlab::Git.blank_ref?(newrev) || !push_rule.commit_validation?
return build_status_object(true) return build_status_object(true)
end end
...@@ -251,7 +251,7 @@ module Gitlab ...@@ -251,7 +251,7 @@ module Gitlab
commits(newrev, oldrev, project).each do |commit| commits(newrev, oldrev, project).each do |commit|
next if commit_from_annex_sync?(commit.safe_message) || old_commit?(commit) next if commit_from_annex_sync?(commit.safe_message) || old_commit?(commit)
if status_object = check_commit(commit, git_hook) if status_object = check_commit(commit, push_rule)
return status_object return status_object
end end
end end
...@@ -270,24 +270,24 @@ module Gitlab ...@@ -270,24 +270,24 @@ module Gitlab
end end
end end
# If commit does not pass git hook validation the whole push should be rejected. # If commit does not pass push rule validation the whole push should be rejected.
# This method should return nil if no error found or status object if there are some errors. # This method should return nil if no error found or status object if there are some errors.
# In case of errors - all other checks will be canceled and push will be rejected. # In case of errors - all other checks will be canceled and push will be rejected.
def check_commit(commit, git_hook) def check_commit(commit, push_rule)
unless git_hook.commit_message_allowed?(commit.safe_message) unless push_rule.commit_message_allowed?(commit.safe_message)
return build_status_object(false, "Commit message does not follow the pattern '#{git_hook.commit_message_regex}'") return build_status_object(false, "Commit message does not follow the pattern '#{push_rule.commit_message_regex}'")
end end
unless git_hook.author_email_allowed?(commit.committer_email) unless push_rule.author_email_allowed?(commit.committer_email)
return build_status_object(false, "Committer's email '#{commit.committer_email}' does not follow the pattern '#{git_hook.author_email_regex}'") return build_status_object(false, "Committer's email '#{commit.committer_email}' does not follow the pattern '#{push_rule.author_email_regex}'")
end end
unless git_hook.author_email_allowed?(commit.author_email) unless push_rule.author_email_allowed?(commit.author_email)
return build_status_object(false, "Author's email '#{commit.author_email}' does not follow the pattern '#{git_hook.author_email_regex}'") return build_status_object(false, "Author's email '#{commit.author_email}' does not follow the pattern '#{push_rule.author_email_regex}'")
end end
# Check whether author is a GitLab member # Check whether author is a GitLab member
if git_hook.member_check if push_rule.member_check
unless User.existing_member?(commit.author_email.downcase) unless User.existing_member?(commit.author_email.downcase)
return build_status_object(false, "Author '#{commit.author_email}' is not a member of team") return build_status_object(false, "Author '#{commit.author_email}' is not a member of team")
end end
...@@ -299,29 +299,29 @@ module Gitlab ...@@ -299,29 +299,29 @@ module Gitlab
end end
end end
if status_object = check_commit_diff(commit, git_hook) if status_object = check_commit_diff(commit, push_rule)
return status_object return status_object
end end
nil nil
end end
def check_commit_diff(commit, git_hook) def check_commit_diff(commit, push_rule)
if git_hook.file_name_regex.present? if push_rule.file_name_regex.present?
commit.diffs.each do |diff| commit.diffs.each do |diff|
if (diff.renamed_file || diff.new_file) && diff.new_path =~ Regexp.new(git_hook.file_name_regex) if (diff.renamed_file || diff.new_file) && diff.new_path =~ Regexp.new(push_rule.file_name_regex)
return build_status_object(false, "File name #{diff.new_path.inspect} is prohibited by the pattern '#{git_hook.file_name_regex}'") return build_status_object(false, "File name #{diff.new_path.inspect} is prohibited by the pattern '#{push_rule.file_name_regex}'")
end end
end end
end end
if git_hook.max_file_size > 0 if push_rule.max_file_size > 0
commit.diffs.each do |diff| commit.diffs.each do |diff|
next if diff.deleted_file next if diff.deleted_file
blob = project.repository.blob_at(commit.id, diff.new_path) blob = project.repository.blob_at(commit.id, diff.new_path)
if blob && blob.size && blob.size > git_hook.max_file_size.megabytes if blob && blob.size && blob.size > push_rule.max_file_size.megabytes
return build_status_object(false, "File #{diff.new_path.inspect} is larger than the allowed size of #{git_hook.max_file_size} MB") return build_status_object(false, "File #{diff.new_path.inspect} is larger than the allowed size of #{push_rule.max_file_size} MB")
end end
end end
end end
...@@ -414,7 +414,7 @@ module Gitlab ...@@ -414,7 +414,7 @@ module Gitlab
def commit_from_annex_sync?(commit_message) def commit_from_annex_sync?(commit_message)
return false unless Gitlab.config.gitlab_shell.git_annex_enabled return false unless Gitlab.config.gitlab_shell.git_annex_enabled
# Commit message starting with <git-annex in > so avoid git hooks on this # Commit message starting with <git-annex in > so avoid push rules on this
commit_message.start_with?('git-annex in') commit_message.start_with?('git-annex in')
end end
......
# Read about factories at https://github.com/thoughtbot/factory_girl # Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do FactoryGirl.define do
factory :git_hook do factory :push_rule do
force_push_regex 'feature\/.*' force_push_regex 'feature\/.*'
deny_delete_tag false deny_delete_tag false
delete_branch_regex 'bug\/.*' delete_branch_regex 'bug\/.*'
...@@ -15,7 +15,7 @@ FactoryGirl.define do ...@@ -15,7 +15,7 @@ FactoryGirl.define do
author_email_regex '.*@veryspecificedomain.com' author_email_regex '.*@veryspecificedomain.com'
end end
factory :git_hook_sample do factory :push_rule_sample do
is_sample true is_sample true
end end
end end
......
require 'spec_helper' require 'spec_helper'
feature 'Merge With Git Hooks Validation', feature: true, js: true do feature 'Merge With Push Rules Validation', feature: true, js: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public, git_hook: git_hook) } let(:project) { create(:project, :public, push_rule: push_rule) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: 'Bug NS-04') } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: 'Bug NS-04') }
before do before do
...@@ -10,7 +10,7 @@ feature 'Merge With Git Hooks Validation', feature: true, js: true do ...@@ -10,7 +10,7 @@ feature 'Merge With Git Hooks Validation', feature: true, js: true do
end end
context 'commit message is invalid' do context 'commit message is invalid' do
let(:git_hook) { create(:git_hook, :commit_message) } let(:push_rule) { create(:push_rule, :commit_message) }
before do before do
login_as user login_as user
...@@ -22,12 +22,12 @@ feature 'Merge With Git Hooks Validation', feature: true, js: true do ...@@ -22,12 +22,12 @@ feature 'Merge With Git Hooks Validation', feature: true, js: true do
expect(page).to have_content('Merge in progress') expect(page).to have_content('Merge in progress')
expect(page).to have_content('This merge request failed to be merged automatically') expect(page).to have_content('This merge request failed to be merged automatically')
expect(page).to have_content("Commit message does not follow the pattern '#{git_hook.commit_message_regex}'") expect(page).to have_content("Commit message does not follow the pattern '#{push_rule.commit_message_regex}'")
end end
end end
context 'author email is invalid' do context 'author email is invalid' do
let(:git_hook) { create(:git_hook, :author_email) } let(:push_rule) { create(:push_rule, :author_email) }
before do before do
login_as user login_as user
...@@ -39,7 +39,7 @@ feature 'Merge With Git Hooks Validation', feature: true, js: true do ...@@ -39,7 +39,7 @@ feature 'Merge With Git Hooks Validation', feature: true, js: true do
expect(page).to have_content('Merge in progress') expect(page).to have_content('Merge in progress')
expect(page).to have_content('This merge request failed to be merged automatically') expect(page).to have_content('This merge request failed to be merged automatically')
expect(page).to have_content("Commit author's email '#{user.email}' does not follow the pattern '#{git_hook.author_email_regex}'") expect(page).to have_content("Commit author's email '#{user.email}' does not follow the pattern '#{push_rule.author_email_regex}'")
end end
end end
......
...@@ -309,12 +309,12 @@ describe Gitlab::GitAccess, lib: true do ...@@ -309,12 +309,12 @@ describe Gitlab::GitAccess, lib: true do
end end
end end
describe 'and git hooks set' do describe 'and push rules set' do
before { project.create_git_hook } before { project.create_push_rule }
describe 'check commit author email' do describe 'check commit author email' do
before do before do
project.git_hook.update(author_email_regex: "@only.com") project.push_rule.update(author_email_regex: "@only.com")
end end
describe 'git annex enabled' do describe 'git annex enabled' do
...@@ -342,7 +342,7 @@ describe Gitlab::GitAccess, lib: true do ...@@ -342,7 +342,7 @@ describe Gitlab::GitAccess, lib: true do
describe 'check max file size' do describe 'check max file size' do
before do before do
allow_any_instance_of(Gitlab::Git::Blob).to receive(:size).and_return(5.megabytes.to_i) allow_any_instance_of(Gitlab::Git::Blob).to receive(:size).and_return(5.megabytes.to_i)
project.git_hook.update(max_file_size: 2) project.push_rule.update(max_file_size: 2)
end end
describe 'git annex enabled' do describe 'git annex enabled' do
...@@ -361,22 +361,22 @@ describe Gitlab::GitAccess, lib: true do ...@@ -361,22 +361,22 @@ describe Gitlab::GitAccess, lib: true do
end end
end end
describe "git_hook_check" do describe "push_rule_check" do
describe "author email check" do describe "author email check" do
it 'returns true' do it 'returns true' do
expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_truthy expect(access.push_rule_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_truthy
end end
it 'returns false' do it 'returns false' do
project.create_git_hook project.create_push_rule
project.git_hook.update(commit_message_regex: "@only.com") project.push_rule.update(commit_message_regex: "@only.com")
expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed
end end
it 'returns true for tags' do it 'returns true for tags' do
project.create_git_hook project.create_push_rule
project.git_hook.update(commit_message_regex: "@only.com") project.push_rule.update(commit_message_regex: "@only.com")
expect(access.git_hook_check(user, project, 'refs/tags/v1', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed expect(access.push_rule_check(user, project, 'refs/tags/v1', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed
end end
it 'allows githook for new branch with an old bad commit' do it 'allows githook for new branch with an old bad commit' do
...@@ -385,11 +385,11 @@ describe Gitlab::GitAccess, lib: true do ...@@ -385,11 +385,11 @@ describe Gitlab::GitAccess, lib: true do
allow(bad_commit).to receive(:refs).and_return([ref_object]) allow(bad_commit).to receive(:refs).and_return([ref_object])
allow_any_instance_of(Repository).to receive(:commits_between).and_return([bad_commit]) allow_any_instance_of(Repository).to receive(:commits_between).and_return([bad_commit])
project.create_git_hook project.create_push_rule
project.git_hook.update(commit_message_regex: "Change some files") project.push_rule.update(commit_message_regex: "Change some files")
# push to new branch, so use a blank old rev and new ref # push to new branch, so use a blank old rev and new ref
expect(access.git_hook_check(user, project, 'refs/heads/new-branch', Gitlab::Git::BLANK_SHA, '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/new-branch', Gitlab::Git::BLANK_SHA, '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed
end end
it 'allows githook for any change with an old bad commit' do it 'allows githook for any change with an old bad commit' do
...@@ -398,11 +398,11 @@ describe Gitlab::GitAccess, lib: true do ...@@ -398,11 +398,11 @@ describe Gitlab::GitAccess, lib: true do
allow(bad_commit).to receive(:refs).and_return([ref_object]) allow(bad_commit).to receive(:refs).and_return([ref_object])
allow_any_instance_of(Repository).to receive(:commits_between).and_return([bad_commit]) allow_any_instance_of(Repository).to receive(:commits_between).and_return([bad_commit])
project.create_git_hook project.create_push_rule
project.git_hook.update(commit_message_regex: "Change some files") project.push_rule.update(commit_message_regex: "Change some files")
# push to new branch, so use a blank old rev and new ref # push to new branch, so use a blank old rev and new ref
expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed
end end
it 'does not allow any change from Web UI with bad commit' do it 'does not allow any change from Web UI with bad commit' do
...@@ -412,41 +412,41 @@ describe Gitlab::GitAccess, lib: true do ...@@ -412,41 +412,41 @@ describe Gitlab::GitAccess, lib: true do
allow(bad_commit).to receive(:refs).and_return([ref_object]) allow(bad_commit).to receive(:refs).and_return([ref_object])
allow_any_instance_of(Repository).to receive(:commits_between).and_return([bad_commit]) allow_any_instance_of(Repository).to receive(:commits_between).and_return([bad_commit])
project.create_git_hook project.create_push_rule
project.git_hook.update(commit_message_regex: "Change some files") project.push_rule.update(commit_message_regex: "Change some files")
# push to new branch, so use a blank old rev and new ref # push to new branch, so use a blank old rev and new ref
expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed
end end
end end
describe "member_check" do describe "member_check" do
before do before do
project.create_git_hook project.create_push_rule
project.git_hook.update(member_check: true) project.push_rule.update(member_check: true)
end end
it 'returns false for non-member user' do it 'returns false for non-member user' do
expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).not_to be_allowed
end end
it 'returns true if committer is a gitlab member' do it 'returns true if committer is a gitlab member' do
create(:user, email: 'dmitriy.zaporozhets@gmail.com') create(:user, email: 'dmitriy.zaporozhets@gmail.com')
expect(access.git_hook_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', '570e7b2abdd848b95f2f578043fc23bd6f6fd24d')).to be_allowed
end end
end end
describe "file names check" do describe "file names check" do
it 'returns false when filename is prohibited' do it 'returns false when filename is prohibited' do
project.create_git_hook project.create_push_rule
project.git_hook.update(file_name_regex: "jpg$") project.push_rule.update(file_name_regex: "jpg$")
expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).not_to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).not_to be_allowed
end end
it 'returns true if file name is allowed' do it 'returns true if file name is allowed' do
project.create_git_hook project.create_push_rule
project.git_hook.update(file_name_regex: "exe$") project.push_rule.update(file_name_regex: "exe$")
expect(access.git_hook_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', '913c66a37b4a45b9769037c55c2d238bd0942d2e', '33f3729a45c02fc67d00adb1b8bca394b0e761d9')).to be_allowed
end end
end end
...@@ -456,22 +456,22 @@ describe Gitlab::GitAccess, lib: true do ...@@ -456,22 +456,22 @@ describe Gitlab::GitAccess, lib: true do
end end
it "returns false when size is too large" do it "returns false when size is too large" do
project.create_git_hook project.create_push_rule
project.git_hook.update(max_file_size: 1) project.push_rule.update(max_file_size: 1)
expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).not_to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).not_to be_allowed
end end
it "returns true when size is allowed" do it "returns true when size is allowed" do
project.create_git_hook project.create_push_rule
project.git_hook.update(max_file_size: 2) project.push_rule.update(max_file_size: 2)
expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).to be_allowed
end end
it "returns true when size is nil" do it "returns true when size is nil" do
allow_any_instance_of(Gitlab::Git::Blob).to receive(:size).and_return(nil) allow_any_instance_of(Gitlab::Git::Blob).to receive(:size).and_return(nil)
project.create_git_hook project.create_push_rule
project.git_hook.update(max_file_size: 2) project.push_rule.update(max_file_size: 2)
expect(access.git_hook_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).to be_allowed expect(access.push_rule_check(user, project, 'refs/heads/master', 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', '913c66a37b4a45b9769037c55c2d238bd0942d2e')).to be_allowed
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe GitHook do describe PushRule do
describe "Associations" do describe "Associations" do
it { should belong_to(:project) } it { should belong_to(:project) }
end end
......
require 'spec_helper' require 'spec_helper'
describe API::API, 'ProjectGitHook', api: true do describe API::API, 'ProjectPushRule', api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
...@@ -11,14 +11,14 @@ describe API::API, 'ProjectGitHook', api: true do ...@@ -11,14 +11,14 @@ describe API::API, 'ProjectGitHook', api: true do
project.team << [user3, :developer] project.team << [user3, :developer]
end end
describe "GET /projects/:id/git_hook" do describe "GET /projects/:id/push_rule" do
before do before do
create(:git_hook, project: project) create(:push_rule, project: project)
end end
context "authorized user" do context "authorized user" do
it "should return project git hook" do it "should return project push rule" do
get api("/projects/#{project.id}/git_hook", user) get api("/projects/#{project.id}/push_rule", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Hash expect(json_response).to be_an Hash
...@@ -27,17 +27,17 @@ describe API::API, 'ProjectGitHook', api: true do ...@@ -27,17 +27,17 @@ describe API::API, 'ProjectGitHook', api: true do
end end
context "unauthorized user" do context "unauthorized user" do
it "should not access project git hooks" do it "should not access project push rule" do
get api("/projects/#{project.id}/git_hook", user3) get api("/projects/#{project.id}/push_rule", user3)
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
end end
describe "POST /projects/:id/git_hook" do describe "POST /projects/:id/push_rule" do
context "authorized user" do context "authorized user" do
it "should add git hook to project" do it "should add push rule to project" do
post api("/projects/#{project.id}/git_hook", user), post api("/projects/#{project.id}/push_rule", user),
deny_delete_tag: true deny_delete_tag: true
expect(response.status).to eq(201) expect(response.status).to eq(201)
...@@ -48,35 +48,35 @@ describe API::API, 'ProjectGitHook', api: true do ...@@ -48,35 +48,35 @@ describe API::API, 'ProjectGitHook', api: true do
end end
context "unauthorized user" do context "unauthorized user" do
it "should not add git hook to project" do it "should not add push rule to project" do
post api("/projects/#{project.id}/git_hook", user3), post api("/projects/#{project.id}/push_rule", user3),
deny_delete_tag: true deny_delete_tag: true
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
end end
describe "POST /projects/:id/git_hook" do describe "POST /projects/:id/push_rule" do
before do before do
create(:git_hook, project: project) create(:push_rule, project: project)
end end
context "with existing git hook" do context "with existing push rule" do
it "should not add git hook to project" do it "should not add push rule to project" do
post api("/projects/#{project.id}/git_hook", user), post api("/projects/#{project.id}/push_rule", user),
deny_delete_tag: true deny_delete_tag: true
expect(response.status).to eq(422) expect(response.status).to eq(422)
end end
end end
end end
describe "PUT /projects/:id/git_hook" do describe "PUT /projects/:id/push_rule" do
before do before do
create(:git_hook, project: project) create(:push_rule, project: project)
end end
it "should update an existing project git hook" do it "should update an existing project push rule" do
put api("/projects/#{project.id}/git_hook", user), put api("/projects/#{project.id}/push_rule", user),
deny_delete_tag: false, commit_message_regex: 'Fixes \d+\..*' deny_delete_tag: false, commit_message_regex: 'Fixes \d+\..*'
expect(response.status).to eq(200) expect(response.status).to eq(200)
...@@ -85,28 +85,28 @@ describe API::API, 'ProjectGitHook', api: true do ...@@ -85,28 +85,28 @@ describe API::API, 'ProjectGitHook', api: true do
end end
end end
describe "PUT /projects/:id/git_hook" do describe "PUT /projects/:id/push_rule" do
it "should error on non existing project git hook" do it "should error on non existing project push rule" do
put api("/projects/#{project.id}/git_hook", user), put api("/projects/#{project.id}/push_rule", user),
deny_delete_tag: false, commit_message_regex: 'Fixes \d+\..*' deny_delete_tag: false, commit_message_regex: 'Fixes \d+\..*'
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "should not update git hook for unauthorized user" do it "should not update push rule for unauthorized user" do
post api("/projects/#{project.id}/git_hook", user3), post api("/projects/#{project.id}/push_rule", user3),
deny_delete_tag: true deny_delete_tag: true
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
describe "DELETE /projects/:id/git_hook" do describe "DELETE /projects/:id/push_rule" do
before do before do
create(:git_hook, project: project) create(:push_rule, project: project)
end end
context "authorized user" do context "authorized user" do
it "should delete git hook from project" do it "should delete push rule from project" do
delete api("/projects/#{project.id}/git_hook", user) delete api("/projects/#{project.id}/push_rule", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Hash expect(json_response).to be_an Hash
...@@ -115,16 +115,16 @@ describe API::API, 'ProjectGitHook', api: true do ...@@ -115,16 +115,16 @@ describe API::API, 'ProjectGitHook', api: true do
context "unauthorized user" do context "unauthorized user" do
it "should return a 403 error" do it "should return a 403 error" do
delete api("/projects/#{project.id}/git_hook", user3) delete api("/projects/#{project.id}/push_rule", user3)
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
end end
describe "DELETE /projects/:id/git_hook" do describe "DELETE /projects/:id/push_rule" do
context "for non existing git hook" do context "for non existing push rule" do
it "should delete git hook from project" do it "should delete push rule from project" do
delete api("/projects/#{project.id}/git_hook", user) delete api("/projects/#{project.id}/push_rule", user)
expect(response.status).to eq(404) expect(response.status).to eq(404)
expect(json_response).to be_an Hash expect(json_response).to be_an Hash
...@@ -132,7 +132,7 @@ describe API::API, 'ProjectGitHook', api: true do ...@@ -132,7 +132,7 @@ describe API::API, 'ProjectGitHook', api: true do
end end
it "should return a 403 error if not authorized" do it "should return a 403 error if not authorized" do
delete api("/projects/#{project.id}/git_hook", user3) delete api("/projects/#{project.id}/push_rule", user3)
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
......
...@@ -75,7 +75,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -75,7 +75,7 @@ describe 'Git HTTP requests', lib: true do
context "with correct credentials" do context "with correct credentials" do
let(:env) { { user: user.username, password: user.password } } let(:env) { { user: user.username, password: user.password } }
it "uploads get status 200 (because Git hooks do the real check)" do it "uploads get status 200 (because Push rules do the real check)" do
upload(path, env) do |response| upload(path, env) do |response|
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
...@@ -324,7 +324,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -324,7 +324,7 @@ describe 'Git HTTP requests', lib: true do
end end
end end
it "uploads get status 200 (because Git hooks do the real check)" do it "uploads get status 200 (because Push rules do the real check)" do
upload(path, user: user.username, password: user.password) do |response| upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
......
...@@ -87,7 +87,7 @@ describe MergeRequests::MergeService, services: true do ...@@ -87,7 +87,7 @@ describe MergeRequests::MergeService, services: true do
context 'commit message validation' do context 'commit message validation' do
before do before do
allow(project).to receive(:git_hook) { build(:git_hook, commit_message_regex: 'unmatched pattern .*') } allow(project).to receive(:push_rule) { build(:push_rule, commit_message_regex: 'unmatched pattern .*') }
end end
it 'returns false and saves error when invalid' do it 'returns false and saves error when invalid' do
...@@ -98,7 +98,7 @@ describe MergeRequests::MergeService, services: true do ...@@ -98,7 +98,7 @@ describe MergeRequests::MergeService, services: true do
context 'authors email validation' do context 'authors email validation' do
before do before do
allow(project).to receive(:git_hook) { build(:git_hook, author_email_regex: '.*@unmatchedemaildomain.com') } allow(project).to receive(:push_rule) { build(:push_rule, author_email_regex: '.*@unmatchedemaildomain.com') }
end end
it 'returns false and saves error when invalid' do it 'returns false and saves error when invalid' do
......
...@@ -129,13 +129,13 @@ describe Projects::CreateService, services: true do ...@@ -129,13 +129,13 @@ describe Projects::CreateService, services: true do
context "git hook sample" do context "git hook sample" do
before do before do
@git_hook_sample = create :git_hook_sample @push_rule_sample = create :push_rule_sample
end end
it "creates git hook from sample" do it "creates git hook from sample" do
git_hook = create_project(@user, @opts).git_hook push_rule = create_project(@user, @opts).push_rule
[:force_push_regex, :deny_delete_tag, :delete_branch_regex, :commit_message_regex].each do |attr_name| [:force_push_regex, :deny_delete_tag, :delete_branch_regex, :commit_message_regex].each do |attr_name|
expect(git_hook.send(attr_name)).to eq @git_hook_sample.send(attr_name) expect(push_rule.send(attr_name)).to eq @push_rule_sample.send(attr_name)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment