rabbitt-githooks 1.2.7 → 1.3.0
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.
- 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
|