Module: LicenseUtils

Defined in:
lib/ratatui_ruby/devtools/tasks/license/license_utils.rb

Overview

Extracts contributor information from git history.

License headers need accurate copyright years and contributor names. Git blame provides line-level authorship. Commit messages contain Co-Authored-By trailers. Combining these sources manually is tedious.

This module queries git blame and parses commit messages. It returns contributor names with their latest modification years. Use it when generating or updating SPDX headers.

Defined Under Namespace

Classes: Contributor

Class Method Summary collapse

Class Method Details

.get_contributors_for_lines(filepath, start_line = nil, end_line = nil) ⇒ Object

Get all contributors who touched lines in a file (or range of lines). Returns a Hash of { “Name <email>” => year } mapping each contributor to their latest year.

This considers both the commit author AND any Co-Authored-By trailers in commit messages.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
93
94
95
# File 'lib/ratatui_ruby/devtools/tasks/license/license_utils.rb', line 40

def get_contributors_for_lines(filepath, start_line = nil, end_line = nil)
  blame_cmd = if start_line && end_line
    %W[git blame -L #{start_line},#{end_line} --porcelain -- #{filepath}]
  else
    %W[git blame --porcelain -- #{filepath}]
  end

  output, _status = Open3.capture2(*blame_cmd)

  contributors = {}  # "Name <email>" => year
  commit_cache = {}  # commit_hash => { year:, author:, co_authors: [] }

  current_commit = nil

  output.each_line do |line|
    if line =~ /^([a-f0-9]{40})/
      current_commit = $1
    elsif line =~ /^author (.+)$/
      commit_cache[current_commit] ||= {}
      commit_cache[current_commit][:author_name] = $1
    elsif line =~ /^author-mail <(.+)>$/
      commit_cache[current_commit] ||= {}
      commit_cache[current_commit][:author_email] = $1
    elsif line =~ /^author-time (\d+)$/
      commit_cache[current_commit] ||= {}
      timestamp = $1.to_i
      commit_cache[current_commit][:year] = Time.at(timestamp).year
    end
  end

  # Now fetch co-authors for each unique commit
  commit_cache.each do |commit_hash, data|
    next if commit_hash == "0" * 40 # Skip uncommitted lines

    # Get commit message for Co-Authored-By parsing
    msg_output, _status = Open3.capture2("git", "log", "-1", "--format=%B", commit_hash)
    co_authors = parse_co_authors(msg_output)
    data[:co_authors] = co_authors

    # Add author
    if data[:author_name] && data[:author_email]
      key = "#{data[:author_name]} <#{data[:author_email]}>"
      year = data[:year] || Date.today.year
      contributors[key] = [contributors[key] || 0, year].max
    end

    # Add co-authors with same year as commit
    co_authors.each do |ca|
      key = "#{ca[:name]} <#{ca[:email]}>"
      year = data[:year] || Date.today.year
      contributors[key] = [contributors[key] || 0, year].max
    end
  end

  contributors
end

.get_other_contributors(filepath, your_identifiers, start_line = nil, end_line = nil) ⇒ Object

Get all contributors EXCEPT you, with their latest years. Returns array of { name:, email:, year: }



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ratatui_ruby/devtools/tasks/license/license_utils.rb', line 114

def get_other_contributors(filepath, your_identifiers, start_line = nil, end_line = nil)
  contributors = get_contributors_for_lines(filepath, start_line, end_line)

  others = []
  contributors.each do |contributor, year|
    next if your_identifiers.any? { |id| contributor.include?(id) }

    # Parse "Name <email>" format
    if contributor =~ /^(.+?)\s*<(.+)>$/
      others << { name: $1.strip, email: $2.strip, year: }
    end
  end

  others
end

.get_your_latest_year(filepath, your_identifiers, start_line = nil, end_line = nil) ⇒ Object

Get YOUR latest year contribution to the file/lines. your_identifiers should be an array of strings that identify you (name, email fragments).



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ratatui_ruby/devtools/tasks/license/license_utils.rb', line 99

def get_your_latest_year(filepath, your_identifiers, start_line = nil, end_line = nil)
  contributors = get_contributors_for_lines(filepath, start_line, end_line)

  your_year = nil
  contributors.each do |contributor, year|
    if your_identifiers.any? { |id| contributor.include?(id) }
      your_year = [your_year || 0, year].max
    end
  end

  your_year || Date.today.year
end