git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH/RFC 1/4] contrib: add git-contacts helper

From
Eric Sunshine <sunshine@sunshineco.com>
Date
Jun 30, 2013, 11:08 UTC
Message-ID
<1372590512-21341-2-git-send-email-sunshine@sunshineco.com>
In-Reply-To
<1372590512-21341-1-git-send-email-sunshine@sunshineco.com>

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, 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 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.

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>
---
To better support Windows, a follow-up patch may want to add
functionality similar to run_cmd_pipe() from git-add--interactive.perl.
 contrib/contacts/git-contacts | 121 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)
 create mode 100755 contrib/contacts/git-contacts
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
new file mode 100755
index 0000000..9007bae
--- /dev/null
+++ b/contrib/contacts/git-contacts
@@ -0,0 +1,121 @@
+#!/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|Reviewed|Acked)-by/;
+my $id_rx = qr/[0-9a-f]{40}/i;
+
+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 =~ /^($id_rx) commit (\d+)/o) {
+			my ($cid, $len) = ($1, $2);
+			die "expected $id but got $cid" 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: $?" 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 --incremental -C -C), '-L', "$start,+$len",
+		'--since', $since, "$from^", '--', $source or die;
+	while (<$f>) {
+		if (/^$id_rx/o) {
+			my $id = $&;
+			$commits->{$id} = { id => $id, contacts => {} };
+		}
+	}
+	close $f;
+}
+
+sub scan_hunks {
+	my ($commits, $id, $f) = @_;
+	my $source;
+	while (<$f>) {
+		if (/^---\s+(\S+)/) {
+			$source = substr($1, 2) unless $1 eq '/dev/null';
+		} elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
+			get_blame($commits, $source, $1, $2, $id);
+		}
+	}
+}
+
+sub commits_from_patch {
+	my ($commits, $file) = @_;
+	open my $f, '<', $file or die "read failure: $file: $!";
+	my $id;
+	while (<$f>) {
+		if (/^From ($id_rx) /o) {
+			$id = $1;
+			last;
+		}
+	}
+	scan_hunks($commits, $id, $f) if $id;
+	close $f;
+}
+
+exit 1 unless @ARGV == 1;
+
+my %commits;
+commits_from_patch(\%commits, $ARGV[0]);
+import_commits(\%commits);
+
+my %count_per_person;
+for my $commit (values %commits) {
+	for my $contact (keys %{$commit->{contacts}}) {
+		$count_per_person{$contact}++;
+	}
+}
+
+my $ncommits = scalar(keys %commits);
+for my $contact (keys %count_per_person) {
+	my $percent = $count_per_person{$contact} * 100 / $ncommits;
+	next if $percent < $min_percent;
+	print "$contact\n";
+}
-- 
1.8.3.2
Previous: Eric SunshineNext: Junio C Hamano
Message 2 of 15 in “Perl rewrite of Ruby git-related”
  1. 0/4 Perl rewrite of Ruby git-relatedEric Sunshine, Jun 30, 2013
  2. 1/4 contrib: add git-contacts helperEric Sunshine, Jun 30, 2013
  3. Junio C HamanoJul 1, 2013
  4. Junio C HamanoJul 1, 2013
  5. Eric SunshineJul 2, 2013
  6. Junio C HamanoJul 2, 2013
  7. Eric SunshineJul 2, 2013
  8. 2/4 contrib: contacts: add support for multiple patchesEric Sunshine, Jun 30, 2013
  9. Junio C HamanoJul 1, 2013
  10. Junio C HamanoJul 1, 2013
  11. Eric SunshineJul 2, 2013
  12. 3/4 contrib: contacts: add ability to parse from committishEric Sunshine, Jun 30, 2013
  13. 4/4 contrib: contacts: interpret committish akin to format-patchEric Sunshine, Jun 30, 2013
  14. Junio C HamanoJul 1, 2013
  15. Eric SunshineJul 2, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.