summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorEric Sunshine <sunshine@sunshineco.com>2013-07-21 06:52:38 -0400
committerJunio C Hamano <gitster@pobox.com>2013-07-21 15:12:30 -0700
commit4d06402b1b29259bd475e5473c4478f58e9376a1 (patch)
tree9dc0d271f43f5652bf0b42d24314138572d6daad /contrib
parentcb5c9521f1e41aa536c2cf64d689e647d035bb90 (diff)
downloadgit-4d06402b1b29259bd475e5473c4478f58e9376a1.tar.gz
contrib: add git-contacts helper
This script lists people that might be interested in a patch by going back through the history for each patch hunk, and finding people that reviewed, acknowledged, signed, authored, or were Cc:'d on 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 participants, it determines each person's relevance by considering how many commits mentioned that person compared with the total number of commits under consideration. The final output consists only of participants who pass a minimum threshold of participation. Several conditions controlling a person's significance are currently hard-coded, such as minimum participation level, blame date-limiting, and -C level for detecting moved and copied lines. In the future, these conditions may become configurable. For example: % git contacts 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 invoked as git-send-email's --cc-cmd option, among other possible uses. This is a Perl rewrite of Felipe Contreras' git-related patch series[1] written in Ruby. [1]: http://thread.gmane.org/gmane.comp.version-control.git/226065/ Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'contrib')
-rwxr-xr-xcontrib/contacts/git-contacts127
1 files changed, 127 insertions, 0 deletions
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
new file mode 100755
index 0000000000..3e6cce8106
--- /dev/null
+++ b/contrib/contacts/git-contacts
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+
+# List people who might be interested in a patch. Useful as the argument to
+# git-send-email --cc-cmd option, and in other situations.
+#
+# Usage: git contacts <file> ...
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+my $since = '5-years-ago';
+my $min_percent = 10;
+my $labels_rx = qr/Signed-off-by|Reviewed-by|Acked-by|Cc/i;
+my %seen;
+
+sub format_contact {
+ my ($name, $email) = @_;
+ return "$name <$email>";
+}
+
+sub parse_commit {
+ my ($commit, $data) = @_;
+ my $contacts = $commit->{contacts};
+ my $inbody = 0;
+ for (split(/^/m, $data)) {
+ if (not $inbody) {
+ if (/^author ([^<>]+) <(\S+)> .+$/) {
+ $contacts->{format_contact($1, $2)} = 1;
+ } elsif (/^$/) {
+ $inbody = 1;
+ }
+ } elsif (/^$labels_rx:\s+([^<>]+)\s+<(\S+?)>$/o) {
+ $contacts->{format_contact($1, $2)} = 1;
+ }
+ }
+}
+
+sub import_commits {
+ my ($commits) = @_;
+ return unless %$commits;
+ my $pid = open2 my $reader, my $writer, qw(git cat-file --batch);
+ for my $id (keys(%$commits)) {
+ print $writer "$id\n";
+ my $line = <$reader>;
+ if ($line =~ /^([0-9a-f]{40}) commit (\d+)/) {
+ my ($cid, $len) = ($1, $2);
+ die "expected $id but got $cid\n" unless $id eq $cid;
+ my $data;
+ # cat-file emits newline after data, so read len+1
+ read $reader, $data, $len + 1;
+ parse_commit($commits->{$id}, $data);
+ }
+ }
+ close $reader;
+ close $writer;
+ waitpid($pid, 0);
+ die "git-cat-file error: $?\n" if $?;
+}
+
+sub get_blame {
+ my ($commits, $source, $start, $len, $from) = @_;
+ $len = 1 unless defined($len);
+ return if $len == 0;
+ open my $f, '-|',
+ qw(git blame --porcelain -C), '-L', "$start,+$len",
+ '--since', $since, "$from^", '--', $source or die;
+ while (<$f>) {
+ if (/^([0-9a-f]{40}) \d+ \d+ \d+$/) {
+ my $id = $1;
+ $commits->{$id} = { id => $id, contacts => {} }
+ unless $seen{$id};
+ $seen{$id} = 1;
+ }
+ }
+ close $f;
+}
+
+sub scan_patches {
+ my ($commits, $f) = @_;
+ my ($id, $source);
+ while (<$f>) {
+ if (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {
+ $id = $1;
+ $seen{$id} = 1;
+ }
+ next unless $id;
+ if (m{^--- (?:a/(.+)|/dev/null)$}) {
+ $source = $1;
+ } elsif (/^--- /) {
+ die "Cannot parse hunk source: $_\n";
+ } elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
+ get_blame($commits, $source, $1, $2, $id);
+ }
+ }
+}
+
+sub scan_patch_file {
+ my ($commits, $file) = @_;
+ open my $f, '<', $file or die "read failure: $file: $!\n";
+ scan_patches($commits, $f);
+ close $f;
+}
+
+if (!@ARGV) {
+ die "No input patch files\n";
+}
+
+my %commits;
+for (@ARGV) {
+ scan_patch_file(\%commits, $_);
+}
+import_commits(\%commits);
+
+my $contacts = {};
+for my $commit (values %commits) {
+ for my $contact (keys %{$commit->{contacts}}) {
+ $contacts->{$contact}++;
+ }
+}
+
+my $ncommits = scalar(keys %commits);
+for my $contact (keys %$contacts) {
+ my $percent = $contacts->{$contact} * 100 / $ncommits;
+ next if $percent < $min_percent;
+ print "$contact\n";
+}