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
24 changes: 24 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ PostCheckout:
description: 'Generating tags file from source'
required_executable: 'ctags'

SubmoduleStatus:
enabled: false
description: 'Checking submodule status'
quiet: true
recursive: false

# Hooks that run after a commit is created.
PostCommit:
ALL:
Expand All @@ -450,6 +456,12 @@ PostCommit:
description: 'Generating tags file from source'
required_executable: 'ctags'

SubmoduleStatus:
enabled: false
description: 'Checking submodule status'
quiet: true
recursive: false

# Hooks that run after `git merge` executes successfully (no merge conflicts).
PostMerge:
ALL:
Expand All @@ -461,6 +473,12 @@ PostMerge:
description: 'Generating tags file from source'
required_executable: 'ctags'

SubmoduleStatus:
enabled: false
description: 'Checking submodule status'
quiet: true
recursive: false

# Hooks that run after a commit is modified by an amend or rebase.
PostRewrite:
ALL:
Expand All @@ -472,6 +490,12 @@ PostRewrite:
description: 'Generating tags file from source'
required_executable: 'ctags'

SubmoduleStatus:
enabled: false
description: 'Checking submodule status'
quiet: true
recursive: false

# Hooks that run during `git push`, after remote refs have been updated but
# before any objects have been transferred.
PrePush:
Expand Down
44 changes: 44 additions & 0 deletions lib/overcommit/git_repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,50 @@ module GitRepo
\s@@.*$
/x

# Regular expression used to extract information from lines of
# `git submodule status` output
SUBMODULE_STATUS_REGEX = /
^\s*(?<prefix>[-+U]?)(?<sha1>\w+)
\s(?<path>[^\s]+?)
(?:\s\((?<describe>.+)\))?$
/x

# Struct encapsulating submodule information extracted from the
# output of `git submodule status`
SubmoduleStatus = Struct.new(:prefix, :sha1, :path, :describe) do
# Returns whether the submodule has not been initialized
def uninitialized?
prefix == '-'
end

# Returns whether the submodule is out of date with the current
# index, i.e. its checked-out commit differs from that stored in
# the index of the parent repo
def outdated?
prefix == '+'
end

# Returns whether the submodule reference has a merge conflict
def merge_conflict?
prefix == 'U'
end
end

# Returns a list of SubmoduleStatus objects, one for each submodule in the
# parent repository.
#
# @option options [Boolean] recursive check submodules recursively
# @return [Array<SubmoduleStatus>]
def submodule_statuses(options = {})
flags = '--recursive' if options[:recursive]

`git submodule status #{flags}`.
scan(SUBMODULE_STATUS_REGEX).
map do |prefix, sha1, path, describe|
SubmoduleStatus.new(prefix, sha1, path, describe)
end
end

# Extract the set of modified lines from a given file.
#
# @param file_path [String]
Expand Down
30 changes: 30 additions & 0 deletions lib/overcommit/hook/post_checkout/submodule_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Overcommit::Hook::PostCheckout
# Checks the status of submodules in the current repository and
# notifies the user if any are uninitialized, out of date with
# the current index, or contain merge conflicts.
class SubmoduleStatus < Base
def run
messages = []
submodule_statuses.each do |submodule_status|
path = submodule_status.path
if submodule_status.uninitialized?
messages << "Submodule #{path} is uninitialized."
elsif submodule_status.outdated?
messages << "Submodule #{path} is out of date with the current index."
elsif submodule_status.merge_conflict?
messages << "Submodule #{path} has merge conflicts."
end
end

return :pass if messages.empty?

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

private

def submodule_statuses
Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
end
end
end
30 changes: 30 additions & 0 deletions lib/overcommit/hook/post_commit/submodule_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Overcommit::Hook::PostCommit
# Checks the status of submodules in the current repository and
# notifies the user if any are uninitialized, out of date with
# the current index, or contain merge conflicts.
class SubmoduleStatus < Base
def run
messages = []
submodule_statuses.each do |submodule_status|
path = submodule_status.path
if submodule_status.uninitialized?
messages << "Submodule #{path} is uninitialized."
elsif submodule_status.outdated?
messages << "Submodule #{path} is out of date with the current index."
elsif submodule_status.merge_conflict?
messages << "Submodule #{path} has merge conflicts."
end
end

return :pass if messages.empty?

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

private

def submodule_statuses
Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
end
end
end
30 changes: 30 additions & 0 deletions lib/overcommit/hook/post_merge/submodule_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Overcommit::Hook::PostMerge
# Checks the status of submodules in the current repository and
# notifies the user if any are uninitialized, out of date with
# the current index, or contain merge conflicts.
class SubmoduleStatus < Base
def run
messages = []
submodule_statuses.each do |submodule_status|
path = submodule_status.path
if submodule_status.uninitialized?
messages << "Submodule #{path} is uninitialized."
elsif submodule_status.outdated?
messages << "Submodule #{path} is out of date with the current index."
elsif submodule_status.merge_conflict?
messages << "Submodule #{path} has merge conflicts."
end
end

return :pass if messages.empty?

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

private

def submodule_statuses
Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're duplicating this helper Struct across each of these hooks, and since submodules are a git primitive of sorts, it seems appropriate to extract the regex, struct, and helpers into the GitRepo module. That would allow us to DRY up a lot of this.

What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I've extracted the regex, struct, and helper to GitRepo. Also made recursive a config setting.

end
end
30 changes: 30 additions & 0 deletions lib/overcommit/hook/post_rewrite/submodule_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Overcommit::Hook::PostRewrite
# Checks the status of submodules in the current repository and
# notifies the user if any are uninitialized, out of date with
# the current index, or contain merge conflicts.
class SubmoduleStatus < Base
def run
messages = []
submodule_statuses.each do |submodule_status|
path = submodule_status.path
if submodule_status.uninitialized?
messages << "Submodule #{path} is uninitialized."
elsif submodule_status.outdated?
messages << "Submodule #{path} is out of date with the current index."
elsif submodule_status.merge_conflict?
messages << "Submodule #{path} has merge conflicts."
end
end

return :pass if messages.empty?

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

private

def submodule_statuses
Overcommit::GitRepo.submodule_statuses(recursive: config['recursive'])
end
end
end
54 changes: 54 additions & 0 deletions spec/overcommit/hook/post_checkout/submodule_status_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'spec_helper'

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

let(:submodule_status) { double('submodule_status') }

before do
submodule_status.stub(:path).and_return('sub')
subject.stub(:submodule_statuses).and_return([submodule_status])
end

context 'when submodule is up to date' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: false,
merge_conflict?: false)
end

it { should pass }
end

context 'when submodule is uninitialized' do
before do
submodule_status.stub(uninitialized?: true,
outdated?: false,
merge_conflict?: false)
end

it { should warn(/uninitialized/) }
end

context 'when submodule is outdated' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: true,
merge_conflict?: false)
end

it { should warn(/out of date/) }
end

context 'when submodule has merge conflicts' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: false,
merge_conflict?: true)
end

it { should warn(/merge conflicts/) }
end
end
54 changes: 54 additions & 0 deletions spec/overcommit/hook/post_commit/submodule_status_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'spec_helper'

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

let(:submodule_status) { double('submodule_status') }

before do
submodule_status.stub(:path).and_return('sub')
subject.stub(:submodule_statuses).and_return([submodule_status])
end

context 'when submodule is up to date' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: false,
merge_conflict?: false)
end

it { should pass }
end

context 'when submodule is uninitialized' do
before do
submodule_status.stub(uninitialized?: true,
outdated?: false,
merge_conflict?: false)
end

it { should warn(/uninitialized/) }
end

context 'when submodule is outdated' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: true,
merge_conflict?: false)
end

it { should warn(/out of date/) }
end

context 'when submodule has merge conflicts' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: false,
merge_conflict?: true)
end

it { should warn(/merge conflicts/) }
end
end
54 changes: 54 additions & 0 deletions spec/overcommit/hook/post_merge/submodule_status_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'spec_helper'

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

let(:submodule_status) { double('submodule_status') }

before do
submodule_status.stub(:path).and_return('sub')
subject.stub(:submodule_statuses).and_return([submodule_status])
end

context 'when submodule is up to date' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: false,
merge_conflict?: false)
end

it { should pass }
end

context 'when submodule is uninitialized' do
before do
submodule_status.stub(uninitialized?: true,
outdated?: false,
merge_conflict?: false)
end

it { should warn(/uninitialized/) }
end

context 'when submodule is outdated' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: true,
merge_conflict?: false)
end

it { should warn(/out of date/) }
end

context 'when submodule has merge conflicts' do
before do
submodule_status.stub(uninitialized?: false,
outdated?: false,
merge_conflict?: true)
end

it { should warn(/merge conflicts/) }
end
end
Loading