{"thread":{"id":"33999","subject":"[PATCH v9 0/4] New git-related helper","startedAt":"2013-05-31T07:46:16Z","lastAt":"2013-06-03T21:05:17Z","messageCount":8,"participants":["Felipe Contreras","Junio C Hamano"],"isPatch":true,"patchVersion":9,"patchTotal":4},"messages":[{"id":"219074","messageId":"1369986380-412-1-git-send-email-felipe.contreras@gmail.com","threadId":"33999","inReplyTo":null,"subject":"[PATCH v9 0/4] New git-related helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-05-31T07:46:16Z","receivedAt":"2013-05-31T07:46:16Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\nI changed my mind, this is the same as v9 but minus some potentially\ncontroversial changes.\n\nFelipe Contreras (4):\n  Add new git-related helper to contrib\n  contrib: related: add support for multiple patches\n  contrib: related: add option to parse from committish\n  contrib: related: parse committish like format-patch\n\n contrib/related/git-related | 172 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 172 insertions(+)\n create mode 100755 contrib/related/git-related\n\n-- \n1.8.3.358.g5a91d05\n"},{"id":"219075","messageId":"1369986380-412-2-git-send-email-felipe.contreras@gmail.com","threadId":"33999","inReplyTo":"1369986380-412-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v9 1/4] Add new git-related helper to contrib","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-05-31T07:46:17Z","receivedAt":"2013-05-31T07:46:17Z","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>\n  Jeff King <peff@peff.net>\n  Max Horn <max@quendi.de>\n  Junio C Hamano <gitster@pobox.com>\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 | 120 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 120 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..1b9b1e7\n--- /dev/null\n+++ b/contrib/related/git-related\n@@ -0,0 +1,120 @@\n+#!/usr/bin/env ruby\n+\n+# This script finds people that might be interested in a patch\n+# usage: git related <file>\n+\n+$since = '5-years-ago'\n+$min_percent = 10\n+\n+class Commit\n+\n+  attr_reader :persons\n+\n+  def initialize(id)\n+    @id = id\n+    @persons = []\n+  end\n+\n+  def parse(data)\n+    msg = nil\n+    data.each_line do |line|\n+      if not msg\n+        case line\n+        when /^author ([^<>]+) <(\\S+)> (.+)$/\n+          @persons << '%s <%s>' % [$1, $2]\n+        when /^$/\n+          msg = true\n+        end\n+      else\n+        if line =~ /^(Signed-off|Reviewed|Acked)-by: ([^<>]+) <(\\S+?)>$/\n+          @persons << '%s <%s>' % [$2, $3]\n+        end\n+      end\n+    end\n+    @persons.uniq!\n+  end\n+\n+end\n+\n+class Commits\n+\n+  def initialize\n+    @items = {}\n+  end\n+\n+  def size\n+    @items.size\n+  end\n+\n+  def each(&block)\n+    @items.each(&block)\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 |line|\n+        if line =~ /^(\\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, len, from)\n+    return if len == 0\n+    len ||= 1\n+    File.popen(['git', 'blame', '--incremental', '-C', '-C',\n+               '-L', '%u,+%u' % [start, len],\n+               '--since', $since, from + '^',\n+               '--', source]) do |p|\n+      p.each do |line|\n+        if line =~ /^\\h{40}/\n+          id = $&\n+          @items[id] = Commit.new(id)\n+        end\n+      end\n+    end\n+  end\n+\n+  def from_patch(file)\n+    from = source = 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 /^@@ -(\\d+)(?:,(\\d+))?/\n+          get_blame(source, $1, $2, from) if source and 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+count_per_person = Hash.new(0)\n+\n+commits.each do |id, commit|\n+  commit.persons.each do |person|\n+    count_per_person[person] += 1\n+  end\n+end\n+\n+count_per_person.each do |person, count|\n+  percent = count.to_f * 100 / commits.size\n+  next if percent < $min_percent\n+  puts person\n+end\n-- \n1.8.3.358.g5a91d05\n"},{"id":"219077","messageId":"1369986380-412-3-git-send-email-felipe.contreras@gmail.com","threadId":"33999","inReplyTo":"1369986380-412-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v9 2/4] contrib: related: add support for multiple patches","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-05-31T07:46:18Z","receivedAt":"2013-05-31T07:46:18Z","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 | 35 +++++++++++++++++++----------------\n 1 file changed, 19 insertions(+), 16 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 1b9b1e7..3379982 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -1,7 +1,7 @@\n #!/usr/bin/env ruby\n \n # This script finds people that might be interested in a patch\n-# usage: git related <file>\n+# usage: git related <files>\n \n $since = '5-years-ago'\n $min_percent = 10\n@@ -40,6 +40,7 @@ class Commits\n \n   def initialize\n     @items = {}\n+    @main_commits = {}\n   end\n \n   def size\n@@ -75,23 +76,27 @@ class Commits\n       p.each do |line|\n         if line =~ /^\\h{40}/\n           id = $&\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-    from = source = 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 /^@@ -(\\d+)(?:,(\\d+))?/\n-          get_blame(source, $1, $2, from) if source and from\n+  def from_patches(files)\n+    source = nil\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 /^@@ -(\\d+)(?:,(\\d+))?/\n+            get_blame(source, $1, $2, from) if source and from\n+          end\n         end\n       end\n     end\n@@ -99,10 +104,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 count_per_person = Hash.new(0)\n-- \n1.8.3.358.g5a91d05\n"},{"id":"219076","messageId":"1369986380-412-4-git-send-email-felipe.contreras@gmail.com","threadId":"33999","inReplyTo":"1369986380-412-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v9 3/4] contrib: related: add option to parse from committish","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-05-31T07:46:19Z","receivedAt":"2013-05-31T07:46:19Z","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 | 38 ++++++++++++++++++++++++++++++++++++--\n 1 file changed, 36 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 3379982..20eb456 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -1,10 +1,12 @@\n #!/usr/bin/env ruby\n \n # This script finds people that might be interested in a patch\n-# usage: git related <files>\n+# usage: git related <files | rev-list options>\n \n $since = '5-years-ago'\n $min_percent = 10\n+$files = []\n+$rev_args = []\n \n class Commit\n \n@@ -102,10 +104,42 @@ class Commits\n     end\n   end\n \n+  def from_rev_args(args)\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 /^@@ -(\\d+)(?:,(\\d+))?/\n+              get_blame(source, $1, $2, id) if source\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+if $files.empty?\n+  commits.from_rev_args($rev_args)\n+else\n+  commits.from_patches($files)\n+end\n commits.import\n \n count_per_person = Hash.new(0)\n-- \n1.8.3.358.g5a91d05\n"},{"id":"219078","messageId":"1369986380-412-5-git-send-email-felipe.contreras@gmail.com","threadId":"33999","inReplyTo":"1369986380-412-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v9 4/4] contrib: related: parse committish like format-patch","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-05-31T07:46:20Z","receivedAt":"2013-05-31T07:46:20Z","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 | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/contrib/related/git-related b/contrib/related/git-related\nindex 20eb456..bded6f6 100755\n--- a/contrib/related/git-related\n+++ b/contrib/related/git-related\n@@ -105,6 +105,21 @@ class Commits\n   end\n \n   def from_rev_args(args)\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+      r = revs[0]\n+      r = '^' + r if r[0] != '-'\n+      args = [ r, 'HEAD' ]\n+    else\n+      args = 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.3.358.g5a91d05\n"},{"id":"219274","messageId":"7v8v2rdnh0.fsf@alter.siamese.dyndns.org","threadId":"33999","inReplyTo":"1369986380-412-4-git-send-email-felipe.contreras@gmail.com","subject":"Re: [PATCH v9 3/4] contrib: related: add option to parse from committish","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-06-03T19:05:15Z","receivedAt":"2013-06-03T19:05:15Z","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> For example master..feature-a.\n>\n> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n> ---\n>  contrib/related/git-related | 38 ++++++++++++++++++++++++++++++++++++--\n>  1 file changed, 36 insertions(+), 2 deletions(-)\n>\n> diff --git a/contrib/related/git-related b/contrib/related/git-related\n> index 3379982..20eb456 100755\n> --- a/contrib/related/git-related\n> +++ b/contrib/related/git-related\n> @@ -1,10 +1,12 @@\n>  #!/usr/bin/env ruby\n>  \n>  # This script finds people that might be interested in a patch\n> -# usage: git related <files>\n> +# usage: git related <files | rev-list options>\n>  \n>  $since = '5-years-ago'\n>  $min_percent = 10\n> +$files = []\n> +$rev_args = []\n>  \n>  class Commit\n>  \n> @@ -102,10 +104,42 @@ class Commits\n>      end\n>    end\n>  \n> +  def from_rev_args(args)\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\nIs there a solid design choice behind -C, or is it just what happens\nto have worked for you in practice?  If the former, it may want to\nbe explained somewhere (either in the log or in the code) so that\nlater tweaks will not break it, especially given that the invocation\nof blame seems to use double-C's.\n\n> +          p.each do |e|\n> +            case e\n> +            when /^---\\s+(\\S+)/\n> +              source = $1 != '/dev/null' ? $1[2..-1] : nil\n> +            when /^@@ -(\\d+)(?:,(\\d+))?/\n> +              get_blame(source, $1, $2, id) if source\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> +if $files.empty?\n> +  commits.from_rev_args($rev_args)\n> +else\n> +  commits.from_patches($files)\n> +end\n>  commits.import\n>  \n>  count_per_person = Hash.new(0)\n"},{"id":"219279","messageId":"7v4ndfdn2s.fsf@alter.siamese.dyndns.org","threadId":"33999","inReplyTo":"1369986380-412-1-git-send-email-felipe.contreras@gmail.com","subject":"Re: [PATCH v9 0/4] New git-related helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-06-03T19:13:47Z","receivedAt":"2013-06-03T19:13:47Z","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> I changed my mind, this is the same as v9 but minus some potentially\n> controversial changes.\n>\n> Felipe Contreras (4):\n>   Add new git-related helper to contrib\n>   contrib: related: add support for multiple patches\n>   contrib: related: add option to parse from committish\n>   contrib: related: parse committish like format-patch\n>\n>  contrib/related/git-related | 172 ++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 172 insertions(+)\n>  create mode 100755 contrib/related/git-related\n\nReplaced what was parked on 'pu' with this.\n"},{"id":"219300","messageId":"CAMP44s2+rNG+VPVJ82UsfrmHugnUwBVJfYF5QRVn0=JC061RFg@mail.gmail.com","threadId":"33999","inReplyTo":"7v8v2rdnh0.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v9 3/4] contrib: related: add option to parse from committish","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-06-03T21:05:17Z","receivedAt":"2013-06-03T21:05:17Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Mon, Jun 3, 2013 at 2:05 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> writes:\n>\n>> For example master..feature-a.\n>>\n>> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n>> ---\n>>  contrib/related/git-related | 38 ++++++++++++++++++++++++++++++++++++--\n>>  1 file changed, 36 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/contrib/related/git-related b/contrib/related/git-related\n>> index 3379982..20eb456 100755\n>> --- a/contrib/related/git-related\n>> +++ b/contrib/related/git-related\n>> @@ -1,10 +1,12 @@\n>>  #!/usr/bin/env ruby\n>>\n>>  # This script finds people that might be interested in a patch\n>> -# usage: git related <files>\n>> +# usage: git related <files | rev-list options>\n>>\n>>  $since = '5-years-ago'\n>>  $min_percent = 10\n>> +$files = []\n>> +$rev_args = []\n>>\n>>  class Commit\n>>\n>> @@ -102,10 +104,42 @@ class Commits\n>>      end\n>>    end\n>>\n>> +  def from_rev_args(args)\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>\n> Is there a solid design choice behind -C, or is it just what happens\n> to have worked for you in practice?  If the former, it may want to\n> be explained somewhere (either in the log or in the code) so that\n> later tweaks will not break it, especially given that the invocation\n> of blame seems to use double-C's.\n\nNo reason in particular.\n\n-- \nFelipe Contreras\n"}]}