{"thread":{"id":"34504","subject":"[PATCH v3 0/5] add git-contacts helper","startedAt":"2013-07-21T10:52:37Z","lastAt":"2013-07-21T10:52:42Z","messageCount":6,"participants":["Eric Sunshine"],"isPatch":true,"patchVersion":3,"patchTotal":5},"messages":[{"id":"223867","messageId":"1374403962-48361-1-git-send-email-sunshine@sunshineco.com","threadId":"34504","inReplyTo":null,"subject":"[PATCH v3 0/5] add git-contacts helper","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-21T10:52:37Z","receivedAt":"2013-07-21T10:52:37Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is v3 of the Perl rewrite of Felipe Contreras' git-related v9\npatch series [1] which was written in Ruby.  v2 of this series is\nhere [2].\n\nv3 adds git mailmap support and documentation.  It requires\ncheck-mailmap [3] functionality (es/check-mailmap in 'pu'), though it\ndoes not otherwise depend upon it textually.\n\nPatch 1/5: Same as v2 except that the variable holding the commit\n    counts for each contact has been renamed and changed from a hash\n    to a hash reference in order to simplify patch 4/5.\n\nPatch 2/5: Same as v2.\n\nPatch 3/5: Same as v2.\n\nPatch 4/5: New. Add mailmap support.\n\nPatch 5/5: New. Add documentation.\n\nPossible future directions and considerations:\n\n* Make participation parameters (minimum-percent, blame since-date,\n  -C level) configurable.\n\n* Optimize by enhancing git-blame to accept multiple -L's.\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/226065/\n[2]: http://thread.gmane.org/gmane.comp.version-control.git/229533/\n[3]: http://git.661346.n2.nabble.com/PATCH-v3-0-2-add-git-check-mailmap-command-td7591791.html\n\nEric Sunshine (5):\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  contrib: contacts: add mailmap support\n  contrib: contacts: add documentation\n\n contrib/contacts/git-contacts     | 188 ++++++++++++++++++++++++++++++++++++++\n contrib/contacts/git-contacts.txt |  94 +++++++++++++++++++\n 2 files changed, 282 insertions(+)\n create mode 100755 contrib/contacts/git-contacts\n create mode 100644 contrib/contacts/git-contacts.txt\n\n-- \n1.8.3.3.803.gd114dc6\n"},{"id":"223868","messageId":"1374403962-48361-2-git-send-email-sunshine@sunshineco.com","threadId":"34504","inReplyTo":"1374403962-48361-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v3 1/5] contrib: add git-contacts helper","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-21T10:52:38Z","receivedAt":"2013-07-21T10:52:38Z","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, acknowledged, 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..3e6cce8\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 $contacts = {};\n+for my $commit (values %commits) {\n+\tfor my $contact (keys %{$commit->{contacts}}) {\n+\t\t$contacts->{$contact}++;\n+\t}\n+}\n+\n+my $ncommits = scalar(keys %commits);\n+for my $contact (keys %$contacts) {\n+\tmy $percent = $contacts->{$contact} * 100 / $ncommits;\n+\tnext if $percent < $min_percent;\n+\tprint \"$contact\\n\";\n+}\n-- \n1.8.3.3.803.gd114dc6\n"},{"id":"223869","messageId":"1374403962-48361-3-git-send-email-sunshine@sunshineco.com","threadId":"34504","inReplyTo":"1374403962-48361-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v3 2/5] contrib: contacts: add ability to parse from committish","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-21T10:52:39Z","receivedAt":"2013-07-21T10:52:39Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"For example:\n\n  % git contacts R1..R2\n\nCommittishes and patch files can be mentioned in the same invocation:\n\n  % git contacts R1..R2 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 3e6cce8..1686ff3 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 $contacts = {};\n-- \n1.8.3.3.803.gd114dc6\n"},{"id":"223870","messageId":"1374403962-48361-4-git-send-email-sunshine@sunshineco.com","threadId":"34504","inReplyTo":"1374403962-48361-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v3 3/5] contrib: contacts: interpret committish akin to format-patch","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-21T10:52:40Z","receivedAt":"2013-07-21T10:52:40Z","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 origin\n\nwill consider commits in the current branch built atop 'origin', just as\n\"git format-patch origin\" will format commits built atop 'origin'.\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 1686ff3..4553add 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.3.803.gd114dc6\n"},{"id":"223871","messageId":"1374403962-48361-5-git-send-email-sunshine@sunshineco.com","threadId":"34504","inReplyTo":"1374403962-48361-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v3 4/5] contrib: contacts: add mailmap support","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-21T10:52:41Z","receivedAt":"2013-07-21T10:52:41Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The purpose of git-contacts is to determine a list of people who might\nhave some interest in a patch or set of changes. It can be used as\ngit-send-email's --cc-cmd argument or the computed list might be used to\nask for comments on a proposed change.  As such, it is important to\nreport up-to-date email addresses in the computed list rather than\npotentially outdated ones recorded with commits.  Apply git's mailmap\nfunctionality to the retrieved contacts in order to achieve this goal.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n contrib/contacts/git-contacts | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts\nindex 4553add..d80f7d1 100755\n--- a/contrib/contacts/git-contacts\n+++ b/contrib/contacts/git-contacts\n@@ -133,6 +133,23 @@ sub scan_rev_args {\n \tclose $f;\n }\n \n+sub mailmap_contacts {\n+\tmy ($contacts) = @_;\n+\tmy %mapped;\n+\tmy $pid = open2 my $reader, my $writer, qw(git check-mailmap --stdin);\n+\tfor my $contact (keys(%$contacts)) {\n+\t\tprint $writer \"$contact\\n\";\n+\t\tmy $canonical = <$reader>;\n+\t\tchomp $canonical;\n+\t\t$mapped{$canonical} += $contacts->{$contact};\n+\t}\n+\tclose $reader;\n+\tclose $writer;\n+\twaitpid($pid, 0);\n+\tdie \"git-check-mailmap error: $?\\n\" if $?;\n+\treturn \\%mapped;\n+}\n+\n if (!@ARGV) {\n \tdie \"No input revisions or patch files\\n\";\n }\n@@ -161,6 +178,7 @@ for my $commit (values %commits) {\n \t\t$contacts->{$contact}++;\n \t}\n }\n+$contacts = mailmap_contacts($contacts);\n \n my $ncommits = scalar(keys %commits);\n for my $contact (keys %$contacts) {\n-- \n1.8.3.3.803.gd114dc6\n"},{"id":"223872","messageId":"1374403962-48361-6-git-send-email-sunshine@sunshineco.com","threadId":"34504","inReplyTo":"1374403962-48361-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v3 5/5] contrib: contacts: add documentation","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-21T10:52:42Z","receivedAt":"2013-07-21T10:52:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Assuming that git-contacts may some day be promoted to a core git\ncommand, the documentation is written and formatted as if it already\nbelongs in Documentation/ even though it presently resides in\ncontrib/contacts.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n contrib/contacts/git-contacts.txt | 94 +++++++++++++++++++++++++++++++++++++++\n 1 file changed, 94 insertions(+)\n create mode 100644 contrib/contacts/git-contacts.txt\n\ndiff --git a/contrib/contacts/git-contacts.txt b/contrib/contacts/git-contacts.txt\nnew file mode 100644\nindex 0000000..dd914d1\n--- /dev/null\n+++ b/contrib/contacts/git-contacts.txt\n@@ -0,0 +1,94 @@\n+git-contacts(1)\n+===============\n+\n+NAME\n+----\n+git-contacts - List people who might be interested in a set of changes\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git contacts' (<patch>|<range>|<rev>)...\n+\n+\n+DESCRIPTION\n+-----------\n+\n+Given a set of changes, specified as patch files or revisions, determine people\n+who might be interested in those changes.  This is done by consulting the\n+history of each patch or revision hunk to find people mentioned by commits\n+which touched the lines of files under consideration.\n+\n+Input consists of one or more patch files or revision arguments.  A revision\n+argument can be a range or a single `<rev>` which is interpreted as\n+`<rev>..HEAD`, thus the same revision arguments are accepted as for\n+linkgit:git-format-patch[1]. Patch files and revision arguments can be combined\n+in the same invocation.\n+\n+This command can be useful for determining the list of people with whom to\n+discuss proposed changes, or for finding the list of recipients to Cc: when\n+submitting a patch series via `git send-email`. For the latter case, `git\n+contacts` can be used as the argument to `git send-email`'s `--cc-cmd` option.\n+\n+\n+DISCUSSION\n+----------\n+\n+`git blame` is invoked for each hunk in a patch file or revision.  For each\n+commit mentioned by `git blame`, the commit message is consulted for people who\n+authored, reviewed, signed, acknowledged, or were Cc:'d.  Once the list of\n+participants is known, each person's relevance is computed by considering how\n+many commits mentioned that person compared with the total number of commits\n+under consideration.  The final output consists only of participants who exceed\n+a minimum threshold of participation.\n+\n+\n+OUTPUT\n+------\n+\n+For each person of interest, a single line is output, terminated by a newline.\n+If the person's name is known, ``Name $$<user@host>$$'' is printed; otherwise\n+only ``$$<user@host>$$'' is printed.\n+\n+\n+EXAMPLES\n+--------\n+\n+* Consult patch files:\n++\n+------------\n+$ git contacts feature/*.patch\n+------------\n+\n+* Revision range:\n++\n+------------\n+$ git contacts R1..R2\n+------------\n+\n+* From a single revision to `HEAD`:\n++\n+------------\n+$ git contacts origin\n+------------\n+\n+* Helper for `git send-email`:\n++\n+------------\n+$ git send-email --cc-cmd='git contacts' feature/*.patch\n+------------\n+\n+\n+LIMITATIONS\n+-----------\n+\n+Several conditions controlling a person's significance are currently\n+hard-coded, such as minimum participation level (10%), blame date-limiting (5\n+years), and `-C` level for detecting moved and copied lines (a single `-C`). In\n+the future, these conditions may become configurable.\n+\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n1.8.3.3.803.gd114dc6\n"}]}