(linenum→info "unix/slp.c:2238")

coreutils/6.9/build-aux/cvsu

    1: #! /usr/bin/perl -w
    2: 
    3: # cvsu - do a quick check to see what files are out of date.
    4: #
    5: # Copyright (C) 2000-2005  Pavel Roskin <proski@gnu.org>
    6: # Initially written by Tom Tromey <tromey@cygnus.com>
    7: # Completely rewritten by Pavel Roskin <proski@gnu.org>
    8: #
    9: # This program is free software; you can redistribute it and/or modify
   10: # it under the terms of the GNU General Public License as published by
   11: # the Free Software Foundation; either version 2, or (at your option)
   12: # any later version.
   13: #
   14: # This program is distributed in the hope that it will be useful,
   15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17: # GNU General Public License for more details.
   18: #
   19: # You should have received a copy of the GNU General Public License
   20: # along with this program; if not, write to the Free Software
   21: # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   22: # 02111-1307, USA.
   23: 
   24: 
   25: require 5.004;
   26: use Getopt::Long;
   27: use File::Basename;
   28: use Time::Local;
   29: use strict;
   30: 
   31: use vars qw($list_types %messages %options @batch_list $batch_cmd
   32:             $no_recurse $explain_type $find_mode $short_print
   33:             $no_cvsignore $nolinks $file $single_filename $curr_dir
   34:             @common_ignores $ignore_rx %entries %subdirs %removed);
   35: 
   36: use constant SUBDIR_FOUND => 1;
   37: use constant SUBDIR_CVS   => 2;
   38: 
   39: # This list comes from the CVS manual.
   40: use constant STANDARD_IGNORES =>
   41:         ('RCS', 'SCCS', 'CVS', 'CVS.adm', 'RCSLOG', 'cvslog.*', 'tags',
   42:          'TAGS', '.make.state', '.nse_depinfo', '*~', '#*', '.#*', ',*',
   43:          "_\$*", "*\$", '*.old', '*.bak', '*.BAK', '*.orig', '*.rej',
   44:          '.del-*', '*.a', '*.olb', '*.o', '*.obj', '*.so', '*.exe',
   45:          '*.Z', '*.elc', '*.ln', 'core');
   46: 
   47: # 3-letter month names in POSIX locale, for fast date decoding
   48: my %months = (
   49:     "Jan" => 0,
   50:     "Feb" => 1,
   51:     "Mar" => 2,
   52:     "Apr" => 3,
   53:     "May" => 4,
   54:     "Jun" => 5,
   55:     "Jul" => 6,
   56:     "Aug" => 7,
   57:     "Sep" => 8,
   58:     "Oct" => 9,
   59:     "Nov" => 10,
   60:     "Dec" => 11
   61: );
   62: 
   63: # print usage information and exit
   64: sub usage ()
   65: {
   66:     print "Usage:\n" .
   67:         "  cvsu [OPTIONS] [FILE] ...\n" .
   68:         "Options:\n" .
   69:         "  --local             Disable recursion\n" .
   70:         "  --explain           Verbosely print status of files\n" .
   71:         "  --find              Emulate find - filenames only\n" .
   72:         "  --short             Don't print paths\n" .
   73:         "  --ignore            Don't read .cvsignore\n" .
   74:         "  --messages          List known file types and long messages\n" .
   75:         "  --nolinks           Disable recognizing hard and soft links\n" .
   76:         "  --types=[^]LIST     Print only file types [not] from LIST\n" .
   77:         "  --batch=COMMAND     Execute this command on files\n" .
   78:         "  --help              Print this usage information\n" .
   79:         "  --version           Print version number\n" .
   80:         "Abbreviations and short options are supported\n";
   81:     exit 0;
   82: }
   83: 
   84: # print version information and exit
   85: sub version ()
   86: {
   87:     print "cvsu - CVS offline examiner, version 0.2.3\n";
   88:     exit 0;
   89: }
   90: 
   91: # If types begin with '^', make inversion
   92: sub adjust_types ()
   93: {
   94:     if ($list_types =~ m{^\^(.*)$}) {
   95:         $list_types = "";
   96:         foreach (keys %messages) {
   97:             $list_types .= $_
   98:                 if (index ($1, $_) < 0);
   99:         }
  100:     }
  101: }
  102: 
  103: # list known messages and exit
  104: sub list_messages ()
  105: {
  106:     my $default_mark;
  107:     print "Recognizable file types are:\n";
  108:     foreach (sort keys %messages) {
  109:         if (index($list_types, $_) >= 0) {
  110:             $default_mark = "*";
  111:         } else {
  112:             $default_mark = " ";
  113:         }
  114:         print "  $default_mark $_ $messages{$_}\n";
  115:     }
  116:     print "* indicates file types listed by default\n";
  117:     exit 0;
  118: }
  119: 
  120: # Initialize @common_ignores
  121: # Also read $HOME/.cvsignore and append it to @common_ignores
  122: sub init_ignores ()
  123: {
  124:     my $HOME = $ENV{"HOME"};
  125: 
  126:     push @common_ignores, STANDARD_IGNORES;
  127: 
  128:     unless (defined($HOME)) {
  129:         return;
  130:     }
  131: 
  132:     my $home_cvsignore = "${HOME}/.cvsignore";
  133: 
  134:     if (-f "$home_cvsignore") {
  135: 
  136:         unless (open (CVSIGNORE, "< $home_cvsignore")) {
  137:             error ("couldn't open $home_cvsignore: $!");
  138:         }
  139: 
  140:         while (<CVSIGNORE>) {
  141:             push (@common_ignores, split);
  142:         }
  143: 
  144:         close (CVSIGNORE);
  145:     }
  146: 
  147:     my $CVSIGNOREENV = $ENV{"CVSIGNORE"};
  148: 
  149:     unless (defined($CVSIGNOREENV)) {
  150:         return;
  151:     }
  152: 
  153:     my @ignores_var = split (/ /, $CVSIGNOREENV);
  154:     push (@common_ignores, @ignores_var);
  155: 
  156: }
  157: 
  158: # Print message and exit (like "die", but without raising an exception).
  159: # Newline is added at the end.
  160: sub error ($)
  161: {
  162:         print STDERR "cvsu: ERROR: " . shift(@_) . "\n";
  163:         exit 1;
  164: }
  165: 
  166: # execute commands from @exec_list with $exec_cmd
  167: sub do_batch ()
  168: {
  169:         my @cmd_list = split (' ', $batch_cmd);
  170:         system (@cmd_list,  @batch_list);
  171: }
  172: 
  173: # print files status
  174: # Parameter 1: status in one-letter representation
  175: sub file_status ($)
  176: {
  177:     my $type = shift (@_);
  178:     my $item;
  179:     my $pathfile;
  180: 
  181:     return
  182:         if $ignore_rx ne '' && $type =~ /[?SLD]/ && $file =~ /$ignore_rx/;
  183: 
  184:     return
  185:         if (index($list_types, $type) < 0);
  186: 
  187:     $pathfile = $curr_dir . $file;
  188: 
  189:     if (defined($batch_cmd)) {
  190:         push (@batch_list, $pathfile);
  191:         # 1000 items in the command line might be too much for HP-UX
  192:         if ($#batch_list > 1000) {
  193:             do_batch();
  194:             undef @batch_list;
  195:         }
  196:     }
  197: 
  198:     if ($short_print) {
  199:         $item = $file;
  200:     } else {
  201:         $item = $pathfile;
  202:     }
  203: 
  204:     if ($find_mode) {
  205:         print "$item\n";
  206:     } else {
  207:         $type = $messages{$type}
  208:             if ($explain_type);
  209:         print "$type $item\n";
  210:     }
  211: }
  212: 
  213: # load entries from CVS/Entries and CVS/Entries.Log
  214: # Parameter 1: file name for CVS/Entries
  215: # Return: list of entries in the format used in CVS/Entries
  216: sub load_entries ($);
  217: sub load_entries ($)
  218: {
  219:     my $entries_file = shift (@_);
  220:     my $entries_log_file = "$entries_file.Log";
  221:     my %ent = ();
  222: 
  223:     unless (open (ENTRIES, "< $entries_file")) {
  224:         error ("couldn't open $entries_file: $!");
  225:     }
  226:     while (<ENTRIES>) {
  227:         chomp;
  228:         $ent{$_} = 1;
  229:     }
  230:     close (ENTRIES);
  231: 
  232:     if (open (ENTRIES, "< $entries_log_file")) {
  233:         while (<ENTRIES>) {
  234:             chomp;
  235:             if ( m{^A (.+)} ) {
  236:                 $ent{$1} = 1;
  237:             } elsif ( m{^R (.+)} ) {
  238:                 delete $ent{$1};
  239:             } else {
  240:                 # Note: "cvs commit" helps even when you are offline
  241:                 error ("$entries_log_file:$.: unrecognizable line, " .
  242:                         "try \"cvs commit\"");
  243:             }
  244:         }
  245:         close (ENTRIES);
  246:     }
  247: 
  248:     return keys %ent;
  249: }
  250: 
  251: # process one directory
  252: # Parameter 1: directory name
  253: sub process_arg ($);
  254: sub process_arg ($)
  255: {
  256:     my $arg = shift (@_);
  257:     my %found_files = ();
  258: 
  259:     # $file, $curr_dir, and $ignore_rx must be seen in file_status
  260:     local $file = "";
  261:     local $ignore_rx = "";
  262:     local $single_filename = 0;
  263: 
  264:     if ( $arg eq "" or -d $arg ) {
  265:         $curr_dir = $arg;
  266:         my $real_curr_dir = $curr_dir eq "" ? "." : $curr_dir;
  267: 
  268:         error ("$real_curr_dir is not a directory")
  269:             unless ( -d $real_curr_dir );
  270: 
  271:         # Scan present files.
  272:         file_status (".");
  273:         opendir (DIR, $real_curr_dir) ||
  274:             error ("couldn't open directory $real_curr_dir: $!");
  275:         foreach (readdir (DIR)) {
  276:             $found_files {$_} = 1;
  277:         }
  278:         closedir (DIR);
  279:     } else {
  280:         $single_filename = basename $arg;
  281:         $curr_dir = dirname $arg;
  282:         $found_files{$single_filename} = 1 if lstat $arg;
  283:     }
  284: 
  285:     $curr_dir .= "/"
  286:         unless ( $curr_dir eq "" || $curr_dir =~ m{/$} );
  287: 
  288:     # Scan CVS/Entries.
  289:     my %entries = ();
  290:     my %subdirs = ();
  291:     my %removed = ();
  292: 
  293:     foreach ( load_entries ("${curr_dir}CVS/Entries") ) {
  294:         if ( m{^D/([^/]+)/} ) {
  295:             $subdirs{$1} = SUBDIR_FOUND if !$single_filename;
  296:         } elsif ( m{^/([^/]+)/([^/])[^/]*/([^/]*)/} ) {
  297:             if ( !$single_filename or $single_filename eq $1 ) {
  298:                 $entries{$1} = $3;
  299:                 $removed{$1} = 1
  300:                     if $2 eq '-';
  301:             }
  302:         } elsif ( m{^D$} ) {
  303:             next;
  304:         } else {
  305:             error ("${curr_dir}CVS/Entries: unrecognizable line");
  306:         }
  307:     }
  308: 
  309:     if ( $single_filename && !$entries{$single_filename} &&
  310:          !$found_files{$single_filename} ) {
  311:         error ("nothing known about $arg");
  312:     }
  313: 
  314:     # Scan .cvsignore if any
  315:     unless ($no_cvsignore) {
  316:         my (@ignore_list) = ();
  317: 
  318:         if (-f "${curr_dir}.cvsignore") {
  319:             open (CVSIGNORE, "< ${curr_dir}.cvsignore")
  320:                 || error ("couldn't open ${curr_dir}.cvsignore: $!");
  321:             while (<CVSIGNORE>) {
  322:                 push (@ignore_list, split);
  323:             }
  324:             close (CVSIGNORE);
  325:         }
  326: 
  327:         my ($iter);
  328:         foreach $iter (@ignore_list, @common_ignores) {
  329:             if ($iter eq '!') {
  330:                 $ignore_rx = ''
  331:             } else {
  332:                 if ($ignore_rx eq '') {
  333:                     $ignore_rx = '^(';
  334:                 } else {
  335:                     $ignore_rx .= '|';
  336:                 }
  337:                 $ignore_rx .= glob_to_rx ($iter);
  338:             }
  339:         }
  340:         $ignore_rx .= ')$'
  341:             if $ignore_rx ne '';
  342:     }
  343: 
  344:     # File is missing
  345:     foreach $file (sort keys %entries) {
  346:         unless ($found_files{$file}) {
  347:             if ($removed{$file}) {
  348:                 file_status("R");
  349:             } else {
  350:                 file_status("U");
  351:             }
  352:         }
  353:     }
  354: 
  355:     foreach $file (sort keys %found_files) {
  356:         next if ($file eq '.' || $file eq '..');
  357:         lstat ($curr_dir . $file) ||
  358:             error ("lstat() failed on $curr_dir . $file");
  359:         if (! $nolinks && -l _) {
  360:             file_status ("L");
  361:         } elsif (-d _) {
  362:             if ($file eq 'CVS') {
  363:                 file_status ("C");
  364:             } elsif ($subdirs{$file}) {
  365:                 $subdirs{$file} = SUBDIR_CVS;
  366:             } else {
  367:                 file_status ("D"); # Unknown directory
  368:             }
  369:         } elsif (! (-f _) && ! (-l _)) {
  370:             file_status ("S"); # This must be something very special
  371:         } elsif (! $nolinks && (stat _) [3] > 1 ) {
  372:             file_status ("H"); # Hard link
  373:         } elsif (! $entries{$file}) {
  374:             file_status ("?");
  375:         } elsif ($entries{$file} =~ /^Initial |^dummy /) {
  376:             file_status ("A");
  377:         } elsif ($entries{$file} =~ /^Result of merge/) {
  378:             file_status ("G");
  379:         } elsif ($entries{$file} !~
  380:                 /^(...) (...) (..) (..):(..):(..) (....)$/) {
  381:             error ("Invalid timestamp for $curr_dir$file: $entries{$file}");
  382:         } else {
  383:             my $cvtime = timegm($6, $5, $4, $3, $months{$2}, $7 - 1900);
  384:             my $mtime = (stat _) [9];
  385:             if ($cvtime == $mtime) {
  386:                 file_status ("F");
  387:             } elsif ($cvtime < $mtime) {
  388:                 file_status ("M");
  389:             } else {
  390:                 file_status ("O");
  391:             }
  392:         }
  393:     }
  394: 
  395:     # Now do directories.
  396:     unless ($no_recurse) {
  397:         my $save_curr_dir = $curr_dir;
  398:         foreach $file (sort keys %subdirs) {
  399:             if ($subdirs{$file} == SUBDIR_FOUND) {
  400:                 $curr_dir = $save_curr_dir;
  401:                 file_status ("X");
  402:             } elsif ($subdirs{$file} == SUBDIR_CVS) {
  403:                 process_arg ($save_curr_dir . $file)
  404:             }
  405:         }
  406:     }
  407: }
  408: 
  409: # Turn a glob into a regexp without recognizing square brackets.
  410: sub glob_to_rx_simple ($)
  411: {
  412:     my ($expr) = @_;
  413:     # Quote all non-word characters, convert ? to . and * to .*
  414:     $expr =~ s/(\W)/\\$1/g;
  415:     $expr =~ s/\\\*/.*/g;
  416:     $expr =~ s/\\\?/./g;
  417:     return $expr;
  418: }
  419: 
  420: # Turn a glob into a regexp
  421: sub glob_to_rx ($)
  422: {
  423:     my $result = '';
  424:     my ($expr) = @_;
  425:     # Find parts in square brackets and copy them literally
  426:     # Text outside brackets is processed by glob_to_rx_simple()
  427:     while ($expr ne '') {
  428:         if ($expr =~ /^(.*?)(\[.*?\])(.*)/) {
  429:             $expr = $3;
  430:             $result .= glob_to_rx_simple ($1) . $2;
  431:         } else {
  432:             $result .= glob_to_rx_simple ($expr);
  433:             last;
  434:         }
  435:     }
  436:     return $result;
  437: }
  438: 
  439: sub Main ()
  440: {
  441:     # types of files to be listed
  442:     $list_types = "^.FCL";
  443: 
  444:     # long status messages
  445:     %messages = (
  446:         "?" => "Unlisted file",
  447:         "." => "Known directory",
  448:         "F" => "Up-to-date file",
  449:         "C" => "CVS admin directory",
  450:         "M" => "Modified file",
  451:         "S" => "Special file",
  452:         "D" => "Unlisted directory",
  453:         "L" => "Symbolic link",
  454:         "H" => "Hard link",
  455:         "U" => "Lost file",
  456:         "X" => "Lost directory",
  457:         "A" => "Newly added",
  458:         "O" => "Older copy",
  459:         "G" => "Result of merge",
  460:         "R" => "Removed file"
  461:     );
  462: 
  463:     undef @batch_list;          # List of files for batch processing
  464:     undef $batch_cmd;           # Command to be executed on files
  465:     $no_recurse = 0;            # If this is set, do only local files
  466:     $explain_type = 0;          # Verbosely print status of files
  467:     $find_mode = 0;             # Don't print status at all
  468:     $short_print = 0;           # Print only filenames without path
  469:     $no_cvsignore = 0;          # Ignore .cvsignore
  470:     $nolinks = 0;               # Do not test for soft- or hard-links
  471:     my $want_msg = 0;           # List possible filetypes and exit
  472:     my $want_help = 0;          # Print help and exit
  473:     my $want_ver = 0;           # Print version and exit
  474: 
  475:     my %options = (
  476:         "types=s"  => \$list_types,
  477:         "batch=s"  => \$batch_cmd,
  478:         "local"           => \$no_recurse,
  479:         "explain"  => \$explain_type,
  480:         "find"    => \$find_mode,
  481:         "short"           => \$short_print,
  482:         "ignore"   => \$no_cvsignore,
  483:         "messages" => \$want_msg,
  484:         "nolinks"  => \$nolinks,
  485:         "help"     => \$want_help,
  486:         "version"  => \$want_ver
  487:     );
  488: 
  489:     GetOptions(%options);
  490: 
  491:     adjust_types();
  492: 
  493:     list_messages() if $want_msg;
  494:     usage() if $want_help;
  495:     version() if $want_ver;
  496: 
  497:     unless ($no_cvsignore) {
  498:         init_ignores();
  499:     }
  500: 
  501:     if ($#ARGV < 0) {
  502:         @ARGV = ("");
  503:     }
  504: 
  505:     foreach (@ARGV) {
  506:         process_arg ($_);
  507:     }
  508: 
  509:     if ($#batch_list >= 0) {
  510:             do_batch();
  511:     }
  512: }
  513: 
  514: Main();
1
Syntax (Markdown)