rabbitt-githooks 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.rubocop.yml +73 -0
  4. data/Gemfile +21 -0
  5. data/Gemfile.lock +45 -0
  6. data/LICENSE.txt +339 -0
  7. data/README.md +4 -0
  8. data/Rakefile +19 -0
  9. data/bin/githooks +15 -0
  10. data/bin/githooks-runner +17 -0
  11. data/hooks/commit-messages.rb +29 -0
  12. data/hooks/formatting.rb +53 -0
  13. data/lib/githooks.rb +89 -0
  14. data/lib/githooks/action.rb +150 -0
  15. data/lib/githooks/cli.rb +93 -0
  16. data/lib/githooks/commands/config.rb +107 -0
  17. data/lib/githooks/core_ext.rb +23 -0
  18. data/lib/githooks/core_ext/array.rb +3 -0
  19. data/lib/githooks/core_ext/array/extract_options.rb +5 -0
  20. data/lib/githooks/core_ext/array/min_max.rb +37 -0
  21. data/lib/githooks/core_ext/array/select_with_index.rb +13 -0
  22. data/lib/githooks/core_ext/numbers.rb +1 -0
  23. data/lib/githooks/core_ext/numbers/infinity.rb +19 -0
  24. data/lib/githooks/core_ext/pathname.rb +27 -0
  25. data/lib/githooks/core_ext/process.rb +7 -0
  26. data/lib/githooks/core_ext/string.rb +3 -0
  27. data/lib/githooks/core_ext/string/git_option_path_split.rb +6 -0
  28. data/lib/githooks/core_ext/string/inflections.rb +67 -0
  29. data/lib/githooks/core_ext/string/strip_empty_lines.rb +9 -0
  30. data/lib/githooks/error.rb +10 -0
  31. data/lib/githooks/hook.rb +159 -0
  32. data/lib/githooks/repository.rb +152 -0
  33. data/lib/githooks/repository/config.rb +170 -0
  34. data/lib/githooks/repository/diff_index_entry.rb +80 -0
  35. data/lib/githooks/repository/file.rb +125 -0
  36. data/lib/githooks/repository/limiter.rb +55 -0
  37. data/lib/githooks/runner.rb +317 -0
  38. data/lib/githooks/section.rb +98 -0
  39. data/lib/githooks/system_utils.rb +109 -0
  40. data/lib/githooks/terminal_colors.rb +63 -0
  41. data/lib/githooks/version.rb +22 -0
  42. data/rabbitt-githooks.gemspec +49 -0
  43. data/thoughts.txt +56 -0
  44. metadata +175 -0
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ githooks
2
+ ========
3
+
4
+ GitHooks Gem
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ =begin
2
+ Copyright (C) 2013 Carl P. Corliss
3
+
4
+ This program is free software; you can redistribute it and/or modify
5
+ it under the terms of the GNU General Public License as published by
6
+ the Free Software Foundation; either version 2 of the License, or
7
+ (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License along
15
+ with this program; if not, write to the Free Software Foundation, Inc.,
16
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ =end
18
+
19
+ require 'bundler/gem_tasks'
data/bin/githooks ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ENV['GITHOOKS_DEV']
4
+ $:.unshift(File.expand_path(File.join(File.expand_path(__FILE__), '/../../lib')))
5
+ end
6
+ require 'githooks'
7
+
8
+ begin
9
+ GitHooks::CLI::Base.start(ARGV)
10
+ rescue GitHooks::Error => e
11
+ puts e.message
12
+ exit 1
13
+ end
14
+
15
+ exit 0
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ENV['GITHOOKS_DEV']
4
+ $:.unshift(File.expand_path(File.join(File.expand_path(__FILE__), '/../../lib')))
5
+ end
6
+ require 'githooks'
7
+
8
+ begin
9
+ GitHooks::Runner.run(
10
+ 'args' => ARGV,
11
+ 'hook' => ENV['GITHOOKS_HOOK']
12
+ )
13
+ rescue GitHooks::Error => e
14
+ puts e.message
15
+ exit 1
16
+ end
17
+ exit 0
@@ -0,0 +1,29 @@
1
+ require 'githooks'
2
+
3
+ SIMPLE_MESSAGES = /^\s*(blah|\.+|foo|bar|baz|nits?|)\s*$/
4
+
5
+ GitHooks::Hook.register 'commit-msg' do
6
+ section 'Commit Message' do
7
+ action 'Message Length > 5 characters' do
8
+ on_argv do |args|
9
+ if args.empty?
10
+ $stderr.puts 'No commit message file passed in - are we executing in the commit-msg phase??'
11
+ return false
12
+ end
13
+
14
+ IO.read(args.first).size > 5 unless args.empty?
15
+ end
16
+ end
17
+
18
+ action 'Verify no simple commit messages' do
19
+ on_argv do |args|
20
+ if args.empty?
21
+ $stderr.puts 'No commit message file passed in - are we executing in the commit-msg phase??'
22
+ return false
23
+ end
24
+ # make sure there is at least one line that isn't a simple message
25
+ IO.read(args.first).split(/\n/).any? { |line| line !~ SIMPLE_MESSAGES }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ require 'githooks'
2
+
3
+ RUBY_FILE_REGEXP = %r{^((app|lib)/.+\.rb|bin/.+)$}.freeze
4
+
5
+ GitHooks::Hook.register 'pre-commit' do
6
+ commands 'scss-lint', :ruby, :rubocop
7
+
8
+ section 'Standards' do
9
+ action 'Validate Ruby Syntax' do
10
+ limit(:type).to :modified, :added, :untracked
11
+ limit(:path).to RUBY_FILE_REGEXP
12
+
13
+ on_each_file do |file|
14
+ ruby '-c', file.path, prefix_output: file.path
15
+ end
16
+ end
17
+
18
+ action 'Validate Ruby Standards' do
19
+ limit(:type).to :modified, :added, :untracked
20
+ limit(:path).to RUBY_FILE_REGEXP
21
+
22
+ on_all_files do |files|
23
+ rubocop '-D', '--format', 'clang', files, strip_empty_lines: true
24
+ end
25
+ end
26
+
27
+ action 'No Leading Tabs in Ruby files' do
28
+ limit(:type).to :modified, :added, :untracked
29
+ limit(:path).to RUBY_FILE_REGEXP
30
+
31
+ on_each_file do |file|
32
+ file.grep(/^[ ]*(\t+)/).tap do |matches|
33
+ matches.each do |line_number, line_text|
34
+ line_text.gsub!(/^[ ]*(\t+)/) do
35
+ underscores = '_' * $1.size
36
+ color_bright_red(underscores)
37
+ end
38
+ $stderr.printf "%s:%#{matches.last.first.to_s.size}d: %s\n", file.path, line_number, line_text
39
+ end
40
+ end.empty?
41
+ end
42
+ end
43
+
44
+ action 'Validate CSS Syntax' do
45
+ limit(:type).to :modified, :added, :untracked
46
+ limit(:path).to %r{^(app|lib)/.+css$}
47
+
48
+ on_all_files do |files|
49
+ scss_lint files.collect(&:path)
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/githooks.rb ADDED
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+ =begin
3
+ Copyright (C) 2013 Carl P. Corliss
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License along
16
+ with this program; if not, write to the Free Software Foundation, Inc.,
17
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+ =end
19
+
20
+ require 'pathname'
21
+ require 'githooks/error'
22
+ require 'githooks/core_ext'
23
+ require 'githooks/version'
24
+
25
+ module GitHooks
26
+ AUTHOR = 'Carl P. Corliss <rabbitt@gmail.com>'
27
+
28
+ autoload :Config, 'githooks/config'
29
+ autoload :CommandRunner, 'githooks/command'
30
+ autoload :Command, 'githooks/command'
31
+ autoload :CLI, 'githooks/cli'
32
+ autoload :Hook, 'githooks/hook'
33
+ autoload :Section, 'githooks/section'
34
+ autoload :Action, 'githooks/action'
35
+ autoload :Repository, 'githooks/repository'
36
+ autoload :Runner, 'githooks/runner'
37
+ autoload :SystemUtils, 'githooks/system_utils'
38
+ autoload :TerminalColors, 'githooks/terminal_colors'
39
+
40
+ class << self
41
+ attr_reader :debug, :verbose, :ignore_script
42
+
43
+ def debug?
44
+ return true if ENV['GITHOOKS_DEBUG']
45
+ return true if ARGV.include?('--debug')
46
+ return true if ARGV.include?('-d')
47
+ debug
48
+ end
49
+
50
+ def debug=(value)
51
+ @debug = !!value
52
+ end
53
+
54
+ def verbose?
55
+ return true if ENV['GITHOOKS_VERBOSE']
56
+ return true if ARGV.include?('--verbose')
57
+ return true if ARGV.include?('-v')
58
+ verbose
59
+ end
60
+
61
+ def verbose=(value)
62
+ @verbose = !!value
63
+ end
64
+
65
+ def ignore_script=(value)
66
+ @ignore_script = !!value
67
+ end
68
+
69
+ def hook_name
70
+ case GitHooks::HOOK_NAME.to_s
71
+ when 'githooks', 'irb', '', nil then 'pre-commit'
72
+ else GitHooks::HOOK_NAME
73
+ end
74
+ end
75
+ end
76
+
77
+ LIB_PATH = Pathname.new(__FILE__).dirname.realpath
78
+ GEM_PATH = LIB_PATH.parent
79
+ BIN_PATH = GEM_PATH + 'bin'
80
+
81
+ SCRIPT_PATH = Pathname.new($0)
82
+ SCRIPT_NAME = SCRIPT_PATH.basename.to_s
83
+ HOOK_NAME = SCRIPT_NAME.to_s
84
+
85
+ if ARGV.include? '--ignore-script'
86
+ ARGV.delete('--ignore-script')
87
+ self.ignore_script = true
88
+ end
89
+ end
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+ =begin
3
+ Copyright (C) 2013 Carl P. Corliss
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License along
16
+ with this program; if not, write to the Free Software Foundation, Inc.,
17
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
+ =end
19
+
20
+ require 'stringio'
21
+ require 'open3'
22
+
23
+ module GitHooks
24
+ class Action
25
+ include TerminalColors
26
+
27
+ attr_reader :title, :section, :on, :limiters, :success, :errors, :warnings
28
+ private :section, :on
29
+ alias_method :success?, :success
30
+
31
+ def initialize(title, section, &block)
32
+ fail ArgumentError, 'Missing required block' unless block_given?
33
+
34
+ @title = title
35
+ @section = section
36
+ @on = nil
37
+ @limiters = []
38
+ @success = true
39
+ @errors = []
40
+ @warnings = []
41
+
42
+ instance_eval(&block)
43
+
44
+ waiting!
45
+ end
46
+
47
+ def manifest
48
+ @manifest ||= section.hook.manifest.filter(@limiters)
49
+ end
50
+
51
+ def colored_title
52
+ status_colorize title
53
+ end
54
+
55
+ def state_symbol
56
+ return status_colorize('?') unless finished?
57
+ status_colorize success? ? '✓' : 'X'
58
+ end
59
+
60
+ %w(finished running waiting).each do |method|
61
+ define_method(:"#{method}?") { @status == method.to_sym }
62
+ define_method(:"#{method}!") { @status = method.to_sym }
63
+ end
64
+
65
+ def status_colorize(text)
66
+ return color_dark_cyan(text) unless finished?
67
+ success? ? color_bright_green(text) : color_bright_red(text)
68
+ end
69
+
70
+ def run # rubocop:disable MethodLength
71
+ warnings, errors = StringIO.new, StringIO.new
72
+
73
+ begin
74
+ running!
75
+ $stdout, $stderr = warnings, errors
76
+ @success &= @on.call
77
+ rescue => error
78
+ errors.puts "Exception thrown during action call: #{error.class.name}: #{error.message}"
79
+
80
+ if !GitHooks.debug?
81
+ hooks_files = error.backtrace.select! { |line| line =~ %r{/hooks/} }
82
+ hooks_files.collect! { |line| line.split(':')[0..1].join(':') }
83
+ errors.puts " -> in hook file:line, #{hooks_files.join("\n\t")}" unless hooks_files.empty?
84
+ else
85
+ errors.puts "\t#{error.backtrace.join("\n\t")}"
86
+ end
87
+
88
+ @success = false
89
+ ensure
90
+ @errors, @warnings = [errors, warnings].collect do |io|
91
+ io.rewind
92
+ io.read.split(/\n/)
93
+ end
94
+
95
+ $stdout, $stderr = STDOUT, STDERR
96
+ finished!
97
+ end
98
+
99
+ @success
100
+ end
101
+
102
+ def method_missing(method, *args, &block)
103
+ command = section.hook.find_command(method)
104
+ return super unless command
105
+ run_command(command, *args, &block)
106
+ end
107
+
108
+ # DSL Methods
109
+
110
+ def limit(what)
111
+ Repository::Limiter.new(what).tap do |limiter|
112
+ @limiters << limiter
113
+ end
114
+ end
115
+
116
+ def on_each_file(&block)
117
+ @on = -> { manifest.collect { |file| block.call(file) }.all? }
118
+ end
119
+
120
+ def on_all_files(&block)
121
+ @on = ->() { block.call manifest }
122
+ end
123
+
124
+ def on_argv(&block)
125
+ @on = ->() { block.call section.hook.args }
126
+ end
127
+
128
+ def on(*args, &block)
129
+ @on = ->() { block.call(*args) }
130
+ end
131
+
132
+ private
133
+
134
+ def run_command(command, *args, &block)
135
+ options = args.extract_options
136
+ prefix = options.delete(:prefix_output)
137
+ args << options
138
+
139
+ command.execute(*args, &block).tap do |res|
140
+ res.output.split(/\n/).each do |line|
141
+ $stdout.puts prefix ? "#{prefix}: #{line}" : line
142
+ end
143
+
144
+ res.error.split(/\n/).each do |line|
145
+ $stderr.puts prefix ? "#{prefix}: #{line}" : line
146
+ end
147
+ end.status.success?
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,93 @@
1
+ require 'thor'
2
+
3
+ module GitHooks
4
+ module CLI
5
+ autoload :Config, 'githooks/commands/config'
6
+
7
+ class Base < Thor
8
+ class_option :verbose, aliases: '-v', type: :boolean, desc: 'verbose output', default: false
9
+ class_option :debug, aliases: '-d', type: :boolean, desc: 'debug output', default: false
10
+
11
+ # githook attach [--hook <hook1,hookN>] [--script <path> | --path <path>] [--bootstrap <path>]
12
+ # -- attaches the listed hooks (or all if none specified) to the githook runner
13
+ # optionally sets the script XOR path
14
+ desc :attach, 'attach githooks to repository hooks'
15
+ method_option :bootstrap, type: :string, desc: 'Path to bootstrap script', default: nil
16
+ method_option :script, aliases: '-s', type: :string, desc: 'Path to script to run', default: nil
17
+ method_option :path, aliases: '-p', type: :string, desc: 'Path to library of tests', default: nil
18
+ method_option :repo, aliases: '-r', type: :string, desc: 'Path to repo to run tests on', default: Dir.getwd
19
+ method_option :hooks, { # rubocop:disable BracesAroundHashParameters
20
+ type: :array,
21
+ desc: 'Path to repo to run tests on',
22
+ enum: Hook::VALID_PHASES,
23
+ default: Hook::VALID_PHASES
24
+ }
25
+ def attach
26
+ GitHooks.verbose = !!options['verbose']
27
+ GitHooks.debug = !!options['debug']
28
+
29
+ unless options['script'] || options['path']
30
+ fail ArgumentError, %q|Neither 'path' nor 'script' were specified - please provide at least one.|
31
+ end
32
+
33
+ Runner.attach(options)
34
+ end
35
+
36
+ # githook dettach [--hook <hook1,hookN>]
37
+ # -- detaches the listed hooks, or all hooks if none specified
38
+ desc :detach, 'detach githooks from repository hooks'
39
+ method_option :repo, aliases: '-r', type: :string, desc: 'Path to repo to run tests on', default: Dir.getwd
40
+ method_option :hooks, { # rubocop:disable BracesAroundHashParameters
41
+ type: :array,
42
+ desc: 'Path to repo to run tests on',
43
+ enum: Hook::VALID_PHASES,
44
+ default: Hook::VALID_PHASES
45
+ }
46
+ def detach
47
+ GitHooks.verbose = !!options['verbose']
48
+ GitHooks.debug = !!options['debug']
49
+ Runner.detach(options['repo'], options['hooks'])
50
+ end
51
+
52
+ # githook list [--hook <hook1,hook2,hookN>]
53
+ # -- lists tests for the given hook(s), or all hooks if none specified
54
+ desc :list, 'list tests assigned to given repository hooks'
55
+ method_option :repo, aliases: '-r', type: :string, desc: 'Path to repo to run tests on', default: Dir.getwd
56
+ def list
57
+ GitHooks.verbose = !!options['verbose']
58
+ GitHooks.debug = !!options['debug']
59
+ Runner.list(options['repo'])
60
+ end
61
+
62
+ # githooks run [--hook <hook1,hookN>] [--[un]staged] [--args -- one two three ... fifty]
63
+ # -- runs the selected hooks (or pre-commit, if none specified) passing
64
+ # the argument list to the script
65
+
66
+ desc :execute, 'Runs the selected hooks, passing the argument list to the script'
67
+ method_option :unstaged, aliases: '-U', type: :boolean, desc: 'test unstaged files', default: false
68
+ method_option :untracked, aliases: '-T', type: :boolean, desc: 'test unstaged files', default: false
69
+ method_option :script, aliases: '-s', type: :string, desc: 'Path to script to run', default: nil
70
+ method_option :path, aliases: '-p', type: :string, desc: 'Path to library of tests', default: nil
71
+ method_option :repo, aliases: '-r', type: :string, desc: 'Path to repo to run tests on', default: Dir.getwd
72
+ method_option :'skip-pre', type: :boolean, desc: 'Skip PreRun Scripts', default: false
73
+ method_option :'skip-post', type: :boolean, desc: 'Skip PostRun Scripts', default: false
74
+ method_option :'skip-bundler', type: :boolean, desc: %Q|Don't load bundler gemfile|, default: false
75
+ method_option :args, type: :array, desc: 'Args to pass to pre/post scripts and main testing script', default: []
76
+ def execute(*args)
77
+ GitHooks.verbose = !!options['verbose']
78
+ GitHooks.debug = !!options['debug']
79
+
80
+ opts = (options).dup
81
+ if opts['untracked'] && !opts['unstaged']
82
+ warn %q|--untracked requires --unstaged. Dropping option --untracked...|
83
+ opts['untracked'] = false
84
+ end
85
+
86
+ GitHooks::Runner.run(opts)
87
+ end
88
+
89
+ desc :config, 'manage githooks configuration'
90
+ subcommand :config, Config
91
+ end
92
+ end
93
+ end