Class: Kettle::Dev::GitCommitFooter

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/dev/git_commit_footer.rb

Constant Summary collapse

NAME_ASSIGNMENT_REGEX =

Regex to extract name = "value" assignments from a gemspec.

Returns:

  • (Regexp)
/\bname\s*=\s*(["'])([^"']+)\1/.freeze
ENV.fetch("GIT_HOOK_FOOTER_APPEND", "false").casecmp("true").zero?
SENTINEL =

The sentinel string that must be present to avoid duplicate footers

Returns:

  • (String, nil)
ENV["GIT_HOOK_FOOTER_SENTINEL"]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeGitCommitFooter

Returns a new instance of GitCommitFooter.



95
96
97
98
99
100
101
# File 'lib/kettle/dev/git_commit_footer.rb', line 95

def initialize
  @pwd = Dir.pwd
  @gemspecs = Dir["*.gemspec"]
  @spec = @gemspecs.first
  @gemspec_path = File.expand_path(@spec, @pwd)
  @gem_name = parse_gemspec_name || derive_gem_name
end

Class Method Details

.commit_goalie_pathObject



51
52
53
# File 'lib/kettle/dev/git_commit_footer.rb', line 51

def commit_goalie_path
  hooks_path_for("commit-subjects-goalie.txt")
end

.git_toplevelString?

Resolve git repository top-level dir, or nil outside a repo.

Returns:

  • (String, nil)


22
23
24
25
26
27
28
29
30
# File 'lib/kettle/dev/git_commit_footer.rb', line 22

def git_toplevel
  toplevel = nil
  begin
    out = %x(git rev-parse --show-toplevel 2>/dev/null)
    toplevel = out.strip unless out.nil? || out.empty?
  rescue StandardError
  end
  toplevel
end

.global_hooks_dirObject



38
39
40
# File 'lib/kettle/dev/git_commit_footer.rb', line 38

def global_hooks_dir
  File.join(ENV["HOME"], ".git-hooks")
end

.goalie_allows_footer?(subject_line) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
58
59
60
61
62
63
64
# File 'lib/kettle/dev/git_commit_footer.rb', line 55

def goalie_allows_footer?(subject_line)
  goalie_path = commit_goalie_path
  return false unless File.file?(goalie_path)

  prefixes = File.read(goalie_path).lines.map { |l| l.strip }.reject { |l| l.empty? || l.start_with?("#") }
  return false if prefixes.empty?

  subj = subject_line.to_s.strip
  prefixes.any? { |prefix| subj.start_with?(prefix) }
end

.hooks_path_for(filename) ⇒ Object



42
43
44
45
46
47
48
49
# File 'lib/kettle/dev/git_commit_footer.rb', line 42

def hooks_path_for(filename)
  local_dir = local_hooks_dir
  if local_dir
    local_path = File.join(local_dir, filename)
    return local_path if File.file?(local_path)
  end
  File.join(global_hooks_dir, filename)
end

.local_hooks_dirObject



32
33
34
35
36
# File 'lib/kettle/dev/git_commit_footer.rb', line 32

def local_hooks_dir
  top = git_toplevel
  return unless top && !top.empty?
  File.join(top, ".git-hooks")
end

.render(*argv) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/kettle/dev/git_commit_footer.rb', line 66

def render(*argv)
  commit_msg = File.read(argv[0])
  subject_line = commit_msg.lines.first.to_s

  # Evaluate configuration at runtime to respect ENV set during tests/CI
  footer_append = ENV.fetch("GIT_HOOK_FOOTER_APPEND", "false").casecmp("true").zero?
  sentinel = ENV["GIT_HOOK_FOOTER_SENTINEL"]

  if footer_append && (sentinel.nil? || sentinel.to_s.empty?)
    raise "Set GIT_HOOK_FOOTER_SENTINEL=<footer sentinel> in .env.local (e.g., '⚡️ A message from a fellow meat-based-AI ⚡️')"
  end

  if footer_append && goalie_allows_footer?(subject_line)
    if commit_msg.include?(sentinel)
      Kettle::Dev::ExitAdapter.exit(0)
    else
      footer_binding = GitCommitFooter.new
      File.open(argv[0], "w") do |file|
        file.print(commit_msg)
        file.print("\n")
        file.print(footer_binding.render)
      end
    end
  else
    # Skipping footer append
  end
end

Instance Method Details

#renderObject



103
104
105
# File 'lib/kettle/dev/git_commit_footer.rb', line 103

def render
  ERB.new(template).result(binding)
end