-Werror=maybe-uninitialized
[LibreOffice.git] / .git-hooks / pre-commit
blob521ed202b9acc5adaf075bfb22b70c18b0b77871
1 #!/usr/bin/env perl
3 # A hook script to verify what is about to be committed.
4 # Called by "git commit" with no arguments. The hook should
5 # exit with non-zero status after issuing an appropriate message
6 # if it wants to stop the commit.
8 use strict;
9 use lib "solenv/clang-format";
10 #use File::Copy;
11 #use Cwd;
13 $ENV{LC_ALL} = "C";
15 sub check_whitespaces($)
17 my ($h) = @_;
18 my $src_limited = "bas|c|cpp|cxx|h|hrc|hxx|idl|inl|java|swift|map|MK|mm|pmk|pl|pm|sdi|sh|src|tab|ui|vb|xcu|xml|xsl|py";
19 my $src_full = "bas|c|cpp|cxx|h|hrc|hxx|idl|inl|java|swift|map|mk|MK|mm|pmk|pl|pm|sdi|sh|src|tab|ui|vb|xcu|xml|xsl|py";
21 my %modules = (basctl=>'basctl', chart2=>'chart', cui=>'cui', dbaccess=>'dba', desktop=>'dkt', editeng=>'editeng',
22 extensions=>'pcr', filter=>'flt', formula=>'for', fpicker=>'fps', framework=>'fwk', reportdesign=>'rpt',
23 sc=>'sc', sd=>'sd', sfx2=>'sfx', starmath=>'sm', svtools=>'svt', svx=>'svx', sw=>'sw', uui=>'uui',
24 vcl=>'vcl', writerperfect=>'wpt', xmlsecurity=>'xsc');
26 my $found_bad = 0;
27 my $filename;
28 my $reported_filename = "";
29 my $lineno;
30 sub bad_line
32 my ($why, $line, $file_filter) = @_;
33 if (!defined $file_filter || $filename =~ /\.($file_filter)$/)
35 if (!$found_bad)
37 print STDERR "*\n";
38 print STDERR "* You have some suspicious patch lines:\n";
39 print STDERR "*\n";
40 $found_bad = 1;
42 if ($reported_filename ne $filename)
44 print STDERR "* In $filename\n";
45 $reported_filename = $filename;
47 print STDERR "* $why (line $lineno)\n";
48 print STDERR "$filename:$lineno:$line\n";
51 open( FILES, "git diff-index -p -M --cached $h |" ) || die "Cannot run git diff-index.";
52 while (<FILES>)
54 if (m|^diff --git a/(.*) b/\1$|)
56 $filename = $1;
57 next;
59 if (/^@@ -\S+ \+(\d+)/)
61 $lineno = $1 - 1;
62 next;
64 if (/^ /)
66 $lineno++;
67 next;
69 if (s/^\+//)
71 $lineno++;
72 chomp;
73 if (/\s$/)
75 bad_line("trailing whitespace", $_ , $src_limited);
77 if (/\r$/)
79 bad_line("DOS lineends", $_ , $src_limited);
81 if (/\s* /)
83 bad_line("indent with Tab", $_, $src_limited);
85 if (/^(?:[<>=]){7}$/)
87 bad_line("unresolved merge conflict", $src_full);
89 if (/SAL_DEBUG/)
91 bad_line("temporary debug in commit", $_, $src_limited);
93 if ((/OOXMLIMPORT/) and ($filename =~ /ooxmlexport/))
95 bad_line("OOXMLIMPORT definition used in a ooxmlexport file", $_, "cxx");
97 if ((/OOXMLEXPORT/) and ($filename =~ /ooxmlimport/))
99 bad_line("OOXMLEXPORT definition used in a ooxmlimport file", $_, "cxx");
101 if ((/<toolbar:toolbaritem/) and not(/\/>/))
103 bad_line("Use /> to close toolbar:toolbaritem", $_, "xml");
105 if (/<property name="use[-_]markup">True<\/property>/)
107 bad_line("use font attributes instead of use-markup", $_, "ui");
109 if (/<property name="tooltip[-_]markup"/ )
111 bad_line("use tooltip-text instead of tooltip_markup", $_, "ui");
113 if (/<property name="margin[-_]left"/ )
115 bad_line("use margin-start instead of margin-left", $_, "ui");
117 if (/<property name="margin[-_]right"/ )
119 bad_line("use margin-end instead of margin-right", $_, "ui");
121 if (/<object class="GtkAlignment"/)
123 bad_line("use margin-start (etc) on child instead of a GtkAlignment", $_, "ui");
125 if (/<property name="use[-_]stock"/ )
127 bad_line("use translation context 'stock' and the English string as button label instead", $_, "ui");
129 if (/<property name="stock[-_]id"/ )
131 bad_line("use an icon-name listed at https://developer.gnome.org/gtk3/stable/gtk3-Stock-Items.html", $_, "ui");
133 if (/<property name="stock"/ )
135 bad_line("use an icon-name listed at https://developer.gnome.org/gtk3/stable/gtk3-Stock-Items.html", $_, "ui");
137 if ((/translatable="yes"/) and not(/context=/))
139 bad_line("translatable .ui file line without context", $_, "ui");
141 if ((/requires/) and (/lib="gtk+/) and not (/version="3.20/))
143 bad_line("min supported version of gtk3 is 3.20", $_, "ui");
145 if ((/<interface/) and not(/domain=/))
147 bad_line(".ui file without translation domain", $_, "ui");
149 if (/<interface domain=/)
151 foreach my $key (keys %modules) {
152 if ((rindex($filename, $key, 0) == 0) and not (/$modules{$key}/))
154 bad_line("interface domain should be '$modules{$key}'", $_, "ui");
160 if ( $found_bad)
162 exit($found_bad);
166 sub check_author()
168 my $author = `git var GIT_AUTHOR_IDENT`;
169 chomp $author;
170 if ($author =~ /^Your Name <you\@example.com>/)
172 print("ERROR: You have a suspicious author identity: '$author'\n");
173 exit(1);
177 sub check_style($)
179 if (! -e "solenv/clang-format/ClangFormat.pm")
181 # Commit happens in a submodule.
182 return;
185 require ClangFormat;
186 ClangFormat->import();
188 my ($h) = @_;
189 my $src = ClangFormat::get_extension_regex();
190 my @bad_names = ();
191 my @bad_renames = ();
192 my $clang_format = ClangFormat::find();
194 ## Check if ClangFormat has get_excludelist or the old
195 ## get_blacklist
196 my $excluded_list_names;
197 eval { ClangFormat::get_excludelist() };
198 if ($@) { $excluded_list_names = ClangFormat::get_blacklist(); }
199 else { $excluded_list_names = ClangFormat::get_excludelist(); }
201 # Get a list of renamed files.
202 my %renames; # key is target pathname, value is source pathname
203 open (IN, "git diff-index --cached --find-renames --diff-filter=R --name-status $h |")
204 || die "Cannot run git diff.";
205 while (my $line = <IN>)
207 chomp $line;
208 $line =~ /^[^\t]+\t([^\t]+)\t([^\t]+)$/ || die "Unexpected response line: $line";
209 $renames{$2} = $1;
212 # Get a list of non-deleted changed files.
213 open (FILES, "git diff-index --cached --diff-filter=AM --name-only $h |") || die "Cannot run git diff.";
214 while (my $filename = <FILES>)
216 chomp $filename;
217 if ($filename =~ /\.($src)$/ and !exists($excluded_list_names->{$filename}))
219 if (!defined($clang_format))
221 my $version = ClangFormat::get_wanted_version();
222 my $opt_lo = ClangFormat::get_own_directory();
224 print("\nWARNING: Commit touches new (non-excluded) files, but no clang-format"
225 . " ${version}\n");
226 print(" found (via CLANG_FORMAT or PATH env vars, or in ${opt_lo}).\n\n");
228 my $platform = "linux64";
229 my $download = "wget";
230 if ($^O eq "cygwin")
232 $platform = "win.exe";
234 elsif ($^O eq "darwin")
236 $platform = "mac";
237 $download = "curl -O";
240 print("To get a suitable binary, please do:\n\n");
241 print("mkdir -p $opt_lo\n");
242 print("cd $opt_lo\n");
243 print("$download https://dev-www.libreoffice.org/bin/clang-format-$version-$platform\n");
244 print("cp clang-format-$version-$platform clang-format\n");
245 print("chmod +x clang-format\n\n");
247 print("(Or read solenv/clang-format/README on how to build it yourself.\n");
248 return;
250 if (!ClangFormat::check_style($clang_format, $filename))
252 if (defined($renames{$filename}))
254 push @bad_renames, $filename;
256 else
258 push @bad_names, $filename;
264 # Enforce style.
265 if (scalar @bad_names || scalar @bad_renames)
267 my $autostyle = `git config libreoffice.autostyle`;
268 chomp $autostyle;
269 if ($autostyle ne "true" or scalar @bad_renames)
271 print("\nThe above differences were found between the code to commit \n");
272 print("and the clang-format rules.\n");
273 if (scalar @bad_names)
275 print("You can apply these changes with:\n");
276 print("\n$clang_format -i " . join(" ", @bad_names) . "\n\n");
278 if (scalar @bad_renames)
280 print("\nATTENTION: Of files detected as renamed by git, the following ones are\n");
281 print("not clang-format'ed and are not listed in the excludelist. If they are\n");
282 print("renames of previously excluded files, they should be added to the\n");
283 print("excludelist:\n\n");
284 foreach my $name (@bad_renames)
286 if (exists($excluded_list_names->{$renames{$name}}))
288 print("* $name got renamed from $renames{$name},\n");
289 print(" which is even still listed in the excludelist!\n");
291 else
293 print("* $name\n");
296 print("\n");
298 print("Aborting commit. Apply changes and commit again or skip checking\n");
299 print("with --no-verify (not recommended).\n");
300 exit(1);
302 else
304 # 'git config libreoffice.autostyle true' was invoked to run
305 # clang-format automatically.
306 print("\nThe above differences were found between the code to commit \n");
307 print("and the clang-format rules. Fixing these now automatically.\n");
308 print("Running '$clang_format -i " . join(" ", @bad_names) . "' for you...\n");
309 system("$clang_format -i " . join(" ", @bad_names));
310 # TODO this stages all local modifications, staging originally
311 # unstaged hunks.
312 system("git add " . join(" ", @bad_names));
313 print("Done.\n");
318 sub check_submodules($)
320 my ($h) = @_;
322 my $toplevel = `git rev-parse --show-toplevel`;
323 chomp $toplevel;
325 # trick to get a list of submodules - directly read from the .gitmodules
326 open(SUBMODULES, "git config --file '$toplevel'/.gitmodules --get-regexp path | awk '{ print \$2 }' |" ) || die "Cannot run git config on the .gitmodules.";
327 while (<SUBMODULES>)
329 chomp;
331 my $ignore = `git config submodule.$_.ignore`;
332 chomp $ignore;
333 if ($ignore eq 'all')
335 print <<EOM;
336 Error: Your git configuration has submodule.$_.ignore set to 'all'.
338 This is dangerous and can lead to accidentally pushing unwanted changes to
339 submodules.
341 To fix it, please do:
343 git config --unset submodule.$_.ignore
346 exit(1);
349 my $diff = `git diff --cached --name-only -z $h -- $_`;
350 chomp $diff;
351 if ($diff ne '')
353 print <<EOM;
354 Error: You are trying to commit changes to submodule $_ from the main repo.
356 Please do not do that, commit only to the submodule, the git hook on the
357 server will make sure the appropriate change is mirrored in the main repo.
359 To remove the change, you can do:
361 git submodule update $_
363 If it fails with 'error: Server does not allow request for unadvertised object',
364 run the following:
366 git submodule sync
367 git submodule update $_
370 exit(1);
375 # Do the work :-)
377 # Initial commit: diff against an empty tree object
378 my $against="4b825dc642cb6eb9a060e54bf8d69288fbee4904";
379 if ( system( "git rev-parse --verify HEAD >/dev/null 2>&1" ) == 0 )
381 $against="HEAD"
384 # If you want to allow non-ascii filenames set this variable to true.
385 my $allownonascii=`git config hooks.allownonascii`;
386 chomp $allownonascii;
388 # Cross platform projects tend to avoid non-ascii filenames; prevent
389 # them from being added to the repository. We exploit the fact that the
390 # printable range starts at the space character and ends with tilde.
391 if ( $allownonascii ne "true" &&
392 # Note that the use of brackets around a tr range is ok here, (it's
393 # even required, for portability to Solaris 10's /usr/bin/tr), since
394 # the square bracket bytes happen to fall in the designated range.
395 `git diff --cached --name-only --diff-filter=A -z $against | \
396 LC_ALL=C tr -d '[ -~]\\0'` ne "" )
398 print <<EOM;
399 Error: Attempt to add a non-ascii file name.
401 This can cause problems if you want to work
402 with people on other platforms.
404 To be portable it is advisable to rename the file ...
406 If you know what you are doing you can disable this
407 check using:
409 git config hooks.allownonascii true
412 exit( 1 );
415 # Block large files.
416 open( FILES, "git diff --cached --name-only --diff-filter=A -z $against |" ) || die "Cannot run git diff-index.";
417 while (<FILES>)
419 if (/\.ui$/) # .ui files can get large
421 continue;
423 if (/\.xsl$/) # XSLT
425 continue;
427 my $size = `git cat-file -s :$_`;
428 # For now let's say large is 500KB
429 my $limit = 500;
430 if ($size > $limit * 1024)
432 print "Error: Attempt to add a large file: $_, pleasy try to fit into $limit KB.\n";
433 exit( 1 );
437 # fix whitespace in code
438 check_whitespaces( $against);
440 # fix style in code
441 check_style($against);
443 # catch missing author info
444 check_author();
446 # catch commits to the submodules
447 check_submodules($against);
449 # all OK
450 exit( 0 );
451 # vi:set shiftwidth=4 expandtab: