• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sds / overcommit / 42866418136

10 Nov 2026 05:34PM UTC coverage: 94.245% (+0.7%) from 93.508%
42866418136

Pull #931

github

web-flow
Merge efe98e579 into 66494580a
Pull Request #931: Support gem hook plugins with explicit sources and signature approvals

139 of 143 new or added lines in 10 files covered. (97.2%)

3603 of 3823 relevant lines covered (94.25%)

2930.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.38
/lib/overcommit/cli.rb
1
# frozen_string_literal: true
2

3
require 'overcommit'
57 ✔
4
require 'optparse'
57 ✔
5

6
module Overcommit
57 ✔
7
  # Responsible for parsing command-line options and executing appropriate
8
  # application logic based on those options.
9
  class CLI # rubocop:disable Metrics/ClassLength
57 ✔
10
    def initialize(arguments, input, logger)
57 ✔
11
      @arguments = arguments
66 ✔
12
      @cli_options = {}
66 ✔
13
      @input     = input
66 ✔
14
      @log       = logger
66 ✔
15
      @options   = {}
66 ✔
16

17
      Overcommit::Utils.log = logger
66 ✔
18
    end
19

20
    def run # rubocop:disable Metrics/MethodLength
57 ✔
21
      parse_arguments
66 ✔
22

23
      case @options[:action]
66 ✔
24
      when :install, :uninstall
25
        install_or_uninstall
36 ✔
26
      when :template_dir
27
        print_template_directory_path
6 ✔
28
      when :sign
29
        sign
×
30
      when :run_all
31
        run_all
12 ✔
32
      when :diff
33
        diff
12 ✔
34
      end
35
    rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e
36
      puts e
×
37
      exit 78 # EX_CONFIG
×
38
    rescue Overcommit::Exceptions::InvalidHookSignature
NEW
39
      exit 78 # EX_CONFIG
×
40
    rescue Overcommit::Exceptions::ConfigurationError,
41
           Overcommit::Exceptions::HookLoadError => e
NEW
42
      puts e
×
NEW
43
      exit 78 # EX_CONFIG
×
44
    rescue Overcommit::Exceptions::HookContextLoadError => e
45
      puts e
×
46
      exit 64 # EX_USAGE
×
47
    end
48

49
    private
57 ✔
50

51
    attr_reader :log
57 ✔
52

53
    def parse_arguments
57 ✔
54
      @parser = create_option_parser
66 ✔
55

56
      begin
57
        @parser.parse!(@arguments, into: @cli_options)
66 ✔
58

59
        # Default action is to install
60
        @options[:action] ||= :install
66 ✔
61

62
        # Unconsumed arguments are our targets
63
        @options[:targets] = @arguments
66 ✔
64
      rescue OptionParser::InvalidOption => e
65
        print_help @parser.help, e
×
66
      end
67
    end
68

69
    def create_option_parser
57 ✔
70
      OptionParser.new do |opts|
66 ✔
71
        opts.banner = "Usage: #{opts.program_name} [options] [target-repo]"
66 ✔
72

73
        add_information_options(opts)
66 ✔
74
        add_installation_options(opts)
66 ✔
75
        add_other_options(opts)
66 ✔
76
      end
77
    end
78

79
    def add_information_options(opts)
57 ✔
80
      opts.on_tail('-h', '--help', 'Show this message') do
66 ✔
81
        print_help opts.help
×
82
      end
83

84
      opts.on_tail('-v', '--version', 'Show version') do
66 ✔
85
        print_version(opts.program_name)
×
86
      end
87

88
      opts.on_tail('-l', '--list-hooks', 'List installed hooks') do
66 ✔
89
        print_installed_hooks
6 ✔
90
      end
91
    end
92

93
    def add_installation_options(opts)
57 ✔
94
      opts.on('-u', '--uninstall', 'Remove Overcommit hooks from a repository') do
66 ✔
95
        @options[:action] = :uninstall
12 ✔
96
      end
97

98
      opts.on('-i', '--install', 'Install Overcommit hooks in a repository') do
66 ✔
99
        @options[:action] = :install
12 ✔
100
      end
101

102
      opts.on('-f', '--force', 'Overwrite any previously installed hooks') do
66 ✔
103
        @options[:force] = true
×
104
      end
105

106
      opts.on('-r [hook]', '--run [hook]', 'Run specified hook against all git tracked files. Defaults to `pre_commit`.') do |arg| # rubocop:disable Layout/LineLength
66 ✔
107
        @options[:action] = :run_all
12 ✔
108
        @options[:hook_to_run] = arg ? arg.to_s : 'run-all'
12 ✔
109
      end
110

111
      opts.on('--diff [ref]', 'Run pre_commit hooks against the diff between a given ref. Defaults to `main`.') do |arg| # rubocop:disable Layout/LineLength
66 ✔
112
        @options[:action] = :diff
12 ✔
113
        arg
12 ✔
114
      end
115
    end
116

117
    def add_other_options(opts)
57 ✔
118
      opts.on('-s', '--sign [hook]', 'Update hook signatures', String) do |hook_to_sign|
66 ✔
119
        @options[:hook_to_sign] = hook_to_sign if hook_to_sign.is_a?(String)
×
120
        @options[:action] = :sign
×
121
      end
122

123
      opts.on('-t', '--template-dir', 'Print location of template directory') do
66 ✔
124
        @options[:action] = :template_dir
6 ✔
125
      end
126
    end
127

128
    def install_or_uninstall
57 ✔
129
      if Array(@options[:targets]).empty?
36 ✔
130
        @options[:targets] = [Overcommit::Utils.repo_root].compact
24 ✔
131
      end
132

133
      if @options[:targets].empty?
36 ✔
134
        log.warning 'You are not in a git repository.'
×
135
        log.log 'You must either specify the path to a repository or ' \
×
136
                'change your current directory to a repository.'
137
        halt 64 # EX_USAGE
×
138
      end
139

140
      @options[:targets].each do |target|
36 ✔
141
        Installer.new(log).run(target, @options)
36 ✔
142
      rescue Overcommit::Exceptions::InvalidGitRepo => e
143
        log.warning "Invalid repo #{target}: #{e}"
6 ✔
144
        halt 69 # EX_UNAVAILABLE
6 ✔
145
      rescue Overcommit::Exceptions::PreExistingHooks => e
146
        log.warning "Unable to install into #{target}: #{e}"
×
147
        halt 73 # EX_CANTCREAT
×
148
      end
149
    end
150

151
    def print_template_directory_path
57 ✔
152
      puts File.join(Overcommit::HOME, 'template-dir')
6 ✔
153
      halt
6 ✔
154
    end
155

156
    def print_help(message, error = nil)
57 ✔
157
      log.error "#{error}\n" if error
×
158
      log.log message
×
159
      halt(error ? 64 : 0) # 64 = EX_USAGE
×
160
    end
161

162
    def print_version(program_name)
57 ✔
163
      log.log "#{program_name} #{Overcommit::VERSION}"
×
164
      halt
×
165
    end
166

167
    # Prints the hooks available in the current repo and whether they're
168
    # enabled/disabled.
169
    def print_installed_hooks
57 ✔
170
      config.all_hook_configs.each do |hook_type, hook_configs|
6 ✔
171
        print_hooks_for_hook_type(config, hook_configs, hook_type)
54 ✔
172
      end
173

174
      halt
6 ✔
175
    end
176

177
    def print_hooks_for_hook_type(repo_config, hook_configs, hook_type)
57 ✔
178
      log.log "#{hook_type}:"
54 ✔
179
      hook_configs.each do |hook_name, config|
54 ✔
180
        log.partial "  #{hook_name}: "
1,008 ✔
181

182
        if config['enabled']
1,008 ✔
183
          log.success('enabled', true)
60 ✔
184
        else
185
          log.error('disabled', true)
948 ✔
186
        end
187

188
        if repo_config.plugin_hook?(hook_type, hook_name)
1,008 ✔
189
          log.warning(' (plugin)')
×
190
        else
191
          log.newline
1,008 ✔
192
        end
193
      end
194
    end
195

196
    def sign
57 ✔
197
      if @options[:hook_to_sign]
×
198
        context = Overcommit::HookContext.create(@options[:hook_to_sign],
×
199
                                                 config,
200
                                                 @arguments,
201
                                                 @input)
202
        Overcommit::HookLoader::PluginHookLoader.new(config,
×
203
                                                     context,
204
                                                     log).update_signatures
205
      else
206
        log.log 'Updating signature for configuration file...'
×
207
        config(verify: false).update_signature!
×
208
      end
209

210
      halt
×
211
    end
212

213
    def run_all
57 ✔
214
      empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input
12 ✔
215
      context = Overcommit::HookContext.create(@options[:hook_to_run], config, @arguments, empty_stdin) # rubocop:disable Layout/LineLength
12 ✔
216
      config.apply_environment!(context, ENV)
12 ✔
217

218
      printer = Overcommit::Printer.new(config, log, context)
12 ✔
219
      runner  = Overcommit::HookRunner.new(config, log, context, printer)
12 ✔
220

221
      status = runner.run
12 ✔
222

223
      halt(status ? 0 : 65)
12 ✔
224
    end
225

226
    def diff
57 ✔
227
      empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input
12 ✔
228
      context = Overcommit::HookContext.create('diff', config, @arguments, empty_stdin, **@cli_options) # rubocop:disable Layout/LineLength
12 ✔
229
      config.apply_environment!(context, ENV)
12 ✔
230

231
      printer = Overcommit::Printer.new(config, log, context)
12 ✔
232
      runner  = Overcommit::HookRunner.new(config, log, context, printer)
12 ✔
233

234
      status = runner.run
12 ✔
235

236
      halt(status ? 0 : 65)
12 ✔
237
    end
238

239
    # Used for ease of stubbing in tests
240
    def halt(status = 0)
57 ✔
241
      exit status
×
242
    end
243

244
    # Returns the configuration for this repository.
245
    def config(options = {})
57 ✔
246
      @config ||= Overcommit::ConfigurationLoader.new(log, options).load_repo_config
156 ✔
247
    end
248
  end
249
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc