{"thread":{"id":"33610","subject":"[PATCH v4 00/11] Add new git-related helper to contrib","startedAt":"2013-04-25T19:59:32Z","lastAt":"2013-05-13T23:17:09Z","messageCount":17,"participants":["Felipe Contreras","Junio C Hamano","Ramkumar Ramachandra"],"isPatch":true,"patchVersion":4,"patchTotal":11},"messages":[{"id":"215488","messageId":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":null,"subject":"[PATCH v4 00/11] Add new git-related helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:32Z","receivedAt":"2013-04-25T19:59:32Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\nPreviously known as git-cc-cmd, I've renamed it git-related (tentatively). I\nremoved support for aliases, as I don't think it's very useful, and I've added\nsupport for mailmap, which I think covers similar use-cases.\n\nThis script allows you to get a list of relevant persons to Cc when sending a\npatch series.\n\n  % git cc-cmd v1.8.1.6^^1..v1.8.1.6^^2\n  Junio C Hamano <gitster@pobox.com> (signer: 84%, author: 15%)\n  \"Nguyễn Thái Ngọc Duy\" <pclouds@gmail.com> (author: 38%, signer: 7%)\n  Michael Haggerty <mhagger@alum.mit.edu> (author: 15%)\n  Jean-Noel Avila <jn.avila@free.fr> (signer: 7%)\n  \"Jean-Noël AVILA\" <avila.jn@gmail.com> (author: 7%)\n  \"Henrik Grubbström\" <grubba@grubba.org> (author: 7%)\n  Clemens Buchacher <drizzd@aon.at> (author: 7%)\n  Joshua Jensen <jjensen@workspacewhiz.com> (author: 7%)\n  Johannes Sixt <j6t@kdbg.org> (signer: 7%)\n\nIt finds people that might be interesting in a patch, by going back through the\nhistory for each single hunk modified, and finding people that reviewed,\nacknowledge, signed, or authored the code the patch is modifying.\n\nIt does this by running 'git blame' incrementally on each hunk, and then\nparsing the commit message. After gathering all the relevant people, it groups\nthem to show what exactly was their role when the participated in the\ndevelopment of the relevant commit, and on how many relevant commits they\nparticipated. They are only displayed if they pass a minimum threshold of\nparticipation.\n\nThe code finds the changes in each commit in the list, runs 'git blame'\nto see which other commits are relevant to those lines, and then adds\nthe author and signer to the list.\n\nFinally, it calculates what percentage of the total relevant commits\neach person was involved in, and if it passes the threshold, it goes in.\n\nYou can also choose to show the commits themselves:\n\n  % git cc-cmd --commits v1.8.1.6^^1..v1.8.1.6^^2\n  9db9eec attr: avoid calling find_basename() twice per path\n  94bc671 Add directory pattern matching to attributes\n  82dce99 attr: more matching optimizations from .gitignore\n  593cb88 exclude: split basename matching code into a separate function\n  b559263 exclude: split pathname matching code into a separate function\n  4742d13 attr: avoid searching for basename on every match\n  f950eb9 rename pathspec_prefix() to common_prefix() and move to dir.[ch]\n  4a085b1 consolidate pathspec_prefix and common_prefix\n  d932f4e Rename git_checkattr() to git_check_attr()\n  2d72174 Extract a function collect_all_attrs()\n  8cf2a84 Add string comparison functions that respect the ignore_case variable.\n  407a963 Merge branch 'rr/remote-helper-doc'\n  ec775c4 attr: Expand macros immediately when encountered.\n\nBut wait, there's more: you can also specify a list of patch files, which means\nthis can be used for 'git send-emails' --cc-cmd option.\n\n\nFelipe Contreras (11):\n  Add new git-related  helper to contrib\n  contrib: related: add option parsing\n  contrib: related: add support for multiple patches\n  contrib: related: add option to show commits\n  contrib: related: add option to parse from committish\n  contrib: related: parse committish like format-patch\n  contrib: related: fix parsing of rev-list args\n  contrib: related: support multiple roles\n  contrib: related: sort by participation\n  contrib: related: group persons with same email\n  contrib: related: add support for mailmap\n\n contrib/related/git-related | 272 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 272 insertions(+)\n create mode 100755 contrib/related/git-related\n\n-- \n1.8.2.1\n"},{"id":"215489","messageId":"1366919983-27521-2-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 01/11] Add new git-related helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:33Z","receivedAt":"2013-04-25T19:59:33Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"This script find people that might be interested in a patch, by going\nback through the history for each single hunk modified, and finding\npeople that reviewed, acknowledge, signed, or authored the code the\npatch is modifying.\n\nIt does this by running 'git blame' incrementally on each hunk, and then\nparsing the commit message. After gathering all the relevant people, it\ngroups them to show what exactly was their role when the participated in\nthe development of the relevant commit, and on how many relevant commits\nthey participated. They are only displayed if they pass a minimum\nthreshold of participation.\n\nFor example:\n\n  % git related 0001-remote-hg-trivial-cleanups.patch\n  Felipe Contreras <felipe.contreras@gmail.com> (author: 100%)\n  Jeff King <peff@peff.net> (signer: 83%)\n  Max Horn <max@quendi.de> (signer: 16%)\n  Junio C Hamano <gitster@pobox.com> (signer: 16%)\n\nThus it can be used for 'git send-email' as a cc-cmd.\n\nThere might be some other related functions to this script, not just to\nbe used as a cc-cmd.\n\nComments-by: Ramkumar Ramachandra <artagnon@gmail.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 131 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 131 insertions(+)\n create mode 100755 contrib/related/git-related\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nnew file mode 100755\nindex 0000000..2d47efa\n--- /dev/null\n+++ b/contrib/related/git-related\n@@ -0,0 +1,131 @@\n+#!/usr/bin/env ruby\n+\n+$since = '3-years-ago'\n+$min_percent = 5\n+\n+class Commit\n+\n+  attr_reader :id, :roles\n+\n+  def initialize(id)\n+    @id = id\n+    @roles = []\n+  end\n+\n+  def parse(data)\n+    author = msg = nil\n+    roles = {}\n+    data.each_line do |line|\n+      if not msg\n+        case line\n+        when /^author ([^<>]+) <(\\S+)> (.+)$/\n+          author = $1, $2\n+          roles[author] = :author\n+        when /^$/\n+          msg = true\n+        end\n+      else\n+        if line =~ /^(Signed-off|Reviewed|Acked)-by: ([^<>]+) <(\\S+?)>$/\n+          person = $2, $3\n+          roles[person] = :signer if person != author\n+        end\n+      end\n+    end\n+    @roles = roles.map do |person, role|\n+      [person, role]\n+    end\n+  end\n+\n+end\n+\n+class Commits\n+\n+  attr_reader :items\n+\n+  def initialize()\n+    @items = {}\n+  end\n+\n+  def size\n+    @items.size\n+  end\n+\n+  def import\n+    return if @items.empty?\n+    File.popen(%w[git cat-file --batch], 'r+') do |p|\n+      p.write(@items.keys.join(\"\\n\"))\n+      p.close_write\n+      p.each do |l|\n+        if l =~ /^(\\h{40}) commit (\\d+)/\n+          id, len = $1, $2\n+          data = p.read($2.to_i)\n+          @items[id].parse(data)\n+        end\n+      end\n+    end\n+  end\n+\n+  def get_blame(source, start, offset, from)\n+    return unless source and offset\n+    File.popen(['git', 'blame', '--incremental', '-C',\n+               '-L', '%u,+%u' % [start, offset],\n+               '--since', $since, from + '^',\n+               '--', source]) do |p|\n+      p.each do |line|\n+        if line =~ /^(\\h{40})/\n+          id = $1\n+          @items[id] = Commit.new(id)\n+        end\n+      end\n+    end\n+  end\n+\n+  def from_patch(file)\n+    source = nil\n+    from = nil\n+    File.open(file) do |f|\n+      f.each do |line|\n+        case line\n+        when /^From (\\h+) (.+)$/\n+          from = $1\n+        when /^---\\s+(\\S+)/\n+          source = $1 != '/dev/null' ? $1[2..-1] : nil\n+        when /^@@\\s-(\\d+),(\\d+)/\n+          get_blame(source, $1, $2, from)\n+        end\n+      end\n+    end\n+  end\n+\n+end\n+\n+exit 1 if ARGV.size != 1\n+\n+commits = Commits.new\n+commits.from_patch(ARGV[0])\n+commits.import\n+\n+# hash of hashes\n+persons = Hash.new { |hash, key| hash[key] = {} }\n+\n+commits.items.values.each do |commit|\n+  commit.roles.each do |person, role|\n+    persons[person][role] ||= 0\n+    persons[person][role] += 1\n+  end\n+end\n+\n+persons.each do |person, roles|\n+  roles = roles.map do |role, count|\n+    percent = count.to_f * 100 / commits.size\n+    next if percent < $min_percent\n+    '%s: %u%%' % [role, percent]\n+  end.compact\n+  next if roles.empty?\n+\n+  name, email = person\n+  # must quote chars?\n+  name = '\"%s\"' % name if name =~ /[^\\w \\-]/i\n+  person = name ? '%s <%s>' % [name, email] : email\n+  puts '%s (%s)' % [person, roles.join(', ')]\n+end\n-- \n1.8.2.1\n"},{"id":"215490","messageId":"1366919983-27521-3-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 02/11] contrib: related: add option parsing","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:34Z","receivedAt":"2013-04-25T19:59:34Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 2d47efa..702836a 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -1,8 +1,25 @@\n #!/usr/bin/env ruby\n \n+require 'optparse'\n+\n $since = '3-years-ago'\n $min_percent = 5\n \n+begin\n+  OptionParser.new do |opts|\n+    opts.program_name = 'git cc-cmd'\n+    opts.banner = 'usage: git cc-cmd [options] <file>'\n+\n+    opts.on('-p', '--min-percent N', Integer, 'Minium percentage of role participation') do |v|\n+      $min_percent = v\n+    end\n+    opts.on('-d', '--since DATE', 'How far back to search for relevant commits') do |v|\n+      $since = v\n+    end\n+  end.parse!\n+rescue OptionParser::InvalidOption\n+end\n+\n class Commit\n \n   attr_reader :id, :roles\n-- \n1.8.2.1\n"},{"id":"215491","messageId":"1366919983-27521-4-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 03/11] contrib: related: add support for multiple patches","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:35Z","receivedAt":"2013-04-25T19:59:35Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 34 ++++++++++++++++++----------------\n 1 file changed, 18 insertions(+), 16 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 702836a..90ec3aa 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -8,7 +8,7 @@ $min_percent = 5\n begin\n   OptionParser.new do |opts|\n     opts.program_name = 'git cc-cmd'\n-    opts.banner = 'usage: git cc-cmd [options] <file>'\n+    opts.banner = 'usage: git cc-cmd [options] <files>'\n \n     opts.on('-p', '--min-percent N', Integer, 'Minium percentage of role participation') do |v|\n       $min_percent = v\n@@ -61,6 +61,7 @@ class Commits\n \n   def initialize()\n     @items = {}\n+    @main_commits = {}\n   end\n \n   def size\n@@ -91,24 +92,27 @@ class Commits\n       p.each do |line|\n         if line =~ /^(\\h{40})/\n           id = $1\n-          @items[id] = Commit.new(id)\n+          @items[id] = Commit.new(id) if not @main_commits.include?(id)\n         end\n       end\n     end\n   end\n \n-  def from_patch(file)\n+  def from_patches(files)\n     source = nil\n-    from = nil\n-    File.open(file) do |f|\n-      f.each do |line|\n-        case line\n-        when /^From (\\h+) (.+)$/\n-          from = $1\n-        when /^---\\s+(\\S+)/\n-          source = $1 != '/dev/null' ? $1[2..-1] : nil\n-        when /^@@\\s-(\\d+),(\\d+)/\n-          get_blame(source, $1, $2, from)\n+    files.each do |file|\n+      from = nil\n+      File.open(file) do |f|\n+        f.each do |line|\n+          case line\n+          when /^From (\\h+) (.+)$/\n+            from = $1\n+            @main_commits[from] = true\n+          when /^---\\s+(\\S+)/\n+            source = $1 != '/dev/null' ? $1[2..-1] : nil\n+          when /^@@\\s-(\\d+),(\\d+)/\n+            get_blame(source, $1, $2, from)\n+          end\n         end\n       end\n     end\n@@ -116,10 +120,8 @@ class Commits\n \n end\n \n-exit 1 if ARGV.size != 1\n-\n commits = Commits.new\n-commits.from_patch(ARGV[0])\n+commits.from_patches(ARGV)\n commits.import\n \n # hash of hashes\n-- \n1.8.2.1\n"},{"id":"215493","messageId":"1366919983-27521-5-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 04/11] contrib: related: add option to show commits","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:36Z","receivedAt":"2013-04-25T19:59:36Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Instead of showing the authors and signers, show the commits themselves.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 90ec3aa..6eed4bc 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -4,6 +4,7 @@ require 'optparse'\n \n $since = '3-years-ago'\n $min_percent = 5\n+$show_commits = false\n \n begin\n   OptionParser.new do |opts|\n@@ -16,6 +17,9 @@ begin\n     opts.on('-d', '--since DATE', 'How far back to search for relevant commits') do |v|\n       $since = v\n     end\n+    opts.on('-c', '--commits[=FORMAT]', [:raw, :full], 'List commits instead of persons') do |v|\n+      $show_commits = v || true\n+    end\n   end.parse!\n rescue OptionParser::InvalidOption\n end\n@@ -124,6 +128,20 @@ commits = Commits.new\n commits.from_patches(ARGV)\n commits.import\n \n+if $show_commits\n+  cmd = nil\n+  case $show_commits\n+  when :raw\n+    puts commits.items.keys\n+  when :full\n+    cmd = %w[git log --patch --no-walk]\n+  else\n+    cmd = %w[git log --oneline --no-walk]\n+  end\n+  system(*cmd + commits.items.keys) if cmd\n+  exit 0\n+end\n+\n # hash of hashes\n persons = Hash.new { |hash, key| hash[key] = {} }\n \n-- \n1.8.2.1\n"},{"id":"215492","messageId":"1366919983-27521-6-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 05/11] contrib: related: add option to parse from committish","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:37Z","receivedAt":"2013-04-25T19:59:37Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"For example master..feature-a.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 36 ++++++++++++++++++++++++++++++++++--\n 1 file changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 6eed4bc..0015b3c 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -5,11 +5,13 @@ require 'optparse'\n $since = '3-years-ago'\n $min_percent = 5\n $show_commits = false\n+$files = []\n+$rev_args = []\n \n begin\n   OptionParser.new do |opts|\n     opts.program_name = 'git cc-cmd'\n-    opts.banner = 'usage: git cc-cmd [options] <files>'\n+    opts.banner = 'usage: git cc-cmd [options] <files | rev-list options>'\n \n     opts.on('-p', '--min-percent N', Integer, 'Minium percentage of role participation') do |v|\n       $min_percent = v\n@@ -122,10 +124,40 @@ class Commits\n     end\n   end\n \n+  def from_rev_args(args)\n+    return if args.empty?\n+    source = nil\n+    File.popen(%w[git rev-list --reverse] + args) do |p|\n+      p.each do |e|\n+        id = e.chomp\n+        @main_commits[id] = true\n+        File.popen(%w[git show -C --oneline] + [id]) do |p|\n+          p.each do |e|\n+            case e\n+            when /^---\\s+(\\S+)/\n+              source = $1 != '/dev/null' ? $1[2..-1] : nil\n+            when /^@@\\s-(\\d+),(\\d+)/\n+              get_blame(source, $1, $2, id)\n+            end\n+          end\n+        end\n+      end\n+    end\n+  end\n+\n+end\n+\n+ARGV.each do |e|\n+  if File.exists?(e)\n+    $files << e\n+  else\n+    $rev_args << e\n+  end\n end\n \n commits = Commits.new\n-commits.from_patches(ARGV)\n+commits.from_patches($files)\n+commits.from_rev_args($rev_args)\n commits.import\n \n if $show_commits\n-- \n1.8.2.1\n"},{"id":"215494","messageId":"1366919983-27521-7-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 06/11] contrib: related: parse committish like format-patch","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:38Z","receivedAt":"2013-04-25T19:59:38Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 0015b3c..2f38ee1 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -126,6 +126,20 @@ class Commits\n \n   def from_rev_args(args)\n     return if args.empty?\n+\n+    revs = []\n+\n+    File.popen(%w[git rev-parse --revs-only --default=HEAD --symbolic] + args).each do |rev|\n+      revs << rev.chomp\n+    end\n+\n+    case revs.size\n+    when 1\n+      committish = [ '%s..HEAD' % revs[0] ]\n+    else\n+      committish = revs\n+    end\n+\n     source = nil\n     File.popen(%w[git rev-list --reverse] + args) do |p|\n       p.each do |e|\n-- \n1.8.2.1\n"},{"id":"215495","messageId":"1366919983-27521-8-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 07/11] contrib: related: fix parsing of rev-list args","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:39Z","receivedAt":"2013-04-25T19:59:39Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"For example '-1'.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 2f38ee1..e8603be 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -23,7 +23,8 @@ begin\n       $show_commits = v || true\n     end\n   end.parse!\n-rescue OptionParser::InvalidOption\n+rescue OptionParser::InvalidOption => e\n+  $rev_args += e.args\n end\n \n class Commit\n@@ -135,9 +136,11 @@ class Commits\n \n     case revs.size\n     when 1\n-      committish = [ '%s..HEAD' % revs[0] ]\n+      r = revs[0]\n+      r = '^' + r if r[0] != '-'\n+      args = [ r, 'HEAD' ]\n     else\n-      committish = revs\n+      args = revs\n     end\n \n     source = nil\n-- \n1.8.2.1\n"},{"id":"215496","messageId":"1366919983-27521-9-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 08/11] contrib: related: support multiple roles","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:40Z","receivedAt":"2013-04-25T19:59:40Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Currently only the roles of 'author' and 'signer' and handler, but now\nthere's also 'reviewer' and 'acker'.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 29 ++++++++++++++++++++---------\n 1 file changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex e8603be..cf6818e 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -27,6 +27,12 @@ rescue OptionParser::InvalidOption => e\n   $rev_args += e.args\n end\n \n+KNOWN_ROLES = {\n+  'Signed-off' => :signer,\n+  'Reviewed' => :reviewer,\n+  'Acked' => :acker,\n+}\n+\n class Commit\n \n   attr_reader :id, :roles\n@@ -38,25 +44,28 @@ class Commit\n \n   def parse(data)\n     author = msg = nil\n-    roles = {}\n+    # hash of arrays\n+    roles = Hash.new { |hash, key| hash[key] = [] }\n     data.each_line do |line|\n       if not msg\n         case line\n         when /^author ([^<>]+) <(\\S+)> (.+)$/\n           author = $1, $2\n-          roles[author] = :author\n+          roles[author] << :author\n         when /^$/\n           msg = true\n         end\n       else\n-        if line =~ /^(Signed-off|Reviewed|Acked)-by: ([^<>]+) <(\\S+?)>$/\n+        role_regex = KNOWN_ROLES.keys.join('|')\n+        if line =~ /^(#{role_regex})-by: ([^<>]+) <(\\S+?)>$/\n           person = $2, $3\n-          roles[person] = :signer if person != author\n+          role = KNOWN_ROLES[$1]\n+          roles[person] << role if person != author\n         end\n       end\n     end\n-    @roles = roles.map do |person, role|\n-      [person, role]\n+    @roles = roles.map do |person, roles|\n+      [person, roles]\n     end\n   end\n \n@@ -195,9 +204,11 @@ end\n persons = Hash.new { |hash, key| hash[key] = {} }\n \n commits.items.values.each do |commit|\n-  commit.roles.each do |person, role|\n-    persons[person][role] ||= 0\n-    persons[person][role] += 1\n+  commit.roles.each do |person, roles|\n+    roles.each do |role|\n+      persons[person][role] ||= 0\n+      persons[person][role] += 1\n+    end\n   end\n end\n \n-- \n1.8.2.1\n"},{"id":"215497","messageId":"1366919983-27521-10-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 09/11] contrib: related: sort by participation","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:41Z","receivedAt":"2013-04-25T19:59:41Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex cf6818e..4e9b916 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -212,7 +212,12 @@ commits.items.values.each do |commit|\n   end\n end\n \n-persons.each do |person, roles|\n+# sort by number of participations\n+count_sort = lambda do |a, b|\n+  b[1].values.reduce(:+) <=> a[1].values.reduce(:+)\n+end\n+\n+persons.sort(&count_sort).each do |person, roles|\n   roles = roles.map do |role, count|\n     percent = count.to_f * 100 / commits.size\n     next if percent < $min_percent\n-- \n1.8.2.1\n"},{"id":"215498","messageId":"1366919983-27521-11-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 10/11] contrib: related: group persons with same email","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:42Z","receivedAt":"2013-04-25T19:59:42Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"We still need the name of the person, so it might make sense to have a\nPerson object to simplify the code. Later.\n\nSuggested-by: Duy Nguyen <pclouds@gmail.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 4e9b916..f85e924 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -202,12 +202,15 @@ end\n \n # hash of hashes\n persons = Hash.new { |hash, key| hash[key] = {} }\n+names = {}\n \n commits.items.values.each do |commit|\n   commit.roles.each do |person, roles|\n+    name, email = person\n+    names[email] ||= name\n     roles.each do |role|\n-      persons[person][role] ||= 0\n-      persons[person][role] += 1\n+      persons[email][role] ||= 0\n+      persons[email][role] += 1\n     end\n   end\n end\n@@ -217,7 +220,7 @@ count_sort = lambda do |a, b|\n   b[1].values.reduce(:+) <=> a[1].values.reduce(:+)\n end\n \n-persons.sort(&count_sort).each do |person, roles|\n+persons.sort(&count_sort).each do |email, roles|\n   roles = roles.map do |role, count|\n     percent = count.to_f * 100 / commits.size\n     next if percent < $min_percent\n@@ -225,7 +228,7 @@ persons.sort(&count_sort).each do |person, roles|\n   end.compact\n   next if roles.empty?\n \n-  name, email = person\n+  name = names[email]\n   # must quote chars?\n   name = '\"%s\"' % name if name =~ /[^\\w \\-]/i\n   person = name ? '%s <%s>' % [name, email] : email\n-- \n1.8.2.1\n"},{"id":"215499","messageId":"1366919983-27521-12-git-send-email-felipe.contreras@gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v4 11/11] contrib: related: add support for mailmap","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-25T19:59:43Z","receivedAt":"2013-04-25T19:59:43Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"This seems to be the way git tools do it.\n\nSuggested-by: Duy Nguyen <pclouds@gmail.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/related/git-related | 36 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 36 insertions(+)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex f85e924..be263e2 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -7,6 +7,8 @@ $min_percent = 5\n $show_commits = false\n $files = []\n $rev_args = []\n+$mailmaps = {}\n+$mailmaps_complex = {}\n \n begin\n   OptionParser.new do |opts|\n@@ -27,6 +29,39 @@ rescue OptionParser::InvalidOption => e\n   $rev_args += e.args\n end\n \n+def get_mailmap(filename)\n+  return unless File.exists?(filename)\n+  File.open(filename) do |f|\n+    f.each do |line|\n+      case line.gsub(/\\s*#.*$/, '')\n+      when /^([^<>]+)\\s+<(\\S+)>$/\n+        $mailmaps[$2] = [ $1, nil ]\n+      when /^<(\\S+)>\\s+<(\\S+)>$/\n+        $mailmaps[$2] = [ nil, $1 ]\n+      when /^([^<>]+)\\s+<(\\S+)>\\s+<(\\S+)>$/\n+        $mailmaps[$3] = [ $1, $2 ]\n+      when /^([^<>]+)\\s+<(\\S+)>\\s+([^<>]+)\\s+<(\\S+)>$/\n+        $mailmaps_complex[[$3, $4]] = [ $1, $2 ]\n+      end\n+    end\n+  end\n+end\n+\n+def mailmap_fix(person)\n+  new = nil\n+  name, email = person\n+  new = $mailmaps_complex[person] if not new and $mailmaps_complex.include?(person)\n+  new = $mailmaps[email] if not new and $mailmaps.include?(email)\n+  return if not new\n+  person[0] = new[0] if new[0]\n+  person[1] = new[1] if new[1]\n+end\n+\n+get_aliases if $get_aliases\n+get_mailmap('.mailmap')\n+mailmap_file = %x[git config mailmap.file].chomp\n+get_mailmap(mailmap_file)\n+\n KNOWN_ROLES = {\n   'Signed-off' => :signer,\n   'Reviewed' => :reviewer,\n@@ -65,6 +100,7 @@ class Commit\n       end\n     end\n     @roles = roles.map do |person, roles|\n+      mailmap_fix(person)\n       [person, roles]\n     end\n   end\n-- \n1.8.2.1\n"},{"id":"215660","messageId":"CAMP44s0nvWvicFAJEqe0jC+zT3ZvA=Qx3MWXK36zYb2-uYV-aA@mail.gmail.com","threadId":"33610","inReplyTo":"1366919983-27521-2-git-send-email-felipe.contreras@gmail.com","subject":"Re: [PATCH v4 01/11] Add new git-related helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-26T22:52:54Z","receivedAt":"2013-04-26T22:52:54Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\nOn Thu, Apr 25, 2013 at 2:59 PM, Felipe Contreras\n<felipe.contreras@gmail.com> wrote:\n> This script find people that might be interested in a patch, by going\n> back through the history for each single hunk modified, and finding\n> people that reviewed, acknowledge, signed, or authored the code the\n> patch is modifying.\n>\n> It does this by running 'git blame' incrementally on each hunk, and then\n> parsing the commit message. After gathering all the relevant people, it\n> groups them to show what exactly was their role when the participated in\n> the development of the relevant commit, and on how many relevant commits\n> they participated. They are only displayed if they pass a minimum\n> threshold of participation.\n\nIs this patch still not understandable? If so, I would gladly strip\naway functionality, like the ability to show the roles. But for the\nfunctionality it provides, I don't see how it could be any simpler.\n\nSure, code comments might help, but first I would like to make the\ncode as self-documenting as possible, so I would give a try at a\nsimplified version first, and then perhaps adding a Person object.\n\nBetter for me if the code was good enough as it is though.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"215661","messageId":"7vhaisvqvl.fsf@alter.siamese.dyndns.org","threadId":"33610","inReplyTo":"CAMP44s0nvWvicFAJEqe0jC+zT3ZvA=Qx3MWXK36zYb2-uYV-aA@mail.gmail.com","subject":"Re: [PATCH v4 01/11] Add new git-related helper to contrib","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-26T23:01:02Z","receivedAt":"2013-04-26T23:01:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> writes:\n\n> On Thu, Apr 25, 2013 at 2:59 PM, Felipe Contreras\n> <felipe.contreras@gmail.com> wrote:\n>> This script find people that might be interested in a patch, by going\n>> back through the history for each single hunk modified, and finding\n>> people that reviewed, acknowledge, signed, or authored the code the\n>> patch is modifying.\n>>\n>> It does this by running 'git blame' incrementally on each hunk, and then\n>> parsing the commit message. After gathering all the relevant people, it\n>> groups them to show what exactly was their role when the participated in\n>> the development of the relevant commit, and on how many relevant commits\n>> they participated. They are only displayed if they pass a minimum\n>> threshold of participation.\n>\n> Is this patch still not understandable?\n\nAmong the people who review patches here and give usable feedback,\nearlier this week Peff said he is away from his mailbox for the rest\nof the week, and I am not reviewing any new topics that are not in\n'next', being busy in preparation for -rc0, so I wouldn't be able to\nanswer that question.\n\nI do not know about the others, but it is understandable from time\nto time there is a period a series is not being reviewed by anybody.\n"},{"id":"215663","messageId":"CAMP44s0XnEJWjzU-g=Gq2jJQu-9MJ8S84fAd-7bdomKWEeTCtw@mail.gmail.com","threadId":"33610","inReplyTo":"7vhaisvqvl.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v4 01/11] Add new git-related helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-04-26T23:23:33Z","receivedAt":"2013-04-26T23:23:33Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Fri, Apr 26, 2013 at 6:01 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> writes:\n>\n>> On Thu, Apr 25, 2013 at 2:59 PM, Felipe Contreras\n>> <felipe.contreras@gmail.com> wrote:\n>>> This script find people that might be interested in a patch, by going\n>>> back through the history for each single hunk modified, and finding\n>>> people that reviewed, acknowledge, signed, or authored the code the\n>>> patch is modifying.\n>>>\n>>> It does this by running 'git blame' incrementally on each hunk, and then\n>>> parsing the commit message. After gathering all the relevant people, it\n>>> groups them to show what exactly was their role when the participated in\n>>> the development of the relevant commit, and on how many relevant commits\n>>> they participated. They are only displayed if they pass a minimum\n>>> threshold of participation.\n>>\n>> Is this patch still not understandable?\n>\n> Among the people who review patches here and give usable feedback,\n> earlier this week Peff said he is away from his mailbox for the rest\n> of the week, and I am not reviewing any new topics that are not in\n> 'next', being busy in preparation for -rc0, so I wouldn't be able to\n> answer that question.\n>\n> I do not know about the others, but it is understandable from time\n> to time there is a period a series is not being reviewed by anybody.\n\nThat's fine, I was mostly asking Ramkumar who earlier argued earlier\nversions of this patch were not understandable.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"215676","messageId":"CALkWK0ks0yveTus9w=4MFoCjB6ujUUax4_o48Gy_yevyT6BeyA@mail.gmail.com","threadId":"33610","inReplyTo":"CAMP44s0XnEJWjzU-g=Gq2jJQu-9MJ8S84fAd-7bdomKWEeTCtw@mail.gmail.com","subject":"Re: [PATCH v4 01/11] Add new git-related helper to contrib","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2013-04-27T03:01:21Z","receivedAt":"2013-04-27T03:01:21Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Felipe Contreras wrote:\n> That's fine, I was mostly asking Ramkumar who earlier argued earlier\n> versions of this patch were not understandable.\n\nSorry, still catching up with list emails.  At a glance, part 1 looks\nmuch better.  Will read through more carefully soon.\n\nThanks.\n"},{"id":"217258","messageId":"CAMP44s2hmc5rHCC9O-6brtC0VeNgRUZsWurmrr7aSUx_Vr4Kyg@mail.gmail.com","threadId":"33610","inReplyTo":"CALkWK0ks0yveTus9w=4MFoCjB6ujUUax4_o48Gy_yevyT6BeyA@mail.gmail.com","subject":"Re: [PATCH v4 01/11] Add new git-related helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-05-13T23:17:09Z","receivedAt":"2013-05-13T23:17:09Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Fri, Apr 26, 2013 at 10:01 PM, Ramkumar Ramachandra\n<artagnon@gmail.com> wrote:\n> Felipe Contreras wrote:\n>> That's fine, I was mostly asking Ramkumar who earlier argued earlier\n>> versions of this patch were not understandable.\n>\n> Sorry, still catching up with list emails.  At a glance, part 1 looks\n> much better.  Will read through more carefully soon.\n\nGentle reminder.\n\n-- \nFelipe Contreras\n"}]}