3 # update-all-config.pl - Update all out-of-date config
8 BEGIN {*VERSION = \'2.0'}
13 use lib
"__BASEDIR__";
21 $shbin = $Girocco::Config
::posix_sh_bin
;
22 defined($shbin) && $shbin ne "" or $shbin = "/bin/sh";
25 exit(&main
(@ARGV)||0);
27 my ($dryrun, $force, $quiet);
30 pod2usage
(-exitval
=> 2);
34 pod2usage
(-verbose
=> 2, -exitval
=> 0);
38 print basename
($0), " version ", $VERSION, "\n";
42 my ($dmode, $dperm, $drwxmode, $fmode, $fmodeoct, $fperm, $wall);
46 $drwxmode='ug+rwx,o+rx';
60 close(DATA
) if fileno(DATA
);
61 Getopt
::Long
::Configure
('bundling');
63 'help|h' => sub {do_help
},
64 'version|V' => sub {do_version
},
65 'dry-run|n' => \
$dryrun,
69 $dryrun and $quiet = 0;
71 -f jailed_file
("/etc/group") or
72 die "Girocco group file not found: " . jailed_file
("/etc/group") . "\n";
74 if (!defined($Girocco::Config
::owning_group
) || $Girocco::Config
::owning_group
eq "") {
75 die "\$Girocco::Config::owning_group unset, refusing to run without --force\n" unless $force;
83 warn "Mode 666 in effect\n" unless $quiet;
84 } elsif (($owning_group_id = scalar(getgrnam($Girocco::Config
::owning_group
))) !~ /^\d+$/) {
85 die "\$Girocco::Config::owning_group invalid ($Girocco::Config::owning_group), refusing to run\n";
88 my @allprojs = Girocco
::Project
::get_full_list
;
91 my $root = $Girocco::Config
::reporoot
;
92 $root or die "\$Girocco::Config::reporoot is invalid\n";
94 $root ne "" or $root = "/";
95 $root = realpath
($root);
97 my %projnames = map {($_ => 1)} @allprojs;
101 -d
$_ and $_ = realpath
($_);
104 if (!exists($projnames{$_})) {
105 warn "$_: unknown to Girocco (not in etc/group)\n"
112 @projects = sort {lc($a) cmp lc($b)} @allprojs;
116 foreach (@projects) {
117 my $projdir = "$root/$_.git";
118 if (! -d
"$projdir") {
119 warn "$_: does not exist -- skipping\n" unless $quiet;
122 if (!is_git_dir
($projdir)) {
123 warn "$_: is not a .git directory -- skipping\n" unless $quiet;
126 if (-e
"$projdir/.noconfig") {
127 warn "$_: found .noconfig -- skipping\n" unless $quiet;
130 process_one_project
($_, $projdir) or $bad = 1;
136 my (@mkdirs, @mkfiles);
137 my (@fixdpermsdirs, @fixdpermsrwx, @fixfpermsfiles, @fixfpermsdirs);
139 @mkdirs = qw(refs info hooks ctags htmlcache bundles reflogs objects objects/info);
140 @mkfiles = qw(config info/lastactivity);
141 @fixdpermsdirs = qw(. refs info ctags htmlcache bundles reflogs objects objects/info);
142 @fixdpermsrwx = qw(refs objects);
143 @fixfpermsfiles = qw(HEAD config description packed-refs README.html info/lastactivity
144 info/alternates info/http-alternates info/packs);
145 @fixfpermsdirs = qw(ctags);
148 my (@boolvars, @falsevars, @false0vars, @truevars);
150 @boolvars = qw(gitweb.statusupdates);
151 @falsevars = qw(core.ignorecase receive.denynonfastforwards);
152 @false0vars = qw(gc.auto receive.autogc);
153 @truevars = qw(receive.updateserverinfo repack.writebitmaps transfer.fsckobjects);
159 return defined($_[0]) ?
$_[0] : $_[1];
162 sub process_one_project
164 my ($proj, $projdir) = @_;
169 if (! -d
"$projdir/$_") {
170 if (-e
"$projdir/$_") {
171 warn "$proj: bypassing project, exists but not directory: $_\n" unless $quiet;
172 $reallybad = $bad = 1;
175 do_mkdir
($proj, $projdir, $_) or $bad = 1, last;
179 return 0 if $reallybad;
181 $ENV{PROJDIR
} = $projdir;
182 -d
"$projdir/$_" && check_dperm
($proj, $projdir, $_) or $bad = 1 foreach (@fixdpermsdirs);
183 my @dirs = split(/\n+/, qx(cd
"\$PROJDIR" &&
184 exec find
@fixdpermsrwx -xdev
-type d
\\( ! -path
"objects/??" -o
-prune
\\) ! -perm
-$drwxmode -print 2>/dev
/null
186 change_dpermrwx
($proj, $projdir, $_) or $bad = 1 foreach (@dirs);
187 @dirs = split(/\n+/, qx(cd
"\$PROJDIR" &&
188 exec find
. -xdev
-type d
\\( ! -path
"./objects/??" -o
-prune
\\) ! -perm
-a
+rx
-print
190 change_dpermrx
($proj, $projdir, $_) or $bad = 1 foreach (@dirs);
193 if (-e
"$projdir/$_") {
194 if (! -f
"$projdir/$_") {
195 warn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
196 $reallybad = $bad = 1;
200 my $result = "(dryrun)";
204 open($tf, '>', "$projdir/$_") && close ($tf) or $result = "FAILED", $bad = 1;
206 pmsg
($proj, "$_: created", $result) unless $quiet;
209 return 0 if $reallybad;
211 $dryrun || check_fperm config
or $bad = 1;
212 my $config = read_config_file_hash
("$projdir/config", !$quiet);
213 if (!defined($config)) {
214 warn "$proj: could not read config file -- skipping\n" unless $quiet;
218 my $do_config = sub {
219 my ($item, $val) = @_;
220 my $oldval = defval
($config->{$item},"");
221 my $result = "(dryrun)";
224 system($Girocco::Config
::git_bin
, "config", "--file", "$projdir/config", "--replace-all", $item, $val) == 0 or
225 $result = "FAILED", $bad = 1;
227 if (!exists($config->{$item})) {
228 pmsg
($proj, "config $item: created \"$val\"", $result) unless $quiet;
230 pmsg
($proj, "config $item: \"$oldval\" -> \"$val\"", $result) unless $quiet;
233 my $do_config_unset = sub {
234 my ($item, $msg) = @_;
235 defined($msg) or $msg = "";
236 $msg eq "" or $msg = " " . $msg;
237 my $oldval = defval
($config->{$item},"");
238 my $result = "(dryrun)";
241 system($Girocco::Config
::git_bin
, "config", "--file", "$projdir/config", "--unset-all", $item) == 0 or
242 $result = "FAILED", $bad = 1;
244 pmsg
($proj, "config $item: removed$msg \"$oldval\"", $result) unless $quiet;
247 my $cmplvl = defval
($config->{'core.compression'},"");
248 if ($cmplvl !~ /^-?\d+$/ || $cmplvl < -1 || $cmplvl > 9 || "" . (0 + $cmplvl) ne "" . $cmplvl) {
249 pmsg
($proj, "WARNING: replacing invalid core.compression value: \"$cmplvl\"") unless $cmplvl eq "" || $quiet;
251 } elsif ($cmplvl != 5) {
252 pmsg
($proj, "WARNING: suboptimal core.compression value left unchanged: \"$cmplvl\"") unless $quiet;
254 $cmplvl ne "" or &$do_config('core.compression', 5);
255 my $grpshr = defval
($config->{'core.sharedrepository'},"");
256 if ($grpshr eq "" || (valid_bool
($grpshr) && !git_bool
($grpshr))) {
257 &$do_config('core.sharedrepository', 1);
258 } elsif (!(valid_bool
($grpshr) && git_bool
($grpshr))) {
259 pmsg
($proj, "WARNING: odd core.sharedrepository value left unchanged: \"$grpshr\"");
261 if (git_bool
($config->{'core.bare'})) {
263 my $laru = $config->{'core.logallrefupdates'};
264 if (defined($laru)) {
265 if (valid_bool
($laru)) {
267 if (git_bool
($laru)) {
268 pmsg
($proj, "WARNING: core.logallrefupdates is true (left unchanged)") unless $quiet;
271 pmsg
($proj, "WARNING: replacing non-boolean core.logallrefupdates value") unless $quiet;
274 !$setlaru or &$do_config('core.logallrefupdates', 'false');
276 pmsg
($proj, "WARNING: core.bare is not true (left unchanged)") unless $quiet;
278 defval
($config->{'transfer.unpacklimit'},"") eq "1" or &$do_config('transfer.unpacklimit', 1);
279 lc(defval
($config->{'receive.denydeletecurrent'},"")) eq "warn" or &$do_config('receive.denydeletecurrent', 'warn');
281 !exists($config->{$_}) || valid_bool
(defval
($config->{$_},"")) or &$do_config_unset($_, "(not a boolean)");
282 } foreach (@boolvars);
284 (valid_bool
(defval
($config->{$_},"")) && !git_bool
($config->{$_})) or &$do_config($_, "false");
285 } foreach (@falsevars);
287 (valid_bool
(defval
($config->{$_},"")) && !git_bool
($config->{$_})) or &$do_config($_, 0);
288 } foreach (@false0vars);
290 (valid_bool
(defval
($config->{$_},"")) && git_bool
($config->{$_})) or &$do_config($_, "true");
291 } foreach (@truevars);
293 if (defined($Girocco::Config
::owning_group
) && $Girocco::Config
::owning_group
ne "") {
294 my @items = split(/\n+/, qx(cd
"\$PROJDIR" &&
295 exec find
. -xdev
\\( -type d
-o
-type f
\\) ! -group
$Girocco::Config
::owning_group
-print
297 change_group
($proj, $projdir, $_) or $bad = 1 foreach (@items);
299 foreach (@fixfpermsfiles) {
300 if (-e
"$projdir/$_") {
301 if (! -f
"$projdir/$_") {
302 warn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
303 $reallybad = $bad = 1;
306 check_fperm
($proj, $projdir, $_) or $bad = 1;
309 return 0 if $reallybad;
311 my @files = split(/\n+/, qx(cd
"\$PROJDIR" &&
312 exec find
@fixfpermsdirs -xdev
-type f
! -perm
$fmodeoct -print 2>/dev/null
314 check_fperm
($proj, $projdir, $_) or $bad = 1 foreach (@files);
315 @files = split(/\n+/, qx(cd
"\$PROJDIR" &&
316 exec find
. -xdev
-type f
! -perm
-a
+r
-print
318 check_fpermr
($proj, $projdir, $_) or $bad = 1 foreach (@files);
319 @files = split(/\n+/, qx(cd
"\$PROJDIR" &&
320 exec find
. -xdev
-type d
\\( -path
./hooks -o -path ./mob
/hooks
\\) -prune
-o
-type f
-perm
+a
+x
-print
322 check_fpermnox
($proj, $projdir, $_) or $bad = 1 foreach (@files);
324 my $bu = defval
($config->{'gitweb.baseurl'},"");
325 if (-e
"$projdir/.nofetch") {
326 $bu eq "" or pmsg
($proj, "WARNING: .nofetch exists but gitweb.baseurl is not empty ($bu)") unless $quiet;
328 $bu ne "" or pmsg
($proj, "WARNING: gitweb.baseurl is empty and .nofetch does not exist") unless $quiet;
336 my ($proj, $projdir, $subdir) = @_;
339 mkdir("$projdir/$subdir") && -d
"$projdir/$subdir" or $result = "FAILED";
341 $result = "(dryrun)";
343 pmsg
($proj, "$subdir/: created", $result);
344 return $result ne "FAILED";
348 my ($proj, $projdir, $subdir) = @_;
349 my $oldmode = (stat("$projdir/$subdir"))[2];
350 if (!defined($oldmode) || $oldmode eq "") {
351 warn "chmod: $projdir/$subdir: No such file or directory\n" unless $quiet;
354 my $newmode = ($oldmode & ~07777) | $dmode;
355 $newmode == $oldmode and return 1;
358 if (!chmod($newmode & 07777, "$projdir/$subdir")) {
360 warn "chmod: $projdir/$subdir: $!\n" unless $quiet;
363 $result = "(dryrun)";
365 pmsg
($proj, "$subdir/:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
366 return $result ne "FAILED";
369 sub change_dpermrwx
{
370 my ($proj, $projdir, $subdir) = @_;
371 my $oldmode = (stat("$projdir/$subdir"))[2];
372 if (!defined($oldmode) || $oldmode eq "") {
373 warn "chmod: $projdir/$subdir: No such file or directory\n" unless $quiet;
376 my $newmode = $oldmode | ($wall ?
0777 : 0775);
377 $newmode == $oldmode and return 1;
380 if (!chmod($newmode & 07777, "$projdir/$subdir")) {
382 warn "chmod: $projdir/$subdir: $!\n" unless $quiet;
385 $result = "(dryrun)";
387 pmsg
($proj, "$subdir/:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
388 return $result ne "FAILED";
392 my ($proj, $projdir, $subdir) = @_;
393 my $oldmode = (stat("$projdir/$subdir"))[2];
394 if (!defined($oldmode) || $oldmode eq "") {
395 warn "chmod: $projdir/$subdir: No such file or directory\n" unless $quiet;
398 my $newmode = $oldmode | 0555;
399 $newmode == $oldmode and return 1;
402 if (!chmod($newmode & 07777, "$projdir/$subdir")) {
404 warn "chmod: $projdir/$subdir: $!\n" unless $quiet;
407 $result = "(dryrun)";
409 pmsg
($proj, "$subdir/:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
410 return $result ne "FAILED";
414 my ($proj, $projdir, $file) = @_;
415 my $oldmode = (stat("$projdir/$file"))[2];
416 if (!defined($oldmode) || $oldmode eq "") {
417 warn "chmod: $projdir/$file: No such file or directory\n" unless $quiet;
420 my $newmode = ($oldmode & ~07777) | $fmode;
421 $newmode == $oldmode and return 1;
424 if (!chmod($newmode & 07777, "$projdir/$file")) {
426 warn "chmod: $projdir/$file: $!\n" unless $quiet;
429 $result = "(dryrun)";
431 pmsg
($proj, "$file:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
432 return $result ne "FAILED";
436 my ($proj, $projdir, $file) = @_;
437 my $oldmode = (stat("$projdir/$file"))[2];
438 if (!defined($oldmode) || $oldmode eq "") {
439 warn "chmod: $projdir/$file: No such file or directory\n" unless $quiet;
442 my $newmode = $oldmode | 0444;
443 $newmode == $oldmode and return 1;
446 if (!chmod($newmode & 07777, "$projdir/$file")) {
448 warn "chmod: $projdir/$file: $!\n" unless $quiet;
451 $result = "(dryrun)";
453 pmsg
($proj, "$file:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
454 return $result ne "FAILED";
458 my ($proj, $projdir, $file) = @_;
459 my $oldmode = (stat("$projdir/$file"))[2];
460 if (!defined($oldmode) || $oldmode eq "") {
461 warn "chmod: $projdir/$file: No such file or directory\n" unless $quiet;
464 my $newmode = $oldmode & ~0111;
465 $newmode == $oldmode and return 1;
468 if (!chmod($newmode & 07777, "$projdir/$file")) {
470 warn "chmod: $projdir/$file: $!\n" unless $quiet;
473 $result = "(dryrun)";
475 pmsg
($proj, "$file:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
476 return $result ne "FAILED";
480 my ($proj, $projdir, $item) = @_;
481 my @info = stat("$projdir/$item");
482 if (@info < 6 || $info[2] eq "" || $info[4] eq "" || $info[5] eq "") {
483 warn "chgrp: $projdir/$item: No such file or directory\n" unless $quiet;
486 $info[5] == $owning_group_id and return 1;
489 if (!chown($info[4], $owning_group_id, "$projdir/$item")) {
491 warn "chgrp: $projdir/$item: $!\n" unless $quiet;
492 } elsif (!chmod($info[2] & 07777, "$projdir/$item")) {
494 warn "chmod: $projdir/$item: $!\n" unless $quiet;
497 $result = "(dryrun)";
499 my $isdir = ((($info[2] >> 12) & 017) == 004) ?
'/' : '';
500 pmsg
($proj, "$item$isdir: group", get_grp_nam
($info[5]), '->', $Girocco::Config
::owning_group
, $result);
501 return $result ne "FAILED";
504 my $wrote; BEGIN {$wrote = ""}
507 my $msg = join(" ", @_);
511 $prefix = $wrote . $proj . ":\n";
514 print $prefix, " ", join(' ', @_), "\n";
551 my $str = $ftypes{($mode >> 12) & 017} .
552 $fperms{($mode >> 6) & 7} .
553 $fperms{($mode >> 3) & 7} .
555 substr($str,3,1) = ($mode & 0100) ?
's' : 'S' if $mode & 04000;
556 substr($str,6,1) = ($mode & 0010) ?
's' : 'S' if $mode & 02000;
557 substr($str,9,1) = ($mode & 0001) ?
't' : 'T' if $mode & 01000;
562 my $mode = (stat($_[0]))[2];
563 defined($mode) or return '??????????';
564 return get_mode_perm
($mode);
569 defined($grpid) or return '?';
570 my $grpnm = scalar(getgrgid($grpid));
571 return defined($grpnm) && $grpnm ne "" ?
$grpnm : $grpid;
575 my $grp = (stat($_[0]))[5];
576 defined($grp) or return '?';
577 return get_grp_nam
($grp);
584 update-all-config.pl - Update all projects' config settings
588 update-all-config.pl [<options>] [<projname>]...
591 -h | --help detailed instructions
592 -V | --version show version
593 -n | --dry-run show what would be done but don't do it
594 -f | --force run without a Config.pm owning_group
595 -q | --quiet suppress change messages
597 <projname> if given, only operate on these projects
603 =item B<-h>, B<--help>
605 Print the full description of update-all-config.pl's options.
607 =item B<-V>, B<--version>
609 Print the version of update-all-config.pl.
611 =item B<-n>, B<--dry-run>
613 Do not actually make any changes, just show what would be done without
616 =item B<-q>, B<--quiet>
618 Suppress the messages about what's actually being changed. This option
619 is ignored if B<--dry-run> is in effect.
621 The warnings about missing and unknown-to-Girocco projects are also
622 suppressed by this option.
624 =item B<-f>, B<--force>
626 Allow running without a $Girocco::Config::owning_group set. This is not
627 recommended as it results in world-writable items being used (instead of
628 just world-readable).
632 If no project names are specified then I<all> projects are processed.
634 If one or more project names are specified then only those projects are
635 processed. Specifying non-existent projects produces a warning for them,
636 but the rest of the projects specified will still be processed.
638 Each B<projname> may be either a full absolute path starting with
639 $Girocco::Config::reporoot or just the project name part with or without
642 Any explicitly specified projects that do exist but are not known to
643 Girocco will be skipped (with a warning).
649 Inspect the C<config> files of Girocco projects (i.e. $GIT_DIR/config) and
650 look for anomalies and out-of-date settings.
652 Additionally check the existence and permissions on various files and
653 directories in the project.
655 If an explicity specified project is located under $Girocco::Config::reporoot
656 but is not actually known to Girocco (i.e. it's not in the etc/group file)
657 then it will be skipped.
659 By default, any anomalies or out-of-date settings will be corrected with a
660 message to that effect. However using B<--dry-run> will only show the
661 correction(s) which would be made without making them and B<--quiet> will make
662 the correction(s) without any messages.
664 Any projects that have a C<$GIT_DIR/.noconfig> file are always skipped (with a
665 message unless B<--quiet> is used).