{"thread":{"id":"32080","subject":"[PATCH] Add new git-cc-cmd helper to contrib","startedAt":"2012-11-11T17:10:04Z","lastAt":"2012-12-06T11:15:37Z","messageCount":4,"participants":["Felipe Contreras","Ramkumar Ramachandra"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"202894","messageId":"1352653804-2203-1-git-send-email-felipe.contreras@gmail.com","threadId":"32080","inReplyTo":null,"subject":"[PATCH] Add new git-cc-cmd helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-11T17:10:04Z","receivedAt":"2012-11-11T17:10:04Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"You can run it like format-patch:\n\n % git cc-cmd master..my-branch\n\nAnd you'll get relevant people to Cc.\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\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n\nThis cannot be used as cc-cmd option for 'git send-email' but it can as\nseries-cc-cmd for which I just send some patches.\n\nAn example output:\n\n% git cc-cmd --role-stat v1.8.0^^1..v1.8.0^^2\nVietor Liu <vietor@vxwo.org> (author: 12%)\n\"Shawn O. Pearce\" <spearce@spearce.org> (signer: 37%)\nAlexandre Erwin Ittner <alexandre@ittner.com.br> (author: 12%)\nPeter Krefting <peter@softwolves.pp.se> (author: 12%)\nPat Thoyts <patthoyts@users.sourceforge.net> (signer: 37%, author: 12%)\nChristian Stimming <stimming@tuhh.de> (author: 12%)\nHeiko Voigt <hvoigt@hvoigt.net> (author: 25%)\nHeiko Voigt <heiko.voigt@mahr.de> (signer: 12%)\nBert Wesarg <bert.wesarg@googlemail.com> (author: 12%)\n\nCheers.\n\n contrib/cc-cmd/git-cc-cmd | 186 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 186 insertions(+)\n create mode 100755 contrib/cc-cmd/git-cc-cmd\n\ndiff --git a/contrib/cc-cmd/git-cc-cmd b/contrib/cc-cmd/git-cc-cmd\nnew file mode 100755\nindex 0000000..17b14d4\n--- /dev/null\n+++ b/contrib/cc-cmd/git-cc-cmd\n@@ -0,0 +1,186 @@\n+#!/usr/bin/env ruby\n+\n+require 'optparse'\n+\n+$since = '3-years-ago'\n+$min_percent = 5\n+$role_stats = false\n+\n+begin\n+  OptionParser.new do |opts|\n+    opts.on('--role-stats') do |v|\n+      $role_stats = v\n+    end\n+    opts.on('--min-percent N', Integer) do |v|\n+      $min_percent = v\n+    end\n+    opts.on('--since DATE') do |v|\n+      $since = v\n+    end\n+  end.parse!\n+rescue OptionParser::InvalidOption\n+end\n+\n+revs = []\n+File.popen(%w[git rev-parse --revs-only --default=HEAD --symbolic] + ARGV).each do |rev|\n+  revs << rev.chomp\n+end\n+\n+case revs.size\n+when 0\n+  committish = '%s..HEAD' % '@{upstream}'\n+when 1\n+  committish = '%s..HEAD' % revs[0]\n+else\n+  committish = revs.join(' ')\n+end\n+\n+$alias_file = \"~/.mutt/aliases\"\n+$aliases = {}\n+\n+if $alias_file\n+  open(File.expand_path($alias_file)).each do |line|\n+    if line =~ /^\\s*alias\\s+(?:-group\\s+\\S+\\s+)*(\\S+)\\s+(.*)$/\n+      key, addresses = $1, $2.split(', ')\n+      addresses.each do |address|\n+        $aliases[address] = key\n+      end\n+    end\n+  end\n+end\n+\n+class Commit\n+\n+  attr_reader :id\n+  attr_accessor :author, :roles\n+\n+  def initialize(id)\n+    @id = id\n+    @roles = []\n+  end\n+\n+  def self.parse(data)\n+    id = author = msg = nil\n+    roles = {}\n+    data.each_line do |line|\n+      if not msg\n+        case line\n+        when /^commit (.+)$/\n+          id = $1\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+      address = \"%s <%s>\" % person\n+      if $aliases.include?(address)\n+        person = nil, $aliases[address]\n+      end\n+      [person, role]\n+    end\n+    [id, author, roles]\n+  end\n+\n+end\n+\n+class Commits\n+\n+  attr_reader :items\n+\n+  def initialize(items = {})\n+    @items = items\n+    import\n+  end\n+\n+  def size\n+    @items.size\n+  end\n+\n+  def import\n+    return if @items.empty?\n+    ids = @items.keys.join(' ')\n+    format = [\n+      'commit %H',\n+      'author %an <%ae>',\n+      '', '%B' ].join('%n')\n+    open(\"|git show -z -s --format='format:#{format}' #{ids}\").each(\"\\0\") do |data|\n+      next if data == \"\\0\" # bug in git show?\n+      id, author, roles = Commit.parse(data)\n+      commit = @items[id]\n+      commit.author = author\n+      commit.roles = roles\n+    end\n+  end\n+\n+  def each_person_role\n+    commit_roles = @items.values.map { |commit| commit.roles }.flatten(1)\n+    commit_roles.group_by { |person, role| person }.each do |person, commit_roles|\n+      commit_roles.group_by { |person, role| role }.each do |role, commit_roles|\n+        yield person, role, commit_roles.size\n+      end\n+    end\n+  end\n+\n+  def self.from_blame(committish)\n+    main_commits = {}\n+    items = {}\n+    open(\"|git rev-list --reverse #{committish}\").each do |e|\n+      id = e.chomp\n+      main_commits[id] = true\n+      open(\"|git --no-pager show -C --oneline #{id}\").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+          next unless @source\n+          open(\"|git blame --incremental -C -L #{$1},+#{$2} --since=#{$since} #{id}^ -- #{@source}\").each do |e|\n+            if e =~ /^(\\h{40})/\n+              items[$1] = Commit.new($1) if not main_commits.include?($1)\n+            end\n+          end\n+        end\n+      end\n+    end\n+    self.new(items)\n+  end\n+\n+end\n+\n+commits = Commits.from_blame(committish)\n+\n+# hash of hashes\n+persons = Hash.new { |hash, key| hash[key] = {} }\n+\n+commits.each_person_role do |person, role, count|\n+  persons[person][role] = count\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+  name, email = person\n+\n+  # has must quote chars?\n+  name = '\"%s\"' % name if name =~ /[^\\w \\-]/i\n+\n+  person = name ? \"%s <%s>\" % [name, email] : email\n+\n+  if $role_stats\n+    puts \"%s (%s)\" % [person, roles.join(', ')]\n+  else\n+    puts person\n+  end\n+end\n-- \n1.8.0\n"},{"id":"202899","messageId":"CALkWK0ksDBbWfxbT=PbKiWaC87vghTADPqcy+ujgJ9iZOVZv0w@mail.gmail.com","threadId":"32080","inReplyTo":"1352653804-2203-1-git-send-email-felipe.contreras@gmail.com","subject":"Re: [PATCH] Add new git-cc-cmd helper to contrib","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2012-11-11T17:18:31Z","receivedAt":"2012-11-11T17:18:31Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi Felipe,\n\nFelipe Contreras wrote:\n> The code finds the changes in each commit in the list, runs 'git blame'\n> to see which other commits are relevant to those lines, and then adds\n> the author and signer to the list.\n>\n> Finally, it calculates what percentage of the total relevant commits\n> each person was involved in, and if it passes the threshold, it goes in.\n\nThis is very cool.\n\n> diff --git a/contrib/cc-cmd/git-cc-cmd b/contrib/cc-cmd/git-cc-cmd\n> new file mode 100755\n> index 0000000..17b14d4\n> --- /dev/null\n> +++ b/contrib/cc-cmd/git-cc-cmd\n> @@ -0,0 +1,186 @@\n> +#!/usr/bin/env ruby\n\nJust out of curiosity, why didn't you write it in Python?  (I noticed\nthat you wrote remote-hg and remote-bzr in Python, and assumed that it\nwas your preferred language)\n\n> +$alias_file = \"~/.mutt/aliases\"\n\nPlease read sendemail.aliasfiletype instead of assuming mutt.\n\nRam\n"},{"id":"202907","messageId":"CAMP44s1--1i+ZtG7eKe0ZpRizcBfq+wJv_VOVZfU+A+gpjLyhQ@mail.gmail.com","threadId":"32080","inReplyTo":"CALkWK0ksDBbWfxbT=PbKiWaC87vghTADPqcy+ujgJ9iZOVZv0w@mail.gmail.com","subject":"Re: [PATCH] Add new git-cc-cmd helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-11T18:10:11Z","receivedAt":"2012-11-11T18:10:11Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\nOn Sun, Nov 11, 2012 at 6:18 PM, Ramkumar Ramachandra\n<artagnon@gmail.com> wrote:\n> Felipe Contreras wrote:\n>> The code finds the changes in each commit in the list, runs 'git blame'\n>> to see which other commits are relevant to those lines, and then adds\n>> the author and signer to the list.\n>>\n>> Finally, it calculates what percentage of the total relevant commits\n>> each person was involved in, and if it passes the threshold, it goes in.\n>\n> This is very cool.\n>\n>> diff --git a/contrib/cc-cmd/git-cc-cmd b/contrib/cc-cmd/git-cc-cmd\n>> new file mode 100755\n>> index 0000000..17b14d4\n>> --- /dev/null\n>> +++ b/contrib/cc-cmd/git-cc-cmd\n>> @@ -0,0 +1,186 @@\n>> +#!/usr/bin/env ruby\n>\n> Just out of curiosity, why didn't you write it in Python?  (I noticed\n> that you wrote remote-hg and remote-bzr in Python, and assumed that it\n> was your preferred language)\n\nNo, in fact I hate python :) I only used it because both bazaar and\nmercurial are written in python.\n\nRuby is my favorite language, after C.\n\nI'm fairly certain that the equivalent code in python would be much\nmore complicated.\n\n>> +$alias_file = \"~/.mutt/aliases\"\n>\n> Please read sendemail.aliasfiletype instead of assuming mutt.\n\nRight. Will do.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"204557","messageId":"CALkWK0m3RVaykPUBw9upc5kpwUwJd4q9D_TecSASxMJ=r3iXoA@mail.gmail.com","threadId":"32080","inReplyTo":"CAMP44s1--1i+ZtG7eKe0ZpRizcBfq+wJv_VOVZfU+A+gpjLyhQ@mail.gmail.com","subject":"Re: [PATCH] Add new git-cc-cmd helper to contrib","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2012-12-06T11:15:37Z","receivedAt":"2012-12-06T11:15:37Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Felipe Contreras wrote:\n> [...]\n\nWhat happened to this code?  I don't see it in `pu`.\n\nRam\n"}]}