[PATCH/RFC 0/4] Perl rewrite of Ruby git-related

DORMANTno replies

Revision rfc of 2 in this series.

5 messages, 1 author, 2016-06-15 · open the first message on its own page

[PATCH/RFC 0/4] Perl rewrite of Ruby git-related

From: Eric Sunshine <hidden>
Date: 2016-06-15 22:57:57

This is a Perl rewrite of Felipe Contreras' git-related v9 patch
series[1] which was written in Ruby. Although that series was ejected
from 'pu'[2], Junio suggested[3,4] that such functionality may be a
useful addition to the official tool-chest, hence this Perl rewrite.

In this submission, the command name has changed to git-contacts since
git-related felt too generic. (git-contacts seemed best of several
possibilities I surveyed: git-people, git-interested, git-mentioned,
git-blame-us.)

This rewrite does not maintain perfect 1-to-1 parity with Felipe's v9
series, however, it is close: minor refactoring was done to eliminate a
small amount of duplicate code; patch files and revision arguments are
allowed in the same invocation rather than being exclusive;
"git cat-file --batch" pipe deadlock is avoided; commit messages are
expanded.

No attempt is made to answer Junio's v9 review[5], as I lack sufficient
insight with '-C' options to be able to respond properly.

My Perl may be rusty and idiomatic usage may be absent.

[1]: http://thread.gmane.org/gmane.comp.version-control.git/226065/
[2]: http://article.gmane.org/gmane.comp.version-control.git/229164/
[3]: http://article.gmane.org/gmane.comp.version-control.git/226425/
[4]: http://thread.gmane.org/gmane.comp.version-control.git/221728/focus=221796
[5]: http://article.gmane.org/gmane.comp.version-control.git/226265/

Eric Sunshine (4):
  contrib: add git-contacts helper
  contrib: contacts: add support for multiple patches
  contrib: contacts: add ability to parse from committish
  contrib: contacts: interpret committish akin to format-patch

 contrib/contacts/git-contacts | 164 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100755 contrib/contacts/git-contacts

-- 
1.8.3.2

[PATCH/RFC 2/4] contrib: contacts: add support for multiple patches

From: Eric Sunshine <hidden>
Date: 2016-06-15 22:57:57

Accept multiple patch files rather than only one. For example:

  % git contacts feature/*.patch

Signed-off-by: Eric Sunshine <redacted>
---
 contrib/contacts/git-contacts | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
index 9007bae..ab11670 100755
--- a/contrib/contacts/git-contacts
+++ b/contrib/contacts/git-contacts
@@ -3,7 +3,7 @@
 # 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>
+# Usage: git contacts <file> ...
 
 use strict;
 use warnings;
@@ -13,6 +13,7 @@ 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;
+my %seen;
 
 sub format_contact {
 	my ($name, $email) = @_;
@@ -68,7 +69,9 @@ sub get_blame {
 	while (<$f>) {
 		if (/^$id_rx/o) {
 			my $id = $&;
-			$commits->{$id} = { id => $id, contacts => {} };
+			$commits->{$id} = { id => $id, contacts => {} }
+				unless $seen{$id};
+			$seen{$id} = 1;
 		}
 	}
 	close $f;
@@ -93,6 +96,7 @@ sub commits_from_patch {
 	while (<$f>) {
 		if (/^From ($id_rx) /o) {
 			$id = $1;
+			$seen{$id} = 1;
 			last;
 		}
 	}
@@ -100,10 +104,8 @@ sub commits_from_patch {
 	close $f;
 }
 
-exit 1 unless @ARGV == 1;
-
 my %commits;
-commits_from_patch(\%commits, $ARGV[0]);
+commits_from_patch(\%commits, $_) for (@ARGV);
 import_commits(\%commits);
 
 my %count_per_person;
-- 
1.8.3.2

[PATCH/RFC 3/4] contrib: contacts: add ability to parse from committish

From: Eric Sunshine <hidden>
Date: 2016-06-15 22:57:57

Committishes can be mentioned along with patch files in the same
invocation. For example:

  % git contacts master..feature extra/*.patch

Signed-off-by: Eric Sunshine <redacted>
---
 contrib/contacts/git-contacts | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
index ab11670..abb90a1 100755
--- a/contrib/contacts/git-contacts
+++ b/contrib/contacts/git-contacts
@@ -3,7 +3,7 @@
 # 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> ...
+# Usage: git contacts <file | rev-list option> ...
 
 use strict;
 use warnings;
@@ -104,8 +104,32 @@ sub commits_from_patch {
 	close $f;
 }
 
+sub commits_from_rev_args {
+	my ($commits, $args) = @_;
+	open my $f, '-|', qw(git rev-list --reverse), @$args or die;
+	while (<$f>) {
+		chomp;
+		my $id = $_;
+		$seen{$id} = 1;
+		open my $g, '-|', qw(git show -C --oneline), $id or die;
+		scan_hunks($commits, $id, $g);
+		close $g;
+	}
+	close $f;
+}
+
+my (@files, @rev_args);
+for (@ARGV) {
+	if (-e) {
+		push @files, $_;
+	} else {
+		push @rev_args, $_;
+	}
+}
+
 my %commits;
-commits_from_patch(\%commits, $_) for (@ARGV);
+commits_from_patch(\%commits, $_) for (@files);
+commits_from_rev_args(\%commits, \@rev_args) if @rev_args;
 import_commits(\%commits);
 
 my %count_per_person;
-- 
1.8.3.2

[PATCH/RFC 4/4] contrib: contacts: interpret committish akin to format-patch

From: Eric Sunshine <hidden>
Date: 2016-06-15 22:57:57

As a convenience, accept the same style committish as accepted by
git-format-patch. For example:

  % git contacts master

will consider commits in the current branch built atop 'master', just as
"git format-patch master" will format commits built atop 'master'.

Signed-off-by: Eric Sunshine <redacted>
---
 contrib/contacts/git-contacts | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
index abb90a1..10d77d3 100755
--- a/contrib/contacts/git-contacts
+++ b/contrib/contacts/git-contacts
@@ -104,9 +104,26 @@ sub commits_from_patch {
 	close $f;
 }
 
+sub parse_rev_args {
+	my @args = @_;
+	open my $f, '-|',
+		qw(git rev-parse --revs-only --default HEAD --symbolic), @args
+		or die;
+	my @revs;
+	while (<$f>) {
+		chomp;
+		push @revs, $_;
+	}
+	close $f;
+	return @revs if scalar(@revs) != 1;
+	return "^$revs[0]", 'HEAD' unless $revs[0] =~ /^-/;
+	return $revs[0], 'HEAD';
+}
+
 sub commits_from_rev_args {
 	my ($commits, $args) = @_;
-	open my $f, '-|', qw(git rev-list --reverse), @$args or die;
+	my @revs = parse_rev_args(@$args);
+	open my $f, '-|', qw(git rev-list --reverse), @revs or die;
 	while (<$f>) {
 		chomp;
 		my $id = $_;
-- 
1.8.3.2

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

From: Eric Sunshine <hidden>
Date: 2016-06-15 22:57:57

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 [off-list ref]
  Jeff King [off-list ref]
  Max Horn [off-list ref]
  Junio C Hamano [off-list ref]

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 <redacted>
---
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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help