#!/usr/bin/perl -w # gentoo-infra: infra/cfengine.git:files/var/lib/gitolite/hooks/post-receive.gentoo-commits use strict; use POSIX qw( getcwd ); use IPC::Open2 qw( open2 ); use Data::Dumper; use Net::SMTP; use LWP::Simple; use Date::Manip; use IO::Socket; # NOTE: This hook requires perl >=5.10! # For gentoo-commits, used in gentoo.conf # Possible variables: %(repo), %(rev) our $GITWEB_URL; # Used in gentoo.conf our $IRC_ADDR; # Used in gentoo.conf our $IRC_PORT; # Used in gentoo.conf our $GITROOT = $ENV{"GL_REPO_BASE_ABS"}; $GITROOT = "/var/gitroot" if !$GITROOT or length($GITROOT) == 0; our %PROJECTS = ( ); # Add repos here to exclude from #gentoo-commits. # Repository must be relative to $GITROOT. Don't use the ".git" suffix! our %IRC_EXCLUDE = ( "gitolite-admin" => 1, # "subdir/test" => 1, ); # gentoo-commits@lists.g.o is disabled by default, add repos here to enable them # for commit mails. # Repository must be relative to $GITROOT. Don't use the ".git" suffix! our %GENTOO_COMMITS = ( # "subdir/test" => 1, ); # Or shall the commit mail go to a other mailinglist than gentoo-commits? Or # both? our %MAILINGLIST = ( # "subdir/test" => [ 'idl0r@gentoo.org', ], ); # Repository settings, like exclude. Exclude must be an array. # Those files will be excluded from #gentoo-comits and gentoo-commits. # Don't forget to escape special chars! Each exclude pattern is a Regex! # The repository name must be relative to $GITROOT! Don't use the ".git" # suffix! our %FILE_EXCLUDE = ( # "repository" => ( 'regex1', 'regex2', ... ), # "subdir/test" => [ '.*\/?Manifest$', '.*\/?files/digest-[0-9].*$', ], ); our $DEBUG = 0; our $DEBUG_SILENT = 0; our $MAX_COMMITS = 25; # Load the config do $ENV{"HOME"}."/.gentoo.conf" if -R $ENV{"HOME"}."/.gentoo.conf"; local $SIG{HUP} = sub { do $ENV{"HOME"}."/.gentoo.conf" if -R $ENV{"HOME"}."/.gentoo.conf"; }; ######################### our @WARNINGS; our $BRANCH; our $REPO; our @REVLIST; our $REV; our %DATA; our $CHANGE_TYPE; our $ISPRIVATE = 0; our $REFNAME; our $OLDHEAD; our $NEWHEAD; # Empty ref, on new/deleted branch/tag use constant EMPTY => "0000000000000000000000000000000000000000"; sub debug { my $fmt = shift; my @args = @_; my $fh; if ($DEBUG == 1) { $fh = *STDERR; } elsif ($DEBUG_SILENT == 1) { open($fh, '>>', '/var/lib/gitolite/hook.debug'); } else { return; } printf $fh ("%s DEBUG: ", scalar(gmtime())); if ($#args >= 0) { printf $fh ("${fmt}\n", @args); } else { print $fh "${fmt}\n"; } if($DEBUG_SILENT == 1 && $DEBUG != 1) { close($fh); } } sub git(@) { my @cmd = @_; my $out; return if ! @cmd or $#cmd == -1; unshift @cmd, 'git'; open(FH, '-|', @cmd); { local $/ = undef; $out = ; } close(FH); chomp($out); return $out; } sub git_config($) { my $var = shift; return git(qw(config --get), $var); } sub git_config_yesno($) { my $var = shift; my $value = git_config($var); return 1 if $value =~ m/^(?:1|true|yes)$/i; return 0 if $value =~ m/^(?:0|false|no)$/i; return undef; } # Usage: sendmail( { From => 'foo@bar', To => 'bar@foo', # Data => [@data], Subject => "foo", "X-VCS-MagicString" => "test" }); # Data can be a string too. # TODO: Add a queue to continue after errors. sub sendmail($) { my $mail = shift; my @allowed = qw( Content-type Message-ID Subject X-VCS-Repository X-VCS-Files X-VCS-Directories X-VCS-Committer X-VCS-Committer-Name X-VCS-Revision X-VCS-Branch ); return if ! $mail; return if ! $mail->{"To"} or @{$mail->{"To"}} le 0; return if ! $mail->{"From"}; return if ! $mail->{"Data"}; $mail->{"Host"} = "localhost" if ! $mail->{"Host"}; $mail->{"Timeout"} = 60 if ! $mail->{"Timeout"}; $mail->{"Debug"} = 0 if ! $mail->{"Debug"}; $mail->{"NoMail"} = 0 if ! $mail->{"NoMail"}; $mail->{"Debug"} = git_config_yesno("gentoo.mail.debug") if defined(git_config_yesno("gentoo.mail.debug")); $mail->{"NoMail"} = git_config_yesno("gentoo.mail.nomail") if defined(git_config_yesno("gentoo.mail.nomail")); foreach my $to (@{$mail->{"To"}}) { if($mail->{"NoMail"} == 0) { my $smtp = Net::SMTP->new( $mail->{"Host"}, Timeout => $mail->{"Timeout"}, Debug => $mail->{"Debug"} ) or do { warn "Error: sendmail(): ${!}\n"; return; }; $smtp->mail($mail->{"From"}); $smtp->to($to, { SkipBad => 1 }); $smtp->data(); $smtp->datasend("From: ".$mail->{"From"}."\n"); $smtp->datasend("To: ".$to."\n"); $smtp->datasend("Content-Transfer-Encoding: 8bit\n"); if(!$mail->{"Content-type"}) { $smtp->datasend("Content-type: text/plain; charset=UTF-8\n"); } if($to eq 'gentoo-commits@lists.gentoo.org') { $smtp->datasend("X-VCS-MagicString: dewofdansopyafIfnecThusjigJuchydEbBosGervolgu\n"); $smtp->datasend('Reply-To: gentoo-dev@lists.gentoo.org, '.$mail->{"From"}."\n"); } elsif($to eq 'gentoo-test@lists.gentoo.org') { $smtp->datasend("X-VCS-MagicString: NejdaydinabWydHoTocCegedDooWryd1ryfryldEc9EnBlynpal\n"); } foreach my $item (@allowed) { if( $mail->{$item} ) { $smtp->datasend($item.": ".$mail->{$item}."\n"); } } $smtp->datasend("\n"); $smtp->datasend($mail->{"Data"}); $smtp->dataend(); $smtp->quit(); } if($mail->{"Debug"} == 1) { $DEBUG = 1 if !$DEBUG_SILENT; debug("From: %s", $mail->{"From"}); debug("To: %s", $to); foreach my $item (@allowed) { if( $mail->{$item} ) { debug("%s: %s", $item, $mail->{$item}); } } debug($mail->{"Data"}); } } } sub mail_errors { my @errors = (@WARNINGS, @_); return if $#errors == -1; my $message = ""; $message .= "ENVIRONMENT DUMP START\n"; foreach my $key (sort(keys(%ENV))) { $message .= "$key = $ENV{$key}\n"; } $message .= "ENVIRONMENT DUMP END\n"; $message .= "OLDHEAD: $OLDHEAD\n" if defined($OLDHEAD); $message .= "NEWHEAD: $NEWHEAD\n" if defined($NEWHEAD); $message .= "REFNAME: $REFNAME\n" if defined($REFNAME); $message .= "\n"; if(%DATA) { $message .= "%DATA:\n"; $message .= Dumper(\%DATA); $message .= "\n"; } $message .= join("", @errors); sendmail( { From => 'gitolite@gentoo.org', To => [ 'root@gentoo.org' ], Data => $message, Subject => "Gentoo Git hook Errors/Warnings", # "NoMail" => 1, "Debug" => 1, } ); die @_ if @_; } BEGIN { $SIG{__WARN__} = sub { push(@WARNINGS, "Warning: @_"); warn "Warning: @_"; }; }; BEGIN { $SIG{__DIE__} = sub { mail_errors("Error: @_"); }; }; sub get_highest_paths { my @files = @_; my %paths; foreach my $file (@files) { my $dir = $file; $dir =~ s/[^\/]+$//; $dir = "/" if !$dir; $paths{$dir} = 0 if ! $paths{$dir}; } return (keys %paths); } # TODO: Add timeout sub tinyurl($) { my $orig = shift; return if ! $orig or length($orig) == 0; my $url = LWP::Simple::get("https://tinyurl.com/api-create.php?url=".$orig) or return $orig; # Verify... return $orig if $url !~ m/^https?:\/\/tinyurl\.com\//; return $url; } sub get_data($) { my $rev = shift; my %data = (); my $date = new Date::Manip::Date; $date->config("Language","English", "DateFormat","US"); $data{"committer_username"} = $ENV{"git_username"} ? $ENV{"git_username"} : $ENV{"GL_USER"}; $data{"committer_email"} = $ENV{"git_email"} if $ENV{"git_email"} and length($ENV{"git_email"}) > 0; # Start with ASCII realname, and then override it with the UTF-8 username if needed. $data{"committer_realname"} = $data{"committer_realname_ascii"} = $ENV{"git_realname_ascii"} if $ENV{"git_realname_ascii"} and length($ENV{"git_realname_ascii"}) > 0; $data{"committer_realname"} = $ENV{"git_realname"} if $ENV{"git_realname"} and length($ENV{"git_realname"}) > 0; # Construct the committer again. $data{"committer"} = sprintf ("%s <%s>", $data{"committer_realname"}, $data{"committer_email"}) if ($data{"committer_realname"} && $data{"committer_email"}); if ($ENV{"GL_TS"}) { $date->parse_format("%Y-%m-%d\.%H:%M:%S", $ENV{"GL_TS"}); $data{"committer_ts"} = $date->printf("%s"); # Needed for create/delete $date->convert("UTC"); # Converts only if the local TZ is *not* UTC otherwise it's the same $data{"committer_date_utc"} = $date->printf("%a %b %e %T %Y %z"); } elsif (!$ENV{"GL_TS"} && $CHANGE_TYPE ne "update") { # Fallback $date->parse("now"); $data{"committer_ts"} = $date->printf("%s"); $date->convert("UTC"); # Converts only if the local TZ is *not* UTC otherwise it's the same $data{"committer_date_utc"} = $date->printf("%a %b %e %T %Y %z"); } $data{"rev"} = $rev ? $rev : ""; $data{"shortrev"} = $rev ? substr($rev, 0, 8) : ""; $data{"branch"} = $BRANCH; # Prefer hardcoded above git config and autodetected $data{"project"} = $PROJECTS{$REPO} if $PROJECTS{$REPO}; if(!$PROJECTS{$REPO}) { $data{"project"} = "gentoo-user" if $REPO =~ m/^user\//; $data{"project"} = "gentoo" if $REPO !~ m/^user\//; } $data{"project"} = git_config("gentoo.project") if ! $PROJECTS{$REPO} and git_config("gentoo.project"); if(!$data{"project"}) { $data{"project"} = $REPO; $data{"project"} =~ s/.*\///; } $data{"repo"} = $REPO; $data{"url"} = ""; if($CHANGE_TYPE eq "update") { # TODO: Extend it a bit more if($GITWEB_URL && !$ISPRIVATE) { $data{"url"} = $GITWEB_URL; $data{"url"} =~ s/%\(repo\)/$data{"repo"}/g; $data{"url"} =~ s/%\(rev\)/$data{"rev"}/g; $data{"url"} =~ s/%\(shortrev\)/$data{"shortrev"}/g; #$data{"url"} = tinyurl($data{"url"}); } my $rawcommit = git(qw(cat-file commit), $rev); my $inheader = 1; my $inpgpsig = 0; my $lineno = 0; my @last_fields; foreach my $line (split("\n", $rawcommit)) { $lineno++; if($inheader eq 1) { if ($line or length($line) > 0) { my @fields = split(/\s+/, $line); my $field = shift(@fields); # PGP headers CAN have a blank line in them, which you cannot extract with the above; if($line =~ m/^\s+$/ && !defined($field) && $inpgpsig) { $field = 'gpgsig'; } # Detect any remaining error cases, like blank lines in other headers push @last_fields, $field; if(!defined($field)) { my $errormsg = sprintf "\$field is undefined, input line (#%d) was '%s', previous field was '%s'; please report the repo & commitid to Gentoo bug #548876\n", $lineno, $last_fields[-2], $line; warn $errormsg; open(my $fh, '>', '/tmp/bug548876-'.$rev.'-'.time()); print $fh $errormsg, "\n\n", Dumper(\%data), $rawcommit; close $fh; } if ($inpgpsig || $field eq "gpgsig") { $inpgpsig = 1; $field = "gpgsig"; if($line =~ m/-----END PGP SIGNATURE-----/) { $inpgpsig = 0; $data{$field} .= join(" ", @fields); } else { $data{$field} .= join(" ", @fields)."\n"; } next; } if($field eq "author" or $field eq "committer") { $data{"${field}_tz"} = pop(@fields); $data{"${field}_ts"} = pop(@fields); $date->parse("epoch ".$data{"${field}_ts"}); $date->convert("UTC"); # Converts only if the local TZ is *not* UTC otherwise it's the same $data{"${field}_date_utc"} = $date->printf("%a %b %e %T %Y %z"); if($field eq "author") { $data{"author_email"} = pop(@fields); $data{"author_email"} =~ s/[<>]//g; $data{"author_realname"} = join(" ", @fields); $data{"author"} = $data{"author_realname"}." <".$data{"author_email"}.">"; } # Committer already got its data from the key metadata # but in case there went something wrong.. if($field eq "committer") { if (!$data{"committer_email"} || !$data{"committer_realname"} || !$data{"committer"}) { { no warnings; warn( sprintf("Warning: falling back to legacy committer detection as no or not enough metadata was provided committer_email='%s' committer_realname='%s' committer='%s'", $data{"committer_email"}, $data{"committer_realname"}, $data{"committer"} ) ); } $data{"committer_email"} = pop(@fields); $data{"committer_email"} =~ s/[<>]//g; $data{"committer_realname"} = join(" ", @fields); $data{"committer"} = $data{"committer_realname"}." <".$data{"committer_email"}.">"; } } } else { $data{$field} = join(" ", @fields); } } else { $inheader = 0; } } else { $data{"logmsg"} .= $line."\n"; #push(@{$data{"logmsg"}}, $line); } } chomp($data{"logmsg"}); $data{"logmsg"} = "" if !exists($data{"logmsg"}); # Obfuscate email addresses $data{"logmsg"} =~ s/([^\s]+)\@(.+\.[^\s]+)/$1 $2/g; ($data{"shortlog"}, undef) = split(/\n/, $data{"logmsg"}, 2); # $data{"shortlog"} = @{$data{"logmsg"}}[0]; # -M -C @{$data{"files"}} = split("\n", git(qw(diff-tree --raw --root -r -B --name-only --no-commit-id), $rev)); @{$data{"files_excluded"}} = (); $data{"files_verbose"} = (); my @EXCLUDE; @EXCLUDE = @{$FILE_EXCLUDE{$data{"repo"}}} if $FILE_EXCLUDE{$data{"repo"}}; @EXCLUDE = (@EXCLUDE, split(/\s+/, git_config("gentoo.files.exclude"))) if git_config("gentoo.files.exclude"); if(@EXCLUDE) { foreach my $exclude (@EXCLUDE) { @{$data{"files_excluded"}} = (grep(/$exclude/, @{$data{"files"}}), @{$data{"files_excluded"}}); @{$data{"files"}} = grep(!/$exclude/, @{$data{"files"}}); } } if($#{$data{"files"}} >= 0) { foreach my $line (split("\n", git(qw(diff-tree --root -r -C -M -B --name-status --no-commit-id),$rev, '--', @{$data{"files"}}))) { if($line =~ m/^([ACDMRT])(?:[0-9]{3})?\t+([^\t]+)(?:\t+([^\t]+))?/) { my $status = $1; my $old = $2; my $new = $3 ? $3 : ""; if($status eq "C") { push(@{$data{"files_verbose"}{"A"}}, $new); } elsif($status eq "R") { push(@{$data{"files_verbose"}{"R"}}, $old." => ".$new); } elsif($status eq "T") { # Skip type changed stuff next; } else { push(@{$data{"files_verbose"}{$status}}, $old); } } else { push(@WARNINGS, "Warning: git diff-tree --root -r -C -M -B --name-status --no-commit-id ${rev}\n"); push(@WARNINGS, "Warning: had some unknown entries: $line"); } } } $data{"Message-ID"} = "<$data{'committer_ts'}.$rev.$data{'committer_username'}\@$data{'project'}>"; } elsif($CHANGE_TYPE eq "create" || $CHANGE_TYPE eq "delete") { @{$data{"files"}} = (); $data{"files_verbose"} = {}; if ($CHANGE_TYPE eq "create") { $data{"logmsg"} = "New tag: ".$1 if $REFNAME =~ m/^refs\/tags\/(.+)/; $data{"logmsg"} = "New branch: ".$data{"branch"} if $REFNAME =~ m/^refs\/(?:heads|remotes)\//; } else { $data{"logmsg"} = "Tag deleted: ".$1 if $REFNAME =~ m/^refs\/tags\/(.+)/; $data{"logmsg"} = "Branch deleted: ".$data{"branch"} if $REFNAME =~ m/^refs\/(?:heads|remotes)\//; } $data{"shortlog"} = $data{"logmsg"}; $data{"Message-ID"} = "<$data{'committer_ts'}.$data{'committer_username'}\@$data{'project'}>"; } debug(Dumper(\%data)); return %data; } # Arguments: # rev - Git revision, unused # # Global variables used: # %DATA keys: author, branch, committer, shortlog, files # $CHANGE_TYPE # $REPO # $IRC_ADDR # $IRC_PORT # # Functions call: # get_highest_paths # IO::Socket::INET->new sub irc(@) { my $rev = shift; my $author = $DATA{"author"}; if($CHANGE_TYPE eq "update") { if(defined($author) and $author =~ m/([^<]+)\s+<([^\@]+)\@(.+)>$/) { my $realname = $1; my $user = $2; my $domain = $3; if ($domain eq 'gentoo.org') { $author = $user; } else { $author = sprintf("%s@%s", $user, $domain); } } } my $committer = $DATA{"committer"}; # I hope this is not undefined! if(defined($committer) and $committer =~ m/([^<]+)\s+<([^\@]+)\@(.+)>$/) { my $realname = $1; my $user = $2; my $domain = $3; if ($domain eq 'gentoo.org') { $committer = $user; } else { $committer = sprintf("%s@%s", $user, $domain); } } my $who = $committer; if (defined($author) && defined($committer) && ($author ne $committer)) { my $who = sprintf("%s (via %s)", $author, $committer); } my $where = $REPO; my $what = $DATA{"shortlog"}; my $path = "/"; if($CHANGE_TYPE eq "update") { my $paths = join(", ", get_highest_paths(@{$DATA{'files'}})); my $modified = 0; while(length($paths) > 80) { $modified = 1; my @tmp = split(", ", $paths); pop(@tmp); $paths = join(", ", @tmp); } $paths .= ", ..." if $modified == 1; $path = $paths ? $paths : "/"; } my $repo_msg = chr(3) . "03" . $where . chr(15); if ($DATA{"branch"} ne "master") { $repo_msg .= ":" . chr(3) . "03" . $DATA{"branch"} . chr(15); } my $message = sprintf( "%s → %s (%s) %s", chr(3) . "02" . $who . chr(15), $repo_msg, $path, chr(3) . "07" . $what . chr(15) ); my $sock = IO::Socket::INET->new( Proto => 'udp', PeerPort => $IRC_PORT, PeerAddr => $IRC_ADDR, ) or return; $sock->send($message); } sub commit_mail(@) { my $rev = shift; my $subject; my $message; # --ignore-all-space --ignore-space-at-eol my @gitshow_opts = qw(--pretty=format: --no-color --root --patch-with-stat --find-copies-harder -C); my @to; if(!$ISPRIVATE and (!defined($GENTOO_COMMITS{$REPO}) or $GENTOO_COMMITS{$REPO} == 1) and (!defined(git_config_yesno("gentoo.mail.gentoocommits")) or git_config_yesno("gentoo.mail.gentoocommits") == 1) and ($DATA{"project"} eq "gentoo" or (defined(git_config_yesno("gentoo.mail.gentoocommits")) and git_config_yesno("gentoo.mail.gentoocommits") == 1) or (defined($GENTOO_COMMITS{$REPO}) and $GENTOO_COMMITS{$REPO} == 1)) ) { push(@to, 'gentoo-commits@lists.gentoo.org'); } if($MAILINGLIST{$REPO}) { push(@to, @{$MAILINGLIST{$REPO}}); } if(git_config("gentoo.mail.extra")) { @to = (@to, split(/\s+/, git_config("gentoo.mail.extra"))); } $message .= sprintf("commit: %s\n", $DATA{"rev"}) if $DATA{"rev"}; if($CHANGE_TYPE eq "update") { my $author = $DATA{"author"}; if($author =~ m/([^<]+)\s+<([^\@]+)\@(.+)>$/) { my $realname = $1; my $user = $2; my $domain = $3; $domain =~ s/\./ /g; $author = sprintf("%s <%s %s>", $realname, $user, $domain); } $message .= sprintf("Author: %s\n", $author); $message .= sprintf("AuthorDate: %s\n", $DATA{"author_date_utc"}); } my $committer = $DATA{"committer"}; if($committer =~ m/([^<]+)\s+<([^\@]+)\@(.+)>$/) { my $realname = $1; my $user = $2; my $domain = $3; $domain =~ s/\./ /g; $committer = sprintf("%s <%s %s>", $realname, $user, $domain); } $message .= sprintf("Commit: %s\n", $committer); $message .= sprintf("CommitDate: %s\n", $DATA{"committer_date_utc"}); $message .= sprintf("URL: %s\n", $DATA{"url"}) if $CHANGE_TYPE eq "update" and $DATA{"url"}; $message .= "\n"; $message .= $DATA{"logmsg"}."\n"; $message .= "\n"; if($CHANGE_TYPE eq "update") { my $paths = join(", ", get_highest_paths(@{$DATA{'files'}})); my $modified = 0; while(length($paths) > 80) { $modified = 1; my @tmp = split(", ", $paths); pop(@tmp); $paths = join(", ", @tmp); } $paths .= ", ..." if $modified == 1; $subject = sprintf("%s:%s commit in: %s", $REPO, $DATA{"branch"}, $paths ? $paths : "/"); my @pretty_diff = split("\n", git('show', @gitshow_opts, $REV, '--', @{$DATA{'files'}})); # Add a empty line between the diff's for better readability my $i = 0; while($i < $#pretty_diff) { for($i=0; $i <= $#pretty_diff; $i++) { if($pretty_diff[$i] =~ m/^diff --git a/) { if(length($pretty_diff[($i - 1)]) > 0) { splice(@pretty_diff, $i, 0, ""); last; # Do it again until we're done because the array changes! } } } } $message .= join("\n", @pretty_diff); } elsif($CHANGE_TYPE eq "create" or $CHANGE_TYPE eq "delete") { $subject = sprintf("%s: %s", $REPO, $DATA{"shortlog"}); } sendmail( { "From" => sprintf('"%s" <%s>', $DATA{"committer_realname"}, $DATA{"committer_email"}), "To" => \@to, "Subject" => $subject, "Message-ID" => $DATA{"Message-ID"}, "Data" => $message, 'X-VCS-Repository' => $DATA{"repo"}, 'X-VCS-Files' => join(" ", @{$DATA{'files'}}), 'X-VCS-Directories' => join(" ", get_highest_paths(@{$DATA{'files'}})), 'X-VCS-Committer' => $DATA{"committer_username"}, 'X-VCS-Committer-Name' => $DATA{"committer_realname"}, 'X-VCS-Revision' => $DATA{"rev"}, 'X-VCS-Branch' => $DATA{"branch"}, # "NoMail" => 1, "Debug" => 1, } ); } # Date::Manip 5.x works different... if (DateManipVersion() < 6.0) { die "Date::Manip >= 6.0 is required, you're using ".DateManipVersion(); } # Don't override DEBUG/DEBUG_SILENT from above $DEBUG = git_config_yesno("gentoo.debug") if ($DEBUG != 1 and defined(git_config_yesno("gentoo.debug"))); $DEBUG_SILENT = git_config_yesno("gentoo.debug.silent") if ($DEBUG_SILENT != 1 and defined(git_config_yesno("gentoo.debug.silent"))); debug("CWD: %s".getcwd()); foreach my $key (keys(%ENV)) { debug("%s => %s", $key, $ENV{$key}); } $REPO = $ENV{"GL_REPO"} if $ENV{"GL_REPO"}; $REPO = getcwd() if !$REPO or length($REPO) == 0; $REPO =~ s/^\Q$GITROOT\E\/?//; $REPO =~ s/\.git$//; # Just to be sure.. # If we are triggering by git mirroring, we must not send email! my $GLUSER = $ENV{"git_username"} ? $ENV{"git_username"} : $ENV{"GL_USER"}; exit 0 if $GLUSER =~ /^server-.*\.gentoo\.org/; exit 0 if $GLUSER eq "git"; debug("repo = %s", $REPO); while(defined(my $line = )) { my $ignore = 0; chomp($line); ($OLDHEAD, $NEWHEAD, $REFNAME) = split(" ", $line); exit(1) if !$REFNAME or length($REFNAME) == 0; exit(1) if !$OLDHEAD or length($OLDHEAD) == 0; exit(1) if !$NEWHEAD or length($NEWHEAD) == 0; # Clear the REVLIST after each run @REVLIST = (); # Don't remove that! $BRANCH = $REFNAME; $BRANCH =~ s/^refs\/(?:heads|tags|remotes)\///; debug("BRANCH: %s", $BRANCH); if (git_config("gentoo.ignorebranch")) { debug("IGNOREBRANCH: %s", git_config("gentoo.ignorebranch")); } foreach my $pattern (split(/\s+/, git_config("gentoo.ignorebranch"))) { my $ignorebranch_error; eval { my $error = "The ignorebranch regex '${pattern}' for repository '${REPO}' is invalid! Please contact the repository owner and/or the overlays team (overlays\@gentoo.org) irc://irc.gentoo.org/gentoo-overlays!"; local $SIG{__WARN__} = sub { $ignorebranch_error = "Warning: $error"; return; }; local $SIG{__DIE__} = sub { $ignorebranch_error = "Error: $error"; return; }; if($BRANCH =~ $pattern) { $ignore = 1; } }; if($ignorebranch_error) { push(@WARNINGS, "$ignorebranch_error\n$@"); printf STDERR "%s\n%s", $ignorebranch_error, $@; } last if $ignore == 1; # exit the foreach loop } next if $ignore == 1; # skip this rev if($OLDHEAD ne EMPTY and $NEWHEAD ne EMPTY) { $CHANGE_TYPE = "update"; # Detect merges and always use the merge commits itself and skip all child commits with more than one parent my @merges = split("\n", git(qw(rev-list --merges), "${OLDHEAD}..${NEWHEAD}")); if($#merges >= 0) { foreach my $merge (split("\n", git(qw(rev-list --reverse), "${OLDHEAD}..${NEWHEAD}"))) { if(grep(/^\Q${merge}\E$/, @merges)) { push(@REVLIST, $merge); } elsif(split("\n", git(qw(branch --contains), $merge)) < 2) { push(@REVLIST, $merge); } } } else { @REVLIST = split("\n", git(qw(rev-list --reverse), "${OLDHEAD}..${NEWHEAD}")); } } else { if($OLDHEAD eq EMPTY) { $CHANGE_TYPE = "create"; push(@REVLIST, EMPTY); } elsif($NEWHEAD eq EMPTY) { $CHANGE_TYPE = "delete"; push(@REVLIST, EMPTY); } } $ISPRIVATE = 1 if $REPO =~ m/^(?:priv|private|infra)\/|(?:.+-private|gitolite-admin)$/; # Limit commits to max. $MAX_COMMITS if ($#REVLIST > $MAX_COMMITS) { for(my $i=0; $#REVLIST >= $MAX_COMMITS; $i++) { shift(@REVLIST); } } foreach $REV (@REVLIST) { %DATA = get_data($REV); # Only excluded files has been modified so skip this commit entirely if(!@{$DATA{'files'}} && (defined($DATA{'files_excluded'}) && @{$DATA{'files_excluded'}}) && $CHANGE_TYPE eq "update") { next; } commit_mail($REV); if((!defined($IRC_EXCLUDE{$REPO}) or $IRC_EXCLUDE{$REPO} eq 0) and (!defined(git_config_yesno("gentoo.irc.exclude")) or git_config_yesno("gentoo.irc.exclude") == 0)) { # No IRC for private/infra repositories if(!$ISPRIVATE) { irc($REV); } } } mail_errors(); } # vim: fileformat=unix tabstop=4 shiftwidth=4 textwidth=0 syntax=perl filetype=perl