sanitize-commit 24.98 KiB
#! /usr/bin/perl
# Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
# Copyright (C) 2014 Petroules Corporation.
# Contact: http://www.qt-project.org/legal
# You may use this file under the terms of the 3-clause BSD license.
# See the file LICENSE from this package for details.
use strict;
use warnings;
use Cwd;
if ($#ARGV < 0 or $#ARGV > 1 || ($#ARGV == 1 && $ARGV[1] !~ /^(strict|gerrit(-rest)?)$/)) {
    print STDERR "Usage: $0 <sha1> [strict]\n";
    exit 2;
my $sha1 = $ARGV[0];
my $gerrit_rest = ($#ARGV == 1 && $ARGV[1] eq "gerrit-rest");
my $gerrit = $gerrit_rest || ($#ARGV == 1 && $ARGV[1] eq "gerrit");
my $strict = $gerrit || ($#ARGV == 1 && $ARGV[1] eq "strict");
my $repo = getcwd();
$repo =~ s,/?\.git$,,;
$repo =~ s,^.*/,,;
my %config = ();
for (`git config --list`) {
    if (/^sanity\.\Q$repo\E\.([^=]+)=(.*$)/) {
        $config{$1} = $2;
my %cfg = ();
if (defined $ENV{GIT_PUSH}) {
    foreach my $c (split ",", $ENV{GIT_PUSH}) {
        $cfg{$c} = 1;
if (defined $config{flags}) {
    foreach my $c (split ",", $config{flags}) {
        $cfg{$c} = 1;
my (%watch_files, %watch_messages);
for my $key (keys %config) {
    $watch_files{$1} = $config{$key} if ($key =~ /^watches\.([^.]+)\.files/);
    $watch_messages{$1} = $config{$key} if ($key =~ /^watches\.([^.]+)\.message/);
my $fail = 0;
my $file = "";
my $lineno = 0;
my $summary;
# Hash (by file) of hashes (by line) of lists (reports) of lists (msg, extra)
my %complaints;
my %footnotes;
sub printerr()
  die "cannot run git: ".$! if ($? < 0);
  die "git crashed with signal ".$? if ($? & 127);
  die "git exited with status ".($? >> 8) if ($?);
sub do_complain($$$;$@)
    my ($line, $msg, $key, $level, @extra) = @_;
    $level = 0 if (!defined($level) || ($level < 0 && $strict && length($key)));
    if ($level >= 0) {
        $fail = $level + 1 if ($level >= $fail);
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
$msg .= " (key \"".$key."\")" if (!$gerrit); } else { $msg = "Hint: ".$msg; } push @{$complaints{$file}{$line}}, [ $msg, @extra ]; } sub complain($$;$) { my ($msg, $key, $level) = @_; do_complain(0, $msg, $key, $level); } sub complain_ln($$;$) { my ($msg, $key, $level) = @_; do_complain($lineno, $msg, $key, $level); } sub complain_cln($$;$) { my ($msg, $key, $level) = @_; do_complain($gerrit_rest ? $lineno : 0, $msg, $key, $level); } my $clike = 0; my $qmake = 0; my $iswip = defined($cfg{wip}); my $badrev = 0; my $badsign = 0; my $badid = defined($cfg{changeid}); my $badurl = defined($cfg{url}); my $badlog = defined($cfg{log}); my $spell_check = !defined($cfg{spell}); my $parents = 0; my $inchangelog = 0; my ($footer, $cherry) = (0, 0); my ($revert1, $revert2, $nonrevert) = (0, 0, 0); # Load spelling errors dataset if available our %MISTAKES; our %MISTAKES_BASE; BEGIN { eval { require Lingua::EN::CommonMistakes }; if (!$@) { # Load US-specific and non-US-specific mistakes so we can give a hint # about US vs British English where appropriate Lingua::EN::CommonMistakes->import(qw(:no-punct %MISTAKES_BASE)); Lingua::EN::CommonMistakes->import(qw(:american :no-punct %MISTAKES)); } } my @spell_fails; sub complain_spelling() { if (@spell_fails) { do_complain(1e9, "Possible spelling errors", "spell", 0, @spell_fails); @spell_fails = (); } } # Given a line of text, searches for likely spelling errors. sub check_spelling() { return if (!%MISTAKES_BASE); my %seen; my (@words) = split(/\b/); foreach my $word (@words) {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
$word = lc $word; next if $seen{$word}; $seen{$word} = 1; if (my $correction = $MISTAKES{$word}) { my $sfx = ""; if (!$MISTAKES_BASE{$word}) { $sfx = ' [*]'; $footnotes{'[*] Please note, Qt prefers American English.'} = 1; } if ($gerrit_rest) { complain_ln("$word -> $correction?$sfx", "spell"); } else { push @spell_fails, " $lineno: $word -> $correction$sfx"; } } } } sub check_apple_terminology() { if ($clike) { if (/\bQ_OS_MAC\b.*&&.*!.*\bQ_OS_IOS\b/) { complain_ln("Use of deprecated idiom 'defined(Q_OS_MAC) && !defined(Q_OS_IOS)';" . " use Q_OS_OSX instead", "terminology"); } # Not invalid, but remind people about how the unfortunately named Q_OS_MAC must be used if (/\bQ_OS_MAC\b/) { complain_ln("Q_OS_MAC covers both OS X and iOS; if you meant only OS X, use Q_OS_OSX", "", -1); } if (/\bQ_OS_MACX\b/) { complain_ln("Using deprecated define Q_OS_MACX; use Q_OS_OSX instead", "terminology"); } } elsif ($qmake) { # check qmake scopes if (/\bmac\s*:\s*!\s*ios\b/) { complain_ln("Use of deprecated idiom 'mac:!ios', use 'osx' instead", "terminology"); } # Not invalid, but remind people about how the unfortunately named 'mac' must be used if (/\bmac\b/) { complain_ln("Possible use of qmake scope 'mac': this covers both OS X and iOS;" . " if you meant only OS X, use 'osx'", "", -1); } # Match the word macx but avoid matching macx- and macx* since these are valid for mkspecs if (/\bmacx\b(?![-*])/) { complain_ln("Using deprecated qmake scope 'macx'; use 'osx' instead", "terminology"); } } # Note that Mac(intosh)? is the name of the hardware that runs the OS X operating system and is # valid for use, however users are likely to use Mac(intosh)? to incorrectly refer to the OS so # we'll still flag it if (/\bmac(([\s_-]*os)([\s_-]*x)?)?\b/i or /\bmacintosh\b/i) { complain_ln("Possible incorrect use of Apple-related terminology", "", -1); } } my $msgline = 0; open MSG, "git log -1 --pretty=raw ".$sha1." |" or die "cannot run git: $!"; while (<MSG>) { chomp; if (!s/^ //) { if (/^parent /) { $parents++ ; } elsif (/^author .*\.\(none\)/) { do_complain($gerrit_rest ? $parents + 1 : 0, "Bogus author email", "email", 1);
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} elsif (/^commiter .*\.\(none\)/) { do_complain($gerrit_rest ? $parents + 3 : 0, "Bogus committer email", "email", 1); } next } $lineno++; $msgline++; if ($msgline == 1) { $summary = $_; $lineno = $parents + 6 if ($gerrit_rest); $file = "!!!!!"; $revert1 = 1 if (/^Revert ".*"$/); if (/^revert(ed|ing)? (commit )?[0-9a-f]{7,40}\.?$/i) { complain_cln("Summary of revert mentions only SHA1", "log"); } if (/\bQT[A-Z]+-\d+\b/) { complain_cln("Bug reference in summary", "log"); } if (!$iswip && $parents < 2 && /\bWIP\b|\*{3}|^(?:squash|fixup)! |^(.)\1*$/i) { complain_cln("Apparently pushing a Work In Progress", "wip", 1); } elsif (!$iswip && !$badlog && length($_) < 7) { complain_cln("Summary is too short", "log"); } elsif (!$badlog && !$revert1 && length($_) > 120) { complain_cln("Summary is excessively long", "log"); } elsif ($parents < 2 && !$revert1 && length($_) > 70) { complain_cln("Aim for shorter summaries", "", -1); } } else { if (/^This reverts commit [[:xdigit:]]{40}\.?$/) { $revert2 = 1; } elsif (!/^[-\w]+:|^$/) { $nonrevert = 1; } if ($msgline == 2) { if (!$badlog && $_ ne "") { complain_cln("2nd line is not empty", "log"); } } elsif ($_ eq "") { $cherry = 0; $inchangelog = 0; # Empty line following footer(s). $footer = -2 if ($footer == -1); } elsif ($cherry) { $cherry = 0 if (/\)/); } else { if (/^Reviewed-by:/i) { $badrev = $lineno; } elsif (/^Signed-off-by:/i) { $badsign = $lineno; } elsif (/^Task-number:/i && !/^Task-number:/) { complain_ln("Capitalization of \"Task-number\" is wrong", ""); } if (!$badid && /\bI[0-9a-f]{40}\b/ && !/^Change-Id: /) { complain_ln("Gerrit change id outside Change-Id footer", "changeid"); } if (!$badurl && /\bhttps?:\/\/(bugreports\.qt-project\.org\/browse\/|codereview\.qt-project\.org\/(\#change,|\#\/c\/)?\d+)/) { complain_ln("URL pointing to Gerrit or JIRA", "url"); } my $ftr = 0; if (/^\((partial(ly)? )?(cherry[- ]pick|(back)?port)(ed)? /) { $cherry = 1 if (!/\)/); # Some bad footers are ok above cherry-pick lines. $badrev = 0; $badsign = 0; # cherry-pick lines count as footers as well. $ftr = 1; } elsif (/^[A-Z][a-z]+(-[A-Za-z][a-z]+)+: /) { $ftr = 1; } elsif (/^(\[change-?log\]|change-?log: )/i && !defined($cfg{changelog})) { $inchangelog = 1;
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
complain_ln("Bad form of [ChangeLog] tag", "changelog") if (!/^\[ChangeLog\]/); } if ($inchangelog) { complain_ln("No empty line between ChangeLog and footers", "changelog") if ($ftr || $footer == -1); if (!$ftr) { complain_ln("Missing space between ChangeLog tags and text", "changelog") if (/\][^\s\[]/); complain_ln("JIRA task referenced from ChangeLog", "changelog") if (/\[QT[A-Z]+-\d+\]/); complain_ln("Current repository referenced from ChangeLog", "changelog") if (/\[\Q$repo\E\]/i); } } if ($ftr) { if ($footer == 0) { # Footer following non-footer $footer = -1; } elsif ($footer == -2) { # Footer following empty line(s) following footer(s) $footer = $lineno; } } else { # Non-footer resets the state, assuming it was a false alert. # Possibly we should make a different complaint instead. $footer = 0; } } } check_spelling() if ($spell_check); check_apple_terminology(); } close MSG; printerr; if ($revert1 && $revert2 && !$nonrevert) { complain("Revert without explanation", "revert", 1); } if ($badrev && !defined($cfg{revby})) { do_complain($badrev, "Bogus Reviewed-by footer", "revby", 1); } if ($badsign) { do_complain($badsign, "Unnecessary Signed-off-by footer", "", -1); } if ($footer > 0 && !defined($cfg{footer})) { do_complain($footer - 1, "Empty lines between footers", "footer"); } complain_spelling(); my $chunk = 0; my @addi = (); my @deli = (); my $nonws; my $ws; my $mixws_check = 0; my %ws_lines = (); # hash of lists my $braces = 0; my $open_key = qr/\s*#\s*if|.*{/; my $close_key = qr/\s*#\s*endif|.*}/; my $kill_all_ws = qr/\s+((?:\"(?:\\.|[^\"])*\"|\S)+)/; # Collapse all whitespace not inside strings. my $kill_nl_ws = qr/((?:\"(?:\\.|[^\"])*\"|\S)+)\s+/; # Collapse all non-leading whitespace not inside strings. $lineno = 0; sub flushChunk() { my $loc_nonws = 0; my $nlonly = 1; my ($ai, $di) = (0, 0); while (!$loc_nonws) { my ($a, $d) = ("", ""); while ($ai < @addi) {
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
$a = $addi[$ai++]; $a =~ s/\s+$//; if (length($a)) { $nlonly = 0; last; } } while ($di < @deli) { $d = $deli[$di++]; $d =~ s/\s+$//; if (length($d)) { $nlonly = 0; last; } } last if (!length($a) && !length($d)); $a =~ /^$close_key/o and $braces--; $d =~ /^$close_key/o and $braces++; if ($braces) { $a =~ s/$kill_nl_ws/$1/go; $d =~ s/$kill_nl_ws/$1/go; } else { $a =~ s/$kill_all_ws/$1/go; $d =~ s/$kill_all_ws/$1/go; } $loc_nonws = 1 if ($a ne $d); $a =~ /^$open_key/o and $braces++; $d =~ /^$open_key/o and $braces--; } while ($ai < @addi) { my $a = $addi[$ai++]; $a =~ /^$close_key/o and $braces--; $a =~ /^$open_key/o and $braces++; } while ($di < @deli) { my $d = $deli[$di++]; $d =~ /^$close_key/o and $braces++; $d =~ /^$open_key/o and $braces--; } if ($loc_nonws) { $nonws = 1; } elsif (!$nlonly) { $ws = 1; push @{$ws_lines{$file}}, $lineno - $#addi; } @addi = @deli = (); $chunk = 0; } sub formatSize($) { my $sz = shift; if ($sz >= 10 * 1024 * 1024) { return int($sz / (1024 * 1024))."MiB"; } elsif ($sz >= 10 * 1024) { return int($sz / 1024)."KiB"; } else { return int($sz)."B"; } } sub isExe($) { my $sha = shift; my $type = `git cat-file -p $sha | file -b -`; return $type =~ /^(ELF|PE32) /; } my @style_fails = ();
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
sub styleFail($) { my $why = shift; if ($gerrit_rest) { complain_ln("$why", "style", -1); } else { push @style_fails, " $lineno: ".$why; } } my $no_copyright = 0; sub flushFile() { if ($no_copyright && $lineno > ($file =~ /^tests\/.*\.qml$/ ? 20 : 10)) { complain("Missing copyright header", "copyright"); } complain_spelling(); if (@style_fails) { do_complain(1e9, "Style issues", "style", -1, @style_fails); @style_fails = (); } } my $merge; my $new_file; my $maybe_bin; my $is_special; my $size; my $check_gen = 0; my $crlf_fail; my $in_plus; my $conflict_fail; my $tabs_check; my $ws_check; my $tsv_check; my $eof_check; my $ctlkw_check; open DIFF, "git diff-tree --patience --no-commit-id --diff-filter=ACMR --src-prefix=\@old\@/ --dst-prefix=\@new\@/ --full-index -r -U100000 --cc -C -l1000 --root ".$sha1." |" or die "cannot run git: $!"; while (<DIFF>) { if (/^-/) { if ($mixws_check) { /^--- / and next; push @deli, substr($_, 1); $chunk = 1; } $in_plus = 0; next; } if ($lineno < 50) { if ($no_copyright && /Copyright/) { $no_copyright = 0; } if ($check_gen && /All changes made in this file will be lost|This file is automatically generated|DO NOT EDIT|DO NOT delete this file|[Gg]enerated by|uicgenerated|produced by gperf|made by GNU Bison/) { complain("Adding generated file (inferred from line ".($lineno + 1).")", "generated") if ($new_file && !defined($cfg{generated})); $ws_check = 0; $check_gen = 0; } } if (/^\+/) { if (/^\+\+\+ /) { # This indicates a text file; binary files have "Binary files ... and ... differ" instead. $maybe_bin = 0; if ($file =~ /(~|\.(old|bak))$/i) { complain("Adding backup file", "backup") if ($new_file && !defined($cfg{backup})); $ws_check = 0; } elsif ($file =~ /\.(prl|la|pc|ilk)$/i) { complain("Adding build artifact", "generated") if ($new_file && !defined($cfg{generated}));
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
$ws_check = 0; } elsif ($file !~ /\.qmltypes$/i) { $check_gen = 1; } next; } $lineno++; if ($merge) { # Consider only lines which are new relative to both parents, i.e., were added during the merge. s/^\+\+// or next; } else { $_ = substr($_, 1); if ($mixws_check) { push @addi, $_; $chunk = 1; } } $in_plus = 1; if (!$crlf_fail && /\r\n$/) { $crlf_fail = 1; if ($lineno != 1) { # A stray CRLF somewhere in the middle. complain_ln("CRLF line endings", "crlf"); } else { # Assume that the whole file is affected. complain("CRLF line endings", "crlf"); } } if (!$conflict_fail && /^(?:[<>=]){7}( |$)/) { complain_ln("Unresolved merge conflict", "conflict"); $conflict_fail = 1; } if ($ws_check) { if ($tsv_check) { styleFail("Mixing spaces with TABs") if (/^ +\t|\t +/); } else { styleFail("Trailing whitespace") if (s/[ \t]+\r?\n$//); styleFail("Space indent followed by a TAB character") if (/^ +\t/); styleFail("TAB character in non-leading whitespace") if (/\S *\t/); if ($tabs_check) { styleFail("Leading tabs") if (/^\t+/); if ($ctlkw_check) { styleFail("Flow control keywords must be followed by a single space") if (/\b(if|for|foreach|Q_FOREACH|while|do|switch)(| +)\(/); } } } } check_spelling() if ($spell_check); check_apple_terminology(); } else { flushChunk() if ($chunk); if (/^ /) { $lineno++ if (!$merge || !/^ -/); $in_plus = 0; next; } if ($eof_check && !$is_special && /^\\ No newline/) { if ($in_plus) { complain("No newline at end of file", "fileend") if (!defined($cfg{fileend})); } next; } if ($merge ? /^\@\@\@ -\S+ -\S+ \+(\d+)/ : /^\@\@ -\S+ \+(\d+)/) { $lineno = $1 - 1; next; } if (/^diff /) { flushFile(); if (/^diff --git \@old\@\/.+ \@new\@\/(.+)$/) {
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
$merge = 0; } elsif (/^diff --cc (.+)$/) { $merge = 1; } else { print STDERR "Warning: cannot parse diff header '".$_."'\n"; next; } $file = $1; #print "*** got file ".$file.".\n"; $clike = ($file =~ /\.(c|cc|cpp|c\+\+|cxx|qdoc|m|mm|h|hpp|hxx|cs|java|js|qs|qml|g|y|ypp|pl|glsl)$/i); $qmake = ($file =~ /\.pr[filo]$/i); for my $key (keys %watch_files) { if ($file =~ /^$watch_files{$key}$/) { complain($watch_messages{$key}, "", -1); } } my $foreign = ($file =~ /(^|\/)3rdparty\//); $new_file = 0; $maybe_bin = 0; $is_special = 0; $crlf_fail = defined($cfg{crlf}); $in_plus = 0; $mixws_check = !$merge && !$foreign && $clike && !defined($cfg{mixws}); $ws_check = !defined($cfg{style}) && !$foreign && ($file !~ /\.(ts|diff|patch)$|^\.gitmodules$/); $tsv_check = $ws_check && ($file =~ /((^|\/)objects\.map$|\.tsv$)/); $tabs_check = $ws_check && !$tsv_check && !defined($cfg{tabs}) && ($file !~ /((^|\/)Makefile\b|debian[.\/]rules|\.(plist(\.[^.\/]+)?|def|spec|changes|[xn]ib|storyboardc?)$)/); $ctlkw_check = $tabs_check && $clike; $eof_check = ($file !~ /\.plist(\.[^.\/]+)?$/); # Xcode consistently forgets the trailing newline # .ts files usually contain languages other than English $spell_check = !defined($cfg{spell}) && !$foreign && ($file !~ /\.ts$/i); $conflict_fail = defined($cfg{conflict}); $braces = 0; $check_gen = 0; $no_copyright = 0; next; } if ($maybe_bin && /^Binary files /) { if ($new_file) { if (!defined($cfg{generated}) && ($file =~ /\.(obj|o|lib|a|dll|so|exe|exp|qm|pdb|idb|suo)$/i || isExe($maybe_bin))) { complain("Adding build artifact", "generated"); } } else { if (!defined($cfg{giant}) && $size > (2 << 20)) { complain("Changing huge binary file (".formatSize($size)." > 2MiB)", "giant", 1); } } next; } if ($_ eq "new file mode 160000\n" || $_ eq "new file mode 120000\n") { $is_special = 1; next; } if (!$is_special && /^index ([\w,]+)\.\.(\w+)(?! 160000)( |$)/) { my ($old_trees, $new_tree) = ($1, $2); #print "*** got index $old_trees $new_tree.\n"; $size = `git cat-file -s $new_tree`; my $issrc = $clike || ($file =~ /\.(s|asm|pas|l|m4|bat|cmd|sh|py|php|qdoc(conf)?)$/i); if ($old_trees =~ /^0{40}(,0{40})*$/) { #print "*** adding ".$file.".\n"; if (!$conflict_fail && $file =~ /\.(BACKUP|BASE|LOCAL|REMOTE)\.[^\/]+$/) { complain("Adding temporary file from merge conflict resolution", "conflict", 1); $conflict_fail = 1; } if (!defined($cfg{alien}) && $file =~ /\.(sln|vcproj|vcxproj|user)$/i) { complain("Warning: Adding alien build system file", "alien"); } if ($size > (2 << 20)) { if (!defined($cfg{giant})) { complain("Adding huge file (".formatSize($size)." > 2MiB)", "giant", 1); }
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
} elsif ($size > 51200 && !$issrc && !defined($cfg{size})) { complain("Warning: Adding big file (".formatSize($size)." > 50KiB)", "size"); } $size = 0; $new_file = 1; $no_copyright = $issrc && $file !~ /\.qdocconf$/i; } elsif ($size > 20000 && !$issrc && !defined($cfg{size})) { my $old_size = 0; for my $old_tree (split(/,/, $old_trees)) { my $osz = `git cat-file -s $old_tree`; $old_size = $osz if ($osz > $old_size); } if ($size > $old_size * 3 / 2) { complain("Warning: Increasing file size by more than 50% (". formatSize($old_size)." => ".formatSize($size).")", "size"); } } $maybe_bin = $new_tree; next; } } } close DIFF; printerr; flushFile(); if ($mixws_check) { flushChunk() if ($chunk); if ($nonws and $ws) { my @extras; for (sort keys %ws_lines) { $file = $_; if ($gerrit_rest) { do_complain($_, "WS-only change", "mixws", -1) foreach (@{$ws_lines{$file}}); } else { push @extras, "WS-only in ".$file.": ".join(", ", @{$ws_lines{$file}}); } } $file = "~~~~~"; do_complain(1e9, "Mixing whitespace-only changes with other changes", "mixws", -1, @extras); } } if ($gerrit_rest) { my %gerrit_review; my @cover_texts; foreach my $fn (sort keys %complaints) { my $file; if (length($fn) && ($fn ne "~~~~~")) { $file = ($fn eq "!!!!!") ? "/COMMIT_MSG" : $fn; my @comments = (); foreach my $ln (keys %{$complaints{$fn}}) { my @texts; push @texts, @$_ foreach (@{$complaints{$fn}{$ln}}); my %comment; $comment{line} = int($ln) if ($ln && $ln < 1e9); $comment{message} = join "\n\n", @texts; push @comments, \%comment; } $gerrit_review{comments}{$file} = \@comments; } else { foreach my $ln (sort { $a <=> $b } keys %{$complaints{$fn}}) { push @cover_texts, @$_ foreach (@{$complaints{$fn}{$ln}}); } } } if (%complaints) { push @cover_texts, $_ for (sort keys %footnotes); push @cover_texts, "See http://qt-project.org/wiki/Early-Warning-System for explanations."; $gerrit_review{message} = join "\n\n", @cover_texts; }
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
require JSON; print JSON::encode_json(\%gerrit_review)."\n"; } elsif (%complaints) { if (!$gerrit) { $summary =~ s/^(.{50}).{5,}$/$1\[...]/; print "***\n*** Suspicious changes in commit ".$sha1." (".$summary."):\n"; } my ($lpfx, $elpfx) = ($gerrit ? ("", "\n") : ("*** ", "***\n")); foreach my $fn (sort keys %complaints) { my $fpfx; if (length($fn) && ($fn ne "~~~~~")) { $file = ($fn eq "!!!!!") ? "commit message" : $fn; print $elpfx.$lpfx.$file.":\n"; $fpfx = $lpfx." - "; } else { print $elpfx; $fpfx = $lpfx."- "; } foreach my $ln (sort { $a <=> $b } keys %{$complaints{$fn}}) { my $pfx = $fpfx; $pfx .= "$ln: " if ($ln && $ln < 1e9); foreach my $ent (@{$complaints{$fn}{$ln}}) { my @ry = @$ent; print $pfx.(shift @ry)."\n"; print $lpfx." $_\n" for (@ry); } } } if (%footnotes) { print $elpfx; print $lpfx.$_."\n" for (sort keys %footnotes); } print $elpfx.$lpfx."See http://qt-project.org/wiki/Early-Warning-System for explanations.\n"; } exit ($gerrit ? (!$fail ? 11 : (10 - $fail)) : $fail)