threads / patch / 33998

v8, 6 partsNew git-related helper

Subject: [PATCH v8 0/6] New git-related helper

## tl;dr

8 messages between May 31, 2013 and May 31, 2013. Diffs are folded; open one to read it.

replies: 7people: 1as markdown or json

Felipe Contreras· May 31, 2013, 07:37 UTC · lore
Hi,

Since there hasn't been any comments of importance this is basically the same as v7, plus a few other patches to make it actually usable (for me).

Unfortunately it turns out Ruby's optparse is not suitable for our needs, so I implemented a very small parser that is.

Felipe Contreras (6):
  Add new git-related helper to contrib
  contrib: related: add option parsing
  contrib: related: add support for multiple patches
  contrib: related: add option to parse from committish
  contrib: related: parse committish like format-patch
  contrib: related: implement custom option parser
 contrib/related/git-related | 247 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 247 insertions(+)
 create mode 100755 contrib/related/git-related
-- 
1.8.3.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:37 UTC · re: Felipe Contreras · lore

[PATCH v8 1/6] Add new git-related helper to contrib

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>
---
 contrib/related/git-related | 120 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)
 create mode 100755 contrib/related/git-related
Show changes to contrib/related/git-related +120 −0
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.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:37 UTC · re: Felipe Contreras · lore

[PATCH v8 2/6] contrib: related: add option parsing

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 contrib/related/git-related | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
Show changes to contrib/related/git-related +16 −0
diff --git a/contrib/related/git-related b/contrib/related/git-related
index 1b9b1e7..bde5b99 100755
--- a/contrib/related/git-related
+++ b/contrib/related/git-related
@@ -3,9 +3,25 @@
 # This script finds people that might be interested in a patch
 # usage: git related <file>
 
+require 'optparse'
+
 $since = '5-years-ago'
 $min_percent = 10
 
+OptionParser.new do |opts|
+  opts.program_name = 'git related'
+  opts.banner = 'usage: git related [options] <file>'
+
+  opts.on('-p', '--min-percent N', Integer,
+          'Minium percentage of role participation') do |v|
+    $min_percent = v
+  end
+  opts.on('-d', '--since DATE',
+          'How far back to search for relevant commits') do |v|
+    $since = v
+  end
+end.parse!
+
 class Commit
 
   attr_reader :persons
-- 
1.8.3.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:37 UTC · re: Felipe Contreras · lore

[PATCH v8 3/6] contrib: related: add support for multiple patches

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 contrib/related/git-related | 35 +++++++++++++++++++----------------
 1 file changed, 19 insertions(+), 16 deletions(-)
Show changes to contrib/related/git-related +19 −16
diff --git a/contrib/related/git-related b/contrib/related/git-related
index bde5b99..0de1c6c 100755
--- a/contrib/related/git-related
+++ b/contrib/related/git-related
@@ -10,7 +10,7 @@ $min_percent = 10
 
 OptionParser.new do |opts|
   opts.program_name = 'git related'
-  opts.banner = 'usage: git related [options] <file>'
+  opts.banner = 'usage: git related [options] <files>'
 
   opts.on('-p', '--min-percent N', Integer,
           'Minium percentage of role participation') do |v|
@@ -56,6 +56,7 @@ class Commits
 
   def initialize
     @items = {}
+    @main_commits = {}
   end
 
   def size
@@ -91,23 +92,27 @@ class Commits
       p.each do |line|
         if line =~ /^\h{40}/
           id = $&
-          @items[id] = Commit.new(id)
+          @items[id] = Commit.new(id) if not @main_commits.include?(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
+  def from_patches(files)
+    source = nil
+    files.each do |file|
+      from = nil
+      File.open(file) do |f|
+        f.each do |line|
+          case line
+          when /^From (\h+) (.+)$/
+            from = $1
+            @main_commits[from] = true
+          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
@@ -115,10 +120,8 @@ class Commits
 
 end
 
-exit 1 if ARGV.size != 1
-
 commits = Commits.new
-commits.from_patch(ARGV[0])
+commits.from_patches(ARGV)
 commits.import
 
 count_per_person = Hash.new(0)
-- 
1.8.3.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:37 UTC · re: Felipe Contreras · lore

[PATCH v8 4/6] contrib: related: add option to parse from committish

For example master..feature-a.
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 contrib/related/git-related | 38 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)
Show changes to contrib/related/git-related +36 −2
diff --git a/contrib/related/git-related b/contrib/related/git-related
index 0de1c6c..3573237 100755
--- a/contrib/related/git-related
+++ b/contrib/related/git-related
@@ -7,10 +7,12 @@ require 'optparse'
 
 $since = '5-years-ago'
 $min_percent = 10
+$files = []
+$rev_args = []
 
 OptionParser.new do |opts|
   opts.program_name = 'git related'
-  opts.banner = 'usage: git related [options] <files>'
+  opts.banner = 'usage: git related [options] <files | rev-list options>'
 
   opts.on('-p', '--min-percent N', Integer,
           'Minium percentage of role participation') do |v|
@@ -118,10 +120,42 @@ class Commits
     end
   end
 
+  def from_rev_args(args)
+    source = nil
+    File.popen(%w[git rev-list --reverse] + args) do |p|
+      p.each do |e|
+        id = e.chomp
+        @main_commits[id] = true
+        File.popen(%w[git show -C --oneline] + [id]) do |p|
+          p.each do |e|
+            case e
+            when /^---\s+(\S+)/
+              source = $1 != '/dev/null' ? $1[2..-1] : nil
+            when /^@@ -(\d+)(?:,(\d+))?/
+              get_blame(source, $1, $2, id) if source
+            end
+          end
+        end
+      end
+    end
+  end
+
+end
+
+ARGV.each do |e|
+  if File.exists?(e)
+    $files << e
+  else
+    $rev_args << e
+  end
 end
 
 commits = Commits.new
-commits.from_patches(ARGV)
+if $files.empty?
+  commits.from_rev_args($rev_args)
+else
+  commits.from_patches($files)
+end
 commits.import
 
 count_per_person = Hash.new(0)
-- 
1.8.3.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:37 UTC · re: Felipe Contreras · lore

[PATCH v8 5/6] contrib: related: parse committish like format-patch

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 contrib/related/git-related | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
Show changes to contrib/related/git-related +15 −0
diff --git a/contrib/related/git-related b/contrib/related/git-related
index 3573237..585572b 100755
--- a/contrib/related/git-related
+++ b/contrib/related/git-related
@@ -121,6 +121,21 @@ class Commits
   end
 
   def from_rev_args(args)
+    revs = []
+
+    File.popen(%w[git rev-parse --revs-only --default HEAD --symbolic] + args).each do |rev|
+      revs << rev.chomp
+    end
+
+    case revs.size
+    when 1
+      r = revs[0]
+      r = '^' + r if r[0] != '-'
+      args = [ r, 'HEAD' ]
+    else
+      args = revs
+    end
+
     source = nil
     File.popen(%w[git rev-list --reverse] + args) do |p|
       p.each do |e|
-- 
1.8.3.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:37 UTC · re: Felipe Contreras · lore

[PATCH v8 6/6] contrib: related: implement custom option parser

Ruby's option parser is not extensible enough to keep unknown options.
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 contrib/related/git-related | 83 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 71 insertions(+), 12 deletions(-)
Show changes to contrib/related/git-related +71 −12
diff --git a/contrib/related/git-related b/contrib/related/git-related
index 585572b..c933898 100755
--- a/contrib/related/git-related
+++ b/contrib/related/git-related
@@ -3,26 +3,85 @@
 # This script finds people that might be interested in a patch
 # usage: git related <file>
 
-require 'optparse'
-
 $since = '5-years-ago'
 $min_percent = 10
 $files = []
 $rev_args = []
 
-OptionParser.new do |opts|
-  opts.program_name = 'git related'
-  opts.banner = 'usage: git related [options] <files | rev-list options>'
+class SimpleParser
+  attr_writer :usage
+
+  class Option
+    attr_reader :short, :long, :help
+
+    def initialize(short, long, help, &block)
+      @block = block
+      @short = short
+      @long = long
+      @help = help
+    end
+
+    def call(v)
+      @block.call(v)
+    end
+  end
 
-  opts.on('-p', '--min-percent N', Integer,
-          'Minium percentage of role participation') do |v|
-    $min_percent = v
+  def initialize
+    @list = {}
+  end
+
+  def on(short=nil, long=nil, help=nil, &block)
+    opt = Option.new(short, long, help, &block)
+    @list[short] = opt if short
+    @list[long] = opt if long
+  end
+
+  def parse
+    i = 0
+    if ARGV.member?('-h') or ARGV.member?('--help')
+      usage
+      exit 1
+    end
+    while cur = ARGV[i] do
+      if cur =~ /^(-.+?)(?:=(.*))?$/
+        opt = @list[$1]
+        if opt
+          v = $2 || ARGV.delete_at(i + 1)
+          opt.call(v)
+          ARGV.delete_at(i)
+          next
+        end
+      end
+      i += 1
+    end
   end
-  opts.on('-d', '--since DATE',
-          'How far back to search for relevant commits') do |v|
-    $since = v
+
+  def usage
+    puts 'usage: %s' % @usage
+    @list.values.uniq.each do |opt|
+      s = '    '
+      s << [opt.short, opt.long].compact.join(', ')
+      s << '%*s%s' % [26 - s.size, '', opt.help] if opt.help
+      puts s
+    end
   end
-end.parse!
+
+end
+
+opts = SimpleParser.new
+opts.usage = 'usage: git related [options] <files | rev-list options>'
+
+opts.on('-p', '--min-percent',
+        'Minium percentage of role participation') do |v|
+  $min_percent = v.to_i
+end
+
+opts.on('-d', '--since',
+        'How far back to search for relevant commits') do |v|
+  $since = v
+end
+
+opts.parse
 
 class Commit
 
-- 
1.8.3.358.g5a91d05
Felipe Contreras· May 31, 2013, 07:48 UTC · re: Felipe Contreras · lore

Re: [PATCH v8 0/6] New git-related helper

On Fri, May 31, 2013 at 2:37 AM, Felipe Contreras <felipe.contreras@gmail.com> wrote:

Show 5 quoted lines
> Since there hasn't been any comments of importance this is basically the same
> as v7, plus a few other patches to make it actually usable (for me).
>
> Unfortunately it turns out Ruby's optparse is not suitable for our needs, so I
> implemented a very small parser that is.

Nevermind, we can drop the option parsing changes for later, it's not important at the moment.

-- 
Felipe Contreras

← back to recent threads