home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / cvs-1.8.7-src.tgz / tar.out / fsf / cvs / src / edit.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  23KB  |  1,020 lines

  1. /* Implementation for "cvs edit", "cvs watch on", and related commands
  2.  
  3.    This program is free software; you can redistribute it and/or modify
  4.    it under the terms of the GNU General Public License as published by
  5.    the Free Software Foundation; either version 2, or (at your option)
  6.    any later version.
  7.  
  8.    This program is distributed in the hope that it will be useful,
  9.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11.    GNU General Public License for more details.
  12.  
  13.    You should have received a copy of the GNU General Public License
  14.    along with this program; if not, write to the Free Software
  15.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  16.  
  17. #include "cvs.h"
  18. #include "getline.h"
  19. #include "watch.h"
  20. #include "edit.h"
  21. #include "fileattr.h"
  22.  
  23. static int watch_onoff PROTO ((int, char **));
  24.  
  25. static int setting_default;
  26. static int turning_on;
  27.  
  28. static int setting_tedit;
  29. static int setting_tunedit;
  30. static int setting_tcommit;
  31.  
  32. static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  33.  
  34. static int
  35. onoff_fileproc (callerdat, finfo)
  36.     void *callerdat;
  37.     struct file_info *finfo;
  38. {
  39.     fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
  40.     return 0;
  41. }
  42.  
  43. static int onoff_filesdoneproc PROTO ((void *, int, char *, char *, List *));
  44.  
  45. static int
  46. onoff_filesdoneproc (callerdat, err, repository, update_dir, entries)
  47.     void *callerdat;
  48.     int err;
  49.     char *repository;
  50.     char *update_dir;
  51.     List *entries;
  52. {
  53.     if (setting_default)
  54.     fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
  55.     return err;
  56. }
  57.  
  58. static int
  59. watch_onoff (argc, argv)
  60.     int argc;
  61.     char **argv;
  62. {
  63.     int c;
  64.     int local = 0;
  65.     int err;
  66.  
  67.     optind = 1;
  68.     while ((c = getopt (argc, argv, "l")) != -1)
  69.     {
  70.     switch (c)
  71.     {
  72.         case 'l':
  73.         local = 1;
  74.         break;
  75.         case '?':
  76.         default:
  77.         usage (watch_usage);
  78.         break;
  79.     }
  80.     }
  81.     argc -= optind;
  82.     argv += optind;
  83.  
  84. #ifdef CLIENT_SUPPORT
  85.     if (client_active)
  86.     {
  87.     start_server ();
  88.  
  89.     ign_setup ();
  90.  
  91.     if (local)
  92.         send_arg ("-l");
  93.     send_file_names (argc, argv, SEND_EXPAND_WILD);
  94.     /* FIXME:  We shouldn't have to send current files, but I'm not sure
  95.        whether it works.  So send the files --
  96.        it's slower but it works.  */
  97.     send_files (argc, argv, local, 0);
  98.     send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
  99.     return get_responses_and_close ();
  100.     }
  101. #endif /* CLIENT_SUPPORT */
  102.  
  103.     setting_default = (argc <= 0);
  104.  
  105.     lock_tree_for_write (argc, argv, local, 0);
  106.  
  107.     err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
  108.                (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  109.                argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  110.                0);
  111.  
  112.     lock_tree_cleanup ();
  113.     return err;
  114. }
  115.  
  116. int
  117. watch_on (argc, argv)
  118.     int argc;
  119.     char **argv;
  120. {
  121.     turning_on = 1;
  122.     return watch_onoff (argc, argv);
  123. }
  124.  
  125. int
  126. watch_off (argc, argv)
  127.     int argc;
  128.     char **argv;
  129. {
  130.     turning_on = 0;
  131.     return watch_onoff (argc, argv);
  132. }
  133.  
  134. static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  135.  
  136. static int
  137. dummy_fileproc (callerdat, finfo)
  138.     void *callerdat;
  139.     struct file_info *finfo;
  140. {
  141.     /* This is a pretty hideous hack, but the gist of it is that recurse.c
  142.        won't call notify_check unless there is a fileproc, so we can't just
  143.        pass NULL for fileproc.  */
  144.     return 0;
  145. }
  146.  
  147. static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  148.  
  149. /* Check for and process notifications.  Local only.  I think that doing
  150.    this as a fileproc is the only way to catch all the
  151.    cases (e.g. foo/bar.c), even though that means checking over and over
  152.    for the same CVSADM_NOTIFY file which we removed the first time we
  153.    processed the directory.  */
  154.  
  155. static int
  156. ncheck_fileproc (callerdat, finfo)
  157.     void *callerdat;
  158.     struct file_info *finfo;
  159. {
  160.     int notif_type;
  161.     char *filename;
  162.     char *val;
  163.     char *cp;
  164.     char *watches;
  165.  
  166.     FILE *fp;
  167.     char *line = NULL;
  168.     size_t line_len = 0;
  169.  
  170.     /* We send notifications even if noexec.  I'm not sure which behavior
  171.        is most sensible.  */
  172.  
  173.     fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
  174.     if (fp == NULL)
  175.     {
  176.     if (!existence_error (errno))
  177.         error (0, errno, "cannot open %s", CVSADM_NOTIFY);
  178.     return 0;
  179.     }
  180.  
  181.     while (getline (&line, &line_len, fp) > 0)
  182.     {
  183.     notif_type = line[0];
  184.     if (notif_type == '\0')
  185.         continue;
  186.     filename = line + 1;
  187.     cp = strchr (filename, '\t');
  188.     if (cp == NULL)
  189.         continue;
  190.     *cp++ = '\0';
  191.     val = cp;
  192.     cp = strchr (val, '\t');
  193.     if (cp == NULL)
  194.         continue;
  195.     *cp++ = '+';
  196.     cp = strchr (cp, '\t');
  197.     if (cp == NULL)
  198.         continue;
  199.     *cp++ = '+';
  200.     cp = strchr (cp, '\t');
  201.     if (cp == NULL)
  202.         continue;
  203.     *cp++ = '\0';
  204.     watches = cp;
  205.     cp = strchr (cp, '\n');
  206.     if (cp == NULL)
  207.         continue;
  208.     *cp = '\0';
  209.  
  210.     notify_do (notif_type, filename, getcaller (), val, watches,
  211.            finfo->repository);
  212.     }
  213.     free (line);
  214.  
  215.     if (ferror (fp))
  216.     error (0, errno, "cannot read %s", CVSADM_NOTIFY);
  217.     if (fclose (fp) < 0)
  218.     error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  219.  
  220.     if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
  221.     error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
  222.  
  223.     return 0;
  224. }
  225.  
  226. static int send_notifications PROTO ((int, char **, int));
  227.  
  228. /* Look through the CVSADM_NOTIFY file and process each item there
  229.    accordingly.  */
  230. static int
  231. send_notifications (argc, argv, local)
  232.     int argc;
  233.     char **argv;
  234.     int local;
  235. {
  236.     int err = 0;
  237.  
  238. #ifdef CLIENT_SUPPORT
  239.     /* OK, we've done everything which needs to happen on the client side.
  240.        Now we can try to contact the server; if we fail, then the
  241.        notifications stay in CVSADM_NOTIFY to be sent next time.  */
  242.     if (client_active)
  243.     {
  244.     if (strcmp (command_name, "release") != 0)
  245.     {
  246.         start_server ();
  247.         ign_setup ();
  248.     }
  249.  
  250.     err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
  251.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  252.                 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  253.                 0);
  254.  
  255.     send_to_server ("noop\012", 0);
  256.     if (strcmp (command_name, "release") == 0)
  257.         err += get_server_responses ();
  258.     else
  259.         err += get_responses_and_close ();
  260.     }
  261.     else
  262. #endif
  263.     {
  264.     /* Local.  */
  265.  
  266.     lock_tree_for_write (argc, argv, local, 0);
  267.     err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
  268.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  269.                 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  270.                 0);
  271.     lock_tree_cleanup ();
  272.     }
  273.     return err;
  274. }
  275.  
  276. static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  277.  
  278. static int
  279. edit_fileproc (callerdat, finfo)
  280.     void *callerdat;
  281.     struct file_info *finfo;
  282. {
  283.     FILE *fp;
  284.     time_t now;
  285.     char *ascnow;
  286.     char *basefilename;
  287.  
  288.     if (noexec)
  289.     return 0;
  290.  
  291.     fp = open_file (CVSADM_NOTIFY, "a");
  292.  
  293.     (void) time (&now);
  294.     ascnow = asctime (gmtime (&now));
  295.     ascnow[24] = '\0';
  296.     fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
  297.          ascnow, hostname, CurDir);
  298.     if (setting_tedit)
  299.     fprintf (fp, "E");
  300.     if (setting_tunedit)
  301.     fprintf (fp, "U");
  302.     if (setting_tcommit)
  303.     fprintf (fp, "C");
  304.     fprintf (fp, "\n");
  305.  
  306.     if (fclose (fp) < 0)
  307.     {
  308.     if (finfo->update_dir[0] == '\0')
  309.         error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  310.     else
  311.         error (0, errno, "cannot close %s/%s", finfo->update_dir,
  312.            CVSADM_NOTIFY);
  313.     }
  314.  
  315.     xchmod (finfo->file, 1);
  316.  
  317.     /* Now stash the file away in CVSADM so that unedit can revert even if
  318.        it can't communicate with the server.  We stash away a writable
  319.        copy so that if the user removes the working file, then restores it
  320.        with "cvs update" (which clears _editors but does not update
  321.        CVSADM_BASE), then a future "cvs edit" can still win.  */
  322.     /* Could save a system call by only calling mkdir_if_needed if
  323.        trying to create the output file fails.  But copy_file isn't
  324.        set up to facilitate that.  */
  325.     mkdir_if_needed (CVSADM_BASE);
  326.     basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
  327.     strcpy (basefilename, CVSADM_BASE);
  328.     strcat (basefilename, "/");
  329.     strcat (basefilename, finfo->file);
  330.     copy_file (finfo->file, basefilename);
  331.     free (basefilename);
  332.  
  333.     return 0;
  334. }
  335.  
  336. static const char *const edit_usage[] =
  337. {
  338.     "Usage: %s %s [-l] [files...]\n",
  339.     "-l: Local directory only, not recursive\n",
  340.     "-a: Specify what actions for temporary watch, one of\n",
  341.     "    edit,unedit,commit.all,none\n",
  342.     NULL
  343. };
  344.  
  345. int
  346. edit (argc, argv)
  347.     int argc;
  348.     char **argv;
  349. {
  350.     int local = 0;
  351.     int c;
  352.     int err;
  353.     int a_omitted;
  354.  
  355.     if (argc == -1)
  356.     usage (edit_usage);
  357.  
  358.     a_omitted = 1;
  359.     setting_tedit = 0;
  360.     setting_tunedit = 0;
  361.     setting_tcommit = 0;
  362.     optind = 1;
  363.     while ((c = getopt (argc, argv, "la:")) != -1)
  364.     {
  365.     switch (c)
  366.     {
  367.         case 'l':
  368.         local = 1;
  369.         break;
  370.         case 'a':
  371.         a_omitted = 0;
  372.         if (strcmp (optarg, "edit") == 0)
  373.             setting_tedit = 1;
  374.         else if (strcmp (optarg, "unedit") == 0)
  375.             setting_tunedit = 1;
  376.         else if (strcmp (optarg, "commit") == 0)
  377.             setting_tcommit = 1;
  378.         else if (strcmp (optarg, "all") == 0)
  379.         {
  380.             setting_tedit = 1;
  381.             setting_tunedit = 1;
  382.             setting_tcommit = 1;
  383.         }
  384.         else if (strcmp (optarg, "none") == 0)
  385.         {
  386.             setting_tedit = 0;
  387.             setting_tunedit = 0;
  388.             setting_tcommit = 0;
  389.         }
  390.         else
  391.             usage (edit_usage);
  392.         break;
  393.         case '?':
  394.         default:
  395.         usage (edit_usage);
  396.         break;
  397.     }
  398.     }
  399.     argc -= optind;
  400.     argv += optind;
  401.  
  402.     if (a_omitted)
  403.     {
  404.     setting_tedit = 1;
  405.     setting_tunedit = 1;
  406.     setting_tcommit = 1;
  407.     }
  408.  
  409.     /* No need to readlock since we aren't doing anything to the
  410.        repository.  */
  411.     err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
  412.                (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  413.                argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  414.                0);
  415.  
  416.     err += send_notifications (argc, argv, local);
  417.  
  418.     return err;
  419. }
  420.  
  421. static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  422.  
  423. static int
  424. unedit_fileproc (callerdat, finfo)
  425.     void *callerdat;
  426.     struct file_info *finfo;
  427. {
  428.     FILE *fp;
  429.     time_t now;
  430.     char *ascnow;
  431.     char *basefilename;
  432.  
  433.     if (noexec)
  434.     return 0;
  435.  
  436.     basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
  437.     strcpy (basefilename, CVSADM_BASE);
  438.     strcat (basefilename, "/");
  439.     strcat (basefilename, finfo->file);
  440.     if (!isfile (basefilename))
  441.     {
  442.     /* This file apparently was never cvs edit'd (e.g. we are uneditting
  443.        a directory where only some of the files were cvs edit'd.  */
  444.     free (basefilename);
  445.     return 0;
  446.     }
  447.  
  448.     if (xcmp (finfo->file, basefilename) != 0)
  449.     {
  450.     printf ("%s has been modified; revert changes? ", finfo->fullname);
  451.     if (!yesno ())
  452.     {
  453.         /* "no".  */
  454.         free (basefilename);
  455.         return 0;
  456.     }
  457.     }
  458.     rename_file (basefilename, finfo->file);
  459.     free (basefilename);
  460.  
  461.     fp = open_file (CVSADM_NOTIFY, "a");
  462.  
  463.     (void) time (&now);
  464.     ascnow = asctime (gmtime (&now));
  465.     ascnow[24] = '\0';
  466.     fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
  467.          ascnow, hostname, CurDir);
  468.  
  469.     if (fclose (fp) < 0)
  470.     {
  471.     if (finfo->update_dir[0] == '\0')
  472.         error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  473.     else
  474.         error (0, errno, "cannot close %s/%s", finfo->update_dir,
  475.            CVSADM_NOTIFY);
  476.     }
  477.  
  478.     xchmod (finfo->file, 0);
  479.     return 0;
  480. }
  481.  
  482. int
  483. unedit (argc, argv)
  484.     int argc;
  485.     char **argv;
  486. {
  487.     int local = 0;
  488.     int c;
  489.     int err;
  490.  
  491.     if (argc == -1)
  492.     usage (edit_usage);
  493.  
  494.     optind = 1;
  495.     while ((c = getopt (argc, argv, "l")) != -1)
  496.     {
  497.     switch (c)
  498.     {
  499.         case 'l':
  500.         local = 1;
  501.         break;
  502.         case '?':
  503.         default:
  504.         usage (edit_usage);
  505.         break;
  506.     }
  507.     }
  508.     argc -= optind;
  509.     argv += optind;
  510.  
  511.     /* No need to readlock since we aren't doing anything to the
  512.        repository.  */
  513.     err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
  514.                (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  515.                argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  516.                0);
  517.  
  518.     err += send_notifications (argc, argv, local);
  519.  
  520.     return err;
  521. }
  522.  
  523. void
  524. mark_up_to_date (file)
  525.     char *file;
  526. {
  527.     char *base;
  528.  
  529.     /* The file is up to date, so we better get rid of an out of
  530.        date file in CVSADM_BASE.  */
  531.     base = xmalloc (strlen (file) + 80);
  532.     strcpy (base, CVSADM_BASE);
  533.     strcat (base, "/");
  534.     strcat (base, file);
  535.     if (unlink_file (base) < 0 && ! existence_error (errno))
  536.     error (0, errno, "cannot remove %s", file);
  537.     free (base);
  538. }
  539.  
  540.  
  541. void
  542. editor_set (filename, editor, val)
  543.     char *filename;
  544.     char *editor;
  545.     char *val;
  546. {
  547.     char *edlist;
  548.     char *newlist;
  549.  
  550.     edlist = fileattr_get0 (filename, "_editors");
  551.     newlist = fileattr_modify (edlist, editor, val, '>', ',');
  552.     if (edlist != NULL)
  553.     free (edlist);
  554.     /* If the attributes is unchanged, don't rewrite the attribute file.  */
  555.     if (!((edlist == NULL && newlist == NULL)
  556.       || (edlist != NULL
  557.           && newlist != NULL
  558.           && strcmp (edlist, newlist) == 0)))
  559.     fileattr_set (filename, "_editors", newlist);
  560.     if (newlist != NULL)
  561.     free (newlist);
  562. }
  563.  
  564. struct notify_proc_args {
  565.     /* What kind of notification, "edit", "tedit", etc.  */
  566.     char *type;
  567.     /* User who is running the command which causes notification.  */
  568.     char *who;
  569.     /* User to be notified.  */
  570.     char *notifyee;
  571.     /* File.  */
  572.     char *file;
  573. };
  574.  
  575. /* Pass as a static until we get around to fixing Parse_Info to pass along
  576.    a void * where we can stash it.  */
  577. static struct notify_proc_args *notify_args;
  578.  
  579. static int notify_proc PROTO ((char *repository, char *filter));
  580.  
  581. static int
  582. notify_proc (repository, filter)
  583.     char *repository;
  584.     char *filter;
  585. {
  586.     FILE *pipefp;
  587.     char *prog;
  588.     char *expanded_prog;
  589.     char *p;
  590.     char *q;
  591.     char *srepos;
  592.     struct notify_proc_args *args = notify_args;
  593.  
  594.     srepos = Short_Repository (repository);
  595.     prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
  596.     /* Copy FILTER to PROG, replacing the first occurrence of %s with
  597.        the notifyee.  We only allocated enough memory for one %s, and I doubt
  598.        there is a need for more.  */
  599.     for (p = filter, q = prog; *p != '\0'; ++p)
  600.     {
  601.     if (p[0] == '%')
  602.     {
  603.         if (p[1] == 's')
  604.         {
  605.         strcpy (q, args->notifyee);
  606.         q += strlen (q);
  607.         strcpy (q, p + 2);
  608.         q += strlen (q);
  609.         break;
  610.         }
  611.         else
  612.         continue;
  613.     }
  614.     *q++ = *p;
  615.     }
  616.     *q = '\0';
  617.  
  618.     /* FIXME: why are we calling expand_proc?  Didn't we already
  619.        expand it in Parse_Info, before passing it to notify_proc?  */
  620.     expanded_prog = expand_path (prog, "notify", 0);
  621.     if (!expanded_prog)
  622.     {
  623.     free (prog);
  624.     return 1;
  625.     }
  626.  
  627.     pipefp = run_popen (expanded_prog, "w");
  628.     if (pipefp == NULL)
  629.     {
  630.     error (0, errno, "cannot write entry to notify filter: %s", prog);
  631.     free (prog);
  632.     free (expanded_prog);
  633.     return 1;
  634.     }
  635.  
  636.     fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
  637.     fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
  638.     fprintf (pipefp, "By %s\n", args->who);
  639.  
  640.     /* Lots more potentially useful information we could add here; see
  641.        logfile_write for inspiration.  */
  642.  
  643.     free (prog);
  644.     free (expanded_prog);
  645.     return (pclose (pipefp));
  646. }
  647.  
  648. void
  649. notify_do (type, filename, who, val, watches, repository)
  650.     int type;
  651.     char *filename;
  652.     char *who;
  653.     char *val;
  654.     char *watches;
  655.     char *repository;
  656. {
  657.     static struct addremove_args blank;
  658.     struct addremove_args args;
  659.     char *watchers;
  660.     char *p;
  661.     char *endp;
  662.     char *nextp;
  663.  
  664.     /* Initialize fields to 0, NULL, or 0.0.  */
  665.     args = blank;
  666.     switch (type)
  667.     {
  668.     case 'E':
  669.         editor_set (filename, who, val);
  670.         break;
  671.     case 'U':
  672.     case 'C':
  673.         editor_set (filename, who, NULL);
  674.         break;
  675.     default:
  676.         return;
  677.     }
  678.  
  679.     watchers = fileattr_get0 (filename, "_watchers");
  680.     p = watchers;
  681.     while (p != NULL)
  682.     {
  683.     char *q;
  684.     char *endq;
  685.     char *nextq;
  686.     char *notif;
  687.  
  688.     endp = strchr (p, '>');
  689.     if (endp == NULL)
  690.         break;
  691.     nextp = strchr (p, ',');
  692.  
  693.     if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
  694.     {
  695.         /* Don't notify user of their own changes.  Would perhaps
  696.            be better to check whether it is the same working
  697.            directory, not the same user, but that is hairy.  */
  698.         p = nextp == NULL ? nextp : nextp + 1;
  699.         continue;
  700.     }
  701.  
  702.     /* Now we point q at a string which looks like
  703.        "edit+unedit+commit,"... and walk down it.  */
  704.     q = endp + 1;
  705.     notif = NULL;
  706.     while (q != NULL)
  707.     {
  708.         endq = strchr (q, '+');
  709.         if (endq == NULL || (nextp != NULL && endq > nextp))
  710.         {
  711.         if (nextp == NULL)
  712.             endq = q + strlen (q);
  713.         else
  714.             endq = nextp;
  715.         nextq = NULL;
  716.         }
  717.         else
  718.         nextq = endq + 1;
  719.  
  720.         /* If there is a temporary and a regular watch, send a single
  721.            notification, for the regular watch.  */
  722.         if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
  723.         {
  724.         notif = "edit";
  725.         }
  726.         else if (type == 'U'
  727.              && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
  728.         {
  729.         notif = "unedit";
  730.         }
  731.         else if (type == 'C'
  732.              && endq - q == 6 && strncmp ("commit", q, 6) == 0)
  733.         {
  734.         notif = "commit";
  735.         }
  736.         else if (type == 'E'
  737.              && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
  738.         {
  739.         if (notif == NULL)
  740.             notif = "temporary edit";
  741.         }
  742.         else if (type == 'U'
  743.              && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
  744.         {
  745.         if (notif == NULL)
  746.             notif = "temporary unedit";
  747.         }
  748.         else if (type == 'C'
  749.              && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
  750.         {
  751.         if (notif == NULL)
  752.             notif = "temporary commit";
  753.         }
  754.         q = nextq;
  755.     }
  756.     if (nextp != NULL)
  757.         ++nextp;
  758.  
  759.     if (notif != NULL)
  760.     {
  761.         struct notify_proc_args args;
  762.         size_t len = endp - p;
  763.         FILE *fp;
  764.         char *usersname;
  765.         char *line = NULL;
  766.         size_t line_len = 0;
  767.  
  768.         args.notifyee = NULL;
  769.         usersname = xmalloc (strlen (CVSroot_directory)
  770.                  + sizeof CVSROOTADM
  771.                  + sizeof CVSROOTADM_USERS
  772.                  + 20);
  773.         strcpy (usersname, CVSroot_directory);
  774.         strcat (usersname, "/");
  775.         strcat (usersname, CVSROOTADM);
  776.         strcat (usersname, "/");
  777.         strcat (usersname, CVSROOTADM_USERS);
  778.         fp = CVS_FOPEN (usersname, "r");
  779.         if (fp == NULL && !existence_error (errno))
  780.         error (0, errno, "cannot read %s", usersname);
  781.         if (fp != NULL)
  782.         {
  783.         while (getline (&line, &line_len, fp) >= 0)
  784.         {
  785.             if (strncmp (line, p, len) == 0
  786.             && line[len] == ':')
  787.             {
  788.             char *cp;
  789.             args.notifyee = xstrdup (line + len + 1);
  790.             cp = strchr (args.notifyee, ':');
  791.             if (cp != NULL)
  792.                 *cp = '\0';
  793.             break;
  794.             }
  795.         }
  796.         if (ferror (fp))
  797.             error (0, errno, "cannot read %s", usersname);
  798.         if (fclose (fp) < 0)
  799.             error (0, errno, "cannot close %s", usersname);
  800.         }
  801.         free (usersname);
  802.         free (line);
  803.  
  804.         if (args.notifyee == NULL)
  805.         {
  806.         args.notifyee = xmalloc (endp - p + 1);
  807.         strncpy (args.notifyee, p, endp - p);
  808.         args.notifyee[endp - p] = '\0';
  809.         }
  810.  
  811.         notify_args = &args;
  812.         args.type = notif;
  813.         args.who = who;
  814.         args.file = filename;
  815.  
  816.         (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
  817.         free (args.notifyee);
  818.     }
  819.  
  820.     p = nextp;
  821.     }
  822.     if (watchers != NULL)
  823.     free (watchers);
  824.  
  825.     switch (type)
  826.     {
  827.     case 'E':
  828.         if (*watches == 'E')
  829.         {
  830.         args.add_tedit = 1;
  831.         ++watches;
  832.         }
  833.         if (*watches == 'U')
  834.         {
  835.         args.add_tunedit = 1;
  836.         ++watches;
  837.         }
  838.         if (*watches == 'C')
  839.         {
  840.         args.add_tcommit = 1;
  841.         }
  842.         watch_modify_watchers (filename, &args);
  843.         break;
  844.     case 'U':
  845.     case 'C':
  846.         args.remove_temp = 1;
  847.         watch_modify_watchers (filename, &args);
  848.         break;
  849.     }
  850. }
  851.  
  852. #ifdef CLIENT_SUPPORT
  853. /* Check and send notifications.  This is only for the client.  */
  854. void
  855. notify_check (repository, update_dir)
  856.     char *repository;
  857.     char *update_dir;
  858. {
  859.     FILE *fp;
  860.     char *line = NULL;
  861.     size_t line_len = 0;
  862.  
  863.     if (! server_started)
  864.     /* We are in the midst of a command which is not to talk to
  865.        the server (e.g. the first phase of a cvs edit).  Just chill
  866.        out, we'll catch the notifications on the flip side.  */
  867.     return;
  868.  
  869.     /* We send notifications even if noexec.  I'm not sure which behavior
  870.        is most sensible.  */
  871.  
  872.     fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
  873.     if (fp == NULL)
  874.     {
  875.     if (!existence_error (errno))
  876.         error (0, errno, "cannot open %s", CVSADM_NOTIFY);
  877.     return;
  878.     }
  879.     while (getline (&line, &line_len, fp) > 0)
  880.     {
  881.     int notif_type;
  882.     char *filename;
  883.     char *val;
  884.     char *cp;
  885.  
  886.     notif_type = line[0];
  887.     if (notif_type == '\0')
  888.         continue;
  889.     filename = line + 1;
  890.     cp = strchr (filename, '\t');
  891.     if (cp == NULL)
  892.         continue;
  893.     *cp++ = '\0';
  894.     val = cp;
  895.  
  896.     client_notify (repository, update_dir, filename, notif_type, val);
  897.     }
  898.  
  899.     if (ferror (fp))
  900.     error (0, errno, "cannot read %s", CVSADM_NOTIFY);
  901.     if (fclose (fp) < 0)
  902.     error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  903.  
  904.     /* Leave the CVSADM_NOTIFY file there, until the server tells us it
  905.        has dealt with it.  */
  906. }
  907. #endif /* CLIENT_SUPPORT */
  908.  
  909.  
  910. static const char *const editors_usage[] =
  911. {
  912.     "Usage: %s %s [files...]\n",
  913.     NULL
  914. };
  915.  
  916. static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  917.  
  918. static int
  919. editors_fileproc (callerdat, finfo)
  920.     void *callerdat;
  921.     struct file_info *finfo;
  922. {
  923.     char *them;
  924.     char *p;
  925.  
  926.     them = fileattr_get0 (finfo->file, "_editors");
  927.     if (them == NULL)
  928.     return 0;
  929.  
  930.     fputs (finfo->fullname, stdout);
  931.  
  932.     p = them;
  933.     while (1)
  934.     {
  935.     putc ('\t', stdout);
  936.     while (*p != '>' && *p != '\0')
  937.         putc (*p++, stdout);
  938.     if (*p == '\0')
  939.     {
  940.         /* Only happens if attribute is misformed.  */
  941.         putc ('\n', stdout);
  942.         break;
  943.     }
  944.     ++p;
  945.     putc ('\t', stdout);
  946.     while (1)
  947.     {
  948.         while (*p != '+' && *p != ',' && *p != '\0')
  949.         putc (*p++, stdout);
  950.         if (*p == '\0')
  951.         {
  952.         putc ('\n', stdout);
  953.         goto out;
  954.         }
  955.         if (*p == ',')
  956.         {
  957.         ++p;
  958.         break;
  959.         }
  960.         ++p;
  961.         putc ('\t', stdout);
  962.     }
  963.     putc ('\n', stdout);
  964.     }
  965.   out:;
  966.     return 0;
  967. }
  968.  
  969. int
  970. editors (argc, argv)
  971.     int argc;
  972.     char **argv;
  973. {
  974.     int local = 0;
  975.     int c;
  976.  
  977.     if (argc == -1)
  978.     usage (editors_usage);
  979.  
  980.     optind = 1;
  981.     while ((c = getopt (argc, argv, "l")) != -1)
  982.     {
  983.     switch (c)
  984.     {
  985.         case 'l':
  986.         local = 1;
  987.         break;
  988.         case '?':
  989.         default:
  990.         usage (editors_usage);
  991.         break;
  992.     }
  993.     }
  994.     argc -= optind;
  995.     argv += optind;
  996.  
  997. #ifdef CLIENT_SUPPORT
  998.     if (client_active)
  999.     {
  1000.     start_server ();
  1001.     ign_setup ();
  1002.  
  1003.     if (local)
  1004.         send_arg ("-l");
  1005.     send_file_names (argc, argv, SEND_EXPAND_WILD);
  1006.     /* FIXME:  We shouldn't have to send current files, but I'm not sure
  1007.        whether it works.  So send the files --
  1008.        it's slower but it works.  */
  1009.     send_files (argc, argv, local, 0);
  1010.     send_to_server ("editors\012", 0);
  1011.     return get_responses_and_close ();
  1012.     }
  1013. #endif /* CLIENT_SUPPORT */
  1014.  
  1015.     return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
  1016.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  1017.                 argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
  1018.                 0);
  1019. }
  1020.