Class: RubyGitHooks::Hook
- Inherits:
-
Object
- Object
- RubyGitHooks::Hook
- Defined in:
- lib/ruby_git_hooks.rb
Overview
applypatch-msg, pre-applypatch, post-applypatch prepare-commit-msg, commit-msg pre-rebase, post-checkout, post-merge, update, post-update, pre-auto-gc, post-rewrite
Direct Known Subclasses
AddWatermarkCommitHook, CaseClashHook, CopyrightCheckHook, EmailNotifyHook, JiraCommentAddHook, JiraReferenceCheckHook, MaxFileSizeHook, NonAsciiCharactersCheckHook, RubyDebugHook
Constant Summary collapse
- HOOK_INFO =
Instances of Hook delegate these methods to the class methods.
[ :files_changed, :file_contents, :file_diffs, :ls_files, :commits, :commit_message, :commit_message_file, :commit_ref_map ]
- HOOK_TYPE_SETUP =
{ # Pre-receive gets no args, but STDIN with a list of changes. "pre-receive" => proc { def commit_date(c) date = Hook.shell!("git log #{c} --pretty=%ct -1").strip.to_i end changes = [] STDIN.each_line do |line| # STDERR.puts line # for debugging base, commit, ref = line.strip.split changes.push [base, commit, ref] end self.commit_ref_map = {} # # commit_ref_map is a list of which new commits are in this push, and which branches they are associated with # as {commit1 => [ref1, ref2], commit2 => [ref1]} # For existing branches, this information is sent in directly "base commit ref" # BUT for branch new branches, the pre/post-receive hook gets "0 commit ref" # ref is of the form refs/heads/branch_name new_branches = changes.select{|base, _, _ | base =~ /\A0+\z/ }.collect{|_,_, ref| ref[/refs\/heads\/(\S+)/,1] } if !new_branches.empty? # For new branches, we will calculate which commits are new by specifically not including commits which are # present in any other branch (and therefore will have been processed with that branch) all_branches = Hook.shell!("git branch").split(/[* \n]+/).select{|b| !b.empty?} # remove spaces and the * # ref is like refs/heads/<branch_name> existing_branches = all_branches - new_branches exclude_branches = existing_branches.inject("") {|str, b| str + " ^" + b} # "^B1 ^B2" end self.files_changed = [] self.file_contents = {} self.file_diffs = {} changes.each do |base, commit, ref| new_branch = base =~ /\A0+\z/ if new_branch # if base is 000 then this is a new branch and we have no easy way know what files were added # so for now just don't include files changed in a new branch # because really this should be done per commit or at least per branch anyway # TODO: we could figure it out based on the branch commit calculations per branch (see below) files_with_status = [] else files_with_status = Hook.shell!("git diff --name-status #{base}..#{commit}").split("\n") end files_with_status.each do |f| status, file_changed = f.scan(/([ACDMRTUXB])\s+(\S+)$/).flatten self.files_changed << file_changed file_diffs[file_changed] = Hook.shell!("git log -p #{commit} -- #{file_changed}") begin file_contents[file_changed] = status == "D" ? "" : Hook.shell!("git show #{commit}:#{file_changed}") rescue # weird bug where some repos can't run the git show command even when it's not a deleted file. # example: noah-gibbs/barkeep/test/fixtures/text_git_repo I haven't figured out what's # weird about it yet but this fails, so put in a hack for now. May want to leave this since # we'd rather continue without the changes than fail, right? file_contents[file_changed] = "" end end # now calculate which commits are new if new_branch # new branch, but we don't want to include all commits from beginning of time # so exclude any commits that are on any other branches # e.g. git rev-list <commit for B3> ^master ^B2 # NOTE: have to use commit, not ref, because if this is called in pre-receive the branch name of ref won't # actually have been set up yet! new_commits = Hook.shell!("git rev-list #{commit} #{exclude_branches}").split("\n") else # existing branch, base..commit is right new_commits = Hook.shell!("git rev-list #{base}..#{commit}").split("\n") end new_commits.each do |one_commit| self.commit_ref_map[one_commit] ||= []; self.commit_ref_map[one_commit] << ref # name of the branch associated with this commit end end # we want the list of commits sorted by commit date self.commits = self.commit_ref_map.keys.sort{|a,b|commit_date(b) <=> commit_date(a)} if !self.commits.empty? file_list_revision = self.commits.first # can't just use HEAD - remote may be on branch with no HEAD self.ls_files = Hook.shell!("git ls-tree --full-tree --name-only -r #{file_list_revision}").split("\n") # TODO should store ls_files per commit (with status)? end }, "pre-commit" => proc { files_with_status = Hook.shell!("git diff --name-status --cached").split("\n") self.files_changed = [] self.file_contents = {} self.file_diffs = {} self.commits = [] files_with_status.each do |f| status, file_changed = f.scan(/([ACDMRTUXB])\s+(\S+)$/).flatten self.files_changed << file_changed file_diffs[file_changed] = Hook.shell!("git diff --cached -- #{file_changed}") file_contents[file_changed] = status == "D"? "": Hook.shell!("git show :#{file_changed}") end self.ls_files = Hook.shell!("git ls-files").split("\n") }, "post-commit" => proc { last_commit_files = Hook.shell!("git log --oneline --name-status -1") # Split, cut off leading line to get actual files with status files_with_status = last_commit_files.split("\n")[1..-1] self.files_changed = [] self.commits = [ Hook.shell!("git log -n 1 --pretty=format:%H").chomp ] self.file_contents = {} self.file_diffs = {} files_with_status.each do |f| status, file_changed = f.scan(/([ACDMRTUXB])\s+(\S+)$/).flatten self.files_changed << file_changed file_diffs[file_changed] = Hook.shell!("git log --oneline -p -1 -- #{file_changed}") file_contents[file_changed] = status == "D"? "": Hook.shell!("git show :#{file_changed}") end self.ls_files = Hook.shell!("git ls-files").split("\n") self. = Hook.shell!("git log -1 --pretty=%B") }, "commit-msg" => proc { files_with_status = Hook.shell!("git diff --name-status --cached").split("\n") self.files_changed = [] self.file_contents = {} self.file_diffs = {} self.commits = [] files_with_status.each do |f| status, file_changed = f.scan(/([ACDMRTUXB])\s+(\S+)$/).flatten self.files_changed << file_changed file_diffs[file_changed] = Hook.shell!("git diff --cached -- #{file_changed}") file_contents[file_changed] = status == "D"? "": Hook.shell!("git show :#{file_changed}") end self.ls_files = Hook.shell!("git ls-files").split("\n") self. = File.read(ARGV[0]) self. = ARGV[0] } }
Class Attribute Summary collapse
-
.commit_message ⇒ Object
Commit message for current commit.
-
.commit_message_file ⇒ Object
Commit message file for current commit.
-
.commit_ref_map ⇒ Object
refs associated with each commit.
-
.commits ⇒ Object
the following are for hooks which involve multiple commits (pre-receive, post-receive): (may be empty in other hooks) All current commits.
-
.file_contents ⇒ Object
Latest contents of all changed files.
-
.file_diffs ⇒ Object
A human-readable diff per file.
-
.files_changed ⇒ Object
Array of what files were changed.
-
.has_run ⇒ Object
readonly
Whether .run has ever been called.
-
.ls_files ⇒ Object
All filenames in repo.
-
.registered_hooks ⇒ Object
readonly
What hooks are running.
-
.run_as ⇒ Object
readonly
What command line was run.
-
.run_as_hook ⇒ Object
readonly
What git hook is being run.
-
.run_from ⇒ Object
readonly
What directory to run from.
Class Method Summary collapse
- .get_hooks_to_run(hook_specs) ⇒ Object
- .initial_setup ⇒ Object
- .register(hook) ⇒ Object
-
.run(*hook_specs) ⇒ Object
Run takes a list of hook specifications.
- .run_as_specific_githook ⇒ Object
- .shell!(*args) ⇒ Object
Instance Method Summary collapse
Class Attribute Details
.commit_message ⇒ Object
Commit message for current commit
50 51 52 |
# File 'lib/ruby_git_hooks.rb', line 50 def end |
.commit_message_file ⇒ Object
Commit message file for current commit
53 54 55 |
# File 'lib/ruby_git_hooks.rb', line 53 def end |
.commit_ref_map ⇒ Object
refs associated with each commit
61 62 63 |
# File 'lib/ruby_git_hooks.rb', line 61 def commit_ref_map @commit_ref_map end |
.commits ⇒ Object
the following are for hooks which involve multiple commits (pre-receive, post-receive): (may be empty in other hooks) All current commits
58 59 60 |
# File 'lib/ruby_git_hooks.rb', line 58 def commits @commits end |
.file_contents ⇒ Object
Latest contents of all changed files
41 42 43 |
# File 'lib/ruby_git_hooks.rb', line 41 def file_contents @file_contents end |
.file_diffs ⇒ Object
A human-readable diff per file
44 45 46 |
# File 'lib/ruby_git_hooks.rb', line 44 def file_diffs @file_diffs end |
.files_changed ⇒ Object
Array of what files were changed
38 39 40 |
# File 'lib/ruby_git_hooks.rb', line 38 def files_changed @files_changed end |
.has_run ⇒ Object (readonly)
Whether .run has ever been called
35 36 37 |
# File 'lib/ruby_git_hooks.rb', line 35 def has_run @has_run end |
.ls_files ⇒ Object
All filenames in repo
47 48 49 |
# File 'lib/ruby_git_hooks.rb', line 47 def ls_files @ls_files end |
.registered_hooks ⇒ Object (readonly)
What hooks are running
23 24 25 |
# File 'lib/ruby_git_hooks.rb', line 23 def registered_hooks @registered_hooks end |
.run_as ⇒ Object (readonly)
What command line was run
26 27 28 |
# File 'lib/ruby_git_hooks.rb', line 26 def run_as @run_as end |
.run_as_hook ⇒ Object (readonly)
What git hook is being run
32 33 34 |
# File 'lib/ruby_git_hooks.rb', line 32 def run_as_hook @run_as_hook end |
.run_from ⇒ Object (readonly)
What directory to run from
29 30 31 |
# File 'lib/ruby_git_hooks.rb', line 29 def run_from @run_from end |
Class Method Details
.get_hooks_to_run(hook_specs) ⇒ Object
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/ruby_git_hooks.rb', line 247 def self.get_hooks_to_run(hook_specs) @registered_hooks ||= {} if hook_specs.empty? return @registered_hooks.values.inject([], &:+) end hook_specs.flat_map do |spec| if @registered_hooks[spec] @registered_hooks[spec] elsif spec.is_a?(Hook) [ spec ] elsif spec.is_a?(String) # A string is assumed to be a class name @registered_hooks[Object.const_get(spec)] else raise "Can't find hook for specification: #{spec.inspect}!" end end end |
.initial_setup ⇒ Object
231 232 233 234 235 236 |
# File 'lib/ruby_git_hooks.rb', line 231 def self.initial_setup return if @run_from @run_from = Dir.getwd @run_as = $0 end |
.register(hook) ⇒ Object
338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/ruby_git_hooks.rb', line 338 def self.register(hook) @registered_hooks ||= {} @registered_hooks[hook.class.name] ||= [] @registered_hooks[hook.class.name].push hook # Figure out when to set this up... #at_exit do # unless RubyGitHooks::Hook.has_run # STDERR.puts "No call to RubyGitHooks.run happened, so no hooks ran!" # end #end end |
.run(*hook_specs) ⇒ Object
Run takes a list of hook specifications. Those can be Hook classnames or instances of type Hook.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/ruby_git_hooks.rb', line 273 def self.run(*hook_specs) if @has_run STDERR.puts "In this version, you can't call .run more than once. For now, please\nregister your hooks individually and then call .run with no args, or\nelse call .run with both as arguments. This may be fixed in a future\nversion. Sorry!\n" exit 1 end @has_run = true initial_setup run_as_specific_githook # By default, run all hooks hooks_to_run = get_hooks_to_run(hook_specs.flatten) failed_hooks = [] val = nil hooks_to_run.each do |hook| begin hook.setup { val = hook.check } # Re-init each time, just in case failed_hooks.push(hook) unless val rescue # Failed. Return non-zero if that makes a difference. STDERR.puts "Hook #{hook.inspect} raised exception: #{$!.inspect}!\n#{$!.backtrace.join("\n")}" failed_hooks.push hook end end if CAN_FAIL_HOOKS.include?(@run_as_hook) && failed_hooks.size > 0 STDERR.puts "Hooks failed: #{failed_hooks}" STDERR.puts "Use 'git commit -eF .git/COMMIT_EDITMSG' to restore your commit message" if STDERR.puts "Exiting!" exit 1 end end |
.run_as_specific_githook ⇒ Object
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/ruby_git_hooks.rb', line 313 def self.run_as_specific_githook return if @run_as_hook # Already did this self.initial_setup # Might have already done this if ARGV.include? "--hook" idx = ARGV.find_index "--hook" @run_as_hook = ARGV[idx + 1] 2.times { ARGV.delete_at(idx) } else @run_as_hook = HOOK_NAMES.detect { |hook| @run_as.include?(hook) } end unless @run_as_hook STDERR.puts "Name #{@run_as.inspect} doesn't include " + "any of: #{HOOK_NAMES.inspect}" exit 1 end unless HOOK_TYPE_SETUP[@run_as_hook] STDERR.puts "No setup defined for hook type #{@run_as_hook.inspect}!" exit 1 end self.instance_eval(&HOOK_TYPE_SETUP[@run_as_hook]) end |
.shell!(*args) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/ruby_git_hooks.rb', line 351 def self.shell!(*args) output = `#{args.join(" ")}` unless $?.success? STDERR.puts "Job #{args.inspect} failed in dir #{Dir.getwd.inspect}" STDERR.puts "Failed job output:\n#{output}\n======" raise "Exec of #{args.inspect} failed: #{$?}!" end output end |
Instance Method Details
#setup ⇒ Object
238 239 240 241 242 243 244 245 |
# File 'lib/ruby_git_hooks.rb', line 238 def setup Dir.chdir Hook.run_from do yield end ensure # Nothing yet end |