git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH] Add new git-cc-cmd helper to contrib
@ 2012-11-11 17:10 Felipe Contreras
  2012-11-11 17:18 ` Ramkumar Ramachandra
  0 siblings, 1 reply; 4+ messages in thread
From: Felipe Contreras @ 2012-11-11 17:10 UTC (permalink / raw
  To: git; +Cc: Felipe Contreras

You can run it like format-patch:

 % git cc-cmd master..my-branch

And you'll get relevant people to Cc.

The code finds the changes in each commit in the list, runs 'git blame'
to see which other commits are relevant to those lines, and then adds
the author and signer to the list.

Finally, it calculates what percentage of the total relevant commits
each person was involved in, and if it passes the threshold, it goes in.

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---

This cannot be used as cc-cmd option for 'git send-email' but it can as
series-cc-cmd for which I just send some patches.

An example output:

% git cc-cmd --role-stat v1.8.0^^1..v1.8.0^^2
Vietor Liu <vietor@vxwo.org> (author: 12%)
"Shawn O. Pearce" <spearce@spearce.org> (signer: 37%)
Alexandre Erwin Ittner <alexandre@ittner.com.br> (author: 12%)
Peter Krefting <peter@softwolves.pp.se> (author: 12%)
Pat Thoyts <patthoyts@users.sourceforge.net> (signer: 37%, author: 12%)
Christian Stimming <stimming@tuhh.de> (author: 12%)
Heiko Voigt <hvoigt@hvoigt.net> (author: 25%)
Heiko Voigt <heiko.voigt@mahr.de> (signer: 12%)
Bert Wesarg <bert.wesarg@googlemail.com> (author: 12%)

Cheers.

 contrib/cc-cmd/git-cc-cmd | 186 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 186 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..17b14d4
--- /dev/null
+++ b/contrib/cc-cmd/git-cc-cmd
@@ -0,0 +1,186 @@
+#!/usr/bin/env ruby
+
+require 'optparse'
+
+$since = '3-years-ago'
+$min_percent = 5
+$role_stats = false
+
+begin
+  OptionParser.new do |opts|
+    opts.on('--role-stats') do |v|
+      $role_stats = v
+    end
+    opts.on('--min-percent N', Integer) do |v|
+      $min_percent = v
+    end
+    opts.on('--since DATE') do |v|
+      $since = v
+    end
+  end.parse!
+rescue OptionParser::InvalidOption
+end
+
+revs = []
+File.popen(%w[git rev-parse --revs-only --default=HEAD --symbolic] + ARGV).each do |rev|
+  revs << rev.chomp
+end
+
+case revs.size
+when 0
+  committish = '%s..HEAD' % '@{upstream}'
+when 1
+  committish = '%s..HEAD' % revs[0]
+else
+  committish = revs.join(' ')
+end
+
+$alias_file = "~/.mutt/aliases"
+$aliases = {}
+
+if $alias_file
+  open(File.expand_path($alias_file)).each do |line|
+    if line =~ /^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/
+      key, addresses = $1, $2.split(', ')
+      addresses.each do |address|
+        $aliases[address] = key
+      end
+    end
+  end
+end
+
+class Commit
+
+  attr_reader :id
+  attr_accessor :author, :roles
+
+  def initialize(id)
+    @id = id
+    @roles = []
+  end
+
+  def self.parse(data)
+    id = author = msg = nil
+    roles = {}
+    data.each_line do |line|
+      if not msg
+        case line
+        when /^commit (.+)$/
+          id = $1
+        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|
+      address = "%s <%s>" % person
+      if $aliases.include?(address)
+        person = nil, $aliases[address]
+      end
+      [person, role]
+    end
+    [id, author, roles]
+  end
+
+end
+
+class Commits
+
+  attr_reader :items
+
+  def initialize(items = {})
+    @items = items
+    import
+  end
+
+  def size
+    @items.size
+  end
+
+  def import
+    return if @items.empty?
+    ids = @items.keys.join(' ')
+    format = [
+      'commit %H',
+      'author %an <%ae>',
+      '', '%B' ].join('%n')
+    open("|git show -z -s --format='format:#{format}' #{ids}").each("\0") do |data|
+      next if data == "\0" # bug in git show?
+      id, author, roles = Commit.parse(data)
+      commit = @items[id]
+      commit.author = author
+      commit.roles = roles
+    end
+  end
+
+  def each_person_role
+    commit_roles = @items.values.map { |commit| commit.roles }.flatten(1)
+    commit_roles.group_by { |person, role| person }.each do |person, commit_roles|
+      commit_roles.group_by { |person, role| role }.each do |role, commit_roles|
+        yield person, role, commit_roles.size
+      end
+    end
+  end
+
+  def self.from_blame(committish)
+    main_commits = {}
+    items = {}
+    open("|git rev-list --reverse #{committish}").each do |e|
+      id = e.chomp
+      main_commits[id] = true
+      open("|git --no-pager show -C --oneline #{id}").each do |e|
+        case e
+        when /^---\s+(\S+)/
+          @source = $1 != '/dev/null' ? $1[2..-1] : nil
+        when /^@@\s-(\d+),(\d+)/
+          next unless @source
+          open("|git blame --incremental -C -L #{$1},+#{$2} --since=#{$since} #{id}^ -- #{@source}").each do |e|
+            if e =~ /^(\h{40})/
+              items[$1] = Commit.new($1) if not main_commits.include?($1)
+            end
+          end
+        end
+      end
+    end
+    self.new(items)
+  end
+
+end
+
+commits = Commits.from_blame(committish)
+
+# hash of hashes
+persons = Hash.new { |hash, key| hash[key] = {} }
+
+commits.each_person_role do |person, role, count|
+  persons[person][role] = count
+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
+
+  # has must quote chars?
+  name = '"%s"' % name if name =~ /[^\w \-]/i
+
+  person = name ? "%s <%s>" % [name, email] : email
+
+  if $role_stats
+    puts "%s (%s)" % [person, roles.join(', ')]
+  else
+    puts person
+  end
+end
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] Add new git-cc-cmd helper to contrib
  2012-11-11 17:10 [PATCH] Add new git-cc-cmd helper to contrib Felipe Contreras
@ 2012-11-11 17:18 ` Ramkumar Ramachandra
  2012-11-11 18:10   ` Felipe Contreras
  0 siblings, 1 reply; 4+ messages in thread
From: Ramkumar Ramachandra @ 2012-11-11 17:18 UTC (permalink / raw
  To: Felipe Contreras; +Cc: git

Hi Felipe,

Felipe Contreras wrote:
> The code finds the changes in each commit in the list, runs 'git blame'
> to see which other commits are relevant to those lines, and then adds
> the author and signer to the list.
>
> Finally, it calculates what percentage of the total relevant commits
> each person was involved in, and if it passes the threshold, it goes in.

This is very cool.

> diff --git a/contrib/cc-cmd/git-cc-cmd b/contrib/cc-cmd/git-cc-cmd
> new file mode 100755
> index 0000000..17b14d4
> --- /dev/null
> +++ b/contrib/cc-cmd/git-cc-cmd
> @@ -0,0 +1,186 @@
> +#!/usr/bin/env ruby

Just out of curiosity, why didn't you write it in Python?  (I noticed
that you wrote remote-hg and remote-bzr in Python, and assumed that it
was your preferred language)

> +$alias_file = "~/.mutt/aliases"

Please read sendemail.aliasfiletype instead of assuming mutt.

Ram

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] Add new git-cc-cmd helper to contrib
  2012-11-11 17:18 ` Ramkumar Ramachandra
@ 2012-11-11 18:10   ` Felipe Contreras
  2012-12-06 11:15     ` Ramkumar Ramachandra
  0 siblings, 1 reply; 4+ messages in thread
From: Felipe Contreras @ 2012-11-11 18:10 UTC (permalink / raw
  To: Ramkumar Ramachandra; +Cc: git

Hi,

On Sun, Nov 11, 2012 at 6:18 PM, Ramkumar Ramachandra
<artagnon@gmail.com> wrote:
> Felipe Contreras wrote:
>> The code finds the changes in each commit in the list, runs 'git blame'
>> to see which other commits are relevant to those lines, and then adds
>> the author and signer to the list.
>>
>> Finally, it calculates what percentage of the total relevant commits
>> each person was involved in, and if it passes the threshold, it goes in.
>
> This is very cool.
>
>> diff --git a/contrib/cc-cmd/git-cc-cmd b/contrib/cc-cmd/git-cc-cmd
>> new file mode 100755
>> index 0000000..17b14d4
>> --- /dev/null
>> +++ b/contrib/cc-cmd/git-cc-cmd
>> @@ -0,0 +1,186 @@
>> +#!/usr/bin/env ruby
>
> Just out of curiosity, why didn't you write it in Python?  (I noticed
> that you wrote remote-hg and remote-bzr in Python, and assumed that it
> was your preferred language)

No, in fact I hate python :) I only used it because both bazaar and
mercurial are written in python.

Ruby is my favorite language, after C.

I'm fairly certain that the equivalent code in python would be much
more complicated.

>> +$alias_file = "~/.mutt/aliases"
>
> Please read sendemail.aliasfiletype instead of assuming mutt.

Right. Will do.

Cheers.

-- 
Felipe Contreras

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] Add new git-cc-cmd helper to contrib
  2012-11-11 18:10   ` Felipe Contreras
@ 2012-12-06 11:15     ` Ramkumar Ramachandra
  0 siblings, 0 replies; 4+ messages in thread
From: Ramkumar Ramachandra @ 2012-12-06 11:15 UTC (permalink / raw
  To: Felipe Contreras; +Cc: git, Junio C Hamano

Felipe Contreras wrote:
> [...]

What happened to this code?  I don't see it in `pu`.

Ram

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2012-12-06 11:16 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-11-11 17:10 [PATCH] Add new git-cc-cmd helper to contrib Felipe Contreras
2012-11-11 17:18 ` Ramkumar Ramachandra
2012-11-11 18:10   ` Felipe Contreras
2012-12-06 11:15     ` Ramkumar Ramachandra

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).