gitlab-org--gitlab-foss/lib/gitlab/git/hook.rb
Douwe Maan bf89e06a45 Merge branch '18627-wildcard-branch-protection' into 'master'
Allow specifying protected branches using wildcards

Closes #18627 

# Tasks

- [ ]  #18627 !4665 Allow specifying protected branches using wildcards
    - [x]  Find existing usages of protected branches
        - Protecting branches
            - `ProtectedBranchesController` is used to mark a branch protected/unprotected
            - `API::Branches` can be used to mark a branch protected/unprotected
        - Enforcing branch protection
            - `Gitlab::GitAccess` has helpers (`can_push_to_branch?`, `check`) that are used to deny pushes if a branch is protected
            - Over SSH: `gitlab-shell` receives a push, and calls `/allowed` on the GitLab API, which calls `GitAccess.check`
            - Over HTTP: 
                - `gitlab-workhorse` receives the request, and forwards it to rails
                - Rails (in the `GitHttpController#git-recieve-pack`) runs basic checks (is the user logged in, not protected branch checks) and returns ok with `GL_ID` and `RepoPath`
                - `gitlab-workhorse` looks at the response, and calls the relevant `gitlab-shell` action from `git-http/handlePostRPC`
                - Rest of this flow is the same as the SSH flow above
    - [x]  Implementation
        - [x]  Backend
            - [x]  Change `project#protected_branch?` to look at wildcard protected branches
            - [x]  Change `project#developers_can_push_to_protected_branch?`
            - [x]  Change `project#open_branches`
            - [x]  Better error message when creating a disallowed branch from the Web UI
        - [x]  Frontend
            - [x]  Protected branches page should allow typing out a wildcard pattern
            - [x]  Add help text explaining the use of wildcards
            - [x]  Show matching branches for each protected branch
                - [x]  ~~On the index page~~
                - [x]  On a show page
                - [x]  Index?
            - [x]  Can't have the "last commit" column for wildcard protected branches
    - [x]  Fix / write tests
    - [x]  What happens if a hook is missing in dev?
    - [x]  Refactor
    - [x]  Test workflows
        - Create a branch matching a wildcard pattern
        - Push to a branch matching a wildcard pattern
        - Force push to a branch matching a wildcard pattern
        - Delete a branch matching a wildcard pattern
        - [x]  Test using Web UI
        - [x]  Test over SSH
        - [x]  Test over HTTP
        - [x]  Test as developer and master
    - [x]  Investigate performance
        - [x]  Test with a large number of protected branches / branches
        - [x]  Paginate list of protected branches
        - [x]  ~~Possibly rewrite `open_branches`~~
    - [x]  Add `iid`s to existing `ProtectedBranch`es
    - [x]  Add documentation
    - [x]  Add CHANGELOG entry
    - [x]  Add screenshots
    - [x]  Make sure [build](2f753e3ed2/builds) passes
    - [x]  Assign to endboss for review
    - [x]  Address @DouweM's comments
        - [x]  `protected_branch_params`
        - [x]  `exact_match` instead of `explicit_match`
        - [x]  When would self.name be blank?
        - [x]  Move `protected_branches.each` to a partial
        - [x]  Move `matching_branches.each` to a partial
        - [x]  If the branch is in @matching_branches, it's not been removed
        - [x]  move this regex to a method and memoize it
        - [x]  `commit_sha` directly for exact matches
        - [x]  Number of matches for wildcard matches, with a link
    - [x]  Wait for [build](43f9ce0e88/builds) to pass
    - [x]  Respond to @DouweM's comments
        - [x]  Don't use iid
        - [x]  Controller should use `@project.protected_branches.new`
        - [x]  move the memoization to `def wildcard_regex`
        - [x]  render with `collection: @protected_branches`
    - [x]  Wait for [build](f7beedf122/builds) to pass
    - [x]  Wait for @DouweM's review
    - [x]  Wait for @jschatz1's review
    - [x]  Respond to @jschatz1's comments
        - [x]  Use the new dropdown style
        - [x]  description should be moved to the description section without the styling
        - [x]  Protect button should be disabled when no branch is selected
    - [x]  Update screenshots
    - [x]  Merge conflicts
    - [x]  Make sure [build](20f3cfe8d5/builds) passes
    - [ ]  Revisit performance, possibly with staging/production data
        - [ ]  Get a dump of staging / run against staging live
            - [ ]  Get SSH access to staging
    - [ ]  Wait for review/merge






# Screenshots

## Creating wildcard protected branches

![1](/uploads/9446afccfdf6fa381e00c800dd2cc82e/1.png)
![2](/uploads/0b154503b297a818d3577488c575d845/2.png)
![3](/uploads/36217f79df9e41cc1550601f02627fe8/3.png)
![4](/uploads/041ca9bd529bcfa5373fca67e917cbcb/4.png)

### Using the `GLDropdown` component

![2016-06-30_14-16-15](/uploads/508afc2a5e2463c2954641409a560d88/2016-06-30_14-16-15.gif)

## Enforcing wildcard protected branches

### From the Web UI

![Screen_Shot_2016-06-20_at_1.21.18_PM](/uploads/8b5d4b1911e9152698a0488daf1880bc/Screen_Shot_2016-06-20_at_1.21.18_PM.png)

### Over SSH

![SSH](/uploads/7365989d7e4c406ef37b6ae5106442c9/SSH.gif)

### Over HTTPS

![HTTPS](/uploads/a7c0f56ae58efcffc75e6700fa2f4ac0/HTTPS.gif)

## Listing matching branches

![Screen_Shot_2016-06-20_at_1.33.44_PM](/uploads/d054113022f5d7ec64c0e57e501ac104/Screen_Shot_2016-06-20_at_1.33.44_PM.png)

See merge request !4665
2016-07-07 22:37:30 +00:00

85 lines
2.1 KiB
Ruby

module Gitlab
module Git
class Hook
GL_PROTOCOL = 'web'.freeze
attr_reader :name, :repo_path, :path
def initialize(name, repo_path)
@name = name
@repo_path = repo_path
@path = File.join(repo_path.strip, 'hooks', name)
end
def exists?
File.exist?(path)
end
def trigger(gl_id, oldrev, newrev, ref)
return [true, nil] unless exists?
case name
when "pre-receive", "post-receive"
call_receive_hook(gl_id, oldrev, newrev, ref)
when "update"
call_update_hook(gl_id, oldrev, newrev, ref)
end
end
private
def call_receive_hook(gl_id, oldrev, newrev, ref)
changes = [oldrev, newrev, ref].join(" ")
exit_status = false
exit_message = nil
vars = {
'GL_ID' => gl_id,
'PWD' => repo_path,
'GL_PROTOCOL' => GL_PROTOCOL
}
options = {
chdir: repo_path
}
Open3.popen3(vars, path, options) do |stdin, stdout, stderr, wait_thr|
exit_status = true
stdin.sync = true
# in git, pre- and post- receive hooks may just exit without
# reading stdin. We catch the exception to avoid a broken pipe
# warning
begin
# inject all the changes as stdin to the hook
changes.lines do |line|
stdin.puts line
end
rescue Errno::EPIPE
end
stdin.close
unless wait_thr.value == 0
exit_status = false
exit_message = retrieve_error_message(stderr, stdout)
end
end
[exit_status, exit_message]
end
def call_update_hook(gl_id, oldrev, newrev, ref)
Dir.chdir(repo_path) do
stdout, stderr, status = Open3.capture3({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
[status.success?, stderr.presence || stdout]
end
end
def retrieve_error_message(stderr, stdout)
err_message = stderr.gets
err_message.blank? ? stdout.gets : err_message
end
end
end
end