markdown: update to latest with table support
[girocco.git] / toolbox / update-all-config.pl
blobfd5bcb7d464bca567b273bfa1ab25db137d4467e
1 #!/usr/bin/perl
3 # update-all-config.pl - Update all out-of-date config
5 use strict;
6 use warnings;
7 use vars qw($VERSION);
8 BEGIN {*VERSION = \'2.0'}
9 use File::Basename;
10 use File::Spec;
11 use Cwd qw(realpath);
12 use POSIX qw();
13 use Getopt::Long;
14 use Pod::Usage;
15 use lib "__BASEDIR__";
16 use Girocco::Config;
17 use Girocco::Util;
18 use Girocco::CLIUtil;
19 use Girocco::Project;
21 my $shbin;
22 BEGIN {
23 $shbin = $Girocco::Config::posix_sh_bin;
24 defined($shbin) && $shbin ne "" or $shbin = "/bin/sh";
27 exit(&main(@ARGV)||0);
29 my ($dryrun, $force, $quiet);
31 sub die_usage {
32 pod2usage(-exitval => 2);
35 sub do_help {
36 pod2usage(-verbose => 2, -exitval => 0);
39 sub do_version {
40 print basename($0), " version ", $VERSION, "\n";
41 exit 0;
44 my ($dmode, $dperm, $drwxmode, $fmode, $fmodeoct, $fperm, $wall);
45 BEGIN {
46 $dmode=02775;
47 $dperm='drwxrwsr-x';
48 $drwxmode='ug+rwx,o+rx';
49 $fmode=0664;
50 $fmodeoct='0664';
51 $fperm='-rw-rw-r--';
52 $wall=0;
55 my $owning_group_id;
57 sub main {
58 local *ARGV = \@_;
59 my ($help, $version);
61 umask 002;
62 close(DATA) if fileno(DATA);
63 Getopt::Long::Configure('bundling');
64 GetOptions(
65 'help|h' => sub {do_help},
66 'version|V' => sub {do_version},
67 'dry-run|n' => \$dryrun,
68 'quiet|q' => \$quiet,
69 'force|f' => \$force,
70 ) or die_usage;
71 $dryrun and $quiet = 0;
73 -f jailed_file("/etc/group") or
74 die "Girocco group file not found: " . jailed_file("/etc/group") . "\n";
76 if (!defined($Girocco::Config::owning_group) || $Girocco::Config::owning_group eq "") {
77 die "\$Girocco::Config::owning_group unset, refusing to run without --force\n" unless $force;
78 $dmode=02777;
79 $dperm='drwxrwsrwx';
80 $drwxmode='a+rwx';
81 $fmode=0666;
82 $fmodeoct='0666';
83 $fperm='-rw-rw-rw-';
84 $wall=1;
85 warn "Mode 666 in effect\n" unless $quiet;
86 } elsif (($owning_group_id = scalar(getgrnam($Girocco::Config::owning_group))) !~ /^\d+$/) {
87 die "\$Girocco::Config::owning_group invalid ($Girocco::Config::owning_group), refusing to run\n";
90 my @allprojs = Girocco::Project::get_full_list;
91 my @projects = ();
93 my $root = $Girocco::Config::reporoot;
94 $root or die "\$Girocco::Config::reporoot is invalid\n";
95 $root =~ s,/+$,,;
96 $root ne "" or $root = "/";
97 $root = realpath($root);
98 if (@ARGV) {
99 my %projnames = map {($_ => 1)} @allprojs;
100 foreach (@ARGV) {
101 s,/+$,,;
102 $_ or $_ = "/";
103 -d $_ and $_ = realpath($_);
104 s,^\Q$root\E/,,;
105 s,\.git$,,;
106 if (!exists($projnames{$_})) {
107 warn "$_: unknown to Girocco (not in etc/group)\n"
108 unless $quiet;
109 next;
111 push(@projects, $_);
113 } else {
114 @projects = sort {lc($a) cmp lc($b)} @allprojs;
117 my $bad = 0;
118 foreach (@projects) {
119 my $projdir = "$root/$_.git";
120 if (! -d $projdir) {
121 warn "$_: does not exist -- skipping\n" unless $quiet;
122 next;
124 if (!is_git_dir($projdir)) {
125 warn "$_: is not a .git directory -- skipping\n" unless $quiet;
126 next;
128 if (-e "$projdir/.noconfig") {
129 warn "$_: found .noconfig -- skipping\n" unless $quiet;
130 next;
132 if (!chdir($projdir)) {
133 warn "$_: chdir to project directory failed: $!\n" unless $quiet;
134 next;
136 process_one_project($_) or $bad = 1;
139 return $bad ? 1 : 0;
142 my (@mkdirs, @mkfiles);
143 my (@fixdpermsdirs, @fixdpermsrwx, @fixfpermsfiles, @fixfpermsdirs);
144 BEGIN {
145 @mkdirs = qw(refs info hooks ctags htmlcache bundles reflogs objects objects/info);
146 @mkfiles = qw(config info/lastactivity);
147 @fixdpermsdirs = qw(. refs info ctags htmlcache bundles reflogs objects objects/info);
148 @fixdpermsrwx = qw(refs objects);
149 @fixfpermsfiles = qw(HEAD config description packed-refs README.html info/lastactivity
150 info/alternates info/http-alternates info/packs);
151 @fixfpermsdirs = qw(ctags);
154 my (@boolvars, @falsevars, @false0vars, @truevars);
155 BEGIN {
156 @boolvars = qw(gitweb.statusupdates);
157 @falsevars = qw(core.ignorecase receive.denynonfastforwards);
158 @false0vars = qw(gc.auto receive.autogc);
159 @truevars = qw(receive.updateserverinfo repack.writebitmaps transfer.fsckobjects);
162 my $hdr;
164 sub defval($$) {
165 return defined($_[0]) ? $_[0] : $_[1];
168 sub openfind_ {
169 my $noe = shift;
170 my $duperr;
171 if ($noe) {{
172 open $duperr, '>&2' or last;
173 my $errfd = POSIX::open(File::Spec->devnull, &POSIX::O_RDWR);
174 defined($errfd) or close($duperr), $duperr = undef, last;
175 POSIX::dup2($errfd, 2) or close($duperr), $duperr = undef;
176 POSIX::close($errfd);
178 my $fd;
179 my $ans = open $fd, '-|', "find", @_;
180 if ($noe && defined($duperr) && defined(fileno($duperr))) {
181 POSIX::dup2(fileno($duperr), 2);
182 close($duperr);
184 $ans or die "find failed: $!\n";
185 return $fd;
188 sub openfind { return openfind_(0, @_); }
189 sub openfindne { return openfind_(1, @_); }
191 sub process_one_project
193 my ($proj) = @_;
194 my $bad = 0;
195 my $reallybad = 0;
196 $hdr = 0;
197 do {
198 if (! -d $_) {
199 if (-e $_) {
200 warn "$proj: bypassing project, exists but not directory: $_\n" unless $quiet;
201 $reallybad = $bad = 1;
202 last;
203 } else {
204 do_mkdir($proj, $_) or $bad = 1, last;
207 } foreach (@mkdirs);
208 return 0 if $reallybad;
210 -d $_ && check_dperm($proj, $_) or $bad = 1 foreach (@fixdpermsdirs);
211 my $fp = openfindne(@fixdpermsrwx, qw(-xdev -type d ( ! -path objects/?? -o -prune ) ! -perm), "-$drwxmode", "-print");
212 while (<$fp>) {
213 chomp;
214 change_dpermrwx($proj, $_) or $bad = 1;
216 close($fp) or $bad = 1;
217 $fp = openfind(qw(. -xdev -type d ( ! -path ./objects/?? -o -prune ) ! -perm -a+rx -print));
218 while (<$fp>) {
219 chomp;
220 change_dpermrx($proj, $_) or $bad = 1;
222 close($fp) or $bad = 1;
224 do {
225 if (-e $_) {
226 if (! -f $_) {
227 warn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
228 $reallybad = $bad = 1;
229 last;
231 } else {
232 my $result = "(dryrun)";
233 if (!$dryrun) {
234 $result = "";
235 my $tf;
236 open($tf, '>', $_) && close ($tf) or $result = "FAILED", $bad = 1;
238 pmsg($proj, "$_: created", $result) unless $quiet;
240 } foreach(@mkfiles);
241 return 0 if $reallybad;
243 $dryrun || check_fperm($proj, "config") or $bad = 1;
244 my $config = read_config_file_hash("config", !$quiet);
245 if (!defined($config)) {
246 warn "$proj: could not read config file -- skipping\n" unless $quiet;
247 return 0;
250 my $do_config = sub {
251 my ($item, $val) = @_;
252 my $oldval = defval($config->{$item},"");
253 my $result = "(dryrun)";
254 if (!$dryrun) {
255 $result = "";
256 system($Girocco::Config::git_bin, "config", "--file", "config", "--replace-all", $item, $val) == 0 or
257 $result = "FAILED", $bad = 1;
259 if (!exists($config->{$item})) {
260 pmsg($proj, "config $item: created \"$val\"", $result) unless $quiet;
261 } else {
262 pmsg($proj, "config $item: \"$oldval\" -> \"$val\"", $result) unless $quiet;
265 my $do_config_unset = sub {
266 my ($item, $msg) = @_;
267 defined($msg) or $msg = "";
268 $msg eq "" or $msg = " " . $msg;
269 my $oldval = defval($config->{$item},"");
270 my $result = "(dryrun)";
271 if (!$dryrun) {
272 $result = "";
273 system($Girocco::Config::git_bin, "config", "--file", "config", "--unset-all", $item) == 0 or
274 $result = "FAILED", $bad = 1;
276 pmsg($proj, "config $item: removed$msg \"$oldval\"", $result) unless $quiet;
279 my $cmplvl = defval($config->{'core.compression'},"");
280 if ($cmplvl !~ /^-?\d+$/ || $cmplvl < -1 || $cmplvl > 9 || "" . (0 + $cmplvl) ne "" . $cmplvl) {
281 pmsg($proj, "WARNING: replacing invalid core.compression value: \"$cmplvl\"") unless $cmplvl eq "" || $quiet;
282 $cmplvl = "";
283 } elsif ($cmplvl != 5) {
284 pmsg($proj, "WARNING: suboptimal core.compression value left unchanged: \"$cmplvl\"") unless $quiet;
286 $cmplvl ne "" or &$do_config('core.compression', 5);
287 my $grpshr = defval($config->{'core.sharedrepository'},"");
288 if ($grpshr eq "" || (valid_bool($grpshr) && !git_bool($grpshr))) {
289 &$do_config('core.sharedrepository', 1);
290 } elsif (!(valid_bool($grpshr) && git_bool($grpshr))) {
291 pmsg($proj, "WARNING: odd core.sharedrepository value left unchanged: \"$grpshr\"");
293 if (git_bool($config->{'core.bare'})) {
294 my $setlaru = 1;
295 my $laru = $config->{'core.logallrefupdates'};
296 if (defined($laru)) {
297 if (valid_bool($laru)) {
298 $setlaru = 0;
299 if (git_bool($laru)) {
300 pmsg($proj, "WARNING: core.logallrefupdates is true (left unchanged)") unless $quiet;
302 } else {
303 pmsg($proj, "WARNING: replacing non-boolean core.logallrefupdates value") unless $quiet;
306 !$setlaru or &$do_config('core.logallrefupdates', 'false');
307 } else {
308 pmsg($proj, "WARNING: core.bare is not true (left unchanged)") unless $quiet;
310 defval($config->{'transfer.unpacklimit'},"") eq "1" or &$do_config('transfer.unpacklimit', 1);
311 lc(defval($config->{'receive.denydeletecurrent'},"")) eq "warn" or &$do_config('receive.denydeletecurrent', 'warn');
312 do {
313 !exists($config->{$_}) || valid_bool(defval($config->{$_},"")) or &$do_config_unset($_, "(not a boolean)");
314 } foreach (@boolvars);
315 do {
316 (valid_bool(defval($config->{$_},"")) && !git_bool($config->{$_})) or &$do_config($_, "false");
317 } foreach (@falsevars);
318 do {
319 (valid_bool(defval($config->{$_},"")) && !git_bool($config->{$_})) or &$do_config($_, 0);
320 } foreach (@false0vars);
321 do {
322 (valid_bool(defval($config->{$_},"")) && git_bool($config->{$_})) or &$do_config($_, "true");
323 } foreach (@truevars);
325 if (defined($Girocco::Config::owning_group) && $Girocco::Config::owning_group ne "") {
326 $fp = openfind(qw(. -xdev ( -type d -o -type f ) ! -group), $Girocco::Config::owning_group, "-print");
327 while (<$fp>) {
328 chomp;
329 change_group($proj, $_) or $bad = 1;
331 close($fp) or $bad = 1;
333 foreach (@fixfpermsfiles) {
334 if (-e $_) {
335 if (! -f $_) {
336 warn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
337 $reallybad = $bad = 1;
338 last;
340 check_fperm($proj, $_) or $bad = 1;
343 return 0 if $reallybad;
345 $fp = openfindne(@fixfpermsdirs, qw(-xdev -type f ! -perm), $fmodeoct, "-print");
346 while (<$fp>) {
347 chomp;
348 check_fperm($proj, $_) or $bad = 1;
350 close($fp) or $bad = 1;
351 $fp = openfind(qw(. -xdev -type f ! -perm -a+r -print));
352 while (<$fp>) {
353 chomp;
354 check_fpermr($proj, $_) or $bad = 1;
356 close($fp) or $bad = 1;
357 $fp = openfind(qw(. -xdev -type d ( -path ./hooks -o -path ./mob/hooks ) -prune -o -type f -perm +a+x -print));
358 while (<$fp>) {
359 chomp;
360 check_fpermnox($proj, $_) or $bad = 1;
362 close($fp) or $bad = 1;
364 my $bu = defval($config->{'gitweb.baseurl'},"");
365 if (-e ".nofetch") {
366 $bu eq "" or pmsg($proj, "WARNING: .nofetch exists but gitweb.baseurl is not empty ($bu)") unless $quiet;
367 } else {
368 $bu ne "" or pmsg($proj, "WARNING: gitweb.baseurl is empty and .nofetch does not exist") unless $quiet;
371 return !$bad;
374 sub do_mkdir
376 my ($proj, $subdir) = @_;
377 my $result = "";
378 if (!$dryrun) {
379 mkdir("$subdir") && -d "$subdir" or $result = "FAILED";
380 } else {
381 $result = "(dryrun)";
383 pmsg($proj, "$subdir/: created", $result);
384 return $result ne "FAILED";
387 sub check_dperm {
388 my ($proj, $subdir) = @_;
389 my $oldmode = (stat($subdir))[2];
390 if (!defined($oldmode) || $oldmode eq "") {
391 warn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
392 return 0;
394 my $newmode = ($oldmode & ~07777) | $dmode;
395 $newmode == $oldmode and return 1;
396 my $result = "";
397 if (!$dryrun) {
398 if (!chmod($newmode & 07777, $subdir)) {
399 $result = "FAILED";
400 warn "chmod: ($proj) $subdir: $!\n" unless $quiet;
402 } else {
403 $result = "(dryrun)";
405 pmsg($proj, "$subdir/:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result);
406 return $result ne "FAILED";
409 sub change_dpermrwx {
410 my ($proj, $subdir) = @_;
411 my $oldmode = (stat($subdir))[2];
412 if (!defined($oldmode) || $oldmode eq "") {
413 warn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
414 return 0;
416 my $newmode = $oldmode | ($wall ? 0777 : 0775);
417 $newmode == $oldmode and return 1;
418 my $result = "";
419 if (!$dryrun) {
420 if (!chmod($newmode & 07777, $subdir)) {
421 $result = "FAILED";
422 warn "chmod: ($proj) $subdir: $!\n" unless $quiet;
424 } else {
425 $result = "(dryrun)";
427 pmsg($proj, "$subdir/:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result);
428 return $result ne "FAILED";
431 sub change_dpermrx {
432 my ($proj, $subdir) = @_;
433 $subdir =~ s,^\./,,;
434 my $oldmode = (stat($subdir))[2];
435 if (!defined($oldmode) || $oldmode eq "") {
436 warn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
437 return 0;
439 my $newmode = $oldmode | 0555;
440 $newmode == $oldmode and return 1;
441 my $result = "";
442 if (!$dryrun) {
443 if (!chmod($newmode & 07777, $subdir)) {
444 $result = "FAILED";
445 warn "chmod: ($proj) $subdir: $!\n" unless $quiet;
447 } else {
448 $result = "(dryrun)";
450 pmsg($proj, "$subdir/:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result);
451 return $result ne "FAILED";
454 sub check_fperm {
455 my ($proj, $file) = @_;
456 my $oldmode = (stat($file))[2];
457 if (!defined($oldmode) || $oldmode eq "") {
458 warn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
459 return 0;
461 my $newmode = ($oldmode & ~07777) | $fmode;
462 $newmode == $oldmode and return 1;
463 my $result = "";
464 if (!$dryrun) {
465 if (!chmod($newmode & 07777, $file)) {
466 $result = "FAILED";
467 warn "chmod: ($proj) $file: $!\n" unless $quiet;
469 } else {
470 $result = "(dryrun)";
472 pmsg($proj, "$file:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result);
473 return $result ne "FAILED";
476 sub check_fpermr {
477 my ($proj, $file) = @_;
478 $file =~ s,^\./,,;
479 my $oldmode = (stat($file))[2];
480 if (!defined($oldmode) || $oldmode eq "") {
481 warn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
482 return 0;
484 my $newmode = $oldmode | 0444;
485 $newmode == $oldmode and return 1;
486 my $result = "";
487 if (!$dryrun) {
488 if (!chmod($newmode & 07777, $file)) {
489 $result = "FAILED";
490 warn "chmod: ($proj) $file: $!\n" unless $quiet;
492 } else {
493 $result = "(dryrun)";
495 pmsg($proj, "$file:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result);
496 return $result ne "FAILED";
499 sub check_fpermnox {
500 my ($proj, $file) = @_;
501 $file =~ s,^\./,,;
502 my $oldmode = (stat($file))[2];
503 if (!defined($oldmode) || $oldmode eq "") {
504 warn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
505 return 0;
507 my $newmode = $oldmode & ~0111;
508 $newmode == $oldmode and return 1;
509 my $result = "";
510 if (!$dryrun) {
511 if (!chmod($newmode & 07777, $file)) {
512 $result = "FAILED";
513 warn "chmod: ($proj) $file: $!\n" unless $quiet;
515 } else {
516 $result = "(dryrun)";
518 pmsg($proj, "$file:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result);
519 return $result ne "FAILED";
522 sub change_group {
523 my ($proj, $item) = @_;
524 $item =~ s,^\./,,;
525 my @info = stat($item);
526 if (@info < 6 || $info[2] eq "" || $info[4] eq "" || $info[5] eq "") {
527 warn "chgrp: ($proj) $item: No such file or directory\n" unless $quiet;
528 return 0;
530 $info[5] == $owning_group_id and return 1;
531 my $result = "";
532 if (!$dryrun) {
533 if (!chown($info[4], $owning_group_id, $item)) {
534 $result = "FAILED";
535 warn "chgrp: ($proj) $item: $!\n" unless $quiet;
536 } elsif (!chmod($info[2] & 07777, $item)) {
537 $result = "FAILED";
538 warn "chmod: ($proj) $item: $!\n" unless $quiet;
540 } else {
541 $result = "(dryrun)";
543 my $isdir = ((($info[2] >> 12) & 017) == 004) ? '/' : '';
544 pmsg($proj, "$item$isdir: group", get_grp_nam($info[5]), '->', $Girocco::Config::owning_group, $result);
545 return $result ne "FAILED";
548 my $wrote; BEGIN {$wrote = ""}
549 sub pmsg {
550 my $proj = shift;
551 my $msg = join(" ", @_);
552 $msg =~ s/\s+$//;
553 my $prefix = "";
554 if (!$hdr) {
555 $prefix = $wrote . $proj . ":\n";
556 $hdr = 1;
558 print $prefix, " ", join(' ', @_), "\n";
559 $wrote = "\n";
562 my %ftypes;
563 BEGIN {%ftypes = (
564 000 => '?',
565 001 => 'p',
566 002 => 'c',
567 003 => '?',
568 004 => 'd',
569 005 => '?',
570 006 => 'b',
571 007 => '?',
572 010 => '-',
573 011 => '?',
574 012 => 'l',
575 013 => '?',
576 014 => 's',
577 015 => '?',
578 016 => 'w',
579 017 => '?'
581 my %fperms;
582 BEGIN {%fperms = (
583 0 => '---',
584 1 => '--x',
585 2 => '-w-',
586 3 => '-wx',
587 4 => 'r--',
588 5 => 'r-x',
589 6 => 'rw-',
590 7 => 'rwx'
593 sub get_mode_perm {
594 my $mode = $_[0];
595 my $str = $ftypes{($mode >> 12) & 017} .
596 $fperms{($mode >> 6) & 7} .
597 $fperms{($mode >> 3) & 7} .
598 $fperms{$mode & 7};
599 substr($str,3,1) = ($mode & 0100) ? 's' : 'S' if $mode & 04000;
600 substr($str,6,1) = ($mode & 0010) ? 's' : 'S' if $mode & 02000;
601 substr($str,9,1) = ($mode & 0001) ? 't' : 'T' if $mode & 01000;
602 return $str;
605 sub get_perm {
606 my $mode = (stat($_[0]))[2];
607 defined($mode) or return '??????????';
608 return get_mode_perm($mode);
611 sub get_grp_nam {
612 my $grpid = $_[0];
613 defined($grpid) or return '?';
614 my $grpnm = scalar(getgrgid($grpid));
615 return defined($grpnm) && $grpnm ne "" ? $grpnm : $grpid;
618 sub get_grp {
619 my $grp = (stat($_[0]))[5];
620 defined($grp) or return '?';
621 return get_grp_nam($grp);
624 __END__
626 =head1 NAME
628 update-all-config.pl - Update all projects' config settings
630 =head1 SYNOPSIS
632 update-all-config.pl [<options>] [<projname>]...
634 Options:
635 -h | --help detailed instructions
636 -V | --version show version
637 -n | --dry-run show what would be done but don't do it
638 -f | --force run without a Config.pm owning_group
639 -q | --quiet suppress change messages
641 <projname> if given, only operate on these projects
643 =head1 OPTIONS
645 =over 8
647 =item B<-h>, B<--help>
649 Print the full description of update-all-config.pl's options.
651 =item B<-V>, B<--version>
653 Print the version of update-all-config.pl.
655 =item B<-n>, B<--dry-run>
657 Do not actually make any changes, just show what would be done without
658 actually doing it.
660 =item B<-q>, B<--quiet>
662 Suppress the messages about what's actually being changed. This option
663 is ignored if B<--dry-run> is in effect.
665 The warnings about missing and unknown-to-Girocco projects are also
666 suppressed by this option.
668 =item B<-f>, B<--force>
670 Allow running without a $Girocco::Config::owning_group set. This is not
671 recommended as it results in world-writable items being used (instead of
672 just world-readable).
674 =item B<<projname>>
676 If no project names are specified then I<all> projects are processed.
678 If one or more project names are specified then only those projects are
679 processed. Specifying non-existent projects produces a warning for them,
680 but the rest of the projects specified will still be processed.
682 Each B<projname> may be either a full absolute path starting with
683 $Girocco::Config::reporoot or just the project name part with or without
684 a trailing C<.git>.
686 Any explicitly specified projects that do exist but are not known to
687 Girocco will be skipped (with a warning).
689 =back
691 =head1 DESCRIPTION
693 Inspect the C<config> files of Girocco projects (i.e. $GIT_DIR/config) and
694 look for anomalies and out-of-date settings.
696 Additionally check the existence and permissions on various files and
697 directories in the project.
699 If an explicity specified project is located under $Girocco::Config::reporoot
700 but is not actually known to Girocco (i.e. it's not in the etc/group file)
701 then it will be skipped.
703 By default, any anomalies or out-of-date settings will be corrected with a
704 message to that effect. However using B<--dry-run> will only show the
705 correction(s) which would be made without making them and B<--quiet> will make
706 the correction(s) without any messages.
708 Any projects that have a C<$GIT_DIR/.noconfig> file are always skipped (with a
709 message unless B<--quiet> is used).
711 =cut