Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,11 @@ PrePush:
required: false
quiet: false

ProtectedBranches:
enabled: false
description: 'Checking for illegal pushes to protected branches'
branches: ['master']

Rspec:
enabled: false
description: 'Running rspec test suite'
Expand Down
2 changes: 1 addition & 1 deletion lib/overcommit/hook/pre_push/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ module Overcommit::Hook::PrePush
class Base < Overcommit::Hook::Base
extend Forwardable

def_delegators :@context, :remote_name, :remote_url, :pushed_commits
def_delegators :@context, :remote_name, :remote_url, :pushed_refs
end
end
27 changes: 27 additions & 0 deletions lib/overcommit/hook/pre_push/protected_branches.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Overcommit::Hook::PrePush
# Prevents destructive updates to specified branches.
class ProtectedBranches < Base
def run
return :pass unless illegal_pushes.any?

messages = illegal_pushes.map do |pushed_ref|
"Deleting or force-pushing to #{pushed_ref.remote_ref} is not allowed."
end

[:fail, messages.join("\n")]
end

private

def branches
@branches ||= config['branches']
end

def illegal_pushes
@illegal_pushes ||= pushed_refs.select do |pushed_ref|
(pushed_ref.deleted? || pushed_ref.forced?) &&
branches.any? { |branch| pushed_ref.remote_ref == "refs/heads/#{branch}" }
end
end
end
end
18 changes: 15 additions & 3 deletions lib/overcommit/hook_context/pre_push.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,25 @@ def remote_url
@args[1]
end

def pushed_commits
def pushed_refs
input_lines.map do |line|
PushedCommit.new(*line.split(' '))
PushedRef.new(*line.split(' '))
end
end

PushedCommit = Struct.new(:local_ref, :local_sha1, :remote_ref, :remote_sha1) do
PushedRef = Struct.new(:local_ref, :local_sha1, :remote_ref, :remote_sha1) do
def forced?
`git rev-list #{remote_sha1} ^#{local_sha1}`.chomp.any?
end

def created?
remote_sha1 == '0' * 40
end

def deleted?
local_sha1 == '0' * 40
end

def to_s
"#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}"
end
Expand Down
76 changes: 76 additions & 0 deletions spec/overcommit/hook/pre_push/protected_branches_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'spec_helper'

describe Overcommit::Hook::PrePush::ProtectedBranches do
let(:config) { Overcommit::ConfigurationLoader.default_configuration }
let(:context) { double('context') }
subject { described_class.new(config, context) }

let(:protected_branch) { 'master' }
let(:unprotected_branch) { 'other' }
let(:pushed_ref) { double('pushed_ref') }

before do
subject.stub(:branches).and_return([protected_branch])
subject.stub(:pushed_refs).and_return([pushed_ref])
end

context 'when pushing to unprotected branch' do
before do
pushed_ref.stub(:remote_ref).and_return("refs/heads/#{unprotected_branch}")
end

context 'when ref is not deleted or force-pushed' do
before do
pushed_ref.stub(deleted?: false, forced?: false)
end

it { should pass }
end

context 'when ref is deleted' do
before do
pushed_ref.stub(deleted?: true)
end

it { should pass }
end

context 'when ref is force-pushed' do
before do
pushed_ref.stub(deleted?: false, forced?: true)
end

it { should pass }
end
end

context 'when pushing to protected branch' do
before do
pushed_ref.stub(:remote_ref).and_return("refs/heads/#{protected_branch}")
end

context 'when ref is not deleted or force-pushed' do
before do
pushed_ref.stub(deleted?: false, forced?: false)
end

it { should pass }
end

context 'when ref is deleted' do
before do
pushed_ref.stub(deleted?: true)
end

it { should fail_hook }
end

context 'when ref is force-pushed' do
before do
pushed_ref.stub(deleted?: false, forced?: true)
end

it { should fail_hook }
end
end
end
16 changes: 8 additions & 8 deletions spec/overcommit/hook_context/pre_push_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
it { should == remote_url }
end

describe '#pushed_commits' do
subject(:pushed_commits) { context.pushed_commits }
describe '#pushed_refs' do
subject(:pushed_refs) { context.pushed_refs }

let(:local_ref) { 'refs/heads/master' }
let(:local_sha1) { random_hash }
Expand All @@ -34,12 +34,12 @@
end

it 'should parse commit info from the input' do
pushed_commits.length.should == 1
pushed_commits.each do |pushed_commit|
pushed_commit.local_ref.should == local_ref
pushed_commit.local_sha1.should == local_sha1
pushed_commit.remote_ref.should == remote_ref
pushed_commit.remote_sha1.should == remote_sha1
pushed_refs.length.should == 1
pushed_refs.each do |pushed_ref|
pushed_ref.local_ref.should == local_ref
pushed_ref.local_sha1.should == local_sha1
pushed_ref.remote_ref.should == remote_ref
pushed_ref.remote_sha1.should == remote_sha1
end
end
end
Expand Down