home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / cvs110.zip / cvs / scripts / commitinfo.cmd < prev    next >
OS/2 REXX Batch file  |  1998-08-21  |  13KB  |  461 lines

  1. /*
  2. ** $Id: commitinfo.cmd,v 1.1.2.1 1998/06/23 12:32:35 ahuber Exp $
  3. **
  4. ** Rexx filter to handle pre-commit checking of files. This program
  5. ** records the last directory where commits will be taking place for
  6. ** use by the loginfo script. For new files, it optionally forces the
  7. ** existence of an RCS "Id" keyword in the first ten lines of the file.
  8. ** For existing files, it checks the version number on the "Id" line
  9. ** to prevent losing changes because an old version of a file was
  10. ** copied into the directory.
  11. **
  12. ** It also performs access control for matching repository names.
  13. **
  14. ** Possible future enhancements:
  15. **
  16. **  Check for cruft left by unresolved conflicts. Search for
  17. **  "^<<<<<<<$", "^-------$", and "^>>>>>>>$".
  18. **
  19. ** Original perl version by David Hampton <hampton@cisco.com>
  20. ** and 'hacked on lots' by Greg A. Woods <woods@web.net>.
  21. **
  22. ** Access control lists originally by David G. Grubbs <dgg@ksr.com>.
  23. **
  24. ** Usage: commitinfo [-?lrcdA] repository files...
  25. **
  26. ** Options:
  27. **  -?  Display usage information.
  28. **  -r  Record this directory as the last one checked.
  29. **  -c  Check the "Id" keyword.
  30. **  -l  Check for existence of a "Log" keyword (not implemented).
  31. **  -d  Enable debugging (not implemented).
  32. **  -A  Perform access control.
  33. **
  34. ** Copyright (C) 1998  Andreas Huber <ahuber@ping.at>
  35. **
  36. ** This program is free software; you can redistribute it and/or
  37. ** modify it under the terms of the GNU General Public License
  38. ** as published by the Free Software Foundation; either version 2
  39. ** of the License, or (at your option) any later version.
  40. **
  41. ** This program is distributed in the hope that it will be useful,
  42. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  43. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  44. ** GNU General Public License for more details.
  45. **
  46. ** You should have received a copy of the GNU General Public License
  47. ** along with this program; see the file COPYING. If not, write to
  48. ** the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  49. ** Boston, MA 02111-1307, USA.
  50. */
  51. call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
  52. call SysLoadFuncs
  53.  
  54. /*
  55. ** Constants.
  56. */
  57. EXIT_SUCCESS    = 0
  58. EXIT_FAILURE    = 1
  59. EXIT_USAGE        = 2
  60. EXIT_SIGNAL        = 3
  61.  
  62. FALSE            = 0
  63. TRUE            = \FALSE
  64.  
  65. TAB                = d2c(9)
  66.  
  67. TEMP_DIR        = value('TMPDIR',, 'OS2ENVIRONMENT')
  68. LAST_FILE        = TEMP_DIR||'/#cvs.files.lastdir'
  69.  
  70. CVSROOT            = get_repository()
  71.  
  72. AVAIL_FILE        = CVSROOT||'/CVSROOT/avail'
  73.  
  74. ENTRIES            = 'CVS/Entries'
  75.  
  76. no_log            =,
  77. '%s - Contains an RCS $'||Log||'$ keyword.  It must not!\n'
  78.  
  79. no_id            =,
  80. '%s - Does not contain a properly formatted line with the keyword "Id:".\n',
  81. '       Please see the template files for an example.\n'
  82.  
  83. no_name            =,
  84. '%s - The ID line should contain only "$'||'Id'||'$"\n',
  85. '       for a newly created file.\n'
  86.  
  87. bad_name        =,
  88. "%s - The file name '%s' in the ID line does not match\n",
  89. "       the actual filename.\n"
  90.  
  91. bad_version        =,
  92. "%s - How dare you!!!  You replaced your copy of the file '%s'\n",
  93. "       which was based upon version '%s', with an %s version based\n",
  94. "       upon %s.  Please move your '%s' out of the way, perform an\n",
  95. "       update to get the current version, and then merge your changes\n",
  96. "       into that file, then try the commit again.\n"
  97.  
  98. /*
  99. ** Configurable options.
  100. */
  101. debug                = FALSE
  102.  
  103. /*
  104. ** Check each file (except dot files) for an RCS "Log" keyword.
  105. */
  106. check_log            = FALSE
  107.  
  108. /*
  109. ** Check each file (except dot files) for an RCS "Id" keyword.
  110. */
  111. check_id            = FALSE
  112.  
  113. /*
  114. ** Record the directory for later use by the loginfo script.
  115. */
  116. record_directory    = FALSE
  117.  
  118. /*
  119. ** Check if user has commit privilege.
  120. */
  121. access_control        = FALSE
  122.  
  123. /*
  124. ** Global names known to all procedures.
  125. */
  126. globals = 'EXIT_SUCCESS EXIT_FAILURE EXIT_USAGE EXIT_SIGNAL',
  127.     'FALSE TRUE TAB argv. argc',
  128.     'TEMP_DIR LAST_FILE AVAIL_FILE ENTRIES CVSROOT',
  129.     'no_log no_id no_name bad_name bad_version',
  130.     'debug check_log check_id record_directory'
  131.  
  132. /*
  133. ** Main body.
  134. */
  135. main:
  136.     argc = 1; argv. = ''; argv.0 = 'commitinfo'
  137.     signal on halt name signal_handler
  138.     do i = 1 to arg(); call setargv arg(i); end
  139.     options = '?rcldA'
  140.     optind = 0
  141.     do forever
  142.         c = getopt(options)
  143.         if c <= 0 then leave
  144.         select
  145.             when c = '?' | c = ':' then call usage
  146.             when c = 'r' then record_directory = TRUE
  147.             when c = 'c' then check_id = TRUE
  148.             when c = 'l' then check_log = TRUE
  149.             when c = 'd' then debug = TRUE
  150.             when c = 'A' then access_control = TRUE
  151.             otherwise exit EXIT_FAILURE
  152.         end
  153.     end
  154.     if optind+2 > argc then call usage
  155.     id = SysGetPPid()
  156.     if access_control then if \check_access() then
  157.         exit EXIT_FAILURE
  158.     dir = argv.optind; optind = optind+1
  159. /*
  160. ** Now check each file name passed in, except for dot files. Dot files
  161. ** are considered to be administrative file by this script.
  162. */
  163.     if check_id then do
  164.         call read_entries
  165.         failed = 0
  166.         do while optind < argc
  167.             file = argv.optind
  168.             optind = optind+1
  169.             if pos(file, '.') = 1 then iterate
  170.             failed = failed+check_version(file)
  171.         end
  172.         if failed > 0 then do
  173.             call lineout ''
  174.             exit EXIT_FAILURE
  175.         end
  176.     end
  177. /*
  178. ** Record this directory as the last one checked. This will be used
  179. ** by the loginfo script to determine when it is processing the final
  180. ** directory of a multi-directory commit.
  181. */
  182.     if record_directory then
  183.         call write_line LAST_FILE||'.'||id, dir
  184.     exit EXIT_SUCCESS
  185.  
  186. /*
  187. ** Subroutines.
  188. */
  189. /*
  190. ** CVS passes to argv. an absolute directory pathname (the repository
  191. ** appended to your %CVSROOT% environment variable), followed by a list
  192. ** of filenames within that directory.
  193. **
  194. ** We walk through the avail file looking for a line that matches both
  195. ** the username and repository.
  196. **
  197. ** A username match is simply the user's name appearing in the second
  198. ** column of the avail line in a space-or-comma separate list.
  199. **
  200. ** A repository match is either:
  201. **  - One element of the third column matches argv.1, or some
  202. **    parent directory of argv.1.
  203. **  - Otherwise *all* file arguments (argv.2..argv.(argc-1)) must
  204. **    be in the file list in one avail line.
  205. **  - In other words, using directory names in the third column of
  206. **    the avail file allows committing of any file (or group of
  207. **    files in a single commit) in the tree below that directory.
  208. **  - If individual file names are used in the third column of
  209. **    the avail file, then files must be committed individually or
  210. **    all files specified in a single commit must all appear in
  211. **    third column of a single avail line.
  212. */
  213. check_access: procedure expose (globals) optind
  214.     user_name = value('LOGNAME',, 'OS2ENVIRONMENT')
  215.     if user_name = '' then
  216.         user_name = value('USER',, 'OS2ENVIRONMENT')
  217.     if stream(AVAIL_FILE, 'c', 'open read') \= 'READY:' then
  218.         exit EXIT_SUCCESS
  219.     parse var argv.optind (CVSROOT) '/' dir
  220.     if dir = '' then dir = argv.optind
  221.     access_granted = TRUE
  222.     universal_off = FALSE
  223.     do while lines(AVAIL_FILE) > 0
  224.         line = space(translate(linein(AVAIL_FILE), '  ', ','||TAB))
  225.         if line = ' ' | left(line, 1) = '#' then iterate
  226.         parse var line avail '|' user_list '|' file_list
  227.         if abbrev('available', avail) then
  228.             avail = TRUE
  229.         else if abbrev('unavailable', avail) then
  230.             avail = FALSE
  231.         else do
  232.             say 'Bad avail line:' line
  233.             iterate
  234.         end
  235.         if \avail & user_list = '' & file_list = '' then
  236.             universal_off = TRUE
  237.         in_user = user_list = '' | wordpos(user_name, user_list) > 0
  238.         in_repo = file_list = ''
  239.         if \in_repo then do
  240.             do i = 1 to words(file_list) while \in_repo
  241.                 file = word(file_list, i)
  242.                 in_repo = dir = file | abbrev(dir, file);
  243.             end
  244.             if \in_repo then do
  245.                 in_repo = TRUE;
  246.                 do i = optind to argc-1 while in_repo
  247.                     file = dir||'/'||argv.i
  248.                     in_repo = wordpos(file, file_list) > 0
  249.                 end
  250.             end
  251.         end
  252.         if in_user & in_repo then
  253.             access_granted = avail
  254.     end
  255.     call stream AVAIL_FILE, 'c', 'close'
  256.     if \access_granted then do
  257.         say '**** Access denied: Insufficient Karma ('||user_name||,
  258.             '|'||dir||').'
  259.     end
  260.     else if universal_off then
  261.         say '**** Access granted: Personal Karma exceeds Environmental',
  262.             'Karma.'
  263.     return access_granted
  264.  
  265. /*
  266. ** Suck in the Entries file.
  267. */
  268. read_entries: procedure expose (globals) cvsversion.
  269.     if stream(ENTRIES, 'c', 'open read') \= 'READY:' then
  270.         call die 'Cannot open' ENTRIES 'for reading.'
  271.     cvsversion. = ''
  272.     do while lines(ENTRIES) > 0
  273.         parse value linein(ENTRIES) with,
  274.             . '/' filename '/' version '/'
  275.         cvsversion.filename = version
  276.     end
  277.     call stream ENTRIES, 'c', 'close'
  278.     return
  279.  
  280. check_version: procedure expose (globals) cvsversion.
  281.     parse arg filename
  282.     if stream(filename, 'c', 'open read') \= 'READY:' then
  283.         call die 'Cannot open' filename 'for reading.'
  284.     pos = 0
  285.     do i = 1 to 10 while lines(filename) > 0
  286.         parse value linein(filename) with line
  287.         pos = pos('$Id', line)
  288.         if pos > 0 then leave
  289.     end
  290.     if pos = 0 then do
  291.         call printf no_id, filename
  292.         return 1
  293.     end
  294.     parse value substr(line, pos) with '$' id rname version . '$'
  295.     if cvsversion.filename = '0' then do
  296.         if rname \= '' then do
  297.             call printf no_name, filename
  298.             return 1
  299.         end
  300.         return 0
  301.     end
  302.     if rname \= filename||',v' then do
  303.         call printf bad_name, filename, left(rname, length(rname)-2)
  304.         return 1
  305.     end
  306.     i = compare_versions(cvsversion.filename, version)
  307.     if i < 0 then do
  308.         call printf bad_version, filename, filename,,
  309.             cvsversion.filename, 'newer', version, filename
  310.         return 1
  311.     end
  312.     if i > 0 then do
  313.         call printf bad_version, filename, filename,,
  314.             cvsversion.filename, 'older', version, filename
  315.         return 1
  316.     end
  317.     return 0
  318.  
  319. compare_versions: procedure
  320.     parse arg a, b
  321.     do forever
  322.         parse var a i '.' a
  323.         parse var b j '.' b
  324.         if i \= j then leave
  325.     end
  326.     if i < j then
  327.         return -1
  328.     else if i > j then
  329.         return +1
  330.     return 0
  331.  
  332. write_line: procedure expose (globals)
  333.     parse arg filename, line
  334.     call SysFileDelete translate(filename, '\', '/')
  335.     if stream(filename, 'c', 'open write') \= 'READY:' then
  336.         die 'Cannot open' filename 'for writing.'
  337.     call lineout filename, line
  338.     call stream filename, 'c', 'close'
  339.     return
  340.  
  341. printf: procedure
  342.     format = arg(1)
  343.     i = 1
  344.     do while format \= ''
  345.         p = verify(format, '%\', 'm')
  346.         if p = 0 then do
  347.             call charout 'stderr:', format
  348.             leave
  349.         end
  350.         if substr(format, p, 2) = '%s' then do
  351.             call charout 'stderr:', left(format, p-1)
  352.             i = i+1
  353.             call charout 'stderr:', arg(i)
  354.         end
  355.         else if substr(format, p, 2) = '\n' then
  356.             call lineout 'stderr:', left(format, p-1)
  357.         else
  358.             call charout 'stderr:', left(format, p+1)
  359.         format = substr(format, p+2)
  360.     end
  361.     return
  362.  
  363. get_repository: procedure
  364.     cvsroot = value('CVSROOT',, 'OS2ENVIRONMENT')
  365.     if left(cvsroot, 1) = ':' then
  366.         parse var cvsroot ':' method ':' cvsroot
  367.     else if pos(cvsroot, ':') = 0 then
  368.         method = 'local'
  369.     else
  370.         method = 'server'
  371.     if method \= 'local' then
  372.         parse var cvsroot . ':' cvsroot
  373.     return cvsroot
  374.  
  375. usage: procedure expose (globals)
  376.     say 'Usage: '||argv.0||' [-?lrcdA] repository files...'
  377.     exit EXIT_USAGE
  378.  
  379. die: procedure expose (globals)
  380.     parse arg text
  381.     call lineout 'stderr:', argv.0||': '||text
  382.     exit EXIT_FAILURE
  383.  
  384. getopt: procedure expose (globals) optind optarg optopt optptr
  385.     parse arg options
  386.     if optind = 0 then optptr = 0
  387.     if optptr = 0 | optptr > length(argv.optind) then do
  388.         if optind >= argc then return -1
  389.         optind = optind+1
  390.         optptr = 1
  391.         if substr(argv.optind, optptr, 1) \= '-' then return 0
  392.         optptr = optptr+1
  393.     end
  394.     optopt = substr(argv.optind, optptr, 1)
  395.     optptr = optptr+1
  396.     if optopt = '-' then do
  397.         optind = optind+1
  398.         optptr = 0
  399.         return -1
  400.     end
  401.     i = pos(optopt, options)
  402.     if optopt = ':' | i = 0 then do
  403.         say argv.0||': -'||optopt||' is not a valid option.'
  404.         return '?'
  405.     end
  406.     if substr(options, i+1, 1) = ':' then do
  407.         if optptr <= length(argv.optind) then do
  408.             optarg = substr(argv.optind, optptr)
  409.             optptr = 0
  410.             return optopt;
  411.         end
  412.         if substr(options, i+2, 1) = ':' then do
  413.             i = optind+1
  414.             optptr = 1
  415.             if i < argc & substr(argv.i, optptr, 1) \= '-' then do
  416.                 optind = i
  417.                 optarg = argv.optind
  418.                 optptr = 0
  419.                 return optopt
  420.             end
  421.             say argv.0||': -'||optopt||' is missing an argument.'
  422.             return ':'
  423.         end
  424.         optptr = 0
  425.     end
  426.     optarg = ''
  427.     return optopt
  428.  
  429. setargv: procedure expose (globals)
  430.     parse arg args
  431.     inquote = FALSE
  432.     do forever
  433.         parse var args arg args
  434.         if arg = '' then leave
  435.         quotes = FALSE
  436.         i = 1
  437.         do forever
  438.             i = pos('"', arg, i)
  439.             if i = 0 then leave
  440.                 if i > 1 then if substr(arg, i-1, 1) = '\' then do
  441.                 arg = delstr(arg, i-1, 1)
  442.                 iterate
  443.             end
  444.             arg = delstr(arg, i, 1)
  445.             quotes = \quotes
  446.         end
  447.         if inquote then
  448.             argv.argc = argv.argc arg
  449.         else do
  450.             argv.argc = arg
  451.             argc = argc+1
  452.         end
  453.         if quotes then inquote = \inquote
  454.     end
  455.     return
  456.  
  457. signal_handler:
  458.     call lineout 'stderr:', argv.0||': interrupted by SIGINT.'
  459.     exit EXIT_SIGNAL
  460.  
  461.