rabbitt-githooks 1.2.7 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/{hooks → .hooks}/commit-messages.rb +0 -0
- data/{hooks → .hooks}/formatting.rb +6 -15
- data/.rubocop.yml +18 -4
- data/Gemfile.lock +20 -14
- data/README.md +109 -3
- data/bin/githooks +1 -1
- data/bin/githooks-runner +1 -0
- data/lib/githooks.rb +11 -11
- data/lib/githooks/action.rb +20 -11
- data/lib/githooks/cli.rb +15 -6
- data/lib/githooks/core_ext/process.rb +7 -5
- data/lib/githooks/hook.rb +24 -7
- data/lib/githooks/repository.rb +35 -18
- data/lib/githooks/repository/config.rb +122 -115
- data/lib/githooks/repository/diff_index_entry.rb +60 -62
- data/lib/githooks/repository/file.rb +77 -75
- data/lib/githooks/repository/limiter.rb +34 -25
- data/lib/githooks/runner.rb +17 -19
- data/lib/githooks/section.rb +17 -4
- data/lib/githooks/system_utils.rb +1 -1
- data/lib/githooks/terminal_colors.rb +5 -6
- data/lib/githooks/version.rb +1 -1
- data/rabbitt-githooks.gemspec +2 -1
- metadata +35 -21
@@ -1,7 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Process
|
2
|
+
class Status
|
3
|
+
def failed?
|
4
|
+
!success?
|
5
|
+
end
|
6
|
+
alias_method :fail?, :failed?
|
7
|
+
alias_method :failure?, :failed?
|
4
8
|
end
|
5
|
-
alias_method :fail?, :failed?
|
6
|
-
alias_method :failure?, :failed?
|
7
9
|
end
|
data/lib/githooks/hook.rb
CHANGED
@@ -63,19 +63,24 @@ module GitHooks
|
|
63
63
|
end
|
64
64
|
|
65
65
|
attr_reader :sections, :phase, :repository, :repository_path
|
66
|
-
attr_accessor :args, :
|
66
|
+
attr_accessor :args, :staged, :untracked, :tracked
|
67
67
|
|
68
68
|
def initialize(phase)
|
69
69
|
@phase = phase
|
70
|
-
@sections =
|
70
|
+
@sections = {}
|
71
71
|
@commands = []
|
72
72
|
@args = []
|
73
|
-
@
|
73
|
+
@staged = true
|
74
|
+
@tracked = false
|
74
75
|
@untracked = false
|
75
76
|
|
76
77
|
repository_path = Dir.getwd # rubocop:disable UselessAssignment
|
77
78
|
end
|
78
79
|
|
80
|
+
def [](name)
|
81
|
+
@sections[name]
|
82
|
+
end
|
83
|
+
|
79
84
|
def repository_path=(path)
|
80
85
|
@repository = Repository.new(path)
|
81
86
|
end
|
@@ -86,7 +91,7 @@ module GitHooks
|
|
86
91
|
|
87
92
|
def run
|
88
93
|
# only run sections that have actions matching files in the manifest
|
89
|
-
runable_sections =
|
94
|
+
runable_sections = sections.select { |section| !section.actions.empty? }
|
90
95
|
runable_sections.collect { |section| section.run }.all?
|
91
96
|
end
|
92
97
|
|
@@ -111,6 +116,10 @@ module GitHooks
|
|
111
116
|
@commands.select { |command| command.aliases.include? name.to_s }.first
|
112
117
|
end
|
113
118
|
|
119
|
+
def sections
|
120
|
+
@sections.values
|
121
|
+
end
|
122
|
+
|
114
123
|
# DSL methods
|
115
124
|
|
116
125
|
def command(name, options = {})
|
@@ -123,7 +132,14 @@ module GitHooks
|
|
123
132
|
end
|
124
133
|
|
125
134
|
def section(name, &block)
|
126
|
-
|
135
|
+
key_name = Section.key_from_name(name)
|
136
|
+
return @sections[key_name] unless block_given?
|
137
|
+
|
138
|
+
if @sections.include? key_name
|
139
|
+
@sections[key_name].instance_eval(&block)
|
140
|
+
else
|
141
|
+
@sections[key_name] ||= Section.new(name, self, &block)
|
142
|
+
end
|
127
143
|
self
|
128
144
|
end
|
129
145
|
|
@@ -141,8 +157,9 @@ module GitHooks
|
|
141
157
|
|
142
158
|
def manifest
|
143
159
|
@files ||= repo.manifest(
|
144
|
-
|
145
|
-
|
160
|
+
staged: hook.staged,
|
161
|
+
tracked: hook.tracked,
|
162
|
+
untracked: hook.untracked
|
146
163
|
)
|
147
164
|
end
|
148
165
|
|
data/lib/githooks/repository.rb
CHANGED
@@ -22,7 +22,7 @@ require 'singleton'
|
|
22
22
|
require 'open3'
|
23
23
|
|
24
24
|
module GitHooks
|
25
|
-
class Repository
|
25
|
+
class Repository # rubocop:disable ClassLength
|
26
26
|
autoload :Config, 'githooks/repository/config'
|
27
27
|
autoload :File, 'githooks/repository/file'
|
28
28
|
autoload :Limiter, 'githooks/repository/limiter'
|
@@ -34,12 +34,12 @@ module GitHooks
|
|
34
34
|
renamed: 'R', retyped: 'T',
|
35
35
|
unknown: 'U', unmerged: 'X',
|
36
36
|
broken: 'B', untracked: '?',
|
37
|
-
any: '*'
|
37
|
+
any: '*', tracked: '^'
|
38
38
|
}.freeze unless defined? CHANGE_TYPE_SYMBOLS
|
39
39
|
|
40
40
|
CHANGE_TYPES = CHANGE_TYPE_SYMBOLS.invert.freeze unless defined? CHANGE_TYPES
|
41
41
|
|
42
|
-
DEFAULT_DIFF_INDEX_OPTIONS = { staged: true
|
42
|
+
DEFAULT_DIFF_INDEX_OPTIONS = { staged: true } unless defined? DEFAULT_DIFF_INDEX_OPTIONS
|
43
43
|
|
44
44
|
@__instance__ = {}
|
45
45
|
@__mutex__ = Mutex.new
|
@@ -91,28 +91,39 @@ module GitHooks
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def manifest(options = {})
|
94
|
-
ref
|
95
|
-
unstaged = options.delete(:unstaged)
|
96
|
-
untracked = options.delete(:untracked)
|
94
|
+
ref = options.delete(:ref)
|
97
95
|
|
98
|
-
return staged_manifest(ref: ref)
|
96
|
+
return staged_manifest(ref: ref) if options.delete(:staged)
|
99
97
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
98
|
+
files = unstaged_manifest(ref: ref)
|
99
|
+
|
100
|
+
tracked_manifest(ref: ref).each do |file|
|
101
|
+
files << file unless files.index { |f| f.path.to_s == file.path.to_s }
|
102
|
+
end if options.delete(:tracked)
|
103
|
+
|
104
|
+
untracked_manifest(ref: ref).each do |file|
|
105
|
+
files << file unless files.index { |f| f.path.to_s == file.path.to_s }
|
106
|
+
end if options.delete(:untracked)
|
107
|
+
|
108
|
+
files.sort_by! { |f| f.path.to_s }
|
109
|
+
files.uniq { |f| f.path.to_s }
|
104
110
|
end
|
105
111
|
|
106
112
|
def staged_manifest(options = {})
|
107
|
-
diff_index(options.merge(
|
113
|
+
diff_index(options.merge(staged: true))
|
108
114
|
end
|
109
115
|
alias_method :commit_manifest, :staged_manifest
|
110
116
|
|
111
117
|
def unstaged_manifest(options = {})
|
112
|
-
diff_index(options.merge(
|
118
|
+
diff_index(options.merge(staged: false))
|
113
119
|
end
|
114
120
|
|
115
|
-
def
|
121
|
+
def tracked_manifest(*)
|
122
|
+
files = git_command('ls-files', '--exclude-standard').output.strip.split(/\s*\n\s*/)
|
123
|
+
files.collect { |path| DiffIndexEntry.from_file_path(path, true).to_repo_file }
|
124
|
+
end
|
125
|
+
|
126
|
+
def untracked_manifest(*)
|
116
127
|
files = git_command('ls-files', '--others', '--exclude-standard').output.strip.split(/\s*\n\s*/)
|
117
128
|
files.collect { |path| DiffIndexEntry.from_file_path(path).to_repo_file }
|
118
129
|
end
|
@@ -122,11 +133,17 @@ module GitHooks
|
|
122
133
|
def diff_index(options = {})
|
123
134
|
options = DEFAULT_DIFF_INDEX_OPTIONS.merge(options)
|
124
135
|
|
125
|
-
|
126
|
-
|
127
|
-
|
136
|
+
if $stdout.tty? && !options[:staged]
|
137
|
+
cmd = %w(diff-files -C -M -B)
|
138
|
+
else
|
139
|
+
cmd = %w(diff-index -C -M -B)
|
140
|
+
cmd << '--cached' if options[:staged]
|
141
|
+
cmd << (options.delete(:ref) || 'HEAD')
|
142
|
+
end
|
143
|
+
|
144
|
+
cmd.compact!
|
128
145
|
|
129
|
-
raw_output = git_command(*cmd).output.strip
|
146
|
+
raw_output = git_command(*cmd.compact).output.strip
|
130
147
|
raw_output.split(/\n/).collect { |data| DiffIndexEntry.new(data).to_repo_file }
|
131
148
|
end
|
132
149
|
|
@@ -22,149 +22,156 @@ require 'singleton'
|
|
22
22
|
require 'open3'
|
23
23
|
|
24
24
|
module GitHooks
|
25
|
-
class Repository
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
OPTIONS.keys.each do |name|
|
38
|
-
method_name = name.gsub(/-/, '_')
|
39
|
-
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
40
|
-
def #{method_name}(options = {})
|
41
|
-
result = get('#{name}', options)
|
42
|
-
OPTIONS['#{name}'][:multiple] ? [result].flatten.compact : result
|
43
|
-
end
|
44
|
-
EOS
|
45
|
-
end
|
25
|
+
class Repository
|
26
|
+
class Config # rubocop:disable ClassLength
|
27
|
+
OPTIONS = {
|
28
|
+
'path' => { type: :path, multiple: false },
|
29
|
+
'script' => { type: :path, multiple: false },
|
30
|
+
'pre-run-execute' => { type: :path, multiple: true },
|
31
|
+
'post-run-execute' => { type: :path, multiple: true }
|
32
|
+
}
|
33
|
+
|
34
|
+
def initialize(path = Dir.getwd)
|
35
|
+
@repository = Repository.instance(path)
|
36
|
+
end
|
46
37
|
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
OPTIONS.keys.each do |name|
|
39
|
+
method_name = name.gsub(/-/, '_')
|
40
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
41
|
+
def #{method_name}(options = {})
|
42
|
+
result = get('#{name}', options)
|
43
|
+
OPTIONS['#{name}'][:multiple] ? [result].flatten.compact : result
|
44
|
+
end
|
45
|
+
EOS
|
46
|
+
end
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
48
|
+
def [](option)
|
49
|
+
send(option.gsub('-', '_'))
|
54
50
|
end
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
errors
|
72
|
-
|
52
|
+
def set(option, value, options = {}) # rubocop:disable CyclomaticComplexity, MethodLength
|
53
|
+
unless OPTIONS.keys.include? option
|
54
|
+
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
55
|
+
end
|
56
|
+
|
57
|
+
repo = options.delete(:repo_path) || repo_path
|
58
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
59
|
+
var_type = "--#{OPTIONS[option][:type]}"
|
60
|
+
add_type = OPTIONS[option][:multiple] ? '--add' : '--replace-all'
|
61
|
+
overwrite = !!options.delete(:overwrite)
|
62
|
+
|
63
|
+
if option == 'path'
|
64
|
+
new_path = Pathname.new(value)
|
65
|
+
errors = []
|
66
|
+
errors << 'path must be a real location' unless new_path.exist?
|
67
|
+
errors << 'path must be a directory' unless new_path.directory?
|
68
|
+
unless (new_path + 'hooks').exist? || (new_path + '.hooks').exist?
|
69
|
+
errors << 'path must have a hooks or .hooks directory in it'
|
70
|
+
end
|
71
|
+
|
72
|
+
if errors.size > 0
|
73
|
+
puts "Unable to change githooks path for [#{repo}]:"
|
74
|
+
errors.each { |error| puts " #{error}" }
|
75
|
+
fail ArgumentError
|
76
|
+
end
|
77
|
+
else
|
78
|
+
fail ArgumentError unless Pathname.new(value).executable?
|
73
79
|
end
|
74
|
-
else
|
75
|
-
fail ArgumentError unless Pathname.new(value).executable?
|
76
|
-
end
|
77
80
|
|
78
|
-
|
81
|
+
value = Pathname.new(value).realpath.to_s
|
79
82
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
83
|
+
if overwrite && !self[option].nil? && !self[option].empty?
|
84
|
+
puts "Overwrite requested for option '#{option}'" if GitHooks.verbose
|
85
|
+
unset(option, repo_path: repo, global: global)
|
86
|
+
end
|
84
87
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
option = "githooks.#{repo}.#{option}"
|
89
|
+
git_command(global ? '--global' : '--local', var_type, add_type, option, value, path: repo).tap do |result|
|
90
|
+
puts "Added option #{option} with value #{value}" if result.status.success?
|
91
|
+
end
|
88
92
|
end
|
89
|
-
end
|
90
93
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
def unset(option, *args)
|
99
|
-
unless OPTIONS.keys.include? option
|
100
|
-
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
94
|
+
def remove_section(options = {})
|
95
|
+
repo = options.delete(:repo_path) || repo_path
|
96
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
97
|
+
option = "githooks.#{repo}"
|
98
|
+
git_command(global ? '--global' : '--local', '--remove-section', option, path: repo)
|
101
99
|
end
|
102
100
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
def unset(option, *args) # rubocop:disable Style/CyclomaticComplexity
|
102
|
+
unless OPTIONS.keys.include? option
|
103
|
+
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
104
|
+
end
|
107
105
|
|
108
|
-
|
106
|
+
options = args.extract_options
|
107
|
+
repo = options.delete(:repo_path) || repo_path
|
108
|
+
global = (opt = options.delete(:global)).nil? ? false : opt
|
109
|
+
option = "githooks.#{repo}.#{option}"
|
109
110
|
|
110
|
-
|
111
|
-
git_command(global ? '--global' : '--local', '--unset-all', option, path: repo)
|
112
|
-
else
|
113
|
-
git_command(global ? '--global' : '--local', '--unset', option, value_regex, path: repo)
|
114
|
-
end.tap do |result|
|
115
|
-
puts "Unset option #{option.git_option_path_split.last}" if result.status.success?
|
116
|
-
end
|
117
|
-
end
|
111
|
+
value_regex = args.first
|
118
112
|
|
119
|
-
|
120
|
-
|
121
|
-
|
113
|
+
if options.delete(:all) || value_regex.nil?
|
114
|
+
git_command(global ? '--global' : '--local', '--unset-all', option, path: repo)
|
115
|
+
else
|
116
|
+
git_command(global ? '--global' : '--local', '--unset', option, value_regex, path: repo)
|
117
|
+
end.tap do |result|
|
118
|
+
puts "Unset option #{option.git_option_path_split.last}" if result.status.success?
|
119
|
+
end
|
122
120
|
end
|
123
121
|
|
124
|
-
|
125
|
-
|
122
|
+
def get(option, options = {})
|
123
|
+
unless OPTIONS.keys.include? option
|
124
|
+
fail ArgumentError, "Unexpected option '#{option}': expected one of: #{OPTIONS.keys.join(', ')}"
|
125
|
+
end
|
126
126
|
|
127
|
-
|
128
|
-
|
127
|
+
repo = options[:repo_path] || repo_path
|
128
|
+
githooks = list(options)['githooks']
|
129
129
|
|
130
|
-
|
131
|
-
|
132
|
-
global = (opt = options.delete(:global)).nil? ? false : opt
|
130
|
+
githooks[repo][option] if githooks && githooks[repo] && githooks[repo][option]
|
131
|
+
end
|
133
132
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
key_parts = key.git_option_path_split
|
133
|
+
def list(options = {})
|
134
|
+
config(options.delete(:repo_path) || repo_path)
|
135
|
+
end
|
138
136
|
|
139
|
-
|
140
|
-
while key_parts.size > 1 && (part = key_parts.shift)
|
141
|
-
ptr = ptr[part] ||= {} # rubocop:disable IndentationWidth
|
142
|
-
end
|
137
|
+
private
|
143
138
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
when Array then ptr[key] << value
|
148
|
-
else ptr[key] = [ptr[key], value].flatten
|
149
|
-
end
|
139
|
+
def repo_path
|
140
|
+
@repository.root_path
|
141
|
+
end
|
150
142
|
|
151
|
-
|
143
|
+
def git_command(*args)
|
144
|
+
args = ['config', *args].flatten
|
145
|
+
@repository.git_command(*args)
|
152
146
|
end
|
153
|
-
end
|
154
147
|
|
155
|
-
|
148
|
+
def config(path = nil) # rubocop:disable MethodLength, CyclomaticComplexity
|
149
|
+
path ||= repo_path
|
156
150
|
|
157
|
-
|
158
|
-
|
159
|
-
|
151
|
+
raw_config = git_command('--list', path: path).output.split("\n")
|
152
|
+
raw_config.sort.uniq.inject({}) do |hash, line|
|
153
|
+
key, value = line.split(/\s*=\s*/)
|
154
|
+
key_parts = key.git_option_path_split
|
160
155
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
156
|
+
ptr = hash[key_parts.shift] ||= {} # rubocop:disable IndentationWidth
|
157
|
+
while key_parts.size > 1 && (part = key_parts.shift)
|
158
|
+
ptr = ptr[part] ||= {} # rubocop:disable IndentationWidth
|
159
|
+
end
|
160
|
+
|
161
|
+
key = key_parts.shift
|
162
|
+
case ptr[key]
|
163
|
+
when nil then ptr[key] = value
|
164
|
+
when Array then ptr[key] << value
|
165
|
+
else ptr[key] = [ptr[key], value].flatten
|
166
|
+
end
|
165
167
|
|
166
|
-
|
167
|
-
|
168
|
+
hash
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def git
|
173
|
+
@repository.git
|
174
|
+
end
|
168
175
|
end
|
169
176
|
end
|
170
177
|
end
|