File Coverage

blib/lib/SVNPlus/TagProtect.pm
Criterion Covered Total %
statement 23 141 16.3
branch 0 76 0.0
condition 0 18 0.0
subroutine 8 12 66.6
pod 0 4 0.0
total 31 251 12.3


line stmt bran cond sub pod time code
1             package SVNPlus::TagProtect;
2              
3 1     1   16904 use 5.008004;
  1         3  
4 1     1   4 use strict;
  1         2  
  1         19  
5 1     1   7 use warnings;
  1         4  
  1         53  
6              
7             our @ISA = qw();
8              
9             our $VERSION = '3.17';
10              
11             # Preloaded methods go here.
12 1     1   694 use autodie; # automatic die if file fails to open
  1         151783  
  1         6  
13 1     1   9064 use Sysadm::Install qw(tap); # to shell out for svnlook/svn
  1         208526  
  1         10  
14              
15             # "glob" strings without hitting the file system
16 1     1   912 use Text::Glob qw(match_glob glob_to_regex glob_to_regex_string);
  1         991  
  1         97  
17 1     1   784 use POSIX qw(strftime); # time stamps
  1         6305  
  1         10  
18 1     1   1686 use Cwd 'abs_path'; # to be able to get full path to this file
  1         2  
  1         22392  
19              
20             # FATAL ERROR
21             my $exitFatalErr = 1;
22              
23             # USER ASKED FOR HELP, PARSE, etc, from command line
24             my $exitSuccess = 0;
25              
26             ################################################################################
27             # ENTER: HARD DEFAULTS FOR CONFIG FILE, VARIABLES SET IN THE CONFIG FILE, etc.
28             # hard default actual variable variable looked for
29             # if not in config init w/useless value in configuration file
30             # CLI (currently 11 variables for command line parse - some can't be set (auto set)
31              
32             # 1
33             # --debug/-d/--debug=N/-dN
34             # $CLIC_DEBUG # CLI command line debug level
35             # $CLIF_DEBUG # debug level from config file parse, if any
36             my $VAR_H_DEBUG = "DEBUG"; # looked for in config file
37             my $DEF_H_DEBUG = 0; # default - some low level debug can only be seen by
38             # changing the default, here, to a high level!
39             # 2
40             # not cli setable, but config setable, or default=/usr/bin/svnlook
41             # $CLISVNLOOK # CLI, path to svnlook program
42             my $VAR_SVNLOOK = "SVNLOOK"; # variable looked for in config file
43             my $DEF_SVNLOOK = "/usr/bin/svnlook"; # default value if not in config;
44              
45             # 3
46             # not cli setable, but config setable, or default=/usr/bin/svn
47             # $CLISVNPATH # CLI, path to svn program
48             my $VAR_SVNPATH = "SVNPATH"; # variable looked for in config file
49             my $DEF_SVNPATH = "/usr/bin/svn"; # default value if not in config
50              
51             # 4
52             # command line can set, or default=0
53             # --build/-b
54             # $CLIBLDPREC # CLI, output PERL pre-build config from config file
55              
56             # 5
57             # command line can set, or default
58             # --parse/-p
59             # $CLIJUSTCFG # CLI, just parse the config and exit
60              
61             # 6
62             # command line can set, or default is STDOUT
63             # --input=/-i
64             # $CLI_INFILE # CLI, name of input file overrides default config files
65              
66             # 7
67             # command line can set, or default is STDOUT
68             # --output=/-o
69             # $CLIOUTFILE # CLI, name of output file, receives config output
70              
71             # 8
72             # command line can set, or default from PERL library name
73             # --revert/-r
74             # $CLIDUMP_PL # CLI, reverse the above, PERL prebuild to config file
75              
76             # 9
77             # not cli setable, this is auto detected
78             # $CLIRUNNING # CLI, flag running from command line?
79              
80             # 10
81             # not cli setable, set by subversion when in PRODUCTION, or "useless" default for debug
82             # $CLISVN_TID # CLI, subversion transaction key
83              
84             # 11
85             # not cli setable, set by subversion when in PRODUCTION, or sensible default for debug
86             # $CLISVNREPO # CLI, path to svn repository
87              
88             # CFG (currently 5 keys)
89             # 1
90             my $VAR_TAGDIRE = "PROTECTED_PARENT"; # variable looked for in config file
91             my $DEF_TAGDIRE = "/tags"; # default value if not in config
92             my $TAGpKEY = "$VAR_TAGDIRE"; # CFG key, key for this N-Tuple, must be a real path
93              
94             # 2
95             # these (missing lines)
96             # not needed for line number
97             my $LINEKEY = "ProtectLineNo"; # CFG key, line number in the config file of this tag directory
98              
99             # 3
100             my $VAR_SUBDIRE = "PROTECTED_PRJDIRS"; # variable looked for in config file
101             my $DEF_SUBDIRE = "${DEF_TAGDIRE}/*"; # default value if not in config
102             my $SUBfKEY = "$VAR_SUBDIRE"; # CFG key, subdirectories will be "globbed"
103              
104             # 4
105             my $VAR_MAKESUB = "PRJDIR_CREATORS"; # variable looked for in config file
106             my $DEF_MAKESUB = "*"; # default value if not in config
107             my $MAKEKEY = "$VAR_MAKESUB"; # CFG key, those who can create sub directories
108              
109             # 5
110             my $VAR_NAME_AF = "ARCHIVE_DIRECTORY"; # variable looked for in config file
111             my $DEF_NAME_AF = "Archive"; # default value if not in config
112             my $NAMEKEY = "$VAR_NAME_AF"; # CFG key, directory name of the archive directory(s)
113              
114             # LEAVE: HARD DEFAULTS FOR CONFIG FILE, VARIABLES SET IN THE CONFIG FILE, etc
115             ################################################################################
116              
117             ################################################################################
118             # ENTER: VARIABLES WITH FILE SCOPE all with sensible defaults
119             my $Tuple_CNT = 0; # count of keys, for building a N-Tuple key
120             my $Tuple_STR = "Config_Tuple"; # string part of a N-Tuple key
121             my $CLIBLD_DEF = 0; # 1 if --generate on command line
122             my $CLIBLDPREC = 0; # 1 if --build on command line
123             my $CLIRUNNING = 0; # 1 if we know we are running CLI
124             my $CLICONFIGF = ""; # name of config file, defaulted below - but it can be changed
125             my $CLIDUMP_PL = 0; # 1 if --dump on command line => revert precompiled config file
126             my $CLIC_DEBUG = $DEF_H_DEBUG; # N if --debug
127             my $CLIF_DEBUG = -1; # -1 => no debug level gotten from configuration file parse
128             my $CLIJUSTCFG = 0; # 1 if --parse on command line
129             my $CLI_INFILE = ""; # file to read input from (depending on command line options)
130             my $CLIOUTFILE = ""; # file to write output to (depending on command line options)
131             my $CLIPRECONF = ""; # name of precompiled config file, defaulted below, it can be changed
132             my $CLISVNREPO = ""; # path to repo -- this from subversion or dummied up
133             my $CLISVN_TID = ""; # transaction id -- this from subversion or dummied up
134             my $CLISVNLOOK = $DEF_SVNLOOK; # path to svnlook, can be changed in config file
135             my $CLISVNPATH = $DEF_SVNPATH; # path to svn, can be changed in config file
136              
137             my $PROGNAME; # program name
138             my $PROGDIRE; # program directory, usually ends with "hooks"
139              
140             # this _must_ be "our" (not "my") because of reading from pre-compiled file
141             our %cfgHofH = (); # hash of hashes - holds all configs
142             my @CommitData; # svnlook output split into an array of files/directories
143             # unless in command line mode
144              
145             # LEAVE: VARIABLES WITH FILE SCOPE all with sensible defaults
146             ################################################################################
147              
148             #{ ENTER #######################################################################
149             ############################## PROTECTED/PRIVATE ###############################
150             ############################# SUPPORT SUBROUTINES ##############################
151             ###############################################################################{
152             my $returnTF = sub { # returnTF
153             my $zeroOne = shift;
154             if ( $zeroOne ) { return 'TRUE'; }
155             return 'FALSE';
156             }; # returnTF
157              
158             my $IsUnderProtection = sub { # IsUnderProtection
159             my $pDir = shift; # protected (parent) directories
160             my $artifact = shift; # to be added
161             my $leftside; # left side of $artifact, length of $pDir
162             my $r; # returned value
163             local $_;
164              
165             if ( $pDir eq "/" )
166             {
167             # THIS IS CODED THIS WAY, HERE INSTEAD OF BELOW, IN CASE "/" IS DISALLOWED IN FUTURE (perhaps it should be?)
168             $r = 1; # this will always match everything!
169             print STDERR "IsUnderProtection: protected directories is \"/\" it always matches everything\n" if ( $CLIC_DEBUG > 7 );
170             }
171             else
172             {
173             # the protected (parent) directory is given literally like: "/tags"
174             # but can contain who knows what (even meta chars to be taken as is)
175             $_ = int( length( $pDir ) );
176             $leftside = substr( $artifact, 0, $_ );
177             if ( $CLIC_DEBUG > 7 )
178             {
179             print STDERR 'IsUnderProtection: $artifact: ' . $artifact . "\n" if ( $CLIC_DEBUG > 7 );
180             print STDERR 'IsUnderProtection: checking exact match (' . $leftside . ' eq ' . $pDir . ") ";
181             }
182             if ( $leftside eq $pDir )
183             {
184             print STDERR "YES\n" if ( $CLIC_DEBUG > 7 );
185             $r = 1;
186             }
187             else
188             {
189             print STDERR "NO\n" if ( $CLIC_DEBUG > 7 );
190             $r = 0;
191             }
192             }
193             print STDERR "IsUnderProtection: return $r\n" if ( $CLIC_DEBUG > 7 );
194             return $r;
195             }; # IsUnderProtection
196              
197             my $AddingArchiveDir = sub { # AddingArchiveDir
198             my $parent = shift; # this does NOT end with SLASH, protected "parent" directory
199             my $allsub = shift; # this does NOT end with SLASH, subdirectories (as a path containing all the "parts" of the path)
200             my $archive = shift; # name of the archive directory(s) for this configuration N-Tuple
201             my $artifact = shift; # may or may not end with SLASH - indicates files or directory
202             my $r = 0; # assume failure
203             my $sstr; # subdirectory string - used for parsing $allsub into the @suball array
204             my @suball; # hold the parts of $allsub, $allsub can be a glob
205             my $glob; # build up from the $allsub string split apart into @suball
206             my $dir = 0; # assume artifact is a file
207             local $_;
208              
209             $_ = $artifact;
210             $dir = 1 if ( m@/$@ );
211              
212             if ( $dir )
213             {
214             $sstr = $allsub; # start with the subdirectory config value
215             print STDERR "AddingArchiveDir: \$sstr=$sstr\n" if ( $CLIC_DEBUG > 7 );
216             $sstr =~ s@^${parent}@@; # remove the parent with FIRST SLASH
217             @suball = split( '/', $sstr );
218              
219             # walk the longest path to the shortest path
220             while ( @suball > 0 )
221             {
222             $glob = $parent . join( "/", @suball );
223             $glob .= "/" if ( !( $glob =~ '/$' ) );
224             $glob .= $archive . "/";
225             if ( match_glob( $glob, $artifact ) )
226             {
227             print STDERR "AddingArchiveDir: match_glob( $glob, $artifact ) = YES\n" if ( $CLIC_DEBUG > 7 );
228             $r = 1; # we have a match
229             last;
230             }
231             elsif ( $CLIC_DEBUG > 7 )
232             {
233             print STDERR "AddingArchiveDir: match_glob( $glob, $artifact ) = NO\n" if ( $CLIC_DEBUG > 7 );
234             }
235             pop @suball;
236             }
237             }
238             elsif ( $CLIC_DEBUG > 7 )
239             {
240             print STDERR "AddingArchiveDir: $artifact is a FILE\n";
241             }
242              
243             print STDERR "AddingArchiveDir: return $r\t\$artifact=$artifact\n" if ( $CLIC_DEBUG > 7 );
244             return $r;
245             }; # AddingArchiveDir
246              
247             my $AddingToArchiveDir = sub { # AddingToArchiveDir
248             my $parent = shift; # this does NOT end with SLASH, protected "parent" directory
249             my $allsub = shift; # this does NOT end with SLASH, subdirectories (as a path containing all the "parts" of the path)
250             my $archive = shift; # name of the archive directory(s) for this configuration N-Tuple
251             my $artifact = shift; # may or may not end with SLASH - indicates files or directory
252             my $r = 0; # assume failure
253             my $sstr; # subdirectory string - used for parsing $allsub into the @suball array
254             my @suball; # hold the parts of $allsub, $allsub can be a glob
255             my $glob; # build up from the $allsub string split apart into @suball
256             my $dir = 0; # assume artifact is a file
257             local $_;
258              
259             $_ = $artifact;
260             $dir = 1 if ( m@/$@ );
261              
262             if ( $dir )
263             {
264             $sstr = $allsub; # start with the subdirectory config value
265             print STDERR "AddingToArchiveDir: \$sstr=$sstr\n" if ( $CLIC_DEBUG > 5 );
266             $sstr =~ s@^${parent}@@; # remove the parent with FIRST SLASH
267             @suball = split( '/', $sstr );
268              
269             # walk the longest path to the shortest path
270             while ( @suball > 0 )
271             {
272             $glob = $parent . join( "/", @suball );
273             $glob .= "/" if ( !( $glob =~ '/$' ) );
274             $glob .= $archive . "/?*/";
275             if ( match_glob( $glob, $artifact ) )
276             {
277             print STDERR "AddingToArchiveDir: ( match_glob( $glob, $artifact ) = YES\n" if ( $CLIC_DEBUG > 5 );
278             $r = 1; # we have a match
279             last;
280             }
281             elsif ( $CLIC_DEBUG > 5 )
282             {
283             print STDERR "AddingToArchiveDir: ( match_glob( $glob, $artifact ) = NO\n" if ( $CLIC_DEBUG > 5 );
284             }
285             pop @suball;
286             }
287             }
288             elsif ( $CLIC_DEBUG > 5 )
289             {
290             print STDERR "AddingToArchiveDir: $artifact is a FILE\n";
291             }
292              
293             print STDERR "AddingToArchiveDir: return $r\t\$artifact=$artifact\n" if ( $CLIC_DEBUG > 5 );
294             return $r;
295             }; # AddingToArchiveDir
296              
297             my $AddingSubDir = sub { # AddingSubDir
298             my $parent = shift; # this does NOT end with SLASH, protected "parent" directory
299             my $allsub = shift; # this does NOT end with SLASH, subdirectory(s) (as a path containing all the "parts" of the path)
300             my $artifact = shift; # may or may not end with SLASH - indicates files or directory
301             my $r = 0; # assume failure
302             my $sstr; # subdirectory string - used for parsing $allsub into the @suball array
303             my @suball; # hold the parts of $allsub, $allsub can be a glob
304             my $glob; # build up from the $allsub string split apart into @suball
305             my $dir = 0; # assume artifact is a file
306             local $_;
307              
308             $_ = $artifact;
309             $dir = 1 if ( m@/$@ );
310              
311             if ( $dir )
312             {
313             $sstr = $allsub; # start with the subdirectory config value
314             print STDERR "AddingSubDir: \$sstr=$sstr\n" if ( $CLIC_DEBUG > 5 );
315             $sstr =~ s@^${parent}@@; # remove the parent with FIRST SLASH
316             @suball = split( '/', $sstr );
317              
318             # walk the longest path to the shortest path
319             while ( @suball > 0 )
320             {
321             $glob = $parent . join( "/", @suball );
322             $glob .= "/" if ( !( $glob =~ '/$' ) );
323             if ( match_glob( $glob, $artifact ) )
324             {
325             print STDERR "AddingSubDir: ( match_glob( $glob, $artifact ) = YES\n" if ( $CLIC_DEBUG > 5 );
326             $r = 1; # we have a match
327             last;
328             }
329             elsif ( $CLIC_DEBUG > 5 )
330             {
331             print STDERR "AddingSubDir: ( match_glob( $glob, $artifact ) = NO\n" if ( $CLIC_DEBUG > 5 );
332             }
333             pop @suball;
334             }
335             }
336             elsif ( $CLIC_DEBUG > 5 )
337             {
338             print STDERR "AddingSubDir: $artifact is a FILE\n";
339             }
340              
341             print STDERR "AddingSubDir: return $r\t\$artifact=$artifact\n" if ( $CLIC_DEBUG > 5 );
342             return $r;
343             }; # AddingSubDir
344              
345             # each artifact has to be tested to see if it is under protection
346             # which means looping through all configurations
347             my $ArtifactUnderProtectedDir = sub { # ArtifactUnderProtectedDir
348             my $artifact = shift;
349             my $parent; # protected directory
350             my $tupleKey;
351             my $returnKey = "";
352             my $isProtected = 0; # assume not protected
353              
354             for $tupleKey ( keys %{ cfgHofH } )
355             {
356             $parent = $cfgHofH{ $tupleKey }{ $TAGpKEY };
357             if ( &$IsUnderProtection( $parent, $artifact ) == 1 )
358             {
359             $returnKey = $tupleKey;
360             $isProtected = 1;
361             last;
362             }
363             }
364             return ( $isProtected, $returnKey );
365             }; # ArtifactUnderProtectedDir
366              
367             my $Authorized = sub { # Authorized
368             my $author = shift; # committer of this change
369             my $authOK = shift; # those allowed to commit
370             my $artifact = shift; # what requires authorization
371             my $msgwords = shift; # description of what is being added
372             my $isauth = 0; # assume failure
373             my @auth;
374             my $user;
375              
376             if ( $authOK eq '*' )
377             {
378             print STDERR "Authorized: allow because authorization is the '*' character\n" if ( $CLIC_DEBUG > 5 );
379             $isauth = 1;
380             }
381             elsif ( $author eq '' )
382             {
383             print STDERR "$PROGNAME: commit failed due to being unable to authenticate.\n";
384             print STDERR "$PROGNAME: the author of this commit is BLANK, apparently there is\n";
385             print STDERR "$PROGNAME: no authentication required by subversion (apache or html server).\n";
386             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
387             exit $exitFatalErr;
388             }
389             else
390             {
391             @auth = split( ",", $authOK );
392             for $user ( @auth )
393             {
394             $user =~ s@\s+@@g; # remove all spaces, user names can not have spaces in them
395             if ( $user eq $author )
396             {
397             print STDERR "Authorized: allow because author matches: $user\n" if ( $CLIC_DEBUG > 5 );
398             $isauth = 1;
399             last;
400             }
401             elsif ( $user eq '*' )
402             {
403             print STDERR "Authorized: allow because one of users is the '*' character\n" if ( $CLIC_DEBUG > 5 );
404             $isauth = 1;
405             last;
406             }
407             }
408             }
409             if ( $isauth == 0 )
410             {
411             print STDERR "$PROGNAME: failed on: $artifact\n";
412             print STDERR "$PROGNAME: authorization failed, you cannot \"$msgwords\"\n";
413             print STDERR "$PROGNAME: commiter \"$author\" does not have authorization\n";
414             }
415             return $isauth;
416             }; # Authorized
417              
418             my $FixPath = sub { # FixPath # trim trailing / chars as need be from the config file
419             local $_ = shift; # path to be "fixed"
420             my $no1stSlash = shift;
421             my $addLastSlash = shift;
422             if ( $_ ne "" and $_ ne "/" )
423             {
424             s/\/+$//; # strip any trailing "/" chars
425             if ( $_ eq "" )
426             {
427             $_ = "/";
428             }
429             elsif ( $no1stSlash )
430             {
431             s@^/@@;
432             }
433             $_ .= "/" if ( $addLastSlash );
434             }
435             return $_;
436             }; # FixPath
437              
438             my $FmtStr = sub { # FmtStr # create a format string used when generating a config file
439             my $l = 0;
440             my $r = 0;
441             my $f = "";
442             $l = length( $VAR_H_DEBUG );
443             $r = $l if ( $l > $r );
444             $l = length( $VAR_SVNLOOK );
445             $r = $l if ( $l > $r );
446             $l = length( $VAR_SVNPATH );
447             $r = $l if ( $l > $r );
448             $l = length( $VAR_TAGDIRE );
449             $r = $l if ( $l > $r );
450             $l = length( $VAR_SUBDIRE );
451             $r = $l if ( $l > $r );
452             $l = length( $VAR_MAKESUB );
453             $r = $l if ( $l > $r );
454             $l = length( $VAR_NAME_AF );
455             $r = $l if ( $l > $r );
456             $f = '%-' . $r . "s";
457             return $f;
458             }; # FmtStr
459              
460             my $PrtStr = sub { # PrtStr # string '$s' returned formatted when generating a config file
461             my $s = shift;
462             my $f = &$FmtStr();
463             my $r = sprintf( $f, $s );
464             return $r;
465             }; # PrtStr
466              
467             my $GenTupleKey = sub { # GenTupleKey
468             my $keyStr = shift;
469             my $keyCnt = shift;
470             my $key;
471             $key = $keyStr . sprintf( "_%03d", $keyCnt ); # build the key for the outer hash
472             return $key;
473             }; # GenTupleKey
474              
475             my $GetMax = sub {
476             my $l = shift; # left
477             my $r = shift; # right
478             return $l if ( $l > $r );
479             return $r;
480             }; # GetMax
481              
482             # THIS IS CALLED DURING CONFIGUATION PARSE - NOT OTHERWISE
483             # the subdirectory given, if not the empty string, must be
484             # a subdirectory of the associated tag directory (the one
485             # to protect). E.g:
486             # if "/tags" is the directory to be protected then
487             # then "/tags/" is acceptable, but
488             # "/foobar/" is NOT
489             # The subdirectory specification must truly be a subdirectory
490             # of the associated directory to be protected.
491             my $ValidateSubDirOrDie = sub { # ValidateSubDirOrDie
492             my $pDire = shift; # directory name of tag to protect
493             my $globc = shift; # the subdirectory "glob" string/path
494             my $lline = shift; # current config file line
495              
496             my $leftP; # left part
497             my $right; # right part
498             local $_;
499              
500             # a BLANK regex means that the tag directory does not allow _any_
501             # project names, hey that's ok! if so there is no need to test
502             if ( $globc ne "" )
503             {
504             $leftP = $globc;
505             $leftP =~ s@(${pDire})(.+)@$1@;
506             $right = $globc;
507             $right =~ s@(${pDire})(.+)@$2@;
508             if ( $pDire ne $leftP )
509             {
510             print STDERR "$PROGNAME: configuration file:\n";
511             print STDERR " \"$CLICONFIGF\"\n";
512             print STDERR "$PROGNAME: is misconfigured at approximately line $lline.\n";
513             print STDERR "$PROGNAME: the variable=value pair:\n";
514             print STDERR " $TAGpKEY=\"$pDire\"\n";
515             print STDERR "$PROGNAME: the variable=value pair:\n";
516             print STDERR " $SUBfKEY=\"$globc\"\n";
517             print STDERR "$PROGNAME: are out of synchronization.\n";
518             print STDERR "$PROGNAME: a correct variable=value pair would be, for example:\n";
519             print STDERR " $SUBfKEY=\"$pDire/*\"\n";
520             print STDERR "$PROGNAME: the $TAGpKEY value (path) MUST be the\n";
521             print STDERR "$PROGNAME: the first path in $SUBfKEY (it must start with that path)\n";
522             print STDERR "$PROGNAME: unless $SUBfKEY is the empty string (path).\n";
523             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
524             exit $exitFatalErr;
525             }
526              
527             # clean up the subdirectory "glob" (or it could be a literal path, we still clean it up)
528             $_ = $right; # the "backslash" is not allowed, it can only lead to problems!
529             print STDERR "ValidateSubDirOrDie: initial \$_=$_\n" if ( $CLIC_DEBUG > 5 );
530             s@\\@@g; # remove all backslash chars - not allowed
531             print STDERR "ValidateSubDirOrDie: rm backslash \$_=$_\n" if ( $CLIC_DEBUG > 5 );
532             s@/+@/@g; # change multiple //* chars into just one /
533             print STDERR "ValidateSubDirOrDie: rm single sep \$_=$_\n" if ( $CLIC_DEBUG > 5 );
534             while ( m@/\.\//@ ) # /../ changed to / in a loop
535             {
536             s@/\.\./@/@g; # remove it
537             s@/+@/@g; # don't see how this could happen, but safety first
538             print STDERR "ValidateSubDirOrDie: in clean loop \$_=$_\n" if ( $CLIC_DEBUG > 5 );
539             }
540             print STDERR "ValidateSubDirOrDie: done \$_=$_\n" if ( $CLIC_DEBUG > 5 );
541             $globc = $leftP . $_; # the "backslash" is not allowed, it can only lead to problems!
542             }
543             print STDERR "ValidateSubDirOrDie: return $globc\n" if ( $CLIC_DEBUG > 5 );
544             return $globc; # possible modified (cleaned up)
545             }; # ValidateSubDirOrDie
546              
547             my $LoadCFGTuple = sub { # LoadCFGTuple # put an N-Tuple into the Hash of hashes
548             # this is what this subroutine "loads", i.e. the 1st is given and
549             # we default the next 3 from the 3 above if they are not there
550             my $inHashRef = shift; # a reference to the "inner" hash
551             my $key; # used to build the key from the string and the number
552              
553             # the outer most hash, named %cfgHofH, will load (copy) the above hash (not the reference)
554             # along with the information needed to construct the key needed to push the above hash into
555             # it. Got that?
556              
557             # check that incoming (inner) hash has a directory in it to be protected
558             if ( ( !exists $inHashRef->{ $NAMEKEY } )
559             || ( !exists $inHashRef->{ $MAKEKEY } )
560             || ( !exists $inHashRef->{ $TAGpKEY } )
561             || ( !exists $inHashRef->{ $LINEKEY } )
562             || ( !exists $inHashRef->{ $SUBfKEY } ) )
563             {
564             # give it bogus value if it has no value
565             $inHashRef->{ $LINEKEY } = 0 if ( !exists $inHashRef->{ $LINEKEY } );
566              
567             print STDERR "$PROGNAME: See configuration file: $CLICONFIGF\n";
568             print STDERR "$PROGNAME: The value of $VAR_TAGDIRE does not exist for the configuration set.\n"
569             if ( !exists $inHashRef->{ $TAGpKEY } );
570             print STDERR "$PROGNAME: The value of $VAR_SUBDIRE does not exist for the configuration set!\n"
571             if ( !exists $inHashRef->{ $SUBfKEY } );
572             print STDERR "$PROGNAME: The value of $VAR_NAME_AF does not exist for the configuration set!\n"
573             if ( !exists $inHashRef->{ $NAMEKEY } );
574             print STDERR "$PROGNAME: The value of $VAR_MAKESUB does not exist for the configuration set!\n"
575             if ( !exists $inHashRef->{ $MAKEKEY } );
576             print STDERR "$PROGNAME: Around line number: $inHashRef->{$LINEKEY}\n";
577             print STDERR "$PROGNAME: Failure in subroutine LoadCFGTuple.\n";
578             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
579             exit $exitFatalErr;
580             }
581             elsif ( $inHashRef->{ $TAGpKEY } eq "" )
582             {
583             # give it bogus value if it has no value
584             $inHashRef->{ $LINEKEY } = 0 if ( !exists $inHashRef->{ $LINEKEY } );
585             print STDERR "$PROGNAME: See configuration file: $CLICONFIGF\n";
586             print STDERR "$PROGNAME: The value of $VAR_TAGDIRE is blank.\n";
587             print STDERR "$PROGNAME: Around line number: $inHashRef->{$LINEKEY}\n";
588             print STDERR "$PROGNAME: Failure in subroutine LoadCFGTuple.\n";
589             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
590             exit $exitFatalErr;
591             }
592              
593             # get new key for outer hash
594             $key = &$GenTupleKey( $Tuple_STR, $Tuple_CNT );
595             $Tuple_CNT++;
596              
597             # insist that this new configuration plays by the rules
598             $inHashRef->{ $SUBfKEY } = &$ValidateSubDirOrDie( $inHashRef->{ $TAGpKEY }, $inHashRef->{ $SUBfKEY }, $inHashRef->{ $LINEKEY } );
599              
600             $cfgHofH{ $key } = { %$inHashRef }; # this allocates (copies) inner hash
601              
602             return; # return no value
603             }; # LoadCFGTuple # put an N-Tuple into the Hash of hashes
604              
605             my $PrintDefaultConfigOptionallyExit = sub { # PrintDefaultConfigOptionallyExit
606             my $print_exit = shift;
607             my $filename = shift;
608             my $ohandle = shift;
609             my $output;
610             my $q = '"';
611             my $str;
612              
613             if ( $filename eq "" )
614             {
615             $output = *STDOUT;
616             $filename = "STDOUT";
617             }
618             else
619             {
620             $output = $ohandle; # caller already opened it
621             }
622             if ( $CLIC_DEBUG > 0 )
623             {
624             if ( $print_exit )
625             {
626             print STDERR "PrintDefaultConfigOptionallyExit: output default header.\n";
627             }
628             else
629             {
630             print STDERR "PrintDefaultConfigOptionallyExit: output default configuration file to: $filename\n";
631             }
632             }
633             print $output "#\n";
634             print $output "# The parsing script will build an 'N-Tuple' from each\n";
635             print $output "# ${VAR_TAGDIRE} variable.\n";
636             print $output "#\n";
637             print $output "# Recognized variable/value pairs are:\n";
638             print $output "# These are for debugging and subversion\n";
639             print $output "# ${VAR_H_DEBUG}\t\t= N\n";
640             print $output "# ${VAR_SVNPATH}\t\t= path to svn\n";
641             print $output "# ${VAR_SVNLOOK}\t\t= path to svnlook\n";
642             print $output "# These make up an N-Tuple\n";
643             print $output "# ${VAR_TAGDIRE}\t\t= /\n";
644             print $output "# e.g.: ${VAR_SUBDIRE}\t= //*\n";
645             print $output "# or e.g.: ${VAR_SUBDIRE}\t= //*/*\n";
646             print $output "# ${VAR_MAKESUB}\t= '*' or ', , ...'\n";
647             print $output "# ${VAR_NAME_AF}\t= \n";
648             print $output "\n";
649             print $output "### These should be first\n";
650             $str = &$PrtStr( $VAR_H_DEBUG );
651             print $output $str . " = $DEF_H_DEBUG\n";
652             $str = &$PrtStr( $VAR_SVNPATH );
653             print $output $str . " = ${q}$DEF_SVNPATH${q}\n";
654             $str = &$PrtStr( $VAR_SVNLOOK );
655             print $output $str . " = ${q}$DEF_SVNLOOK${q}\n";
656             print $output "\n";
657             print $output "### These comprise an N-Tuple, can be repeated as many times as wanted,\n";
658             print $output "### but each ${VAR_TAGDIRE} value must be unique. It is not allowed to\n";
659             print $output "### try to configure the same directory twice (or more)!\n";
660              
661             if ( $print_exit == 1 )
662             {
663             $str = &$PrtStr( $VAR_TAGDIRE );
664             print $output $str . " = ${q}$DEF_TAGDIRE${q}\n";
665             $str = &$PrtStr( $VAR_SUBDIRE );
666             print $output $str . " = ${q}$DEF_SUBDIRE${q}\n";
667             $str = &$PrtStr( $VAR_MAKESUB );
668             print $output $str . " = ${q}$DEF_MAKESUB${q}\n";
669             $str = &$PrtStr( $VAR_NAME_AF );
670             print $output $str . " = ${q}$DEF_NAME_AF${q}\n";
671             print STDERR "PrintDefaultConfigOptionallyExit: exit successful after generate default config file.\n" if ( $CLIC_DEBUG > 0 );
672             exit $exitSuccess; # only exit if doing the whole thing
673             }
674             }; # PrintDefaultConfigOptionallyExit
675              
676             my $PrintUsageAndExit = sub { # PrintUsageAndExit # output and exit
677             my $look = $CLISVNLOOK;
678             $look =~ s@.*/@@;
679             $look =~ s@.*\\@@;
680              
681             my $svn = $CLISVNPATH;
682             $svn =~ s@.*/@@;
683             $svn =~ s@.*\\@@;
684              
685             print STDOUT "\n";
686             print STDOUT "usage: $PROGNAME repo-name transaction-id - Normal usage under Subversion.\n";
687             print STDOUT "OR: $PROGNAME --help - Get this printout.\n";
688             print STDOUT "OR: $PROGNAME [--debug=N] [options] - configuration testing and debugging.\n";
689             print STDOUT "\n";
690             print STDOUT " THIS SCRIPT IS A HOOK FOR SUBVERSION AND IS NOT RUN FROM THE COMMAND\n";
691             print STDOUT " LINE DURING PRODUCTION USAGE.\n";
692             print STDOUT "\n";
693             print STDOUT " The required arguments, repo-name and transaction-id, are\n";
694             print STDOUT " provided by subversion. This subversion hook uses:\n";
695             print STDOUT " '$look'\n";
696             print STDOUT ' the path of which can be configured and defaults to: ' . "'" . $CLISVNLOOK . "'\n";
697             print STDOUT " and '$svn'\n";
698             print STDOUT ' the path of which can be configured and defaults to: ' . "'" . $CLISVNPATH . "'\n";
699             print STDOUT "\n";
700             print STDOUT " It uses the configuration file:\n";
701             print STDOUT " $CLICONFIGF\n";
702             print STDOUT " If it exists, this \"precompiled\" file will take precedence:\n";
703             print STDOUT " $CLIPRECONF\n";
704             print STDOUT " and the configuration file will not be read.\n";
705             print STDOUT "\n";
706             print STDOUT " When invoked from the command line it will accept these additional\n";
707             print STDOUT " options, there is no way you can give these in PRODUCTION while running\n";
708             print STDOUT " under subversion.\n";
709             print STDOUT " --help | -h Show usage information and exit.\n";
710             print STDOUT "\n";
711             print STDOUT " --debug[=n] | -d[n] Increment or set the debug value. If given this\n";
712             print STDOUT " command line option should be first.\n";
713             print STDOUT "\n";
714             print STDOUT " --generate | -g Generate a default configuration file with\n";
715             print STDOUT " comments and write it to standard output.\n";
716             print STDOUT "\n";
717             print STDOUT " --parse | -p Parse the configuration file then exit.\n";
718             print STDOUT " Errors found in the configuration will be printed\n";
719             print STDOUT " to standard error. If there are no errors you will\n";
720             print STDOUT " get no output unless debug is greater than zero(0).\n";
721             print STDOUT "\n";
722             print STDOUT " --build | -b Build a \"precompiled\" configuration file with\n";
723             print STDOUT " comments from the configuration file and write\n";
724             print STDOUT " it to standard output. This speeds up reading\n";
725             print STDOUT " the configuration in PRODUCTION but is only needed\n";
726             print STDOUT " by sites with a large large number of configurations,\n";
727             print STDOUT " say 20 or more, your mileage may vary - and only if\n";
728             print STDOUT " the server is old and slow. If a precompiled\n";
729             print STDOUT " configuration exists it will be read and the regular\n";
730             print STDOUT " regular configuration file will be ignored.\n";
731             print STDOUT "\n";
732             print STDOUT " --revert | -r Opposite of, --build, write to standard output a\n";
733             print STDOUT " configuration file from a previously built\n";
734             print STDOUT " \"precompiled\" configuration file.\n";
735             print STDOUT "\n";
736             print STDOUT " --input=file | -ifile Input from \"file\", or output to \"file\", these options\n";
737             print STDOUT " --output=file | -ofile are used to name an alternate configuration file or an\n";
738             print STDOUT " alternate pre-compiled configuration file.\n";
739             print STDOUT "\n";
740             print STDOUT " --version | -v Output the version and exit.\n";
741             print STDOUT "\n";
742             print STDOUT "\n";
743             print STDOUT "NOTE: a typical command line usage for debugging purposes would look\n";
744             print STDOUT " like this\n";
745             print STDOUT " ./$PROGNAME --debug=N [options] < /dev/null\n";
746             print STDOUT "\n";
747             print STDOUT "$PROGNAME: " . $VERSION . "\n";
748             print STDOUT "\n";
749             exit $exitSuccess;
750             }; # PrintUsageAndExit
751              
752             my $PrintVersionAndExit = sub { # PrintVersionAndExit
753             print STDOUT $VERSION . "\n";
754             exit $exitSuccess;
755             }; # PrintVersionAndExit
756              
757             # cannot determine what the commit does - one or more artifacts cannot be correctly parsed
758             # this is called when everything else fails, increase debug for more information
759             my $SayImpossible = sub { # SayImpossible
760             print STDERR "$PROGNAME: commit failed, re: UNKNOWN!\n";
761             print STDERR "$PROGNAME: it appears this commit does not modify, add, or delete anything!\n";
762             return 0;
763             }; # SayImpossible
764              
765             my $SayNoDelete = sub { # SayNoDelete
766             my $what = shift;
767             print STDERR "$PROGNAME: commit failed, delete of protected directories is not allowed!\n";
768             print STDERR "$PROGNAME: commit failed on: $what\n";
769             return 0;
770             }; # SayNoDelete
771              
772             my $SvnGetAuthor = sub { # SvnGetAuthor
773             my @tapCmd; # array to hold output
774             my $svnErrors; # STDERR of command SVNLOOK - any errors
775             my $svnAuthor; # STDOUT of command SVNLOOK - creator
776             my $svnExit; # exit value of command SVNLOOK
777             my $what = "author";
778              
779             @tapCmd = ( $CLISVNLOOK, "--transaction", $CLISVN_TID, $what, $CLISVNREPO, );
780             print STDERR 'SvnGetAuthor: tap' . " " . join( " ", @tapCmd ) . "\n" if ( $CLIC_DEBUG > 5 );
781             ( $svnAuthor, $svnErrors, $svnExit ) = tap @tapCmd;
782             chop( $svnAuthor );
783             if ( $CLIC_DEBUG > 5 )
784             {
785             if ( $CLIC_DEBUG > 5 )
786             {
787             print STDERR "SvnGetAuthor: \$svnExit= >>$svnExit<<\n";
788             print STDERR "SvnGetAuthor: \$svnErrors=>>$svnErrors<<\n";
789             }
790             print STDERR "SvnGetAuthor: \$svnAuthor=>>$svnAuthor<<\n";
791             }
792             if ( $svnExit )
793             {
794             print STDERR "$PROGNAME: \"$CLISVNLOOK\" failed to get \"$what\" (exit=$svnExit), re: $svnErrors\n";
795             print STDERR "$PROGNAME: command: >>tap " . join( " ", @tapCmd ) . "\n";
796             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
797             exit $exitFatalErr;
798             }
799             print STDERR "SvnGetAuthor: return \"$svnAuthor\"\n" if ( $CLIC_DEBUG > 5 );
800             return $svnAuthor;
801             }; # SvnGetAuthor
802              
803             my $SvnGetCommit = sub { # SvnGetCommit
804             my @tapCmd; # array to hold output
805             my $svnErrors; # STDERR of command SVNLOOK - any errors
806             my $svnOutput; # STDOUT of command SVNLOOK - commit data
807             my $svnExit; # exit value of command SVNLOOK
808             my $itmp = 0; # index into @Changed
809             local $_; # regex'ing
810             my $what = "changed";
811              
812             @tapCmd = ( $CLISVNLOOK, "--transaction", $CLISVN_TID, $what, $CLISVNREPO, );
813             print STDERR 'SvnGetCommit: tap' . " " . join( " ", @tapCmd ) . "\n" if ( $CLIC_DEBUG > 5 );
814             ( $svnOutput, $svnErrors, $svnExit ) = tap @tapCmd;
815             if ( $CLIC_DEBUG > 5 )
816             {
817             print STDERR "SvnGetCommit: \$svnExit= >>$svnExit<<\n";
818             print STDERR "SvnGetCommit: \$svnErrors=>>$svnErrors<<\n";
819             print STDERR "SvnGetCommit: \$svnOutput=>>\n$svnOutput<<\n";
820             }
821             if ( $svnExit )
822             {
823             print STDERR "$PROGNAME: \"$CLISVNLOOK\" failed to get \"$what\" (exit=$svnExit), re: $svnErrors\n";
824             print STDERR "$PROGNAME: command: >>tap " . join( " ", @tapCmd ) . "\n";
825             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
826             exit $exitFatalErr;
827             }
828             @CommitData = split( "\n", $svnOutput );
829             if ( $CLIC_DEBUG > 5 )
830             {
831             foreach $_ ( @CommitData )
832             {
833             print STDERR "SvnGetCommit BEFORE: CommitData>>$_\n";
834             }
835             }
836             @CommitData = sort @CommitData; # needed?
837             if ( $CLIC_DEBUG > 5 )
838             {
839             foreach $_ ( @CommitData )
840             {
841             print STDERR "SvnGetCommit AFTER: CommitData>>$_\n";
842             }
843             }
844             return @CommitData # $svnOutput split into an array of files/directories;
845             }; # SvnGetCommit
846              
847             my $SvnGetList = sub { # SvnGetList
848             my $path = shift; # path to list
849             my $full = shift; # protocol, repo, path
850             my @tapCmd; # array to hold output
851             my $svnErrors; # STDERR of command SVNPATH list - any errors
852             my $svnList; # STDOUT of command SVNPATH list - data
853             my $svnExit; # exit value of command SVNPATH
854             my @List; # $svnList split into an array of files/directories
855             local $_; # regex'ing
856              
857             # build the full protocol / repository / path string
858             $full = "file://" . $CLISVNREPO . $path;
859              
860             @tapCmd = ( $CLISVNPATH, "list", $full );
861             print STDERR 'SvnGetList: tap' . " " . join( " ", @tapCmd ) . "\n" if ( $CLIC_DEBUG > 5 );
862             ( $svnList, $svnErrors, $svnExit ) = tap @tapCmd;
863             if ( $CLIC_DEBUG > 5 ) # "2", not "0" because the array is printed below
864             {
865             print STDERR "SvnGetList: \$svnExit= >>$svnExit<<\n";
866             print STDERR "SvnGetList: \$svnErrors=>>$svnErrors<<\n";
867             print STDERR "SvnGetList: \$svnList= >>$svnList<<\n";
868             }
869             if ( $svnExit )
870             {
871             # is this a true error or simply that the path listed does not exist?
872             $_ = $svnErrors;
873             if ( !m/non-existent in that revision/ )
874             {
875             print STDERR "$PROGNAME: \"$CLISVNPATH\" failed to list \"$path\" (exit=$svnExit), re: $svnErrors";
876             print STDERR "$PROGNAME: command: >>tap " . join( " ", @tapCmd ) . "\n";
877             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
878             exit $exitFatalErr;
879             }
880             }
881             @List = split( "\n", $svnList );
882             print STDERR "SvnGetList: LEAVE: svn list of $full\n" if ( $CLIC_DEBUG > 5 );
883             return @List; # $svnList split into an array of files/directories
884             }; # SvnGetList
885              
886             my $TagIsInArchive = sub { # TagIsInArchive
887             my $aTag = shift; # new artifact, tag that is being created
888             my $arch = shift; # name of archive directory
889             my @list;
890             my $rvalue = 0; # returned value, assume not in archive
891             my $head;
892             my $tail;
893             my $path;
894              
895             $head = $aTag;
896             $head =~ s@/$@@;
897             $tail = $head;
898              
899             $head =~ s@(.*)/(.*)@$1@;
900             $tail =~ s@(.*)/(.*)@$2@;
901              
902             $path = $head . "/" . $arch . "/" . $tail;
903              
904             @list = &$SvnGetList( $path );
905             if ( ( scalar @list ) > 0 ) { $rvalue = 1; }
906             return $rvalue;
907             }; # TagIsInArchive
908              
909             my $TheAddIsAllowed = sub { # TheAddIsAllowed
910             my $author = shift; # committer of this change
911             my $ADDref = shift; # array reference to the "array of stuff to add"
912             my $aDire; # archive directory name
913             my $aMake; # users that can create new project directories
914             my $artifact; # user wants to add
915             my $commit = 1; # assume OK to commit
916             my $arrayRef; # pointer to the inner array
917             my $pDire; # protected (parent) directory
918             my $sDire; # subdirectory under $pDire, can be BLANK
919             my $tupKey; # N-Tuple key used to find data in $CFGref
920             my $glob; # a "glob" pattern to check for matches
921              
922             if ( $CLIC_DEBUG > 7 )
923             {
924             print STDERR "TheAddIsAllowed: ENTER: listing array of N-Tuple keys and the artifact to test with the key\n";
925             for $arrayRef ( @{ $ADDref } )
926             {
927             ( $tupKey, $artifact ) = ( @{ $arrayRef } );
928             print STDERR "TheAddIsAllowed: with Configuration key=$tupKey test artifact=$artifact\n";
929             }
930             print STDERR "TheAddIsAllowed: LEAVE: listing array of N-Tuple keys and the artifact to test with the key\n";
931             }
932             for $arrayRef ( @{ $ADDref } ) # we know all these are protected and to be added
933             {
934             ( $tupKey, $artifact ) = ( @{ $arrayRef } );
935             $pDire = $cfgHofH{ $tupKey }{ $TAGpKEY }; # protected directory
936             $aMake = $cfgHofH{ $tupKey }{ $MAKEKEY }; # authorised to make subdirectories
937             $aDire = $cfgHofH{ $tupKey }{ $NAMEKEY }; # archive directory name
938             $sDire = $cfgHofH{ $tupKey }{ $SUBfKEY }; # subdirectory name - glob is allowed here
939              
940             if ( $CLIC_DEBUG > 6 )
941             {
942             print STDERR 'TheAddIsAllowed: N-TupleKey: $tupKey' . "\t= $tupKey\n";
943             print STDERR 'TheAddIsAllowed: Commited: $artifact' . "\t= $artifact\n";
944             print STDERR 'TheAddIsAllowed: Parent Dir: $pDire' . "\t\t= $pDire\n";
945             print STDERR 'TheAddIsAllowed: Sub "glob" Dir: $sDire' . "\t\t= $sDire\n";
946             print STDERR 'TheAddIsAllowed: Archive Dir: $aDire' . "\t\t= $aDire\n";
947             print STDERR 'TheAddIsAllowed: Authorized: $aMake' . "\t\t= $aMake\n";
948             }
949              
950             # IN ORDER TO ENSURE CORRECTLY FIGURING OUT WHAT THE USER IS DOING TEST IN THIS ORDER:
951             # 1) attempting to add to the Archive directory?
952             # 2) attempting to add to a tag?
953             # 3) attempting to add _the_ Archive directory itself?
954             # 4) attempting to add a project directory?
955             # 5) attempting to add the protected directory _itself_ ?
956             # 6) attempting to add a directory? <= this should never happen, above takes care of it
957             # 7) attempting to add a file that is not part of a tag?
958              
959             # 1) attempting to add to the Archive?
960             print STDERR "TheAddIsAllowed: TESTING -> ATTEMPT TO ADD TO AN ARCHIVE DIRECTORY? $artifact\n" if ( $CLIC_DEBUG > 4 );
961             if ( $sDire eq "" and $aDire eq "" ) { $glob = ""; } # no subdirectory, no archive directory name
962             elsif ( $sDire eq "" and $aDire ne "" ) { $glob = $pDire . '/' . $aDire . "/?*"; } # no subdirectory, yes archive directory name
963             elsif ( $sDire ne "" and $aDire eq "" ) { $glob = ""; } # yes subdirectory, not arhive directory name
964             elsif ( $sDire ne "" and $aDire ne "" ) { $glob = $sDire . '/' . $aDire . "/?*"; } # yes subdirectory, yes archive directory name
965             if ( $glob ne "" )
966             {
967             print STDERR
968             'TheAddIsAllowed: if (&$AddingArchiveDir(' . "$pDire, $sDire, $aDire, $artifact) is the test to see if adding to an archive directory\n"
969             if ( $CLIC_DEBUG > 6 );
970             if ( &$AddingArchiveDir( $pDire, $sDire, $aDire, $artifact ) == 1 )
971             {
972             print STDERR 'TheAddIsAllowed: $artifact=' . "$artifact IS UNDER AN ARCHIVE DIRECTORY\n" if ( $CLIC_DEBUG > 4 );
973             print STDERR "$PROGNAME: you can only move existing tags to an archive directory\n";
974             print STDERR "$PROGNAME: commit failed, you cannot add anything to an existing archive directory!\n";
975             print STDERR "$PROGNAME: commit failed on: $artifact\n";
976             $commit = 0;
977             last;
978             }
979             }
980             print STDERR "TheAddIsAllowed: KEEP TESTING -> NOT ADDING TO AN ARCHIVE DIRECTORY WITH: $artifact\n" if ( $CLIC_DEBUG > 5 );
981              
982             # 2) attempting to add to a tag?
983             print STDERR "TheAddIsAllowed: TESTING -> ATTEMPT TO ADD A TAG? $artifact\n" if ( $CLIC_DEBUG > 4 );
984             if ( $sDire eq "" ) { $glob = $pDire . "/?*/"; } # no subdirectory
985             else { $glob = $sDire . "/?*/"; }
986             print STDERR "TheAddIsAllowed: if ( match_glob( $glob, $artifact ) ) is the test to see if adding a new tag\n" if ( $CLIC_DEBUG > 6 );
987             if ( match_glob( $glob, $artifact ) )
988             {
989             print STDERR
990             'TheAddIsAllowed: if ( &$TagIsInArchive(' . "$artifact, $aDire) == 1 ) is the test to see if adding to an archive directory\n"
991             if ( $CLIC_DEBUG > 6 );
992             if ( &$TagIsInArchive( $artifact, $aDire ) == 1 )
993             {
994             print STDERR
995             "TheAddIsAllowed: stop TESTING -> CANNOT ADD tag that already exists in the archive directory: artifact=$artifact\n"
996             if ( $CLIC_DEBUG > 4 );
997             print STDERR "$PROGNAME: you cannot add this tag because it already exists in an archive directory!\n";
998             print STDERR "$PROGNAME: commit failed on: $artifact\n";
999             $commit = 0;
1000             last;
1001             }
1002              
1003             # no problem - we are simply adding a tag
1004             print STDERR "TheAddIsAllowed: stop TESTING -> THIS IS OK AND IS A NEW TAG artifact=$artifact\n" if ( $CLIC_DEBUG > 4 );
1005             }
1006             else
1007             {
1008             print STDERR "TheAddIsAllowed: KEEP TESTING -> THIS IS NOT A NEW TAG $artifact\n" if ( $CLIC_DEBUG > 5 );
1009              
1010             # 3) attempting to add the _Archive directory_ itself?
1011             print STDERR "TheAddIsAllowed: TESTING -> ATTEMPT TO ADD THE ARCHIVE DIRECTORY ITSELF? artifact=$artifact\n" if ( $CLIC_DEBUG > 4 );
1012             if ( $aDire ne "" )
1013             {
1014             print STDERR
1015             'TheAddIsAllowed: if ( &$AddingArchiveDir(' . "$pDire, $sDire, $aDire, $artifact) == 1 ) is the test to see if adding an archive directory\n"
1016             if ( $CLIC_DEBUG > 6 );
1017             if ( &$AddingArchiveDir( $pDire, $sDire, $aDire, $artifact ) == 1 )
1018             {
1019             print STDERR 'TheAddIsAllowed: $artifact=' . "$artifact IS AN ARCHIVE DIRECTORY\n" if ( $CLIC_DEBUG > 4 );
1020             $commit = &$Authorized( $author, $aMake, $artifact, 'add an archive directory' );
1021             last if ( $commit == 0 );
1022             next;
1023             }
1024             }
1025             print STDERR "TheAddIsAllowed: KEEP TESTING -> NOT ADDING THE ARCHIVE DIRECTORY ITSELF WITH artifact=$artifact\n" if ( $CLIC_DEBUG > 5 );
1026              
1027             # 4) attempting to add a project directory?
1028             print STDERR "TheAddIsAllowed: TESTING -> ATTEMPT TO ADD A SUB DIRECTORY? artifact=$artifact\n" if ( $CLIC_DEBUG > 4 );
1029             print STDERR
1030             'TheAddIsAllowed: if ( &$AddingSubDir( ' . "$pDire, $sDire, $artifact) == 1 ) is the test to see if adding a sub directory\n"
1031             if ( $CLIC_DEBUG > 6 );
1032             if ( &$AddingSubDir( $pDire, $sDire, $artifact ) == 1 )
1033             {
1034             print STDERR
1035             "TheAddIsAllowed: stop TESTING -> THIS IS A NEW PROJECT SUB DIRECTORY, calling Authorized artifact=$artifact\n"
1036             if ( $CLIC_DEBUG > 4 );
1037             $commit = &$Authorized( $author, $aMake, $artifact, 'add a project (or sub) directory' );
1038             last if ( $commit == 0 );
1039             next;
1040             }
1041             print STDERR "TheAddIsAllowed: KEEP TESTING -> NOT ATTEMPT TO ADD A SUB DIRECTORY WITH artifact=$artifact\n" if ( $CLIC_DEBUG > 5 );
1042              
1043             # 5) attempting to add the protected directory _itself_ ?
1044             print STDERR "TheAddIsAllowed: TESTING -> ATTEMPT TO ADD THE PROTECTED DIRECTORY ITSELF? artifact=$artifact\n" if ( $CLIC_DEBUG > 4 );
1045             print STDERR 'TheAddIsAllowed: if ( ' . "\"$pDire/\" eq $artifact ) is the test to see if adding a sub directory\n" if ( $CLIC_DEBUG > 6 );
1046             if ( "$pDire/" eq $artifact ) # trying to add the parent directory itself
1047             {
1048             print STDERR
1049             "TheAddIsAllowed: stop TESTING -> THIS IS A THE PROTECTED DIRECTORY, calling Authorized artifact=$artifact\n"
1050             if ( $CLIC_DEBUG > 4 );
1051             $commit = &$Authorized( $author, $aMake, $artifact, 'create the protected directory' );
1052             last if ( $commit == 0 );
1053             next;
1054             }
1055             else # attempting to add a file instead of a tag
1056             {
1057             print STDERR
1058             "TheAddIsAllowed: stop TESTING -> CANNOT ADD ARBITRARY DIRECTORY OR FILE TO A PROTECTED DIRECTORY artifact=$artifact\n"
1059             if ( $CLIC_DEBUG > 4 );
1060             print STDERR "$PROGNAME: you can only only add new tags\n";
1061             if ( $artifact =~ m@/$@ )
1062             {
1063             # 6) attempting to add a directory? <= this should never happen, above takes care of it
1064             print STDERR "$PROGNAME: commit failed, you cannot add a directory to a protected directory!\n";
1065             }
1066             else
1067             {
1068             # 7) attempting to add a file that is not part of a tag?
1069             print STDERR "$PROGNAME: commit failed, you cannot add a file to a protected directory!\n";
1070             }
1071             print STDERR "$PROGNAME: commit failed on: $artifact\n";
1072             $commit = 0;
1073             last;
1074             }
1075             }
1076             }
1077             print STDERR "TheAddIsAllowed: return " . &$returnTF( $commit ) . "\n" if ( $CLIC_DEBUG > 3 );
1078             return $commit;
1079             }; # TheAddIsAllowed
1080              
1081             my $TheMoveIsAllowed = sub { # TheMoveIsAllowed
1082             my $what = shift; # committer of this change
1083             my $author = shift; # committer of this change
1084             my $ADDref = shift; # reference to the array of stuff to add
1085             my $DELref = shift; # reference to the array of stuff to delete
1086             my $addKey; # N-Tuple key from the "add" array
1087             my $artifact; # path from the "add" array
1088             my $artifactNoArch; # path from the "add" array with next to last directory with "Arhive name" removed
1089             my $addRef; # reference for add array
1090             my $archive; # name of an archive directory for this N-Tuple
1091             my $check1st; # path to check before putting a path into @justAdditions
1092             my $commit = 1; # assume OK to commit
1093             my $count; # of elements in @justAdditions
1094             my $delNdx; # found the thing in the del array this is in the add array?
1095             my $delKey; # N-Tuple key from the "del" array
1096             my $delPath; # path from the "del" array
1097             my $delRef; # reference for the del array
1098             my $justAdd; # true if the path in the add array has no matching path in the del array
1099             my $ok2add; # ok to put a path into @justAdditions because it is not there already
1100             my $ref; # reference into @justAdditions
1101             my $stmp; # tmp string
1102             my @justAdditions; # array of additions found that do not have matching delete/move
1103             my @tmp; # used to load the @justAdditions array with data
1104              
1105             # walk each of the artifacts to be added
1106             for $addRef ( @{ $ADDref } )
1107             {
1108             ( $addKey, $artifact ) = ( @{ $addRef } );
1109             print STDERR "TheMoveIsAllowed: add cfgkey is: $addKey, add artifact is: $artifact\n" if ( $CLIC_DEBUG > 5 );
1110             $archive = $cfgHofH{ $addKey }{ $NAMEKEY };
1111             if ( $archive eq "" )
1112             {
1113             print STDERR
1114             "TheMoveIsAllowed: KEEP TESTING -> no archive directory so this must be a just add condition with artifact: $artifact\n"
1115             if ( $CLIC_DEBUG > 4 );
1116             $justAdd = 1;
1117             }
1118             else
1119             {
1120             $justAdd = 0;
1121             print STDERR
1122             "TheMoveIsAllowed: if ( $artifact " . '=~ m@^(.+)/' . "${archive}" . '/([^/]+/)$@' . " ) is the test to see if adding to an archive directory\n"
1123             if ( $CLIC_DEBUG > 6 );
1124             if ( $artifact =~ m@^(.+)/${archive}/([^/]+/)$@ ) # does path have "archive directory name" in it as next to last directory
1125             {
1126             $artifactNoArch = "$1/$2";
1127             print STDERR
1128             'TheMoveIsAllowed: KEEP TESTING -> does the archive artifact to add have a corresponding tag being deleted, artifact: ' .
1129             "$artifact, corresponding: $artifactNoArch\n"
1130             if ( $CLIC_DEBUG > 4 );
1131             $delNdx = -1; # impossible value
1132             $count = 0;
1133              
1134             # walk each of the artifacts to be deleted and look to see if the thing added is related to the
1135             # artifact being deleted by an archive directory name
1136             for $delRef ( @{ $DELref } )
1137             {
1138             ( $delKey, $delPath ) = ( @{ $delRef } );
1139             print STDERR "TheMoveIsAllowed: delete cfgkey is: $delKey, add artifact is: $delPath\n" if ( $CLIC_DEBUG > 5 );
1140             if ( $addKey eq $delKey and $artifactNoArch eq $delPath )
1141             {
1142             $delNdx = $count;
1143             if ( $CLIC_DEBUG > 6 )
1144             {
1145             print STDERR "TheMoveIsAllowed: DEL is moving to Arhive, that's OK\n";
1146             print STDERR "TheMoveIsAllowed: ADD KEY >>$addKey<<\n";
1147             print STDERR "TheMoveIsAllowed: DEL KEY >>$delKey<<\n";
1148             print STDERR "TheMoveIsAllowed: ADD PATH >>$artifact<<\n";
1149             print STDERR "TheMoveIsAllowed: DEL PATH >>$delPath<<\n";
1150             }
1151             last;
1152             }
1153             $count++;
1154             }
1155             if ( $delNdx != -1 ) # was the index into the del array found?
1156             {
1157             print STDERR
1158             "TheMoveIsAllowed: KEEP TESTING -> remove this artifact from delete array because it is moving to archive directory: $artifact\n"
1159             if ( $CLIC_DEBUG > 4 );
1160             splice @{ $DELref }, $delNdx, 1; # ignore any returned value, not needed
1161             }
1162             else
1163             {
1164             print STDERR
1165             "TheMoveIsAllowed: KEEP TESTING -> the artifact to be added has no corresponding delete from a tag: $artifact\n"
1166             if ( $CLIC_DEBUG > 4 );
1167             }
1168             }
1169             else # found a path to add but it does not have "archive directory name" as next to last directory
1170             {
1171             print STDERR
1172             "TheMoveIsAllowed: KEEP TESTING -> no archive directory match so this is an add condition with artifact: $artifact\n"
1173             if ( $CLIC_DEBUG > 4 );
1174             $justAdd = 1;
1175             }
1176             }
1177             if ( $justAdd )
1178             {
1179             print STDERR "TheMoveIsAllowed: KEEP TESTING -> ADDING TO THE ARRAY OF justAdditions: $artifact\n" if ( $CLIC_DEBUG > 3 );
1180             $ok2add = 1; # assume so
1181             $count = int( @justAdditions );
1182             if ( $count > 0 )
1183             {
1184             $ref = $justAdditions[$count - 1];
1185             ( $stmp, $check1st ) = @{ $ref };
1186             if ( length( $artifact ) >= length( $check1st ) )
1187             {
1188             $ok2add = 0 if ( $artifact =~ $check1st );
1189             }
1190             }
1191             if ( $ok2add )
1192             {
1193             @tmp = ( $addKey, $artifact );
1194             print STDERR "TheMoveIsAllowed: KEEP TESTING - pushing path to array for futher testing, artifact: $artifact\n" if ( $CLIC_DEBUG > 4 );
1195             push @justAdditions, [@tmp];
1196             }
1197             else
1198             {
1199             print STDERR "TheMoveIsAllowed: duplicate pathing, not pushing path to array for futher testing, artifact: $artifact\n" if ( $CLIC_DEBUG > 4 );
1200             }
1201             }
1202             }
1203             if ( $CLIC_DEBUG > 5 )
1204             {
1205             print STDERR "TheMoveIsAllowed: LOOP IS DONE\n";
1206             print STDERR "TheMoveIsAllowed: left over delete count is: $#$DELref (0 or more means there are some deletes not part of moves)\n";
1207             print STDERR "TheMoveIsAllowed: count of just additions is: " . int( @justAdditions ) . "\n";
1208             }
1209             if ( !( $#$DELref < 0 ) ) # if there is something left over to be deleted then it is not a "move"
1210             {
1211             for $delRef ( @{ $DELref } )
1212             {
1213             ( $delKey, $delPath ) = ( @{ $delRef } );
1214             $commit = &$SayNoDelete( "D $delPath" ); # always returns 0
1215             last; # just do one
1216             }
1217             }
1218             elsif ( int( @justAdditions ) > 0 ) # there is something left over to be added and must check that on its own
1219             {
1220             print STDERR "TheMoveIsAllowed: KEEP TESTING - call " . '&TheAddIsAllowed' . " to test addtions not matched with deletions\n" if ( $CLIC_DEBUG > 3 );
1221             $commit = &$TheAddIsAllowed( $author, \@justAdditions );
1222             }
1223             if ( $CLIC_DEBUG > 1 )
1224             {
1225             print STDERR "TheMoveIsAllowed: return " . &$returnTF( $commit ) . "\n";
1226             }
1227             return $commit;
1228             }; # TheMoveIsAllowed
1229              
1230             # if the (now parsed into PERL hash of hash) configuration file has the _identical_
1231             # tag directory to protect repeated (i.e. given more that once) error out and die.
1232             # a tag directory to protect can only be given once.
1233             my $ValidateCFGorDie = sub { # ValidateCFGorDie
1234             my $count_1 = 0; # index for outer count
1235             my $count_2 = 0; # index for inner count
1236             my $key_1; # to loop through keys
1237             my $key_2; # to loop through keys
1238             my $protected_1; # 1st protected directory to compare with
1239             my $protected_2; # 2nd protected directory to compare with
1240             my $error = 0; # error count
1241              
1242             while ( $count_1 < $Tuple_CNT )
1243             {
1244             $key_1 = &$GenTupleKey( $Tuple_STR, $count_1 );
1245             $protected_1 = $cfgHofH{ $key_1 }{ $TAGpKEY }; # data to compare
1246             $count_2 = $count_1 + 1;
1247             while ( $count_2 < $Tuple_CNT )
1248             {
1249             $key_2 = &$GenTupleKey( $Tuple_STR, $count_2 );
1250             $protected_2 = $cfgHofH{ $key_2 }{ $TAGpKEY }; # data to compare
1251             if ( $protected_2 eq $protected_1 )
1252             {
1253             if ( $error == 0 )
1254             {
1255             print STDERR "$PROGNAME: error with configuration file: \"$CLICONFIGF\"\n";
1256             }
1257             else
1258             {
1259             print STDERR "\n";
1260             }
1261             print STDERR "$PROGNAME: the protected path \"$protected_1\" is duplicated\n";
1262             print STDERR "$PROGNAME: lines with duplications are:";
1263             print STDERR " $cfgHofH{$key_1}{$LINEKEY}";
1264             print STDERR " and";
1265             print STDERR " $cfgHofH{$key_2}{$LINEKEY}\n";
1266             $error = 1;
1267             }
1268             $count_2++;
1269             }
1270             $count_1++;
1271             }
1272             if ( $error > 0 ) # die if errors
1273             {
1274             print STDERR "$PROGNAME: ABORTING - tell the subversion administrator.\n";
1275             exit $exitFatalErr;
1276             }
1277             return;
1278             }; # ValidateCFGorDie
1279              
1280             my $ZeroOneOrN = sub { # ZeroOneOrN # return 0, 1, or any N
1281             local $_ = shift;
1282             my $rvalue;
1283             if ( m/^[0-9]+$/ )
1284             {
1285             s@^0*@@;
1286             s@^$@0@;
1287             $rvalue = int( $_ );
1288             }
1289             elsif ( m/^on$/i ) { $rvalue = 1; }
1290             elsif ( m/^yes$/i ) { $rvalue = 1; }
1291             elsif ( m/^true$/i ) { $rvalue = 1; }
1292             elsif ( m/^enable$/i ) { $rvalue = 1; }
1293             else { $rvalue = 0; } # default to zero
1294             return $rvalue;
1295             }; # ZeroOneOrN
1296              
1297             # THERE IS NO NEED TO OFFSET THE DEBUG LEVEL IN THE COMMAND LINE
1298             # PARSE ROUTINE. IF IN PRODUCTION YOU DO NOT WANT TO PARSE THE
1299             # COMMAND LINE - IT IS TRIVIAL AND RATHER USELESS. IN DEBUG AND/OR
1300             # TEST MODE THE DEBUG LEVEL SHOULD BE "low" THRESHOLD NOT OFFSET.
1301             my $ParseCLI = sub { # ParseCLI # ENTER: parse command line OR DIE
1302             my $argsRef = shift; # array reference of command line args
1303             my $ohandle = *STDOUT;
1304             my $total; # count number of requested actions
1305             my $debugLVL = $CLIC_DEBUG; # parsing changes, --debug should be first on command line
1306              
1307             # in production the $PROGDIR directory is "/hooks",
1308             # where /svndir is the absolute path to a subversion repository.
1309             $CLICONFIGF = "$PROGDIRE/$PROGNAME.conf"; # the name of the config file itself
1310             $CLIPRECONF = "$PROGDIRE/$PROGNAME.conf.pl"; # the name of the "pre-compiled" file
1311              
1312             while ( scalar( @{ $argsRef } ) > 0 )
1313             {
1314             print STDERR "ParseCLI: " . scalar( @{ $argsRef } ) . "\t$argsRef->[0]\n" if ( $debugLVL > 4 );
1315              
1316             # ENTER: options that cause an immediate exit after doing their job
1317             if ( $argsRef->[0] eq '--help' or $argsRef->[0] eq '-h' )
1318             {
1319             &$PrintUsageAndExit();
1320             }
1321             elsif ( $argsRef->[0] eq '--version' or $argsRef->[0] eq '-v' )
1322             {
1323             &$PrintVersionAndExit();
1324             }
1325              
1326             # LEAVE: options that cause an immediate exit after doing their job
1327              
1328             # ENTER: options that mean we are not running under subversion
1329             elsif ( $argsRef->[0] eq '--generate' or $argsRef->[0] eq '-g' )
1330             {
1331             $CLIBLD_DEF = 1;
1332             $CLIRUNNING = 1; # running on comamnd line
1333             }
1334             elsif ( $argsRef->[0] eq '--parse' or $argsRef->[0] eq '-p' )
1335             {
1336             $CLIJUSTCFG = 1;
1337             $CLIRUNNING = 1; # running on comamnd line
1338             }
1339             elsif ( $argsRef->[0] eq '--build' or $argsRef->[0] eq '-b' )
1340             {
1341             $CLIBLDPREC = 1;
1342             $CLIRUNNING = 1; # running on comamnd line
1343             }
1344             elsif ( $argsRef->[0] eq '--revert' or $argsRef->[0] eq '-r' )
1345             {
1346             $CLIDUMP_PL = 1;
1347             $CLIRUNNING = 1; # running on comamnd line
1348             }
1349             elsif ( $argsRef->[0] =~ '--input=?+' )
1350             {
1351             $CLI_INFILE = $argsRef->[0];
1352             $CLI_INFILE =~ s@--input=@@;
1353             $CLIRUNNING = 1; # running on comamnd line
1354             }
1355             elsif ( $argsRef->[0] =~ '-i..*' )
1356             {
1357             $CLI_INFILE = $argsRef->[0];
1358             $CLI_INFILE =~ s@-i@@;
1359             $CLIRUNNING = 1; # running on comamnd line
1360             }
1361             elsif ( $argsRef->[0] =~ '--output=?+' )
1362             {
1363             $CLIOUTFILE = $argsRef->[0];
1364             $CLIOUTFILE =~ s@--output=@@;
1365             $CLIRUNNING = 1; # running on comamnd line
1366             }
1367             elsif ( $argsRef->[0] =~ '-o..*' )
1368             {
1369             $CLIOUTFILE = $argsRef->[0];
1370             $CLIOUTFILE =~ s@-o@@;
1371             $CLIRUNNING = 1; # running on comamnd line
1372             }
1373              
1374             elsif ( $argsRef->[0] eq '--nodebug' or $argsRef->[0] eq '-D' )
1375             {
1376             $CLIC_DEBUG = 0;
1377             $debugLVL = $CLIC_DEBUG;
1378             $CLIRUNNING = 1; # running on command line
1379             }
1380             elsif ( $argsRef->[0] eq '--debug' or $argsRef->[0] eq '-d' )
1381             {
1382             if ( $CLIC_DEBUG <= 0 ) { $CLIC_DEBUG = 1; }
1383             else { $CLIC_DEBUG++; }
1384             $debugLVL = $CLIC_DEBUG;
1385             $CLIRUNNING = 1; # running on command line
1386             }
1387             elsif ( $argsRef->[0] =~ '--debug=[0-9]+' )
1388             {
1389             $CLIC_DEBUG = $argsRef->[0];
1390             $CLIC_DEBUG =~ s@--debug=@@;
1391             $debugLVL = $CLIC_DEBUG;
1392             $CLIRUNNING = 1; # running on command line
1393             }
1394             elsif ( $argsRef->[0] =~ '-d[0-9]+' )
1395             {
1396             $CLIC_DEBUG = $argsRef->[0];
1397             $CLIC_DEBUG =~ s@-d@@;
1398             $debugLVL = $CLIC_DEBUG;
1399             $CLIRUNNING = 1; # running on command line
1400             }
1401             elsif ( $argsRef->[0] =~ '-d=[0-9]+' )
1402             {
1403             $CLIC_DEBUG = $argsRef->[0];
1404             $CLIC_DEBUG =~ s@-d=@@;
1405             $debugLVL = $CLIC_DEBUG;
1406             $CLIRUNNING = 1; # running on command line
1407             }
1408              
1409             # LEAVE: options that mean we are not running under subversion
1410              
1411             # ENTER: fatal errors
1412             elsif ( $argsRef->[0] =~ '^-.*' )
1413             {
1414             print STDERR "$PROGNAME: unrecognized command line option: \"$argsRef->[0]\"!\n";
1415             print STDERR "$PROGNAME: ABORTING!\n";
1416             exit $exitFatalErr;
1417             }
1418             elsif ( scalar( @{ $argsRef } ) != 2 )
1419             {
1420             my $aHave = scalar( @{ $argsRef } );
1421             my $aNeed = 2;
1422             print STDERR "$PROGNAME: incorrect command line argument count is: $aHave (it should be $aNeed).\n";
1423             print STDERR "$PROGNAME: perhaps you are not running under subversion? if so give two dummy command line options.\n";
1424             print STDERR "$PROGNAME: ABORTING!\n";
1425             exit $exitFatalErr;
1426             }
1427              
1428             # LEAVE: fatal errors
1429              
1430             # ENTER: in PRODUCTION, under Subversion, only this block is ever invoked
1431             else # two command line arguments left
1432             {
1433             $CLISVNREPO = $argsRef->[0];
1434             shift @{ $argsRef };
1435             $CLISVN_TID = $argsRef->[0];
1436             }
1437              
1438             # LEAVE: in PRODUCTION, under Subversion, only this block is ever invoked
1439              
1440             shift @{ $argsRef };
1441             }
1442              
1443             # if debugging from the command line (because that is the only way
1444             # this could happen) and the command line did not give the expected
1445             # subversion command line arguments then give them here so the
1446             # program can continue, usually just to parse the config file.
1447             if ( $CLISVNREPO eq "" or $CLISVN_TID eq "" )
1448             {
1449             $CLISVNREPO = $PROGDIRE; # this should now be the path to the repo, unless in development
1450             $CLISVNREPO =~ s@/hooks$@@;
1451             $CLISVN_TID = "HEAD"; # this will be useless talking to subversion with svnlook
1452             }
1453              
1454             # svnRepo path must end with slash
1455             $_ = $CLISVNREPO;
1456             $CLISVNREPO .= "/" if ( !m@/$@ );
1457              
1458             $total = $CLIBLD_DEF + $CLIJUSTCFG + $CLIBLDPREC + $CLIDUMP_PL;
1459             if ( $total > 1 )
1460             {
1461             print STDERR "$PROGNAME: too many actions requested!\n";
1462             print STDERR "$PROGNAME: only one of --generate/--parse/--build/--revert can be given.\n";
1463             print STDERR "$PROGNAME: ABORTING\n";
1464             exit $exitFatalErr;
1465             }
1466              
1467             # produce a default file and exit, if command line requested
1468             if ( $CLIBLD_DEF )
1469             {
1470             &$PrintDefaultConfigOptionallyExit( 1, $CLIOUTFILE, $ohandle );
1471             }
1472              
1473             print STDERR "ParseCLI: return successful (with no value) after command line parse\n" if ( $debugLVL > 0 );
1474             return; # nothing useful can be returned
1475             }; # ParseCLI # LEAVE: parse command line OR DIE
1476              
1477             my $ParseCFG = sub { # ParseCFG # ENTER: parse config file
1478             my $var = "";
1479             my $val = "";
1480             my $ch_1st = "";
1481             my $chLast = "";
1482             my $errors = 0;
1483             my $unknown = 0;
1484             my $itmp = 0;
1485             my %cfg = (); # "one config" for a protected directory
1486             my $cfgh; # open config handle
1487             my $tKey; # N-Tuple key
1488             my $cKey; # configuration key
1489             my $spch; # string of space characters
1490             my $readPreComp = 0; # read the precompiled config file
1491             my $ohandle = *STDOUT;
1492             my $str;
1493             my $localDEBUG = 0; # offset if in PRODUCTION since config parse is not wanted at low debug levels
1494             $localDEBUG = -1 if ( $CLIRUNNING == 0 ); # invoke the offset - we are in PRODUCTION
1495              
1496             # do not read the pre-compiled file if we have to build it
1497             # and do not read the pre-compiled file it we have been asked to parse the configuration file
1498             if ( $CLIBLDPREC == 0 && $CLIJUSTCFG == 0 )
1499             {
1500             $CLIPRECONF = $CLI_INFILE if ( $CLI_INFILE ne "" );
1501             $readPreComp = 1 if ( -f $CLIPRECONF ); # if the precompiled file exists it will be read in
1502             }
1503              
1504             if ( $readPreComp ) # if precompiled file, and no command line options to the contrary, just require it and done!
1505             {
1506             $itmp = $CLIC_DEBUG; # hold
1507             print STDERR "ParseCFG: read precompiled configuration file \"$CLIPRECONF\"\n" if ( $localDEBUG > 1 );
1508             require "$CLIPRECONF";
1509              
1510             # if the command line has set the debug higher than what it now is then it set back to the command line value
1511             $CLIF_DEBUG = $CLIC_DEBUG; # f_debug is now the actual value gotten from the parse
1512             $CLIC_DEBUG = &$GetMax( $CLIF_DEBUG, $CLIF_DEBUG ); # use the max value to work with, usually c_debug
1513             }
1514              
1515             # read the regular config file
1516             else
1517             {
1518             $CLIPRECONF = $CLI_INFILE if ( $CLI_INFILE ne "" );
1519             if ( !-f $CLICONFIGF )
1520             {
1521             print STDERR "ParseCFG: No configuration file \"$CLICONFIGF\"\n" if ( $localDEBUG > 0 );
1522             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" does not exist, aborting.\n";
1523             print STDERR "$PROGNAME: tell the subversion administrator.\n";
1524             exit $exitFatalErr;
1525             }
1526             else
1527             {
1528             print STDERR "ParseCFG: open for read $CLICONFIGF\n" if ( $localDEBUG > 2 );
1529             open $cfgh, "<", $CLICONFIGF;
1530             print STDERR "ParseCFG: read $CLICONFIGF\n" if ( $localDEBUG > 1 );
1531             while ( <$cfgh> )
1532             {
1533             ###############################################
1534             # ENTER: fix and split up the line just read in
1535             chop;
1536             s/#.*//; # remove comments
1537             s/\s*$//; # remove trailing white space
1538             next if $_ eq "";
1539             print STDERR "ParseCFG: RAW: $_\n" if ( $localDEBUG > 5 );
1540              
1541             if ( !m/=/ )
1542             {
1543             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1544             print STDERR "$PROGNAME: line $. >>$_<< is not a comment and does not contain an equal sign(=) character!\n";
1545             $errors++;
1546             next;
1547             }
1548             $var = $_; # init to input
1549             $var =~ s/^\s*//; # remove initial white space
1550             $var =~ s/^([A-Za-z0-9_]+)\s*=.*/$1/; # remove optional white space and equal sign
1551             $val = $_; # init to input
1552             $val =~ s/\s*$var\s*=\s*//; # remove VAR= with optional white space
1553             $val =~ s/\s*;\s*//; # remove trailing ';' and white space, if any
1554             $ch_1st = $val;
1555             $ch_1st =~ s/^(.)(.*)(.)\Z/$1/; # first char
1556             $chLast = $val;
1557             $chLast =~ s/^(.)(.*)(.)\Z/$3/; # last char
1558              
1559             if ( $localDEBUG > 4 )
1560             {
1561             print STDERR "ParseCFG: \$var=\"$var\"\n";
1562             print STDERR "ParseCFG: \$val=\"$val\"\n";
1563             print STDERR "ParseCFG: \$ch_1st=\"$ch_1st\"\n";
1564             print STDERR "ParseCFG: \$chLast=\"$chLast\"\n";
1565             }
1566             if ( $ch_1st eq $chLast and $ch_1st eq '"' )
1567             { # extact dq string
1568             $val =~ s/^(.)(.*)(.)\Z/$2/;
1569             }
1570             elsif ( $ch_1st eq $chLast and $ch_1st eq "'" )
1571             { # extact sq string
1572             $val =~ s/^(.)(.*)(.)\Z/$2/;
1573             }
1574             elsif ( $ch_1st eq '"' or $ch_1st eq "'" )
1575             {
1576             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1577             print STDERR "$PROGNAME: line $. >>$_<< badly quoted!\n";
1578             $errors++;
1579             next;
1580             }
1581              
1582             #else { $val is good as it is }
1583              
1584             if ( $localDEBUG > 4 )
1585             {
1586             print STDERR 'ParseCFG: $var="' . "$var" . '"' . "\n";
1587             print STDERR 'ParseCFG: $val="' . "$val" . '"' . "\n";
1588             }
1589              
1590             # LEAVE: fix and split up the line just read in
1591             ###############################################
1592              
1593             ############################################################
1594             # ENTER: find the variable and store the value for "GLOBALS"
1595             if ( $var =~ m/^${VAR_H_DEBUG}\Z/i )
1596             {
1597             $CLIF_DEBUG = &$ZeroOneOrN( $val );
1598             $CLIC_DEBUG = &$GetMax( $CLIC_DEBUG, $CLIF_DEBUG ); # use the max value to work with, usually c_debug
1599             }
1600             elsif ( $var =~ m/^${VAR_SVNPATH}\Z/i )
1601             {
1602             $ch_1st = $val;
1603             $ch_1st =~ s/(.)(.+)/$1/; # first char
1604             if ( $ch_1st ne "/" )
1605             {
1606             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1607             print STDERR "$PROGNAME: line $. >>$_<< svn path does not start with slash(/)!\n";
1608             $errors++;
1609             next;
1610             }
1611             $CLISVNPATH = $val;
1612             print STDERR 'ParseCFG: $CLISVNPATH="' . $CLISVNPATH . '"' . "\n" if ( $localDEBUG > 4 );
1613             }
1614             elsif ( $var =~ m/^${VAR_SVNLOOK}\Z/i )
1615             {
1616             $ch_1st = $val;
1617             $ch_1st =~ s/(.)(.+)/$1/; # first char
1618             if ( $ch_1st ne "/" )
1619             {
1620             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1621             print STDERR "$PROGNAME: line $. >>$_<< svnlook path does not start with slash(/)!\n";
1622             $errors++;
1623             next;
1624             }
1625             $CLISVNLOOK = $val;
1626             print STDERR 'ParseCFG: $CLISVNLOOK = "' . $CLISVNLOOK . '"' . "\n" if ( $localDEBUG > 4 );
1627             }
1628              
1629             # LEAVE: find the variable and store the value for "GLOBALS"
1630             ############################################################
1631              
1632             ###########################################################
1633             # ENTER: find the variable and store the value for "N-Tuple"
1634             # can be given in _any_ order
1635             # 1) tag directory - cannot be BLANK
1636             # 2) subdirectories - can be BLANK means NOT ALLOWED
1637             # 3) subdirectory creators - can be BLANK means NO ONE -- they have made'm all and no more are allowed
1638             # 4) archive name - can be BLANK means NOT ALLOWED
1639              
1640             # 1) tag directory
1641             elsif ( $var =~ m/^${VAR_TAGDIRE}\Z/i )
1642             {
1643             # before processing this "$var" (a "protected tag directory" from the config file)
1644             # if there is a "protected tag directory" outstanding, load it and its corresponding
1645             # configuration values
1646             if ( keys %cfg )
1647             {
1648             $cfg{ $LINEKEY } = $. if ( !exists $cfg{ $LINEKEY } );
1649              
1650             # we need to load this protected directory and all the
1651             # members of the "tuple" into the configuration hash
1652             print STDERR "ParseCFG: $TAGpKEY = $cfg{$TAGpKEY} in the while loop\n" if ( $localDEBUG > 4 );
1653             &$LoadCFGTuple( \%cfg, \%cfgHofH );
1654             %cfg = (); # clear it to hold next parse
1655             }
1656              
1657             # now process the just read in "protected tag directory"
1658             $ch_1st = $val;
1659             $ch_1st =~ s/(.)(.+)/$1/; # first char
1660             if ( $ch_1st ne "/" )
1661             {
1662             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1663             print STDERR "$PROGNAME: line $. >>$_<< tag directory to protect does not start with slash(/)!\n";
1664             $errors++;
1665             next;
1666             }
1667             $cfg{ $TAGpKEY } = &$FixPath( $val, 1, 1 ); # strip first slash, add last slash
1668             $cfg{ $LINEKEY } = $.; # keep the line this was read in on
1669             # safety/security check
1670             if ( $cfg{ $TAGpKEY } eq "" )
1671             {
1672             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1673             print STDERR "$PROGNAME: line $. >>$_<<";
1674             print STDERR " (which becomes \"$cfg{$TAGpKEY}\")" if ( $_ ne $cfg{ $TAGpKEY } );
1675             print STDERR " cannot be blank!\n";
1676             $errors++;
1677             next;
1678             }
1679             }
1680              
1681             # 2) subdirectories
1682             elsif ( $var =~ m/^${VAR_SUBDIRE}\Z/i )
1683             {
1684             $val = &$FixPath( $val, 1, 0 ); # strip 1st slash, can end up being BLANK, that's ok, add last slash
1685             # if $val is BLANK it means the next tags directory to be protected
1686             # will have NO subdirectories
1687             $cfg{ $SUBfKEY } = $val;
1688             if ( $localDEBUG > 4 )
1689             {
1690             if ( $val eq "" )
1691             {
1692             print STDERR "ParseCFG: $SUBfKEY = has been cleared, configuration to have no subdirectories.\n";
1693             }
1694             else
1695             {
1696             print STDERR "ParseCFG: $SUBfKEY = $cfg{$SUBfKEY}\n";
1697             }
1698             }
1699             $cfg{ $LINEKEY } = $. if ( !exists $cfg{ $LINEKEY } );
1700             }
1701              
1702             # 3) creators
1703             elsif ( $var =~ m/^${VAR_MAKESUB}\Z/i )
1704             {
1705             $cfg{ $MAKEKEY } = "$val"; # can be BLANK
1706             print STDERR "ParseCFG: $MAKEKEY = $cfg{$MAKEKEY}\n" if ( $localDEBUG > 4 );
1707             $cfg{ $LINEKEY } = $. if ( !exists $cfg{ $LINEKEY } );
1708             }
1709              
1710             # 4) archive name
1711             elsif ( $var =~ m/^${VAR_NAME_AF}\Z/i )
1712             {
1713             $val = &$FixPath( $val, 0, 0 ); # do not strip 1st slash, can end up being BLANK, that's ok, no last slash
1714             $val = $DEF_NAME_AF if ( $val eq "" ); # asked for a reset
1715             $val = &$FixPath( $val, 0, 0 ); # won't be BLANK any longer, no last slash
1716             if ( $val =~ m@/@ )
1717             {
1718             print STDERR "$PROGNAME: configuration file \"$CLICONFIGF\" is misconfigured.\n" if ( $errors == 0 );
1719             print STDERR "$PROGNAME: line $. >>$_<< archive directory name contains a slash(/) character, that is not allowed!\n";
1720             $errors++;
1721             next;
1722             }
1723             $cfg{ $NAMEKEY } = $val;
1724             print STDERR "ParseCFG: $NAMEKEY = $cfg{$NAMEKEY}\n" if ( $localDEBUG > 4 );
1725             $cfg{ $LINEKEY } = $. if ( !exists $cfg{ $LINEKEY } );
1726             }
1727              
1728             # the "variable = value" pair is unrecognized
1729             else
1730             {
1731             # useless to output error message unless debug is enabled, or
1732             # we are running from the command line, because otherwise
1733             # subversion will just throw them away!
1734             if ( $localDEBUG > 0 || $CLIRUNNING > 0 ) # insure STDERR is not useless
1735             {
1736             if ( $unknown == 0 )
1737             {
1738             print STDERR "$PROGNAME: useless configuration variables found while parsing\n";
1739             print STDERR "$PROGNAME: configuration file: \"$CLICONFIGF\"\n";
1740             print STDERR "$PROGNAME: tell the subversion administrator.\n";
1741             }
1742             print STDERR "$PROGNAME: unrecognized \"variable = value\" on line $.\n";
1743             print STDERR "$PROGNAME: variable: \"$var\"\n";
1744             print STDERR "$PROGNAME: value: \"$val\"\n";
1745             print STDERR "$PROGNAME: line: >>$_<<\n";
1746             $unknown++;
1747             }
1748             }
1749              
1750             # LEAVE: find the variable and store the value for "N-Tuple"
1751             # can be given in _any_ order
1752             # 1) tag directory - cannot be BLANK
1753             # 2) subdirectory - can be BLANK means NOT ALLOWED
1754             # 3) subdirectory creators - can be BLANK means NO ONE
1755             # 4) archive name - can be BLANK means NOT ALLOWED
1756             ############################################################
1757             }
1758             if ( $errors > 0 ) { exit $exitFatalErr; }
1759              
1760             # there can be one left in the "cache"
1761             if ( keys %cfg )
1762             {
1763             print STDERR "ParseCFG: $TAGpKEY = $cfg{$TAGpKEY} AT END OF WHILE LOOP\n" if ( $localDEBUG > 4 );
1764             &$LoadCFGTuple( \%cfg, \%cfgHofH );
1765             }
1766             close $cfgh;
1767             }
1768             &$ValidateCFGorDie();
1769             }
1770              
1771             # DUMP (revert) THE PRECOMPILED FILE BACK TO A REGULAR CONFIGURATION FILE
1772             if ( $CLIDUMP_PL > 0 )
1773             {
1774             $CLIPRECONF = $CLIOUTFILE if ( $CLIOUTFILE ne "" );
1775             if ( $readPreComp == 0 )
1776             {
1777             print STDERR "$PROGNAME: precompiled configuration file is:\n";
1778             print STDERR "$PROGNAME: \"$CLIPRECONF\"\n";
1779             print STDERR "$PROGNAME: precompiled configuration file was not read in. Unable to\n";
1780             print STDERR "$PROGNAME: revert the precompiled configuration file to a (regular) configuration file.\n";
1781             if ( !-f "$CLIPRECONF" )
1782             {
1783             print STDERR "$PROGNAME: it does not exist.\n";
1784             }
1785             print STDERR "$PROGNAME: ABORTING!\n";
1786             exit $exitFatalErr;
1787             }
1788             if ( $CLIOUTFILE ne "" )
1789             {
1790             print STDERR "ParseCFG: open for write $CLIOUTFILE\n" if ( $localDEBUG > 2 );
1791             open $ohandle, ">", $CLIOUTFILE;
1792             }
1793             if ( $localDEBUG > 1 )
1794             {
1795             my $where = "STDOUT";
1796             $where = $CLIOUTFILE if ( $CLIOUTFILE ne "" );
1797             print STDERR "ParseCFG: output default pre-compiled configuration file to: $where\n";
1798             }
1799              
1800             # OUTPUT THE HEADER PART, next function will not exit
1801             &$PrintDefaultConfigOptionallyExit( 0, $CLIOUTFILE, $ohandle );
1802             $_ = 0;
1803             for $tKey ( sort keys %cfgHofH )
1804             {
1805             my $q = "'";
1806             print $ohandle "\n\n" if ( $_ > 0 );
1807             $_ = 1;
1808             %cfg = %{ $cfgHofH{ $tKey } };
1809             $str = &$PrtStr( $VAR_TAGDIRE );
1810             print $ohandle $str . " = ${q}$cfg{$TAGpKEY}${q}\n";
1811             $str = &$PrtStr( $VAR_SUBDIRE );
1812             print $ohandle $str . " = ${q}$cfg{$SUBfKEY}${q}\n";
1813             $str = &$PrtStr( $VAR_MAKESUB );
1814             print $ohandle $str . " = ${q}$cfg{$MAKEKEY}${q}\n";
1815             $str = &$PrtStr( $VAR_NAME_AF );
1816             print $ohandle $str . " = ${q}$cfg{$NAMEKEY}${q}\n";
1817             }
1818             exit $exitSuccess;
1819             }
1820              
1821             # OUTPUT (build) THE PRECOMPILED CONFIGURATION FILE FROM THE CONFIGURATION FILE JUST READ IN
1822             elsif ( $CLIBLDPREC > 0 )
1823             {
1824             my $where = "STDOUT";
1825             if ( $CLIOUTFILE ne "" )
1826             {
1827             print STDERR "ParseCFG: open for write $CLIOUTFILE\n" if ( $localDEBUG > 2 );
1828             open $ohandle, ">", $CLIOUTFILE;
1829             }
1830             if ( $localDEBUG > 1 )
1831             {
1832             $where = $CLIOUTFILE if ( $CLIOUTFILE ne "" );
1833             print STDERR "ParseCFG: output default pre-compiled configuration file to: $where\n";
1834             }
1835             my $Oline = '%cfgHofH = ('; # open the HASH of HASH lines
1836             my $Sline = ' '; # spaces
1837             my $Cline = ' );'; # close the HASH of HASH lines
1838             my $tStamp = strftime( '%d %B %Y %T', localtime );
1839             my $user = $ENV{ 'USER' };
1840             $user = "UNKNOWN" if ( $user eq "" );
1841              
1842             # output the header
1843             print $ohandle "#\n";
1844             print $ohandle "# Pre-compiled configuration file created:\n";
1845             print $ohandle "# Date: $tStamp\n";
1846             print $ohandle "# From: $CLICONFIGF\n";
1847             print $ohandle "# User: $user\n";
1848             print $ohandle "#\n";
1849             print $ohandle "\n";
1850              
1851             # output configuration for the 3
1852             # use "c_debug" here, not "f_debug"
1853             print $ohandle '$CLIC_DEBUG = 0; # always set to zero by default' . "\n";
1854             print $ohandle '$CLISVNLOOK = "' . $CLISVNLOOK . '";' . "\n";
1855             print $ohandle '$CLISVNPATH = "' . $CLISVNPATH . '";' . "\n";
1856             print $ohandle "\n";
1857              
1858             # output all the N-Tuples
1859             print $ohandle "$Oline\n"; # open cfgHofH declaration line
1860             $spch = ' ';
1861             for $tKey ( sort keys %cfgHofH )
1862             {
1863             %cfg = %{ $cfgHofH{ $tKey } };
1864             print $ohandle $Sline . "'$tKey' => { # started on line " . $cfg{ $LINEKEY } . "\n"; # INITIAL SLASHES PUT BACK
1865             $spch = $tKey;
1866             $spch =~ s@.@ @g; # $spch is now just spaces
1867             print $ohandle $Sline . "$spch '$TAGpKEY' => " . '"/' . $cfg{ $TAGpKEY } . '",' . "\n";
1868             print $ohandle $Sline . "$spch '$SUBfKEY' => " . '"/' . $cfg{ $SUBfKEY } . '",' . "\n";
1869             print $ohandle $Sline . "$spch '$MAKEKEY' => " . '"' . $cfg{ $MAKEKEY } . '",' . "\n";
1870             print $ohandle $Sline . "$spch '$NAMEKEY' => " . '"' . $cfg{ $NAMEKEY } . '",' . "\n";
1871             print $ohandle $Sline . "$spch },\n";
1872             }
1873             print $ohandle "$Cline\n"; # close cfgHofH declaration line
1874             print STDERR "ParseCFG: exit $exitSuccess because building of precompiled configuration file is done\n" if ( $localDEBUG > 0 );
1875             exit $exitSuccess; # yes, this exits right here, we are done with building the precompiled configuration file
1876             }
1877              
1878             # OUTPUT THE INTERNAL HASH OF HASHES if debug is high enough
1879             if ( $localDEBUG > 2 )
1880             {
1881             print STDERR "$VAR_H_DEBUG=" . $CLIC_DEBUG . "\n";
1882             print STDERR "$VAR_SVNLOOK=" . $CLISVNLOOK . "\n";
1883             print STDERR "$VAR_SVNPATH=" . $CLISVNPATH . "\n";
1884             print STDERR "\n";
1885             for $tKey ( sort keys %cfgHofH )
1886             {
1887             $spch = $tKey;
1888             $spch =~ s@.@ @g; # make a string of spaces
1889             %cfg = %{ $cfgHofH{ $tKey } }; # load the config hash
1890             print STDERR "$tKey = {";
1891             print STDERR " # started on line $cfg{$LINEKEY} INITIAL SLASHES REMOVED" if ( exists $cfg{ $LINEKEY } );
1892             print STDERR "\n";
1893             print STDERR "$spch $VAR_TAGDIRE=" . '"' . $cfgHofH{ $tKey }{ $TAGpKEY } . '"' . " # literal only\n";
1894             print STDERR "$spch $VAR_SUBDIRE=" . '"' . $cfgHofH{ $tKey }{ $SUBfKEY } . '"' . " # literal, a glob, or blank\n";
1895             print STDERR "$spch $VAR_MAKESUB=" . '"' . $cfgHofH{ $tKey }{ $MAKEKEY } . '"' . " # authorized committers
1896             # - they can create subdirectories/subprojects\n";
1897             print STDERR "$spch $VAR_NAME_AF=" . '"' . $cfgHofH{ $tKey }{ $NAMEKEY } . '"' . " # authorised committers only
1898             # - name of directory for archiving\n";
1899             print STDERR "$spch }\n";
1900             }
1901             }
1902             if ( $CLIJUSTCFG )
1903             {
1904             print STDERR "ParseCFG: exit $exitSuccess because just parse configuration file is in effect\n" if ( $localDEBUG > 0 );
1905             exit $exitSuccess;
1906             }
1907             print STDERR "ParseCFG: return successful (with no value) after configuration file parse\n" if ( $localDEBUG > 0 );
1908             return; # nothing useful can be returned
1909             }; # ParseCFG # LEAVE: parse config file
1910             ################################################################################
1911             ############################## PROTECTED/PRIVATE ###############################
1912             ############################# SUPPORT SUBROUTINES ##############################
1913             # } LEAVE #####################################################################}
1914              
1915             # { ENTER ######################################################################
1916             ############################# "PUBLIC" SUBROUTINES #############################
1917             ###############################################################################{
1918             sub new
1919             {
1920 0     0 0   my $class = shift;
1921 0           my $fullname = shift; # but it might not be!
1922 0           my $argsRef = shift; # array reference of command line args
1923 0           my $self = {};
1924              
1925 0           $PROGNAME = &abs_path( $fullname ); # print STDERR "NAME=$NAME\n";
1926 0           $PROGDIRE = $PROGNAME; # init to finding directory we live in
1927 0           $PROGNAME =~ s@.*\\@@; # print STDERR "NAME=$NAME\n";
1928 0           $PROGNAME =~ s@.*/@@; # print STDERR "NAME=$NAME\n";
1929 0           $PROGDIRE =~ s@\\[^\\][^\\]*$@@; # print STDERR "DIRE=$DIRE\n";
1930 0           $PROGDIRE =~ s@/[^/][^/]*$@@; # print STDERR "DIRE=$DIRE\n";
1931              
1932 0 0         $argsRef->[0] = "--help" if ( scalar( @{ $argsRef } ) == 0 );
  0            
1933              
1934 0           &$ParseCLI( $argsRef ); # ParseCLI dies if it fails
1935 0           &$ParseCFG(); # ParseCFG dies if it fails
1936 0           return bless $self, $class;
1937             }
1938              
1939 0     0 0   sub GetDebugLevel { return $CLIC_DEBUG; }
1940              
1941             sub SimplyAllow # ENTER: determine if we can simply allow this commit or of a protected directory is part of the commit
1942             {
1943 0     0 0   my $justAllow = 1; # assume most commits are not tags
1944 0           my $pDir; # protected directory
1945             my $tupleKey; # N-Tuple keys found in the configuration ref
1946 0           my $artifact; # N-Tuple keys found in the configuration ref
1947 0           my $isProtected; # returned by IsUnderProtection
1948 0           local $_; # artifact to be committed, changed or whatever
1949              
1950 0 0         print STDERR "SimplyAllow: call SvnGetCommit\n" if ( $CLIC_DEBUG > 9 );
1951 0           &$SvnGetCommit;
1952              
1953 0           foreach $_ ( @CommitData )
1954             {
1955 0 0         print STDERR "SimplyAllow: >>$_<<\n" if ( $CLIC_DEBUG > 8 );
1956 0           $artifact = $_;
1957              
1958 0           $artifact =~ s/^[A-Z_]+\s+//; # trim first two char(s) and two spaces
1959              
1960 0 0         print STDERR "SimplyAllow: >>$artifact<<\n" if ( $CLIC_DEBUG > 7 );
1961              
1962 0           for $tupleKey ( keys %cfgHofH )
1963             {
1964 0           $pDir = $cfgHofH{ $tupleKey }{ $TAGpKEY }; # protected directory
1965 0           $isProtected = &$IsUnderProtection( $pDir, $artifact );
1966 0 0         print STDERR "SimplyAllow: \$isProtected=" . &$returnTF( $isProtected ) . " artifact=$artifact\n" if ( $CLIC_DEBUG > 2 );
1967              
1968             # if the artifact is under a protected directory we cannot simply allow
1969 0 0         if ( &$IsUnderProtection( $pDir, $artifact ) == 1 )
1970             {
1971 0           $justAllow = 0; # nope, we gotta work!
1972 0           last;
1973             }
1974             }
1975             }
1976 0 0         if ( $CLIC_DEBUG > 1 )
1977             {
1978 0           print STDERR "SimplyAllow: return " . &$returnTF( $justAllow ) . "\n";
1979             }
1980 0           return $justAllow;
1981             } # SimplyAllow: LEAVE: determine if we can simply allow this commit or of a protected directory is part of the commit
1982              
1983             #### FROM: http://svnbook.red-bean.com/nightly/en/svn.ref.svnlook.c.changed.html
1984             ####
1985             #### Name
1986             #### svnlook changed — Print the paths that were changed.
1987             #### Synopsis
1988             #### svnlook changed REPOS_PATH
1989             #### Description
1990             #### Print the paths that were changed in a particular revision or transaction, as well as “svn update-style” status letters in the first two columns:
1991             #### 'A ' Item added to repository
1992             #### 'D ' Item deleted from repository
1993             #### 'U ' File contents changed
1994             #### '_U' Properties of item changed; note the leading underscore
1995             #### 'UU' File contents and properties changed
1996             #### Files and directories can be distinguished, as directory paths are displayed with a trailing “/” character.
1997             sub AllowCommit
1998             {
1999 0     0 0   my $author; # person making commit
2000             my $artifact; # the thing being commmitted
2001 0           my $change; # a D or an A
2002 0           my $check1st; # to avoid duplicates
2003 0           my $commit = 1; # assume OK to commit
2004 0           my $count; # of array elements
2005             my $isProtected; # is it protected?
2006 0           my $ok2add; # push to the add array?
2007 0           my $ref; # to Nth array element
2008 0           my $stmp; # tmp string
2009 0           my $tupleKey; # key into configuration HoH
2010 0           my @add = (); # things adding
2011 0           my @del = (); # things deleting
2012 0           my @tmp; # used to push an array into @add or @del
2013             my $element; # artifact to be committed, changed or whatever
2014 0           local $_; # artifact to be committed, changed or whatever
2015              
2016 0 0         if ( $CLIC_DEBUG > 8 )
2017             {
2018 0           print STDERR "AllowCommit: ENTER: listing array of commits\n";
2019 0           foreach $_ ( @CommitData )
2020             {
2021 0           print STDERR "AllowCommit: CommitData>>$_\n";
2022             }
2023 0           print STDERR "AllowCommit: LEAVE: listing array of commits\n";
2024             }
2025 0           foreach $element ( @CommitData )
2026             {
2027 0 0         print STDERR "AllowCommit: >>$element<<\n" if ( $CLIC_DEBUG > 7 );
2028              
2029             # get the next array element, $element
2030             # use a regexp "split" into 3 parts, the middle part is thrown away (it is just 2 spaces)
2031             # 1st part is 2 chars loaded to $change
2032             # 2nd part is 2 spaces, ignored
2033             # 3rd part is the $artifact
2034 0           ( $change, $artifact ) = $element =~ m@^(..) (.+)@; # two space chars ignored
2035 0           $change =~ s@\s@@g; # remove trailing space, sometimes there is one
2036 0 0         if ( $CLIC_DEBUG > 7 )
2037             {
2038 0           print STDERR 'AllowCommit: $change="' . $change . '"' . "\n";
2039 0           print STDERR 'AllowCommit: $artifact="' . $artifact . '"' . "\n";
2040             }
2041              
2042 0           ( $isProtected, $tupleKey ) = &$ArtifactUnderProtectedDir( $artifact );
2043 0 0         if ( $CLIC_DEBUG > 3 )
2044             {
2045 0           print STDERR 'AllowCommit: $isProtected = ' . &$returnTF( $isProtected ) . " \$artifact=$artifact\n";
2046 0 0         print STDERR 'AllowCommit: $tupleKey = ' . "$tupleKey\n" if ( $CLIC_DEBUG > 4 );
2047             }
2048              
2049 0 0         if ( $isProtected == 1 )
2050             {
2051 0 0 0       if ( $change eq 'U' or $change eq '_U' or $change eq 'UU' )
      0        
2052             {
2053 0           print STDERR "$PROGNAME: commit failed, modifications to protected directories or files is not allowed!\n";
2054 0           print STDERR "$PROGNAME: commit failed on: $_\n";
2055 0           $commit = 0;
2056 0           last;
2057             }
2058             else
2059             {
2060 0           $ok2add = 1; # assume this path has not been added
2061 0 0         if ( $change eq 'D' )
    0          
2062             {
2063 0           $count = int( @del );
2064 0 0         if ( $count > 0 )
2065             {
2066 0           $ref = $del[$count - 1];
2067 0           ( $stmp, $check1st ) = @{ $ref };
  0            
2068 0 0         if ( length( $artifact ) >= length( $check1st ) )
2069             {
2070 0 0         $ok2add = 0 if ( $artifact =~ $check1st );
2071             }
2072             }
2073             }
2074             elsif ( $change eq 'A' ) # hey that is all it can be
2075             {
2076 0           $count = int( @add );
2077 0 0         if ( $count > 0 )
2078             {
2079 0           $ref = $add[$count - 1];
2080 0           ( $stmp, $check1st ) = @{ $ref };
  0            
2081 0 0         if ( length( $artifact ) >= length( $check1st ) )
2082             {
2083 0 0         $ok2add = 0 if ( $artifact =~ $check1st );
2084             }
2085             }
2086             }
2087             else
2088             { # THIS SHOULD NEVER HAPPEN AND IS HERE IN CASE SUBVERSION CHANGES
2089             # this is a safety check - just comment it out to keep on trunking
2090 0           print STDERR "$PROGNAME: commit failed, unknown value for \$change=\"$change\"\n";
2091 0           print STDERR "$PROGNAME: commit failed on: $element\n";
2092 0           $commit = 0;
2093 0           last;
2094             }
2095 0 0         if ( $ok2add )
    0          
2096             {
2097 0           @tmp = ( $tupleKey, $artifact );
2098 0 0         if ( $change eq 'D' )
2099             {
2100 0 0         print STDERR "AllowCommit: push to array of artifacts being deleted: artifact=$artifact\n" if ( $CLIC_DEBUG > 3 );
2101 0           push @del, [@tmp];
2102             }
2103             else
2104             {
2105 0 0         print STDERR "AllowCommit: push to array of artifacts being added: artifact=$artifact\n" if ( $CLIC_DEBUG > 3 );
2106 0           push @add, [@tmp];
2107             }
2108             }
2109             elsif ( $CLIC_DEBUG > 4 )
2110             {
2111 0 0         if ( $change eq 'D' )
2112             {
2113 0           print STDERR "AllowCommit: duplicate, do NOT push to array of artifacts being deleted: artifact=$artifact\n";
2114             }
2115             else
2116             {
2117 0           print STDERR "AllowCommit: duplicate, do NOT push to array of artifacts being added: artifact=$artifact\n";
2118             }
2119             }
2120             }
2121             }
2122             }
2123 0 0         if ( $commit == 1 )
2124             {
2125             # See if attempting a delete only
2126 0 0 0       if ( int( @add ) == 0 && int( @del ) != 0 )
    0 0        
    0 0        
    0 0        
2127             {
2128 0 0         print STDERR "AllowCommit: the protected commit is a DELETE ONLY\n" if ( $CLIC_DEBUG > 3 );
2129 0           $commit = &$SayNoDelete( $artifact ); # always returns 0
2130             }
2131              
2132             # See if attempting an add only
2133             elsif ( int( @add ) != 0 && int( @del ) == 0 )
2134             {
2135 0           $author = &$SvnGetAuthor();
2136 0 0         print STDERR "AllowCommit: the protected commit is an ADD ONLY\n" if ( $CLIC_DEBUG > 3 );
2137 0           $commit = &$TheAddIsAllowed( $author, \@add ); # returns 0 or 1
2138             }
2139              
2140             # See if attempting an add and a delete, only do this if moving a tag to an archive directory
2141             elsif ( int( @add ) != 0 && int( @del ) != 0 )
2142             {
2143 0           $author = &$SvnGetAuthor();
2144 0 0         print STDERR "AllowCommit: the protected commit has both ADD AND DELETE\n" if ( $CLIC_DEBUG > 3 );
2145 0           $commit = &$TheMoveIsAllowed( $element, $author, \@add, \@del ); # returns 0 or 1
2146             }
2147              
2148             # Not attempting anything! What? That's impossible, something is wrong.
2149             elsif ( int( @add ) == 0 && int( @del ) == 0 )
2150             {
2151 0 0         print STDERR "AllowCommit: the protected commit is IMPOSSIPLE\n" if ( $CLIC_DEBUG > 3 );
2152 0           $commit = &$SayImpossible(); # always returns 0
2153             }
2154             }
2155 0 0         if ( $CLIC_DEBUG > 1 )
2156             {
2157 0           print STDERR "AllowCommit: return " . &$returnTF( $commit ) . "\n";
2158             }
2159 0           return $commit;
2160             } # AllowCommit
2161             ################################################################################
2162             ############################# "PUBLIC" SUBROUTINES #############################
2163             # } LEAVE #####################################################################}
2164              
2165             1;
2166             __END__