git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 01/11] Add new git-cc-cmd helper to contrib

From
Felipe Contreras <felipe.contreras@gmail.com>
Date
Apr 19, 2013, 19:30 UTC
Message-ID
<1366399831-5964-2-git-send-email-felipe.contreras@gmail.com>
In-Reply-To
<1366399831-5964-1-git-send-email-felipe.contreras@gmail.com>

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@gmail.com> (author: 100%)
  Jeff King <peff@peff.net> (signer: 83%)
  Max Horn <max@quendi.de> (signer: 16%)
  Junio C Hamano <gitster@pobox.com> (signer: 16%)
Thus it can be used for 'git send-email' as a cc-cmd.
Comments-by: Ramkumar Ramachandra <artagnon@gmail.com>
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 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
Previous: Felipe ContrerasNext: Felipe Contreras
Message 2 of 17 in “Add new git-cc-cmd helper to contrib”
  1. 00/11 Add new git-cc-cmd helper to contribFelipe Contreras, Apr 19, 2013
  2. 01/11 Add new git-cc-cmd helper to contribFelipe Contreras, Apr 19, 2013
  3. Felipe ContrerasApr 19, 2013
  4. Junio C HamanoApr 19, 2013
  5. 02/11 contrib: cc-cmd: add option parsingFelipe Contreras, Apr 19, 2013
  6. 03/11 contrib: cc-cmd: add support for multiple patchesFelipe Contreras, Apr 19, 2013
  7. 04/11 contrib: cc-cmd: add option to show commitsFelipe Contreras, Apr 19, 2013
  8. 05/11 contrib: cc-cmd: add option to parse from committishFelipe Contreras, Apr 19, 2013
  9. 06/11 contrib: cc-cmd: parse committish like format-patchFelipe Contreras, Apr 19, 2013
  10. 07/11 contrib: cc-cmd: fix parsing of rev-list argsFelipe Contreras, Apr 19, 2013
  11. 08/11 contrib: cc-cmd: add option to fetch aliasesFelipe Contreras, Apr 19, 2013
  12. 09/11 contrib: cc-cmd: support multiple rolesFelipe Contreras, Apr 19, 2013
  13. 10/11 contrib: cc-cmd: sort by participationFelipe Contreras, Apr 19, 2013
  14. 11/11 contrib: cc-cmd: ignore chunks with no original linesFelipe Contreras, Apr 19, 2013
  15. Junio C HamanoApr 19, 2013
  16. Duy NguyenApr 20, 2013
  17. Felipe ContrerasApr 20, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.