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