home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / prgramer / rcs / sources / commit.c < prev    next >
C/C++ Source or Header  |  1992-02-23  |  22KB  |  740 lines

  1. #ifndef lint
  2. static char rcsid[] = "$Id: commit.c,v 1.28.1.2 91/01/29 07:16:59 berliner Exp $";
  3. #endif 
  4.  
  5. /*
  6.  *    Copyright (c) 1989, Brian Berliner
  7.  *
  8.  *    You may distribute under the terms of the GNU General Public License
  9.  *    as specified in the README file that comes with the CVS 1.0 kit.
  10.  *
  11.  * Commit Files
  12.  *
  13.  *    "commit" commits the present version to the RCS repository, AFTER
  14.  *    having done a test on conflicts.  The call is:
  15.  *        cvs commit [options] files...
  16.  *
  17.  *    "commit" accepts the following options:
  18.  *        -f        Force a commit, even if the RCS $Id string
  19.  *                is not found
  20.  *        -n        Causes "commit" to *not* run any commit prog
  21.  *        -a        Commits all files in the current directory
  22.  *                that have been modified.
  23.  *        -m 'message'    Does not start up the editor for the
  24.  *                log message; just gleans it from the
  25.  *                'message' argument.
  26.  *        -r Revision    Allows committing to a particular *numeric*
  27.  *                revision number.
  28.  *
  29.  *    Note that "commit" does not do a recursive commit.  You must do
  30.  *    "commit" in each directory where there are files that you'd
  31.  *    like to commit.
  32.  */
  33.  
  34. #include <sys/types.h>
  35. #include <sys/stat.h>
  36. #include <fcntl.h>
  37. #include <ctype.h>
  38. #include "cvs.h"
  39.  
  40. static int force_commit_no_rcsid = 0;
  41.  
  42. extern int run_module_prog;
  43.  
  44. commit(argc, argv)
  45.     int argc;
  46.     char *argv[];
  47. {
  48.     int commit_all = 0, err = 0;
  49.     char *rev = "";            /* not NULL! */
  50.     char line[MAXLINELEN], message[MAXMESGLEN];
  51.     int c;
  52.  
  53.     if (argc == -1)
  54.     commit_usage();
  55.     /*
  56.      * For log purposes, do not allow "root" to commit files
  57.      */
  58. #ifndef OS2
  59.     if (geteuid() == 0)
  60.     error(0, "cannot commit files as 'root'");
  61. #endif
  62.     optind = 1;
  63.     while ((c = getopt(argc, argv, "fnam:r:")) != -1) {
  64.     switch (c) {
  65.     case 'f':
  66.         force_commit_no_rcsid = 1;
  67.         break;
  68.     case 'n':
  69.         run_module_prog = 0;
  70.         break;
  71.     case 'a':
  72.         commit_all = 1;
  73.         break;
  74.     case 'm':
  75.         use_editor = FALSE;
  76.         if (strlen(optarg) >= sizeof(message)) {
  77.         warn(0, "warning: message too long; truncated!");
  78.         (void) strncpy(message, optarg, sizeof(message));
  79.         message[sizeof(message) - 1] = '\0';
  80.         } else
  81.         (void) strcpy(message, optarg);
  82.         break;
  83.     case 'r':
  84.         if (!isdigit(optarg[0]))
  85.         error(0, "specified revision %s must be numeric!", optarg);
  86.         rev = optarg;
  87.         break;
  88.     case '?':
  89.     default:
  90.         commit_usage();
  91.         break;
  92.     }
  93.     }
  94.     argc -= optind;
  95.     argv += optind;
  96.     if (!commit_all && argc == 0)
  97.     error(0, "must specify the files you'd like to check-in");
  98.     if (commit_all && argc != 0)
  99.     error(0, "cannot specify files with the -a option");
  100.     Name_Repository();
  101.     Writer_Lock();
  102.     if (commit_all) {
  103.     Find_Names(&fileargc, fileargv, ALL);
  104.     argc = fileargc;
  105.     argv = fileargv;
  106.     }
  107.     if (rev[0] != '\0') {
  108.     register int i;
  109.     FILE *fptty;
  110.  
  111.     fptty = open_file(CONSOLE, "r");
  112.     printf("WARNING:\n");
  113.     printf("\tCommitting with a specific revision number\n");
  114.     printf("\tbypasses all consistency checks.  Are you abosulutely\n");
  115.     printf("\tsure you want to continue (y/n) [n] ? ");
  116.     (void) fflush(stdout);
  117.     if (fgets(line, sizeof(line), fptty) == NULL ||
  118.         (line[0] != 'y' && line[0] != 'Y')) {
  119.         error(0, "commit of revision %s aborted", rev);
  120.     }
  121.     (void) fclose(fptty);
  122.     /*
  123.      * When committing with a specific revision number, we simply
  124.      * fudge the lists that Collect_Sets() would have created for
  125.      * us.  This is all so gross, but sometimes useful.
  126.      */
  127.     Clist[0] = Glist[0] = Mlist[0] = Olist[0] = Dlist[0] = '\0';
  128.     Alist[0] = Rlist[0] = Wlist[0] = Llist[0] = Blist[0] = '\0';
  129.     for (i = 0; i < argc; i++) {
  130.         (void) strcat(Mlist, " ");
  131.         (void) strcat(Mlist, argv[i]);
  132.     }
  133.     } else {
  134.     err += Collect_Sets(argc, argv);
  135.     }
  136.     if (err == 0) {
  137.     err += commit_process_lists(message, rev);
  138.     if (err == 0 && run_module_prog) {
  139.         char *cp;
  140.         FILE *fp;
  141.  
  142.         /*
  143.          * It is not an error if Checkin.prog does not exist.
  144.          */
  145.         if ((fp = fopen(CVSADM_CIPROG, "r")) != NULL) {
  146.         if (fgets(line, sizeof(line), fp) != NULL) {
  147.             if ((cp = rindex(line, '\n')) != NULL)
  148.             *cp = '\0';
  149.             (void) sprintf(prog, "%s %s", line, Repository);
  150.             printf("%s %s: Executing '%s'\n", progname, command, prog);
  151.             (void) system(prog);
  152.         }
  153.         (void) fclose(fp);
  154.         }
  155.     }
  156.     Update_Logfile(Repository, message);
  157.     }
  158.     Lock_Cleanup(0);
  159.     exit(err);
  160. }
  161.  
  162. /*
  163.  * Process all the lists, returning the number of errors found.
  164.  */
  165. static
  166. commit_process_lists(message, rev)
  167.     char *message;
  168.     char *rev;
  169. {
  170.     char line[MAXLISTLEN], fname[MAXPATHLEN], revision[50];
  171.     FILE *fp;
  172.     char *cp;
  173.     int first, err = 0;
  174.  
  175.     /*
  176.      * Doesn't make much sense to commit a directory...
  177.      */
  178.     if (Dlist[0])
  179.     warn(0, "committing directories ignored -%s", Dlist);
  180.     /*
  181.      * Is everything up-to-date?
  182.      * Only if Glist, Olist, and Wlist are all NULL!
  183.      */
  184.     if (Glist[0] || Olist[0] || Wlist[0]) {
  185.     (void) fprintf(stderr, "%s: the following files are not ", progname);
  186.     (void) fprintf(stderr,
  187.                "up to date; use '%s update' first:\n", progname);
  188.     if (Glist[0] != '\0')
  189.         (void) fprintf(stderr, "\t%s\n", Glist);
  190.     if (Olist[0] != '\0')
  191.         (void) fprintf(stderr, "\t%s\n", Olist);
  192.     if (Wlist[0] != '\0')
  193.         (void) fprintf(stderr, "\t%s\n", Wlist);
  194.     Lock_Cleanup(0);
  195.     exit(1);
  196.     }
  197.     /*
  198.      * Is there anything to do in the first place?
  199.      */
  200.     if (Mlist[0] == '\0' && Rlist[0] == '\0' && Alist[0] == '\0')
  201.     error(0, "there is nothing to commit!");
  202.     /*
  203.      * First we make sure that the file has an RCS $Id string in it
  204.      * and if it does not, the user is prompted for verification to continue.
  205.      */
  206.     if (force_commit_no_rcsid == 0) {
  207.     (void) strcpy(line, Mlist);
  208.     (void) strcat(line, Alist);
  209.     for (first = 1, cp = strtok(line, " \t"); cp;
  210.         cp = strtok((char *)NULL, " \t")) {
  211.         (void) sprintf(prog, "%s -s %s %s", GREP, RCSID_PAT, cp);
  212.         if (system(prog) != 0) {
  213.         if (first) {
  214.             printf("%s %s: WARNING!\n", progname, command);
  215.             printf("\tThe following file(s) do not contain an RCS $Id keyword:\n");
  216.             first = 0;
  217.         }
  218.         printf("\t\t%s\n", cp);
  219.         }
  220.     }
  221.     if (first == 0) {
  222.         FILE *fptty = open_file(CONSOLE, "r");
  223.         printf("\tAre you sure you want to continue (y/n) [n] ? ");
  224.         (void) fflush(stdout);
  225.         if (fgets(line, sizeof(line), fptty) == NULL ||
  226.         (line[0] != 'y' && line[0] != 'Y')) {
  227.         error(0, "commit aborted");
  228.         }
  229.         (void) fclose(fptty);
  230.     }
  231.     }
  232.     if (use_editor)
  233.     do_editor(message);
  234.     /*
  235.      * Mlist is the "modified, needs committing" list
  236.      */
  237.     (void) strcpy(line, Mlist);
  238.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  239.     (void) strcpy(User, cp);
  240.     (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  241.     if (lock_RCS(rev) != 0)
  242.         err++;
  243.     }
  244.     /*
  245.      * Rlist is the "to be removed" list
  246.      */
  247.     (void) strcpy(line, Rlist);
  248.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  249.     (void) strcpy(User, cp);
  250.     (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  251.     if (lock_RCS(rev) != 0)
  252.         err++;
  253.     }
  254.     /*
  255.      * Alist is the "to be added" list
  256.      */
  257.     (void) strcpy(line, Alist);
  258.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  259.     (void) strcpy(User, cp);
  260.     (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  261.     (void) sprintf(prog, "%s -i -t%s%c%s", RCS, CVSEXT_LOG, DIRSEP, User);
  262.     (void) sprintf(fname, "%s%c%s", CVSEXT_OPT, DIRSEP, User);
  263.     fp = open_file(fname, "r");
  264.     while (fgets(fname, sizeof(fname), fp) != NULL) {
  265.         if ((cp = rindex(fname, '\n')) != NULL)
  266.         *cp = '\0';
  267.         (void) strcat(prog, " ");
  268.         (void) strcat(prog, fname);
  269.     }
  270.     (void) fclose(fp);
  271.     (void) strcat(prog, " ");
  272.     (void) strcat(prog, Rcs);
  273.     if (system(prog) == 0) {
  274.         fix_rcs_modes(Rcs, User);
  275.     } else {
  276.         warn(0, "could not create %s", Rcs);
  277.         err++;
  278.     }
  279.     }
  280.     /*
  281.      * If something failed, release all locks and restore the default
  282.      * branches
  283.      */
  284.     if (err) {
  285.     int didllist = 0;
  286.     char *branch;
  287.  
  288.     for (cp = strtok(Llist, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  289.         didllist = 1;
  290.         (void) strcpy(User, cp);
  291.         (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  292.         (void) sprintf(prog, "%s -q -u %s", RCS, Rcs);
  293.         if (system(prog) != 0)
  294.         warn(0, "could not UNlock %s", Rcs);
  295.     }
  296.     if (didllist) {
  297.         for (cp=strtok(Blist, " \t"); cp; cp=strtok((char *)NULL, " \t")) {
  298.         if ((branch = rindex(cp, ':')) == NULL)
  299.             continue;
  300.         *branch++ = '\0';
  301.         (void) strcpy(User, cp);
  302.         (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  303.         (void) sprintf(prog, "%s -q -b%s %s", RCS, branch, Rcs);
  304.         if (system(prog) != 0)
  305.             warn(0, "could not restore branch %s to %s", branch, Rcs);
  306.         }
  307.     }
  308.     for (cp = strtok(Alist, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  309.         (void) strcpy(User, cp);
  310.         (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  311.         (void) unlink(Rcs);
  312.     }
  313.     Lock_Cleanup(0);
  314.     exit(1);
  315.     }
  316.     /*
  317.      * Got them all, now go ahead;
  318.      * First, add the files in the Alist
  319.      */
  320.     if (Alist[0] != '\0') {
  321.     int maxrev, rev;
  322.  
  323.     /* scan the entries file looking for the max revision number */
  324.     fp = open_file(CVSADM_ENT, "r");
  325.     maxrev = 0;
  326.     while (fgets(line, sizeof(line), fp) != NULL) {
  327.         rev = atoi(line);
  328.         if (rev > maxrev)
  329.         maxrev = rev;
  330.     }
  331.     if (maxrev == 0)
  332.         maxrev = 1;
  333.     (void) fclose(fp);
  334.     (void) sprintf(revision, "-r%d", maxrev);
  335.     (void) strcpy(line, Alist);
  336.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  337.         (void) strcpy(User, cp);
  338.         if (Checkin(revision, message) != 0)
  339.         err++;
  340.         (void) sprintf(fname, "%s%c%s", CVSEXT_OPT, DIRSEP, User);
  341.         (void) unlink(fname);
  342.         (void) rmdir(CVSEXT_OPT);
  343.         (void) sprintf(fname, "%s%c%s", CVSEXT_LOG, DIRSEP, User);
  344.         (void) unlink(fname);
  345.         (void) rmdir(CVSEXT_LOG);
  346.     }
  347.     }
  348.     /*
  349.      * Everyone else uses the head as it is set in the RCS file,
  350.      * or the revision that was specified on the command line.
  351.      */
  352.     if (rev[0] != '\0')
  353.     (void) sprintf(revision, "-r%s", rev);
  354.     else
  355.     revision[0] = '\0';
  356.     /*
  357.      * Commit the user modified files in Mlist
  358.      */
  359.     (void) strcpy(line, Mlist);
  360.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  361.     (void) strcpy(User, cp);
  362.     if (Checkin(revision, message) != 0)
  363.         err++;
  364.     }
  365.     /*
  366.      * And remove the RCS files in Rlist, by placing it in the Attic
  367.      */
  368.     (void) strcpy(line, Rlist);
  369.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  370.     int omask;
  371.  
  372.     (void) strcpy(User, cp);
  373.     (void) sprintf(Rcs, "%s%c%s%s", Repository, DIRSEP, User, RCSEXT);
  374.     (void) sprintf(fname, "%s%c%s", Repository, DIRSEP, CVSATTIC);
  375.     omask = umask(2);
  376.     (void) mkdir(fname, 0777);
  377.     (void) umask(omask);
  378.     (void) sprintf(fname, "%s%c%s%c%s%s", Repository, DIRSEP, CVSATTIC,
  379.                DIRSEP, User, RCSEXT);
  380.     (void) sprintf(prog, "%s -u -q %s", RCS, Rcs);
  381.     if ((system(prog) == 0 && rename(Rcs, fname) != -1) ||
  382.         (!isreadable(Rcs) && isreadable(fname)))
  383.         Scratch_Entry(User);
  384.     else
  385.         err++;
  386.     }
  387.     return (err);
  388. }
  389.  
  390. /*
  391.  * Attempt to place a lock on the RCS file; returns 0 if it could and
  392.  * 1 if it couldn't.  If the RCS file currently has a branch as the head,
  393.  * we must move the head back to the trunk before locking the file, and
  394.  * be sure to put the branch back as the head if there are any errors.
  395.  */
  396. static
  397. lock_RCS(rev)
  398.     char *rev;
  399. {
  400.     char branch[50];
  401.     int err = 0;
  402.  
  403.     branch[0] = '\0';
  404.     /*
  405.      * For a specified, numeric revision of the form "1" or "1.1",
  406.      * (or when no revision is specified ""), definitely move the
  407.      * branch to the trunk before locking the RCS file.
  408.      *
  409.      * The assumption is that if there is more than one revision
  410.      * on the trunk, the head points to the trunk, not a branch...
  411.      * and as such, it's not necessary to move the head in this case.
  412.      */
  413.     if (numdots(rev) < 2) {
  414.     branch_number(Rcs, branch);
  415.     if (branch[0] != '\0') {
  416.         (void) sprintf(prog, "%s -q -b %s", RCS, Rcs);
  417.         if (system(prog) != 0) {
  418.         warn(0, "cannot change branch to default for %s", Rcs);
  419.         return (1);
  420.         }
  421.     }
  422.     (void) sprintf(prog, "%s -q -l %s", RCS, Rcs);
  423.     err = system(prog);
  424.     } else {
  425.     (void) sprintf(prog, "%s -q -l%s %s 2>%s", RCS, rev, Rcs, DEVNULL);
  426.     (void) system(prog);
  427.     }
  428.     if (err == 0) {
  429.     (void) strcat(Llist, " ");
  430.     (void) strcat(Llist, User);
  431.     (void) strcat(Blist, " ");
  432.     (void) strcat(Blist, User);
  433.     if (branch[0] != '\0') {
  434.         (void) strcat(Blist, ":");
  435.         (void) strcat(Blist, branch);
  436.     }
  437.     return (0);
  438.     }
  439.     if (branch[0] != '\0') {
  440.     (void) sprintf(prog, "%s -q -b%s %s", RCS, branch, Rcs);
  441.     if (system(prog) != 0)
  442.         warn(0, "cannot restore branch to %s for %s", branch, Rcs);
  443.     }
  444.     return (1);
  445. }
  446.  
  447. /*
  448.  * A special function used only by lock_RCS() to determine if the current
  449.  * head is pointed at a branch.  Returns the result in "branch" as a null
  450.  * string if the trunk is the head, or as the branch number if the branch
  451.  * is the head.
  452.  */
  453. static
  454. branch_number(rcs, branch)
  455.     char *rcs;
  456.     char *branch;
  457. {
  458.     char line[MAXLINELEN];
  459.     FILE *fp;
  460.     char *cp;
  461.  
  462.     branch[0] = '\0';            /* Assume trunk is head */
  463.     fp = open_file(rcs, "r");
  464.     if (fgets(line, sizeof(line), fp) == NULL) {
  465.     (void) fclose(fp);
  466.     return 0;
  467.     }
  468.     if (fgets(line, sizeof(line), fp) == NULL) {
  469.     (void) fclose(fp);
  470.     return 0;
  471.     }
  472.     (void) fclose(fp);
  473.     if (strncmp(line, RCSBRANCH, sizeof(RCSBRANCH) - 1) != 0 ||
  474.     !isspace(line[sizeof(RCSBRANCH) - 1]) ||
  475.     (cp = rindex(line, ';')) == NULL)
  476.     return 0;
  477.     *cp = '\0';                /* strip the ';' */
  478.     if ((cp = rindex(line, ' ')) == NULL &&
  479.     (cp = rindex(line, '\t')) == NULL)
  480.     return 0;
  481.     cp++;
  482.     if (*cp == 0)
  483.     return 0;
  484.     (void) strcpy(branch, cp);
  485. }
  486.  
  487. /*
  488.  * Puts a standard header on the output which is either being prepared for
  489.  * an editor session, or being sent to a logfile program.  The modified, added,
  490.  * and removed files are included (if any) and formatted to look pretty.
  491.  */
  492. static
  493. setup_tmpfile(fp, prefix)
  494.     FILE *fp;
  495.     char *prefix;
  496. {
  497.     if (Mlist[0] != '\0') {
  498.     (void) fprintf(fp, "%sModified Files:\n", prefix);
  499.     fmt(fp, Mlist, prefix);
  500.     }
  501.     if (Alist[0] != '\0') {
  502.     (void) fprintf(fp, "%sAdded Files:\n", prefix);
  503.     fmt(fp, Alist, prefix);
  504.     }
  505.     if (Rlist[0] != '\0') {
  506.     (void) fprintf(fp, "%sRemoved Files:\n", prefix);
  507.     fmt(fp, Rlist, prefix);
  508.     }
  509. }
  510.  
  511. /*
  512.  * Breaks the files list into reasonable sized lines to avoid line
  513.  * wrap...  all in the name of pretty output.
  514.  */
  515. static
  516. fmt(fp, instring, prefix)
  517.     FILE *fp;
  518.     char *instring;
  519.     char *prefix;
  520. {
  521.     char line[MAXLISTLEN];    /* UNOFFICIAL bug fix, was MAXLINELEN */
  522.     char *cp;
  523.     int col;
  524.  
  525.     (void) strcpy(line, instring);    /* since strtok() is destructive */
  526.     (void) fprintf(fp, "%s\t", prefix);
  527.     col = 8;                /* assumes that prefix is < 8 chars */
  528.     for (cp = strtok(line, " \t"); cp; cp = strtok((char *)NULL, " \t")) {
  529.     if ((col + strlen(cp)) > 70) {
  530.         (void) fprintf(fp, "\n%s\t", prefix);
  531.         col = 8;
  532.     }
  533.     (void) fprintf(fp, "%s ", cp);
  534.     col += strlen(cp) + 1;
  535.     }
  536.     (void) fprintf(fp, "\n%s\n", prefix);
  537. }
  538.  
  539. /*
  540.  * Builds a temporary file using setup_tmpfile() and invokes the user's
  541.  * editor on the file.  The header garbage in the resultant file is then
  542.  * stripped and the log message is stored in the "message" argument.
  543.  */
  544. static
  545. do_editor(message)
  546.     char *message;
  547. {
  548.     FILE *fp;
  549.     char line[MAXLINELEN], fname[MAXPATHLEN];
  550.     int fd;
  551.  
  552.     message[0] = '\0';
  553.     (void) strcpy(fname, CVSTEMP);
  554.     if ((fd = mkstemp(fname)) < 0)
  555.     error(0, "cannot create temporary file %s", fname);
  556.     if ((fp = fdopen(fd, "w+")) == NULL)
  557.     error(0, "cannot create FILE * to %s", fname);
  558.     setup_tmpfile(fp, CVSEDITPREFIX);
  559.     (void) fprintf(fp, "%sEnter Log.  Lines beginning with '%s' are removed automatically\n",
  560.            CVSEDITPREFIX, CVSEDITPREFIX);
  561.     (void) fprintf(fp, "%s----------------------------------------------------------------------\n", CVSEDITPREFIX);
  562.     (void) fclose(fp);
  563.     (void) sprintf(prog, "%s %s", Editor, fname);
  564.     if (system(prog) != 0)
  565.     warn(0, "warning: editor session failed");
  566.     fp = open_file(fname, "r");
  567.     while (fgets(line, sizeof(line), fp) != NULL) {
  568.     if (strncmp(line, CVSEDITPREFIX, sizeof(CVSEDITPREFIX)-1) == 0)
  569.         continue;
  570.     if ((strlen(message) + strlen(line)) >= MAXMESGLEN) {
  571.         warn(0, "warning: log message truncated!");
  572.         break;
  573.     }
  574.     (void) strcat(message, line);
  575.     }
  576.     (void) fclose(fp);
  577.     (void) unlink(fname);
  578. }
  579.  
  580. /*
  581.  * Uses setup_tmpfile() to pass the updated message on directly to
  582.  * any logfile programs that have a regular expression match for the
  583.  * checked in directory in the source repository.  The log information
  584.  * is fed into the specified program as standard input.
  585.  */
  586. Update_Logfile(repository, message)
  587.     char *repository;
  588.     char *message;
  589. {
  590.     FILE *fp_info;
  591.     char logfile[MAXPATHLEN], title[MAXLISTLEN+MAXPATHLEN], line[MAXLINELEN];
  592.     char path[MAXPATHLEN], default_filter[MAXLINELEN];
  593.     char *exp, *filter, *cp, *short_repository;
  594.     int filter_run, line_number;
  595.  
  596.     if (CVSroot == NULL) {
  597.     warn(0, "CVSROOT variable not set; no log message will be sent");
  598.     return 0;
  599.     }
  600.     (void) sprintf(logfile, "%s%c%s", CVSroot, DIRSEP, CVSROOTADM_LOGINFO);
  601.     if ((fp_info = fopen(logfile, "r")) == NULL) {
  602.     warn(0, "warning: cannot open %s", logfile);
  603.     return 0;
  604.     }
  605.     if (CVSroot != NULL)
  606.     (void) sprintf(path, "%s%c", CVSroot, DIRSEP);
  607.     else
  608.     (void) strcpy(path, REPOS_STRIP);
  609.     if (strncmp(repository, path, strlen(path)) == 0)
  610.     short_repository = repository + strlen(path);
  611.     else
  612.     short_repository = repository;
  613.     (void) sprintf(title, "'%s%s'", short_repository, Llist);
  614.     default_filter[0] = '\0';
  615.     filter_run = line_number = 0;
  616.     while (fgets(line, sizeof(line), fp_info) != NULL) {
  617.     line_number++;
  618.     if (line[0] == '#')
  619.         continue;
  620.     for (cp = line; *cp && isspace(*cp); cp++)
  621.         ;
  622.     if (*cp == '\0')
  623.         continue;            /* blank line */
  624.     for (exp = cp; *cp && !isspace(*cp); cp++)
  625.         ;
  626.     if (*cp != '\0')
  627.         *cp++ = '\0';
  628.     while (*cp && isspace(*cp))
  629.         cp++;
  630.     if (*cp == '\0') {
  631.         warn(0, "syntax error at line %d file %s; ignored",
  632.          line_number, logfile);
  633.         continue;
  634.     }
  635.     filter = cp;
  636.     if ((cp = rindex(filter, '\n')) != NULL)
  637.         *cp = '\0';            /* strip the newline */
  638.     /*
  639.      * At this point, exp points to the regular expression, and
  640.      * filter points to the program to exec.  Evaluate the regular
  641.      * expression against short_repository and exec the filter
  642.      * if it matches.
  643.      */
  644.     if (strcmp(exp, "DEFAULT") == 0) {
  645.         (void) strcpy(default_filter, filter);
  646.         continue;
  647.     }
  648.     /*
  649.      * For a regular expression of "ALL", send the log message
  650.      * to the requested filter *without* noting that a filter was run.
  651.      * This allows the "DEFAULT" regular expression to be more
  652.      * meaningful with all updates going to a master log file.
  653.      */
  654.     if (strcmp(exp, "ALL") == 0) {
  655.         (void) logfile_write(repository, filter, title, message);
  656.         continue;
  657.     }
  658.     if ((cp = re_comp(exp)) != NULL) {
  659.         warn(0, "bad regular expression at line %d file %s: %s",
  660.          line_number, logfile, cp);
  661.         continue;
  662.     }
  663.     if (re_exec(short_repository) == 0)
  664.         continue;            /* no match */
  665.     if (logfile_write(repository, filter, title, message) == 0)
  666.         filter_run = 1;
  667.     }
  668.     if (filter_run == 0 && default_filter[0] != '\0')
  669.     (void) logfile_write(repository, default_filter, title, message);
  670. }
  671.  
  672. /*
  673.  * Since some systems don't define this...
  674.  */
  675. #ifndef MAXHOSTNAMELEN
  676. #define    MAXHOSTNAMELEN    64
  677. #endif
  678.  
  679. /*
  680.  * Writes some stuff to the logfile "filter" and returns the status of the
  681.  * filter program.
  682.  */
  683. static
  684. logfile_write(repository, filter, title, message)
  685.     char *repository;
  686.     char *filter;
  687.     char *title;
  688.     char *message;
  689. {
  690.     char cwd[MAXPATHLEN], host[MAXHOSTNAMELEN];
  691.     FILE *fp;
  692.     char *cp;
  693.  
  694.     /*
  695.      * A maximum of 6 %s arguments are supported in the filter
  696.      */
  697.     (void) sprintf(prog, filter, title, title, title, title, title, title);
  698.     if ((fp = popen(prog, "w")) == NULL) {
  699.     warn(0, "cannot write entry to log filter: %s", prog);
  700.     return (1);
  701.     }
  702.     if (gethostname(host, sizeof(host)) < 0)
  703.     (void) strcpy(host, "(unknown)");
  704.     (void) fprintf(fp, "Update of %s\n", repository);
  705.     (void) fprintf(fp, "In directory %s:%s\n\n", host,
  706.            (cp = getwd(cwd)) ? cp : cwd);
  707.     setup_tmpfile(fp, "");
  708.     (void) fprintf(fp, "Log Message:\n%s\n", message);
  709.     return (pclose(fp));
  710. }
  711.  
  712. /*
  713.  * Called when "add"ing files to the RCS respository, as it is necessary
  714.  * to preserve the file modes in the same fashion that RCS does.  This would
  715.  * be automatic except that we are placing the RCS ,v file very far away from
  716.  * the user file, and I can't seem to convince RCS of the location of the
  717.  * user file.  So we munge it here, after the ,v file has been successfully
  718.  * initialized with "rcs -i".
  719.  */
  720. static
  721. fix_rcs_modes(rcs, user)
  722.     char *rcs;
  723.     char *user;
  724. {
  725.     struct stat sb;
  726.  
  727.     if (stat(user, &sb) != -1) {
  728.     (void) chmod(rcs, (int) sb.st_mode & ~0222);
  729.     }
  730. }
  731.  
  732. static
  733. commit_usage()
  734. {
  735.     (void) fprintf(stderr,
  736.     "%s %s [-fn] [-a] [-m 'message'] [-r revision] [files...]\n",
  737.            progname, command);
  738.     exit(1);
  739. }
  740.