From af5e252846d08a579835e9b3bd0b004727252850 Mon Sep 17 00:00:00 2001
From: Holger Weiss <holger@zedat.fu-berlin.de>
Date: Sat, 24 Oct 2009 22:55:44 +0200
Subject: git-notify: Don't generate duplicate notifications

Never notify on a given commit more than once, even if it's referenced
via multiple branch heads.  We make sure this won't happen simply by
maintaining a list of commits we notified about.  The file path used for
saving this list can be specified using the new "-t" option.  (The
contrib/hooks/post-receive-email script distributed with Git tries hard
to avoid such a list, but it doesn't get the necessary magic right.)

diff --git a/tools/git-notify b/tools/git-notify
index a158e87..a89104a 100755
--- a/tools/git-notify
+++ b/tools/git-notify
@@ -3,6 +3,7 @@
 # Tool to send git commit notifications
 #
 # Copyright 2005 Alexandre Julliard
+# Copyright 2009 Nagios Plugins Development Team
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
@@ -19,6 +20,7 @@
 #   -n max    Set max number of individual mails to send
 #   -r name   Set the git repository name
 #   -s bytes  Set the maximum diff size in bytes (-1 for no limit)
+#   -t file   Set the file to use for reading and saving state
 #   -u url    Set the URL to the gitweb browser
 #   -i branch If at least one -i is given, report only for specified branches
 #   -x branch Exclude changes to the specified branch from reports
@@ -27,6 +29,7 @@
 
 use strict;
 use open ':utf8';
+use Fcntl ':flock';
 use Encode 'encode';
 use Cwd 'realpath';
 
@@ -76,6 +79,9 @@ my @include_list = split /\s+/, git_config( "notify.include" ) || "";
 # branches to exclude
 my @exclude_list = split /\s+/, git_config( "notify.exclude" ) || "";
 
+# the state file we use (can be changed with the -t option)
+my $state_file = git_config( "notify.statefile" ) || "/var/tmp/git-notify.state";
+
 # Extra options to git rev-list
 my @revlist_options;
 
@@ -87,6 +93,7 @@ sub usage()
     print "   -n max    Set max number of individual mails to send\n";
     print "   -r name   Set the git repository name\n";
     print "   -s bytes  Set the maximum diff size in bytes (-1 for no limit)\n";
+    print "   -t file   Set the file to use for reading and saving state\n";
     print "   -u url    Set the URL to the gitweb browser\n";
     print "   -i branch If at least one -i is given, report only for specified branches\n";
     print "   -x branch Exclude changes to the specified branch from reports\n";
@@ -127,6 +134,59 @@ sub git_rev_list(@)
     return $revlist;
 }
 
+# append the given commit hashes to the state file
+sub save_commits($)
+{
+    my $commits = shift;
+
+    open STATE, ">>", $state_file or die "Cannot open $state_file: $!";
+    flock STATE, LOCK_EX or die "Cannot lock $state_file";
+    print STATE "$_\n" for @$commits;
+    flock STATE, LOCK_UN or die "Cannot unlock $state_file";
+    close STATE or die "Cannot close $state_file: $!";
+}
+
+# for the given range, return the new hashes and append them to the state file
+sub get_new_commits($$)
+{
+    my ($old_sha1, $new_sha1) = @_;
+    my ($seen, @args);
+    my $newrevs = [];
+
+    @args = ( "^$old_sha1" ) unless $old_sha1 eq '0' x 40;
+    push @args, $new_sha1, @exclude_list;
+
+    my $revlist = git_rev_list(@args);
+
+    if (not -e $state_file)  # initialize the state file with all hashes
+    {
+        save_commits(git_rev_list("--all", "--full-history"));
+        return $revlist;
+    }
+
+    open STATE, $state_file or die "Cannot open $state_file: $!";
+    flock STATE, LOCK_SH or die "Cannot lock $state_file";
+    while (<STATE>)
+    {
+        chomp;
+        die "Invalid commit: $_" if not /^[0-9a-f]{40}$/;
+        $seen->{$_} = 1;
+    }
+    flock STATE, LOCK_UN or die "Cannot unlock $state_file";
+    close STATE or die "Cannot close $state_file: $!";
+
+    # FIXME: if another git-notify process reads the $state_file at *this*
+    # point, that process might generate duplicates of our notifications.
+
+    save_commits($revlist);
+
+    foreach my $commit (@$revlist)
+    {
+        push @$newrevs, $commit unless $seen->{$commit};
+    }
+    return $newrevs;
+}
+
 # truncate the given string if it exceeds the specified number of characters
 sub truncate_str($$)
 {
@@ -217,6 +277,7 @@ sub parse_options()
         elsif ($arg eq '-n') { $max_individual_notices = shift @ARGV; }
         elsif ($arg eq '-r') { $repos_name = shift @ARGV; }
         elsif ($arg eq '-s') { $max_diff_size = shift @ARGV; }
+        elsif ($arg eq '-t') { $state_file = shift @ARGV; }
         elsif ($arg eq '-u') { $gitweb_url = shift @ARGV; }
         elsif ($arg eq '-i') { push @include_list, shift @ARGV; }
         elsif ($arg eq '-x') { push @exclude_list, shift @ARGV; }
-- 
cgit v0.10-9-g596f

