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

[PATCH v7] Add new git-related helper to contrib

From
Felipe Contreras <felipe.contreras@gmail.com>
Date
May 30, 2013, 03:32 UTC
Message-ID
<1369884777-7227-1-git-send-email-felipe.contreras@gmail.com>

This script find people that might be interested 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 related 0001-remote-hg-trivial-cleanups.patch
  Felipe Contreras <felipe.contreras@gmail.com>
  Jeff King <peff@peff.net>
  Max Horn <max@quendi.de>
  Junio C Hamano <gitster@pobox.com>
Thus it can be used for 'git send-email' as a cc-cmd.

There might be some other related functions to this script, not just to be used as a cc-cmd.

Comments-by: Ramkumar Ramachandra <artagnon@gmail.com>
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
Small changes since v6:
--- a/contrib/related/git-related
+++ b/contrib/related/git-related
@@ -6,10 +6,6 @@
 $since = '5-years-ago'
 $min_percent = 10
 
-def fmt_person(name, email)
-  '%s <%s>' % [name, email]
-end
-
 class Commit
 
   attr_reader :persons
@@ -25,13 +21,13 @@ class Commit
       if not msg
         case line
         when /^author ([^<>]+) <(\S+)> (.+)$/
-          @persons << fmt_person($1, $2)
+          @persons << '%s <%s>' % [$1, $2]
         when /^$/
           msg = true
         end
       else
         if line =~ /^(Signed-off|Reviewed|Acked)-by: ([^<>]+) <(\S+?)>$/
-          @persons << fmt_person($2, $3)
+          @persons << '%s <%s>' % [$2, $3]
         end
       end
     end
@@ -72,12 +68,12 @@ class Commits
   def get_blame(source, start, len, from)
     return if len == 0
     len ||= 1
-    File.popen(['git', 'blame', '--incremental', '-CCC',
+    File.popen(['git', 'blame', '--incremental', '-C', '-C',
                '-L', '%u,+%u' % [start, len],
                '--since', $since, from + '^',
                '--', source]) do |p|
       p.each do |line|
-        if line =~ /^(\h{40})/
+        if line =~ /^\h{40}/
           id = $&
           @items[id] = Commit.new(id)
         end
@@ -95,7 +91,7 @@ class Commits
         when /^---\s+(\S+)/
           source = $1 != '/dev/null' ? $1[2..-1] : nil
         when /^@@ -(\d+)(?:,(\d+))?/
-          get_blame(source, $1, $2, from)
+          get_blame(source, $1, $2, from) if source and from
         end
       end
     end

 contrib/related/git-related | 120 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)
 create mode 100755 contrib/related/git-related

diff --git a/contrib/related/git-related b/contrib/related/git-related
new file mode 100755
index 0000000..1b9b1e7
--- /dev/null
+++ b/contrib/related/git-related
@@ -0,0 +1,120 @@
+#!/usr/bin/env ruby
+
+# This script finds people that might be interested in a patch
+# usage: git related <file>
+
+$since = '5-years-ago'
+$min_percent = 10
+
+class Commit
+
+  attr_reader :persons
+
+  def initialize(id)
+    @id = id
+    @persons = []
+  end
+
+  def parse(data)
+    msg = nil
+    data.each_line do |line|
+      if not msg
+        case line
+        when /^author ([^<>]+) <(\S+)> (.+)$/
+          @persons << '%s <%s>' % [$1, $2]
+        when /^$/
+          msg = true
+        end
+      else
+        if line =~ /^(Signed-off|Reviewed|Acked)-by: ([^<>]+) <(\S+?)>$/
+          @persons << '%s <%s>' % [$2, $3]
+        end
+      end
+    end
+    @persons.uniq!
+  end
+
+end
+
+class Commits
+
+  def initialize
+    @items = {}
+  end
+
+  def size
+    @items.size
+  end
+
+  def each(&block)
+    @items.each(&block)
+  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 |line|
+        if line =~ /^(\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, len, from)
+    return if len == 0
+    len ||= 1
+    File.popen(['git', 'blame', '--incremental', '-C', '-C',
+               '-L', '%u,+%u' % [start, len],
+               '--since', $since, from + '^',
+               '--', source]) do |p|
+      p.each do |line|
+        if line =~ /^\h{40}/
+          id = $&
+          @items[id] = Commit.new(id)
+        end
+      end
+    end
+  end
+
+  def from_patch(file)
+    from = source = 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 /^@@ -(\d+)(?:,(\d+))?/
+          get_blame(source, $1, $2, from) if source and from
+        end
+      end
+    end
+  end
+
+end
+
+exit 1 if ARGV.size != 1
+
+commits = Commits.new
+commits.from_patch(ARGV[0])
+commits.import
+
+count_per_person = Hash.new(0)
+
+commits.each do |id, commit|
+  commit.persons.each do |person|
+    count_per_person[person] += 1
+  end
+end
+
+count_per_person.each do |person, count|
+  percent = count.to_f * 100 / commits.size
+  next if percent < $min_percent
+  puts person
+end
-- 
1.8.3.rc3.312.g47657de
Next: Ramkumar Ramachandra
Message 1 of 9 in “Add new git-related helper to contrib”
  1. Add new git-related helper to contribFelipe Contreras, May 30, 2013
  2. Ramkumar RamachandraMay 30, 2013
  3. Felipe ContrerasMay 30, 2013
  4. Ramkumar RamachandraMay 30, 2013
  5. Felipe ContrerasMay 30, 2013
  6. Felipe ContrerasMay 31, 2013
  7. Ramkumar RamachandraMay 31, 2013
  8. Felipe ContrerasMay 31, 2013
  9. Ramkumar RamachandraMay 31, 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.