{"thread":{"id":"34338","subject":"[PATCH v2 1/3] contrib: add git-contacts helper","startedAt":"2013-07-03T20:59:39Z","lastAt":"2013-07-03T20:59:42Z","messageCount":4,"participants":["Eric Sunshine"],"isPatch":true,"patchVersion":2,"patchTotal":3},"messages":[{"id":"222505","messageId":"1372885182-5512-1-git-send-email-sunshine@sunshineco.com","threadId":"34338","inReplyTo":null,"subject":"[PATCH v2 0/3] Perl rewrite of Ruby git-related","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-03T20:59:39Z","receivedAt":"2013-07-03T20:59:39Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is v2 of the Perl rewrite of Felipe Contreras' git-related v9 patch\nseries[1] which was written in Ruby. v2 addresses (hopefully all of)\nJunio's v1[2] review points.\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/226065/\n[2]: http://thread.gmane.org/gmane.comp.version-control.git/229266/\n\n\nChanges since v1:\n\n* Match Signed-off-by:, etc. labels case-insensitively.\n\n* Recognize Cc: in addition to Signed-off-by: and other typical labels.\n\n* Match SHA-1's and surrounding context more strictly in output of\n  git-format-patch, git-blame, git-cat-file.\n\n* Mention -C in commit message of [1/3].\n\n* Pass just a single -C to git-blame and git-diff. Previously, git-blame\n  was invoked inconsistently with '-C -C' and git-diff with '-C'.\n\n* Employ git-blame --porcelain rather than --incremental.\n\n* Fix bug where a \"new file\" ('--- /dev/null') hunk following a \"changed\n  file\" ('--- a/path/to/file') hunk would incorrectly invoke git-blame\n  on \"changed file\" with line numbers from \"new file\".\n\n* Fix bug where filename containing whitespace was not recognized\n  correctly when scanning hunk source lines ('--- /a/file name').\n\n* Abort when unable to parse hunk source ('--- path') from diff with\n  custom --src-prefix=.\n\n* Emit diagnostic, rather than exiting silently, when user neglects to\n  provide input arguments.\n\n* Combine v1 patches [1/4] and [2/4]. As Junio pointed, a single patch\n  file may contain multiple patches (for example \"git format-patch\n  --stdout -4 >P.mbox\"), so the machinery for dealing with multiple\n  patches must already be present in [1/4] whether the patches come from\n  one file or many. Consequently, adding \"multiple file\" support\n  involved only a very minor change to @ARGV handling, not deserving of\n  a separate [2/4] patch.\n\n* Drop Perl source file and line number information from user-facing\n  error messages.\n\n\nPossible future directions and considerations:\n\n* Add mailmap support.\n\n* Make participation parameters (minimum-percent, blame since-date, -C\n  level) configurable.\n\n* Optimize by coalescing ranges into a single git-blame -L invocation;\n  or enhance git-blame to accept multiple -L's.\n\n* Improve Windows support if needed, possibly via grabbing\n  run_cmd_pipe() from git-add--interactive.perl or similar.\n\n\nEric Sunshine (3):\n  contrib: add git-contacts helper\n  contrib: contacts: add ability to parse from committish\n  contrib: contacts: interpret committish akin to format-patch\n\n contrib/contacts/git-contacts | 170 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 170 insertions(+)\n create mode 100755 contrib/contacts/git-contacts\n\n-- \n1.8.3.2\n"},{"id":"222502","messageId":"1372885182-5512-2-git-send-email-sunshine@sunshineco.com","threadId":"34338","inReplyTo":"1372885182-5512-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 1/3] contrib: add git-contacts helper","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-03T20:59:40Z","receivedAt":"2013-07-03T20:59:40Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This script lists people that might be interested in a patch by going\nback through the history for each patch hunk, and finding people that\nreviewed, acknowledge, signed, authored, or were Cc:'d on 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 participants, it\ndetermines each person's relevance by considering how many commits\nmentioned that person compared with the total number of commits under\nconsideration. The final output consists only of participants who pass a\nminimum threshold of participation.\n\nSeveral conditions controlling a person's significance are currently\nhard-coded, such as minimum participation level, blame date-limiting,\nand -C level for detecting moved and copied lines. In the future, these\nconditions may become configurable.\n\nFor example:\n\n  % git contacts 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 invoked as git-send-email's --cc-cmd option, among other\npossible uses.\n\nThis is a Perl rewrite of Felipe Contreras' git-related patch series[1]\nwritten in Ruby.\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/226065/\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n contrib/contacts/git-contacts | 127 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 127 insertions(+)\n create mode 100755 contrib/contacts/git-contacts\n\ndiff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts\nnew file mode 100755\nindex 0000000..6e9ab45\n--- /dev/null\n+++ b/contrib/contacts/git-contacts\n@@ -0,0 +1,127 @@\n+#!/usr/bin/perl\n+\n+# List people who might be interested in a patch.  Useful as the argument to\n+# git-send-email --cc-cmd option, and in other situations.\n+#\n+# Usage: git contacts <file> ...\n+\n+use strict;\n+use warnings;\n+use IPC::Open2;\n+\n+my $since = '5-years-ago';\n+my $min_percent = 10;\n+my $labels_rx = qr/Signed-off-by|Reviewed-by|Acked-by|Cc/i;\n+my %seen;\n+\n+sub format_contact {\n+\tmy ($name, $email) = @_;\n+\treturn \"$name <$email>\";\n+}\n+\n+sub parse_commit {\n+\tmy ($commit, $data) = @_;\n+\tmy $contacts = $commit->{contacts};\n+\tmy $inbody = 0;\n+\tfor (split(/^/m, $data)) {\n+\t\tif (not $inbody) {\n+\t\t\tif (/^author ([^<>]+) <(\\S+)> .+$/) {\n+\t\t\t\t$contacts->{format_contact($1, $2)} = 1;\n+\t\t\t} elsif (/^$/) {\n+\t\t\t\t$inbody = 1;\n+\t\t\t}\n+\t\t} elsif (/^$labels_rx:\\s+([^<>]+)\\s+<(\\S+?)>$/o) {\n+\t\t\t$contacts->{format_contact($1, $2)} = 1;\n+\t\t}\n+\t}\n+}\n+\n+sub import_commits {\n+\tmy ($commits) = @_;\n+\treturn unless %$commits;\n+\tmy $pid = open2 my $reader, my $writer, qw(git cat-file --batch);\n+\tfor my $id (keys(%$commits)) {\n+\t\tprint $writer \"$id\\n\";\n+\t\tmy $line = <$reader>;\n+\t\tif ($line =~ /^([0-9a-f]{40}) commit (\\d+)/) {\n+\t\t\tmy ($cid, $len) = ($1, $2);\n+\t\t\tdie \"expected $id but got $cid\\n\" unless $id eq $cid;\n+\t\t\tmy $data;\n+\t\t\t# cat-file emits newline after data, so read len+1\n+\t\t\tread $reader, $data, $len + 1;\n+\t\t\tparse_commit($commits->{$id}, $data);\n+\t\t}\n+\t}\n+\tclose $reader;\n+\tclose $writer;\n+\twaitpid($pid, 0);\n+\tdie \"git-cat-file error: $?\\n\" if $?;\n+}\n+\n+sub get_blame {\n+\tmy ($commits, $source, $start, $len, $from) = @_;\n+\t$len = 1 unless defined($len);\n+\treturn if $len == 0;\n+\topen my $f, '-|',\n+\t\tqw(git blame --porcelain -C), '-L', \"$start,+$len\",\n+\t\t'--since', $since, \"$from^\", '--', $source or die;\n+\twhile (<$f>) {\n+\t\tif (/^([0-9a-f]{40}) \\d+ \\d+ \\d+$/) {\n+\t\t\tmy $id = $1;\n+\t\t\t$commits->{$id} = { id => $id, contacts => {} }\n+\t\t\t\tunless $seen{$id};\n+\t\t\t$seen{$id} = 1;\n+\t\t}\n+\t}\n+\tclose $f;\n+}\n+\n+sub scan_patches {\n+\tmy ($commits, $f) = @_;\n+\tmy ($id, $source);\n+\twhile (<$f>) {\n+\t\tif (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {\n+\t\t\t$id = $1;\n+\t\t\t$seen{$id} = 1;\n+\t\t}\n+\t\tnext unless $id;\n+\t\tif (m{^--- (?:a/(.+)|/dev/null)$}) {\n+\t\t\t$source = $1;\n+\t\t} elsif (/^--- /) {\n+\t\t\tdie \"Cannot parse hunk source: $_\\n\";\n+\t\t} elsif (/^@@ -(\\d+)(?:,(\\d+))?/ && $source) {\n+\t\t\tget_blame($commits, $source, $1, $2, $id);\n+\t\t}\n+\t}\n+}\n+\n+sub scan_patch_file {\n+\tmy ($commits, $file) = @_;\n+\topen my $f, '<', $file or die \"read failure: $file: $!\\n\";\n+\tscan_patches($commits, $f);\n+\tclose $f;\n+}\n+\n+if (!@ARGV) {\n+\tdie \"No input patch files\\n\";\n+}\n+\n+my %commits;\n+for (@ARGV) {\n+\tscan_patch_file(\\%commits, $_);\n+}\n+import_commits(\\%commits);\n+\n+my %count_per_person;\n+for my $commit (values %commits) {\n+\tfor my $contact (keys %{$commit->{contacts}}) {\n+\t\t$count_per_person{$contact}++;\n+\t}\n+}\n+\n+my $ncommits = scalar(keys %commits);\n+for my $contact (keys %count_per_person) {\n+\tmy $percent = $count_per_person{$contact} * 100 / $ncommits;\n+\tnext if $percent < $min_percent;\n+\tprint \"$contact\\n\";\n+}\n-- \n1.8.3.2\n"},{"id":"222504","messageId":"1372885182-5512-3-git-send-email-sunshine@sunshineco.com","threadId":"34338","inReplyTo":"1372885182-5512-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 2/3] contrib: contacts: add ability to parse from committish","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-03T20:59:41Z","receivedAt":"2013-07-03T20:59:41Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"For example:\n\n  % git contacts master..feature\n\nCommittishes and patch files can be mentioned in the same\ninvocation:\n\n  % git contacts master..feature extra/*.patch\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n contrib/contacts/git-contacts | 38 ++++++++++++++++++++++++++++++++------\n 1 file changed, 32 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts\nindex 6e9ab45..42e6059 100755\n--- a/contrib/contacts/git-contacts\n+++ b/contrib/contacts/git-contacts\n@@ -3,7 +3,7 @@\n # List people who might be interested in a patch.  Useful as the argument to\n # git-send-email --cc-cmd option, and in other situations.\n #\n-# Usage: git contacts <file> ...\n+# Usage: git contacts <file | rev-list option> ...\n \n use strict;\n use warnings;\n@@ -77,8 +77,8 @@ sub get_blame {\n }\n \n sub scan_patches {\n-\tmy ($commits, $f) = @_;\n-\tmy ($id, $source);\n+\tmy ($commits, $id, $f) = @_;\n+\tmy $source;\n \twhile (<$f>) {\n \t\tif (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {\n \t\t\t$id = $1;\n@@ -98,18 +98,44 @@ sub scan_patches {\n sub scan_patch_file {\n \tmy ($commits, $file) = @_;\n \topen my $f, '<', $file or die \"read failure: $file: $!\\n\";\n-\tscan_patches($commits, $f);\n+\tscan_patches($commits, undef, $f);\n+\tclose $f;\n+}\n+\n+sub scan_rev_args {\n+\tmy ($commits, $args) = @_;\n+\topen my $f, '-|', qw(git rev-list --reverse), @$args or die;\n+\twhile (<$f>) {\n+\t\tchomp;\n+\t\tmy $id = $_;\n+\t\t$seen{$id} = 1;\n+\t\topen my $g, '-|', qw(git show -C --oneline), $id or die;\n+\t\tscan_patches($commits, $id, $g);\n+\t\tclose $g;\n+\t}\n \tclose $f;\n }\n \n if (!@ARGV) {\n-\tdie \"No input patch files\\n\";\n+\tdie \"No input revisions or patch files\\n\";\n }\n \n-my %commits;\n+my (@files, @rev_args);\n for (@ARGV) {\n+\tif (-e) {\n+\t\tpush @files, $_;\n+\t} else {\n+\t\tpush @rev_args, $_;\n+\t}\n+}\n+\n+my %commits;\n+for (@files) {\n \tscan_patch_file(\\%commits, $_);\n }\n+if (@rev_args) {\n+\tscan_rev_args(\\%commits, \\@rev_args)\n+}\n import_commits(\\%commits);\n \n my %count_per_person;\n-- \n1.8.3.2\n"},{"id":"222503","messageId":"1372885182-5512-4-git-send-email-sunshine@sunshineco.com","threadId":"34338","inReplyTo":"1372885182-5512-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 3/3] contrib: contacts: interpret committish akin to format-patch","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-03T20:59:42Z","receivedAt":"2013-07-03T20:59:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"As a convenience, accept the same style <since> committish as accepted\nby git-format-patch. For example:\n\n  % git contacts master\n\nwill consider commits in the current branch built atop 'master', just as\n\"git format-patch master\" will format commits built atop 'master'.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n contrib/contacts/git-contacts | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts\nindex 42e6059..3cd0ea2 100755\n--- a/contrib/contacts/git-contacts\n+++ b/contrib/contacts/git-contacts\n@@ -102,9 +102,26 @@ sub scan_patch_file {\n \tclose $f;\n }\n \n+sub parse_rev_args {\n+\tmy @args = @_;\n+\topen my $f, '-|',\n+\t\tqw(git rev-parse --revs-only --default HEAD --symbolic), @args\n+\t\tor die;\n+\tmy @revs;\n+\twhile (<$f>) {\n+\t\tchomp;\n+\t\tpush @revs, $_;\n+\t}\n+\tclose $f;\n+\treturn @revs if scalar(@revs) != 1;\n+\treturn \"^$revs[0]\", 'HEAD' unless $revs[0] =~ /^-/;\n+\treturn $revs[0], 'HEAD';\n+}\n+\n sub scan_rev_args {\n \tmy ($commits, $args) = @_;\n-\topen my $f, '-|', qw(git rev-list --reverse), @$args or die;\n+\tmy @revs = parse_rev_args(@$args);\n+\topen my $f, '-|', qw(git rev-list --reverse), @revs or die;\n \twhile (<$f>) {\n \t\tchomp;\n \t\tmy $id = $_;\n-- \n1.8.3.2\n"}]}