This script find people that might be interesting in a patch, by going back through the history for each single hunk modified, and finding people that reviewed, acknowledge, signed, or authored the code the patch is modifying. It does this by running 'git blame' incrementally on each hunk, and then parsing the commit message. After gathering all the relevant people, it groups them to show what exactly was their role when the participated in the development of the relevant commit, and on how many relevant commits they participated. They are only displayed if they pass a minimum threshold of participation. For example: % git cc-cmd 0001-remote-hg-trivial-cleanups.patch Felipe Contreras <felipe.contreras@xxxxxxxxx> (author: 100%) Jeff King <peff@xxxxxxxx> (signer: 83%) Max Horn <max@xxxxxxxxx> (signer: 16%) Junio C Hamano <gitster@xxxxxxxxx> (signer: 16%) Thus it can be used for 'git send-email' as a cc-cmd. Comments-by: Ramkumar Ramachandra <artagnon@xxxxxxxxx> Signed-off-by: Felipe Contreras <felipe.contreras@xxxxxxxxx> --- contrib/cc-cmd/git-cc-cmd | 131 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100755 contrib/cc-cmd/git-cc-cmd diff --git a/contrib/cc-cmd/git-cc-cmd b/contrib/cc-cmd/git-cc-cmd new file mode 100755 index 0000000..aa83a1a --- /dev/null +++ b/contrib/cc-cmd/git-cc-cmd @@ -0,0 +1,131 @@ +#!/usr/bin/env ruby + +$since = '3-years-ago' +$min_percent = 5 + +class Commit + + attr_reader :id, :roles + + def initialize(id) + @id = id + @roles = [] + end + + def parse(data) + author = msg = nil + roles = {} + data.each_line do |line| + if not msg + case line + when /^author ([^<>]+) <(\S+)>$/ + author = $1, $2 + roles[author] = :author + when /^$/ + msg = true + end + else + if line =~ /^(Signed-off|Reviewed|Acked)-by: ([^<>]+) <(\S+?)>$/ + person = $2, $3 + roles[person] = :signer if person != author + end + end + end + @roles = roles.map do |person, role| + [person, role] + end + end + +end + +class Commits + + attr_reader :items + + def initialize() + @items = {} + end + + def size + @items.size + end + + def import + return if @items.empty? + File.popen(%w[git cat-file --batch], 'r+') do |p| + p.write(@items.keys.join("\n")) + p.close_write + p.each do |l| + if l =~ /^(\h{40}) commit (\d+)/ + id, len = $1, $2 + data = p.read($2.to_i) + @items[id].parse(data) + end + end + end + end + + def get_blame(source, start, offset, from) + return unless source + File.popen(['git', 'blame', '--incremental', '-C', + '-L', '%u,+%u' % [start, offset], + '--since', $since, from + '^', + '--', source]) do |p| + p.each do |line| + if line =~ /^(\h{40})/ + id = $1 + @items[id] = Commit.new(id) + end + end + end + end + + def from_patch(file) + source = nil + from = nil + File.open(file) do |f| + f.each do |line| + case line + when /^From (\h+) (.+)$/ + from = $1 + when /^---\s+(\S+)/ + source = $1 != '/dev/null' ? $1[2..-1] : nil + when /^@@\s-(\d+),(\d+)/ + get_blame(source, $1, $2, from) + end + end + end + end + +end + +exit 1 if ARGV.size != 1 + +commits = Commits.new +commits.from_patch(ARGV[0]) +commits.import + +# hash of hashes +persons = Hash.new { |hash, key| hash[key] = {} } + +commits.items.values.each do |commit| + commit.roles.each do |person, role| + persons[person][role] ||= 0 + persons[person][role] += 1 + end +end + +persons.each do |person, roles| + roles = roles.map do |role, count| + percent = count.to_f * 100 / commits.size + next if percent < $min_percent + '%s: %u%%' % [role, percent] + end.compact + next if roles.empty? + + name, email = person + # must quote chars? + name = '"%s"' % name if name =~ /[^\w \-]/i + person = name ? '%s <%s>' % [name, email] : email + puts '%s (%s)' % [person, roles.join(', ')] +end -- 1.8.2.1.790.g4588561 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html