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 / logmsg.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  15KB  |  574 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * Copyright (c) 1989-1992, Brian Berliner
  4.  * 
  5.  * You may distribute under the terms of the GNU General Public License as
  6.  * specified in the README file that comes with the CVS 1.4 kit.
  7.  */
  8.  
  9. #include "cvs.h"
  10. #include "getline.h"
  11.  
  12. static int find_type PROTO((Node * p, void *closure));
  13. static int fmt_proc PROTO((Node * p, void *closure));
  14. static int logfile_write PROTO((char *repository, char *filter, char *title,
  15.               char *message, FILE * logfp, List * changes));
  16. static int rcsinfo_proc PROTO((char *repository, char *template));
  17. static int title_proc PROTO((Node * p, void *closure));
  18. static int update_logfile_proc PROTO((char *repository, char *filter));
  19. static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
  20. static int editinfo_proc PROTO((char *repository, char *template));
  21.  
  22. static FILE *fp;
  23. static char *str_list;
  24. static char *editinfo_editor;
  25. static Ctype type;
  26.  
  27. /*
  28.  * Puts a standard header on the output which is either being prepared for an
  29.  * editor session, or being sent to a logfile program.  The modified, added,
  30.  * and removed files are included (if any) and formatted to look pretty. */
  31. static char *prefix;
  32. static int col;
  33. static char *tag;
  34. static void
  35. setup_tmpfile (xfp, xprefix, changes)
  36.     FILE *xfp;
  37.     char *xprefix;
  38.     List *changes;
  39. {
  40.     /* set up statics */
  41.     fp = xfp;
  42.     prefix = xprefix;
  43.  
  44.     type = T_MODIFIED;
  45.     if (walklist (changes, find_type, NULL) != 0)
  46.     {
  47.     (void) fprintf (fp, "%sModified Files:\n", prefix);
  48.     col = 0;
  49.     (void) walklist (changes, fmt_proc, NULL);
  50.     (void) fprintf (fp, "\n");
  51.     if (tag != NULL)
  52.     {
  53.         free (tag);
  54.         tag = NULL;
  55.     }
  56.     }
  57.     type = T_ADDED;
  58.     if (walklist (changes, find_type, NULL) != 0)
  59.     {
  60.     (void) fprintf (fp, "%sAdded Files:\n", prefix);
  61.     col = 0;
  62.     (void) walklist (changes, fmt_proc, NULL);
  63.     (void) fprintf (fp, "\n");
  64.     if (tag != NULL)
  65.     {
  66.         free (tag);
  67.         tag = NULL;
  68.     }
  69.     }
  70.     type = T_REMOVED;
  71.     if (walklist (changes, find_type, NULL) != 0)
  72.     {
  73.     (void) fprintf (fp, "%sRemoved Files:\n", prefix);
  74.     col = 0;
  75.     (void) walklist (changes, fmt_proc, NULL);
  76.     (void) fprintf (fp, "\n");
  77.     if (tag != NULL)
  78.     {
  79.         free (tag);
  80.         tag = NULL;
  81.     }
  82.     }
  83. }
  84.  
  85. /*
  86.  * Looks for nodes of a specified type and returns 1 if found
  87.  */
  88. static int
  89. find_type (p, closure)
  90.     Node *p;
  91.     void *closure;
  92. {
  93.     struct logfile_info *li;
  94.  
  95.     li = (struct logfile_info *) p->data;
  96.     if (li->type == type)
  97.     return (1);
  98.     else
  99.     return (0);
  100. }
  101.  
  102. /*
  103.  * Breaks the files list into reasonable sized lines to avoid line wrap...
  104.  * all in the name of pretty output.  It only works on nodes whose types
  105.  * match the one we're looking for
  106.  */
  107. static int
  108. fmt_proc (p, closure)
  109.     Node *p;
  110.     void *closure;
  111. {
  112.     struct logfile_info *li;
  113.  
  114.     li = (struct logfile_info *) p->data;
  115.     if (li->type == type)
  116.     {
  117.         if (li->tag == NULL
  118.         ? tag != NULL
  119.         : tag == NULL || strcmp (tag, li->tag) != 0)
  120.     {
  121.         if (col > 0)
  122.             (void) fprintf (fp, "\n");
  123.         (void) fprintf (fp, "%s", prefix);
  124.         col = strlen (prefix);
  125.         while (col < 6)
  126.         {
  127.             (void) fprintf (fp, " ");
  128.         ++col;
  129.         }
  130.  
  131.         if (li->tag == NULL)
  132.             (void) fprintf (fp, "No tag");
  133.         else
  134.             (void) fprintf (fp, "Tag: %s", li->tag);
  135.  
  136.         if (tag != NULL)
  137.             free (tag);
  138.         tag = xstrdup (li->tag);
  139.  
  140.         /* Force a new line.  */
  141.         col = 70;
  142.     }
  143.  
  144.     if (col == 0)
  145.     {
  146.         (void) fprintf (fp, "%s\t", prefix);
  147.         col = 8;
  148.     }
  149.     else if (col > 8 && (col + (int) strlen (p->key)) > 70)
  150.     {
  151.         (void) fprintf (fp, "\n%s\t", prefix);
  152.         col = 8;
  153.     }
  154.     (void) fprintf (fp, "%s ", p->key);
  155.     col += strlen (p->key) + 1;
  156.     }
  157.     return (0);
  158. }
  159.  
  160. /*
  161.  * Builds a temporary file using setup_tmpfile() and invokes the user's
  162.  * editor on the file.  The header garbage in the resultant file is then
  163.  * stripped and the log message is stored in the "message" argument.
  164.  * 
  165.  * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
  166.  * is NULL, use the CVSADM_TEMPLATE file instead.
  167.  */
  168. void
  169. do_editor (dir, messagep, repository, changes)
  170.     char *dir;
  171.     char **messagep;
  172.     char *repository;
  173.     List *changes;
  174. {
  175.     static int reuse_log_message = 0;
  176.     char *line;
  177.     int line_length;
  178.     size_t line_chars_allocated;
  179.     char *fname;
  180.     struct stat pre_stbuf, post_stbuf;
  181.     int retcode = 0;
  182.     char *p;
  183.  
  184.     if (noexec || reuse_log_message)
  185.     return;
  186.  
  187.     /* Abort creation of temp file if no editor is defined */
  188.     if (strcmp (Editor, "") == 0 && !editinfo_editor)
  189.     error(1, 0, "no editor defined, must use -e or -m");
  190.  
  191.     /* Create a temporary file */
  192.     fname = cvs_temp_name ();
  193.   again:
  194.     if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
  195.     error (1, 0, "cannot create temporary file %s", fname);
  196.  
  197.     if (*messagep)
  198.     {
  199.     (void) fprintf (fp, "%s", *messagep);
  200.  
  201.     if ((*messagep)[strlen (*messagep) - 1] != '\n')
  202.         (void) fprintf (fp, "\n");
  203.     }
  204.     else
  205.     (void) fprintf (fp, "\n");
  206.  
  207.     if (repository != NULL)
  208.     /* tack templates on if necessary */
  209.     (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
  210.     else
  211.     {
  212.     FILE *tfp;
  213.     char buf[1024];
  214.     char *p;
  215.     size_t n;
  216.     size_t nwrite;
  217.  
  218.     /* Why "b"?  */
  219.     tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
  220.     if (tfp == NULL)
  221.     {
  222.         if (!existence_error (errno))
  223.         error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
  224.     }
  225.     else
  226.     {
  227.         while (!feof (tfp))
  228.         {
  229.         n = fread (buf, 1, sizeof buf, tfp);
  230.         nwrite = n;
  231.         p = buf;
  232.         while (nwrite > 0)
  233.         {
  234.             n = fwrite (p, 1, nwrite, fp);
  235.             nwrite -= n;
  236.             p += n;
  237.         }
  238.         if (ferror (tfp))
  239.             error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
  240.         }
  241.         if (fclose (tfp) < 0)
  242.         error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
  243.     }
  244.     }
  245.  
  246.     (void) fprintf (fp,
  247.   "%s----------------------------------------------------------------------\n",
  248.             CVSEDITPREFIX);
  249.     (void) fprintf (fp,
  250.   "%sEnter Log.  Lines beginning with `%s' are removed automatically\n%s\n",
  251.             CVSEDITPREFIX, CVSEDITPREFIX, CVSEDITPREFIX);
  252.     if (dir != NULL && *dir)
  253.     (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
  254.             dir, CVSEDITPREFIX);
  255.     if (changes != NULL)
  256.     setup_tmpfile (fp, CVSEDITPREFIX, changes);
  257.     (void) fprintf (fp,
  258.   "%s----------------------------------------------------------------------\n",
  259.             CVSEDITPREFIX);
  260.  
  261.     /* finish off the temp file */
  262.     if (fclose (fp) == EOF)
  263.         error (1, errno, "%s", fname);
  264.     if ( CVS_STAT (fname, &pre_stbuf) == -1)
  265.     pre_stbuf.st_mtime = 0;
  266.  
  267.     if (editinfo_editor)
  268.     free (editinfo_editor);
  269.     editinfo_editor = (char *) NULL;
  270.     if (repository != NULL)
  271.     (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
  272.  
  273.     /* run the editor */
  274.     run_setup ("%s", editinfo_editor ? editinfo_editor : Editor);
  275.     run_arg (fname);
  276.     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
  277.                  RUN_NORMAL | RUN_SIGIGNORE)) != 0)
  278.     error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
  279.            editinfo_editor ? "Logfile verification failed" :
  280.            "warning: editor session failed");
  281.  
  282.     /* put the entire message back into the *messagep variable */
  283.  
  284.     fp = open_file (fname, "r");
  285.  
  286.     if (*messagep)
  287.     free (*messagep);
  288.  
  289.     if ( CVS_STAT (fname, &post_stbuf) != 0)
  290.         error (1, errno, "cannot find size of temp file %s", fname);
  291.  
  292.     if (post_stbuf.st_size == 0)
  293.     *messagep = NULL;
  294.     else
  295.     {
  296.     /* On NT, we might read less than st_size bytes, but we won't
  297.        read more.  So this works.  */
  298.     *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
  299.      *messagep[0] = '\0';
  300.     }
  301.  
  302.     line = NULL;
  303.     line_chars_allocated = 0;
  304.  
  305.     if (*messagep)
  306.     {
  307.     p = *messagep;
  308.     while (1)
  309.     {
  310.         line_length = getline (&line, &line_chars_allocated, fp);
  311.         if (line_length == -1)
  312.         {
  313.         if (ferror (fp))
  314.             error (0, errno, "warning: cannot read %s", fname);
  315.         break;
  316.         }
  317.         if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0)
  318.         continue;
  319.         (void) strcpy (p, line);
  320.         p += line_length;
  321.     }
  322.     }
  323.     if (fclose (fp) < 0)
  324.     error (0, errno, "warning: cannot close %s", fname);
  325.  
  326.     if (pre_stbuf.st_mtime == post_stbuf.st_mtime ||
  327.     *messagep == NULL ||
  328.     strcmp (*messagep, "\n") == 0)
  329.     {
  330.     for (;;)
  331.     {
  332.         (void) printf ("\nLog message unchanged or not specified\n");
  333.         (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
  334.         (void) printf ("Action: (continue) ");
  335.         (void) fflush (stdout);
  336.         line_length = getline (&line, &line_chars_allocated, stdin);
  337.         if (line_length <= 0
  338.             || *line == '\n' || *line == 'c' || *line == 'C')
  339.         break;
  340.         if (*line == 'a' || *line == 'A')
  341.         {
  342.             if (unlink_file (fname) < 0)
  343.             error (0, errno, "warning: cannot remove temp file %s", fname);
  344.             error (1, 0, "aborted by user");
  345.         }
  346.         if (*line == 'e' || *line == 'E')
  347.         goto again;
  348.         if (*line == '!')
  349.         {
  350.         reuse_log_message = 1;
  351.         break;
  352.         }
  353.         (void) printf ("Unknown input\n");
  354.     }
  355.     }
  356.     if (line)
  357.     free (line);
  358.     if (unlink_file (fname) < 0)
  359.     error (0, errno, "warning: cannot remove temp file %s", fname);
  360.     free (fname);
  361. }
  362.  
  363. /*
  364.  * callback proc for Parse_Info for rcsinfo templates this routine basically
  365.  * copies the matching template onto the end of the tempfile we are setting
  366.  * up
  367.  */
  368. /* ARGSUSED */
  369. static int
  370. rcsinfo_proc (repository, template)
  371.     char *repository;
  372.     char *template;
  373. {
  374.     static char *last_template;
  375.     FILE *tfp;
  376.  
  377.     /* nothing to do if the last one included is the same as this one */
  378.     if (last_template && strcmp (last_template, template) == 0)
  379.     return (0);
  380.     if (last_template)
  381.     free (last_template);
  382.     last_template = xstrdup (template);
  383.  
  384.     if ((tfp = CVS_FOPEN (template, "r")) != NULL)
  385.     {
  386.     char *line = NULL;
  387.     size_t line_chars_allocated = 0;
  388.  
  389.     while (getline (&line, &line_chars_allocated, tfp) >= 0)
  390.         (void) fputs (line, fp);
  391.     if (ferror (tfp))
  392.         error (0, errno, "warning: cannot read %s", template);
  393.     if (fclose (tfp) < 0)
  394.         error (0, errno, "warning: cannot close %s", template);
  395.     if (line)
  396.         free (line);
  397.     return (0);
  398.     }
  399.     else
  400.     {
  401.     error (0, errno, "Couldn't open rcsinfo template file %s", template);
  402.     return (1);
  403.     }
  404. }
  405.  
  406. /*
  407.  * Uses setup_tmpfile() to pass the updated message on directly to any
  408.  * logfile programs that have a regular expression match for the checked in
  409.  * directory in the source repository.  The log information is fed into the
  410.  * specified program as standard input.
  411.  */
  412. static char *title;
  413. static FILE *logfp;
  414. static char *message;
  415. static List *changes;
  416.  
  417. void
  418. Update_Logfile (repository, xmessage, xlogfp, xchanges)
  419.     char *repository;
  420.     char *xmessage;
  421.     FILE *xlogfp;
  422.     List *xchanges;
  423. {
  424.     char *srepos;
  425.  
  426.     /* nothing to do if the list is empty */
  427.     if (xchanges == NULL || xchanges->list->next == xchanges->list)
  428.     return;
  429.  
  430.     /* set up static vars for update_logfile_proc */
  431.     message = xmessage;
  432.     logfp = xlogfp;
  433.     changes = xchanges;
  434.  
  435.     /* figure out a good title string */
  436.     srepos = Short_Repository (repository);
  437.  
  438.     /* allocate a chunk of memory to hold the title string */
  439.     if (!str_list)
  440.     str_list = xmalloc (MAXLISTLEN);
  441.     str_list[0] = '\0';
  442.  
  443.     type = T_TITLE;
  444.     (void) walklist (changes, title_proc, NULL);
  445.     type = T_ADDED;
  446.     (void) walklist (changes, title_proc, NULL);
  447.     type = T_MODIFIED;
  448.     (void) walklist (changes, title_proc, NULL);
  449.     type = T_REMOVED;
  450.     (void) walklist (changes, title_proc, NULL);
  451.     title = xmalloc (strlen (srepos) + strlen (str_list) + 1 + 2); /* for 's */
  452.     (void) sprintf (title, "'%s%s'", srepos, str_list);
  453.  
  454.     /* to be nice, free up this chunk of memory */
  455.     free (str_list);
  456.     str_list = (char *) NULL;
  457.  
  458.     /* call Parse_Info to do the actual logfile updates */
  459.     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
  460.  
  461.     /* clean up */
  462.     free (title);
  463. }
  464.  
  465. /*
  466.  * callback proc to actually do the logfile write from Update_Logfile
  467.  */
  468. static int
  469. update_logfile_proc (repository, filter)
  470.     char *repository;
  471.     char *filter;
  472. {
  473.     return (logfile_write (repository, filter, title, message, logfp,
  474.                changes));
  475. }
  476.  
  477. /*
  478.  * concatenate each name onto str_list
  479.  */
  480. static int
  481. title_proc (p, closure)
  482.     Node *p;
  483.     void *closure;
  484. {
  485.     struct logfile_info *li;
  486.  
  487.     li = (struct logfile_info *) p->data;
  488.     if (li->type == type)
  489.     {
  490.     (void) strcat (str_list, " ");
  491.     (void) strcat (str_list, p->key);
  492.     }
  493.     return (0);
  494. }
  495.  
  496. /*
  497.  * Since some systems don't define this...
  498.  */
  499. #ifndef MAXHOSTNAMELEN
  500. #define    MAXHOSTNAMELEN    256
  501. #endif
  502.  
  503. /*
  504.  * Writes some stuff to the logfile "filter" and returns the status of the
  505.  * filter program.
  506.  */
  507. static int
  508. logfile_write (repository, filter, title, message, logfp, changes)
  509.     char *repository;
  510.     char *filter;
  511.     char *title;
  512.     char *message;
  513.     FILE *logfp;
  514.     List *changes;
  515. {
  516.     char cwd[PATH_MAX];
  517.     FILE *pipefp;
  518.     char *prog = xmalloc (MAXPROGLEN);
  519.     char *cp;
  520.     int c;
  521.     int pipestatus;
  522.  
  523.     /*
  524.      * Only 1 %s argument is supported in the filter
  525.      */
  526.     (void) sprintf (prog, filter, title);
  527.     if ((pipefp = run_popen (prog, "w")) == NULL)
  528.     {
  529.     if (!noexec)
  530.         error (0, 0, "cannot write entry to log filter: %s", prog);
  531.     free (prog);
  532.     return (1);
  533.     }
  534.     (void) fprintf (pipefp, "Update of %s\n", repository);
  535.     (void) fprintf (pipefp, "In directory %s:%s\n\n", hostname,
  536.             ((cp = getwd (cwd)) != NULL) ? cp : cwd);
  537.     setup_tmpfile (pipefp, "", changes);
  538.     (void) fprintf (pipefp, "Log Message:\n%s\n", message);
  539.     if (logfp != (FILE *) 0)
  540.     {
  541.     (void) fprintf (pipefp, "Status:\n");
  542.     rewind (logfp);
  543.     while ((c = getc (logfp)) != EOF)
  544.         (void) putc ((char) c, pipefp);
  545.     }
  546.     free (prog);
  547.     pipestatus = pclose (pipefp);
  548.     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
  549. }
  550.  
  551. /*
  552.  * We choose to use the *last* match within the editinfo file for this
  553.  * repository.  This allows us to have a global editinfo program for the
  554.  * root of some hierarchy, for example, and different ones within different
  555.  * sub-directories of the root (like a special checker for changes made to
  556.  * the "src" directory versus changes made to the "doc" or "test"
  557.  * directories.
  558.  */
  559. /* ARGSUSED */
  560. static int
  561. editinfo_proc(repository, editor)
  562.     char *repository;
  563.     char *editor;
  564. {
  565.     /* nothing to do if the last match is the same as this one */
  566.     if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
  567.     return (0);
  568.     if (editinfo_editor)
  569.     free (editinfo_editor);
  570.  
  571.     editinfo_editor = xstrdup (editor);
  572.     return (0);
  573. }
  574.