-
Oswald Buddenhagen authored
produces JSON object that is accepted by gerrit >= 2.6. Started-by:
Orgad Shaneh <orgad.shaneh@audiocodes.com> Change-Id: I7e9bf99d18beadac8a715e10e4f241c953442d16 Reviewed-by:
Orgad Shaneh <orgads@gmail.com>
c482d4f3
sanitize-commit 24.96 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_people);
for my $key (keys %config) {
$watch_files{$1} = $config{$key} if ($key =~ /^watches\.([^.]+)\.files/);
$watch_people{$1} = $config{$key} if ($key =~ /^watches\.([^.]+)\.people/);
}
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()
{
my %seen;
my (@words) = split(/\b/);
foreach my $word (@words) {
$word = lc $word;
next if $seen{$word};
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
$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);
} elsif (/^commiter .*\.\(none\)/) {
do_complain($gerrit_rest ? $parents + 3 : 0, "Bogus committer email", "email", 1);
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
}
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;
complain_ln("Bad form of [ChangeLog] tag", "changelog") if (!/^\[ChangeLog\]/);
}
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
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) {
$a = $addi[$ai++];
$a =~ s/\s+$//;
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
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 = ();
sub styleFail($)
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
{
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}));
$ws_check = 0;
} elsif ($file !~ /\.qmltypes$/i) {
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
$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("Space indent followed by a TAB character") if (/^ +\t/);
styleFail("TAB character in non-leading whitespace") if (/\S *\t/);
styleFail("Trailing whitespace") if (/[ \t]\r?\n$/);
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\@\/(.+)$/) {
$merge = 0;
} elsif (/^diff --cc (.+)$/) {
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
$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("Changing this file is risky. Please add $watch_people{$key} as reviewer(s).", "", -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);
}
} elsif ($size > 51200 && !$issrc && !defined($cfg{size})) {
complain("Warning: Adding big file (".formatSize($size)." > 50KiB)", "size");
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
}
$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 $file (sort keys %ws_lines) {
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) {
use JSON;
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;
}
print encode_json(\%gerrit_review)."\n";
} elsif (%complaints) {
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
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)