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 source distribution. 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, 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 static int verifymsg_proc PROTO((char *repository, char *script)); 22 23 static FILE *fp; 24 static char *str_list; 25 static char *str_list_format; /* The format for str_list's contents. */ 26 static char *editinfo_editor; 27 static char *verifymsg_script; 28 static Ctype type; 29 30 /* 31 * Puts a standard header on the output which is either being prepared for an 32 * editor session, or being sent to a logfile program. The modified, added, 33 * and removed files are included (if any) and formatted to look pretty. */ 34 static char *prefix; 35 static int col; 36 static char *tag; 37 static void 38 setup_tmpfile (xfp, xprefix, changes) 39 FILE *xfp; 40 char *xprefix; 41 List *changes; 42 { 43 /* set up statics */ 44 fp = xfp; 45 prefix = xprefix; 46 47 type = T_MODIFIED; 48 if (walklist (changes, find_type, NULL) != 0) 49 { 50 (void) fprintf (fp, "%sModified Files:\n", prefix); 51 col = 0; 52 (void) walklist (changes, fmt_proc, NULL); 53 (void) fprintf (fp, "\n"); 54 if (tag != NULL) 55 { 56 free (tag); 57 tag = NULL; 58 } 59 } 60 type = T_ADDED; 61 if (walklist (changes, find_type, NULL) != 0) 62 { 63 (void) fprintf (fp, "%sAdded Files:\n", prefix); 64 col = 0; 65 (void) walklist (changes, fmt_proc, NULL); 66 (void) fprintf (fp, "\n"); 67 if (tag != NULL) 68 { 69 free (tag); 70 tag = NULL; 71 } 72 } 73 type = T_REMOVED; 74 if (walklist (changes, find_type, NULL) != 0) 75 { 76 (void) fprintf (fp, "%sRemoved Files:\n", prefix); 77 col = 0; 78 (void) walklist (changes, fmt_proc, NULL); 79 (void) fprintf (fp, "\n"); 80 if (tag != NULL) 81 { 82 free (tag); 83 tag = NULL; 84 } 85 } 86 } 87 88 /* 89 * Looks for nodes of a specified type and returns 1 if found 90 */ 91 static int 92 find_type (p, closure) 93 Node *p; 94 void *closure; 95 { 96 struct logfile_info *li; 97 98 li = (struct logfile_info *) p->data; 99 if (li->type == type) 100 return (1); 101 else 102 return (0); 103 } 104 105 /* 106 * Breaks the files list into reasonable sized lines to avoid line wrap... 107 * all in the name of pretty output. It only works on nodes whose types 108 * match the one we're looking for 109 */ 110 static int 111 fmt_proc (p, closure) 112 Node *p; 113 void *closure; 114 { 115 struct logfile_info *li; 116 117 li = (struct logfile_info *) p->data; 118 if (li->type == type) 119 { 120 if (li->tag == NULL 121 ? tag != NULL 122 : tag == NULL || strcmp (tag, li->tag) != 0) 123 { 124 if (col > 0) 125 (void) fprintf (fp, "\n"); 126 (void) fprintf (fp, "%s", prefix); 127 col = strlen (prefix); 128 while (col < 6) 129 { 130 (void) fprintf (fp, " "); 131 ++col; 132 } 133 134 if (li->tag == NULL) 135 (void) fprintf (fp, "No tag"); 136 else 137 (void) fprintf (fp, "Tag: %s", li->tag); 138 139 if (tag != NULL) 140 free (tag); 141 tag = xstrdup (li->tag); 142 143 /* Force a new line. */ 144 col = 70; 145 } 146 147 if (col == 0) 148 { 149 (void) fprintf (fp, "%s\t", prefix); 150 col = 8; 151 } 152 else if (col > 8 && (col + (int) strlen (p->key)) > 70) 153 { 154 (void) fprintf (fp, "\n%s\t", prefix); 155 col = 8; 156 } 157 (void) fprintf (fp, "%s ", p->key); 158 col += strlen (p->key) + 1; 159 } 160 return (0); 161 } 162 163 /* 164 * Builds a temporary file using setup_tmpfile() and invokes the user's 165 * editor on the file. The header garbage in the resultant file is then 166 * stripped and the log message is stored in the "message" argument. 167 * 168 * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it 169 * is NULL, use the CVSADM_TEMPLATE file instead. 170 */ 171 void 172 do_editor (dir, messagep, repository, changes) 173 char *dir; 174 char **messagep; 175 char *repository; 176 List *changes; 177 { 178 static int reuse_log_message = 0; 179 char *line; 180 int line_length; 181 size_t line_chars_allocated; 182 char *fname; 183 struct stat pre_stbuf, post_stbuf; 184 int retcode = 0; 185 186 if (noexec || reuse_log_message) 187 return; 188 189 /* Abort creation of temp file if no editor is defined */ 190 if (strcmp (Editor, "") == 0 && !editinfo_editor) 191 error(1, 0, "no editor defined, must use -e or -m"); 192 193 /* Create a temporary file */ 194 /* FIXME - It's possible we should be relying on cvs_temp_file to open 195 * the file here - we get race conditions otherwise. 196 */ 197 fname = cvs_temp_name (); 198 again: 199 if ((fp = CVS_FOPEN (fname, "w+")) == NULL) 200 error (1, 0, "cannot create temporary file %s", fname); 201 202 if (*messagep) 203 { 204 (void) fprintf (fp, "%s", *messagep); 205 206 if ((*messagep)[0] == '\0' || 207 (*messagep)[strlen (*messagep) - 1] != '\n') 208 (void) fprintf (fp, "\n"); 209 } 210 else 211 (void) fprintf (fp, "\n"); 212 213 if (repository != NULL) 214 /* tack templates on if necessary */ 215 (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1); 216 else 217 { 218 FILE *tfp; 219 char buf[1024]; 220 size_t n; 221 size_t nwrite; 222 223 /* Why "b"? */ 224 tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb"); 225 if (tfp == NULL) 226 { 227 if (!existence_error (errno)) 228 error (1, errno, "cannot read %s", CVSADM_TEMPLATE); 229 } 230 else 231 { 232 while (!feof (tfp)) 233 { 234 char *p = buf; 235 n = fread (buf, 1, sizeof buf, tfp); 236 nwrite = n; 237 while (nwrite > 0) 238 { 239 n = fwrite (p, 1, nwrite, fp); 240 nwrite -= n; 241 p += n; 242 } 243 if (ferror (tfp)) 244 error (1, errno, "cannot read %s", CVSADM_TEMPLATE); 245 } 246 if (fclose (tfp) < 0) 247 error (0, errno, "cannot close %s", CVSADM_TEMPLATE); 248 } 249 } 250 251 (void) fprintf (fp, 252 "%s----------------------------------------------------------------------\n", 253 CVSEDITPREFIX); 254 (void) fprintf (fp, 255 "%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n", 256 CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX, 257 CVSEDITPREFIX); 258 if (dir != NULL && *dir) 259 (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX, 260 dir, CVSEDITPREFIX); 261 if (changes != NULL) 262 setup_tmpfile (fp, CVSEDITPREFIX, changes); 263 (void) fprintf (fp, 264 "%s----------------------------------------------------------------------\n", 265 CVSEDITPREFIX); 266 267 /* finish off the temp file */ 268 if (fclose (fp) == EOF) 269 error (1, errno, "%s", fname); 270 if ( CVS_STAT (fname, &pre_stbuf) == -1) 271 pre_stbuf.st_mtime = 0; 272 273 if (editinfo_editor) 274 free (editinfo_editor); 275 editinfo_editor = (char *) NULL; 276 #ifdef CLIENT_SUPPORT 277 if (current_parsed_root->isremote) 278 ; /* nothing, leave editinfo_editor NULL */ 279 else 280 #endif 281 if (repository != NULL) 282 (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0); 283 284 /* run the editor */ 285 run_setup (editinfo_editor ? editinfo_editor : Editor); 286 run_arg (fname); 287 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 288 RUN_NORMAL | RUN_SIGIGNORE)) != 0) 289 error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0, 290 editinfo_editor ? "Logfile verification failed" : 291 "warning: editor session failed"); 292 293 /* put the entire message back into the *messagep variable */ 294 295 fp = open_file (fname, "r"); 296 297 if (*messagep) 298 free (*messagep); 299 300 if ( CVS_STAT (fname, &post_stbuf) != 0) 301 error (1, errno, "cannot find size of temp file %s", fname); 302 303 if (post_stbuf.st_size == 0) 304 *messagep = NULL; 305 else 306 { 307 /* On NT, we might read less than st_size bytes, but we won't 308 read more. So this works. */ 309 *messagep = (char *) xmalloc (post_stbuf.st_size + 1); 310 *messagep[0] = '\0'; 311 } 312 313 line = NULL; 314 line_chars_allocated = 0; 315 316 if (*messagep) 317 { 318 size_t message_len = post_stbuf.st_size + 1; 319 size_t offset = 0; 320 while (1) 321 { 322 line_length = get_line (&line, &line_chars_allocated, fp); 323 if (line_length == -1) 324 { 325 if (ferror (fp)) 326 error (0, errno, "warning: cannot read %s", fname); 327 break; 328 } 329 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0) 330 continue; 331 if (offset + line_length >= message_len) 332 expand_string (messagep, &message_len, 333 offset + line_length + 1); 334 (void) strcpy (*messagep + offset, line); 335 offset += line_length; 336 } 337 } 338 if (fclose (fp) < 0) 339 error (0, errno, "warning: cannot close %s", fname); 340 341 if (pre_stbuf.st_mtime == post_stbuf.st_mtime || 342 *messagep == NULL || 343 strcmp (*messagep, "\n") == 0) 344 { 345 for (;;) 346 { 347 (void) printf ("\nLog message unchanged or not specified\n"); 348 (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n"); 349 (void) printf ("Action: (abort) "); 350 (void) fflush (stdout); 351 line_length = get_line (&line, &line_chars_allocated, stdin); 352 if (line_length < 0) 353 { 354 error (0, errno, "cannot read from stdin"); 355 if (unlink_file (fname) < 0) 356 error (0, errno, 357 "warning: cannot remove temp file %s", fname); 358 error (1, 0, "aborting"); 359 } 360 else if (line_length == 0 361 || *line == '\n' || *line == 'a' || *line == 'A') 362 { 363 if (unlink_file (fname) < 0) 364 error (0, errno, "warning: cannot remove temp file %s", fname); 365 error (1, 0, "aborted by user"); 366 } 367 if (*line == 'c' || *line == 'C') 368 break; 369 if (*line == 'e' || *line == 'E') 370 goto again; 371 if (*line == '!') 372 { 373 reuse_log_message = 1; 374 break; 375 } 376 (void) printf ("Unknown input\n"); 377 } 378 } 379 if (line) 380 free (line); 381 if (unlink_file (fname) < 0) 382 error (0, errno, "warning: cannot remove temp file %s", fname); 383 free (fname); 384 } 385 386 /* Runs the user-defined verification script as part of the commit or import 387 process. This verification is meant to be run whether or not the user 388 included the -m atribute. unlike the do_editor function, this is 389 independant of the running of an editor for getting a message. 390 */ 391 void 392 do_verify (message, repository) 393 char *message; 394 char *repository; 395 { 396 FILE *fp; 397 char *fname; 398 int retcode = 0; 399 400 #ifdef CLIENT_SUPPORT 401 if (current_parsed_root->isremote) 402 /* The verification will happen on the server. */ 403 return; 404 #endif 405 406 /* FIXME? Do we really want to skip this on noexec? What do we do 407 for the other administrative files? */ 408 if (noexec) 409 return; 410 411 /* If there's no message, then we have nothing to verify. Can this 412 case happen? And if so why would we print a message? */ 413 if (message == NULL) 414 { 415 cvs_output ("No message to verify\n", 0); 416 return; 417 } 418 419 /* open a temporary file, write the message to the 420 temp file, and close the file. */ 421 422 if ((fp = cvs_temp_file (&fname)) == NULL) 423 error (1, errno, "cannot create temporary file %s", fname); 424 else 425 { 426 fprintf (fp, "%s", message); 427 if ((message)[0] == '\0' || 428 (message)[strlen (message) - 1] != '\n') 429 (void) fprintf (fp, "%s", "\n"); 430 if (fclose (fp) == EOF) 431 error (1, errno, "%s", fname); 432 433 /* Get the name of the verification script to run */ 434 435 if (repository != NULL) 436 (void) Parse_Info (CVSROOTADM_VERIFYMSG, repository, 437 verifymsg_proc, 0); 438 439 /* Run the verification script */ 440 441 if (verifymsg_script) 442 { 443 run_setup (verifymsg_script); 444 run_arg (fname); 445 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 446 RUN_NORMAL | RUN_SIGIGNORE)) != 0) 447 { 448 /* Since following error() exits, delete the temp file 449 now. */ 450 if (unlink_file (fname) < 0) 451 error (0, errno, "cannot remove %s", fname); 452 453 error (1, retcode == -1 ? errno : 0, 454 "Message verification failed"); 455 } 456 } 457 458 /* Delete the temp file */ 459 460 if (unlink_file (fname) < 0) 461 error (0, errno, "cannot remove %s", fname); 462 free (fname); 463 } 464 } 465 466 /* 467 * callback proc for Parse_Info for rcsinfo templates this routine basically 468 * copies the matching template onto the end of the tempfile we are setting 469 * up 470 */ 471 /* ARGSUSED */ 472 static int 473 rcsinfo_proc (repository, template) 474 char *repository; 475 char *template; 476 { 477 static char *last_template; 478 FILE *tfp; 479 480 /* nothing to do if the last one included is the same as this one */ 481 if (last_template && strcmp (last_template, template) == 0) 482 return (0); 483 if (last_template) 484 free (last_template); 485 last_template = xstrdup (template); 486 487 if ((tfp = CVS_FOPEN (template, "r")) != NULL) 488 { 489 char *line = NULL; 490 size_t line_chars_allocated = 0; 491 492 while (get_line (&line, &line_chars_allocated, tfp) >= 0) 493 (void) fputs (line, fp); 494 if (ferror (tfp)) 495 error (0, errno, "warning: cannot read %s", template); 496 if (fclose (tfp) < 0) 497 error (0, errno, "warning: cannot close %s", template); 498 if (line) 499 free (line); 500 return (0); 501 } 502 else 503 { 504 error (0, errno, "Couldn't open rcsinfo template file %s", template); 505 return (1); 506 } 507 } 508 509 /* 510 * Uses setup_tmpfile() to pass the updated message on directly to any 511 * logfile programs that have a regular expression match for the checked in 512 * directory in the source repository. The log information is fed into the 513 * specified program as standard input. 514 */ 515 static FILE *logfp; 516 static char *message; 517 static List *changes; 518 519 void 520 Update_Logfile (repository, xmessage, xlogfp, xchanges) 521 char *repository; 522 char *xmessage; 523 FILE *xlogfp; 524 List *xchanges; 525 { 526 /* nothing to do if the list is empty */ 527 if (xchanges == NULL || xchanges->list->next == xchanges->list) 528 return; 529 530 /* set up static vars for update_logfile_proc */ 531 message = xmessage; 532 logfp = xlogfp; 533 changes = xchanges; 534 535 /* call Parse_Info to do the actual logfile updates */ 536 (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1); 537 } 538 539 /* 540 * callback proc to actually do the logfile write from Update_Logfile 541 */ 542 static int 543 update_logfile_proc (repository, filter) 544 char *repository; 545 char *filter; 546 { 547 return (logfile_write (repository, filter, message, logfp, changes)); 548 } 549 550 /* 551 * concatenate each filename/version onto str_list 552 */ 553 static int 554 title_proc (p, closure) 555 Node *p; 556 void *closure; 557 { 558 struct logfile_info *li; 559 char *c; 560 561 li = (struct logfile_info *) p->data; 562 if (li->type == type) 563 { 564 /* Until we decide on the correct logging solution when we add 565 directories or perform imports, T_TITLE nodes will only 566 tack on the name provided, regardless of the format string. 567 You can verify that this assumption is safe by checking the 568 code in add.c (add_directory) and import.c (import). */ 569 570 str_list = xrealloc (str_list, strlen (str_list) + 5); 571 (void) strcat (str_list, " "); 572 573 if (li->type == T_TITLE) 574 { 575 str_list = xrealloc (str_list, 576 strlen (str_list) + strlen (p->key) + 5); 577 (void) strcat (str_list, p->key); 578 } 579 else 580 { 581 /* All other nodes use the format string. */ 582 583 for (c = str_list_format; *c != '\0'; c++) 584 { 585 switch (*c) 586 { 587 case 's': 588 str_list = 589 xrealloc (str_list, 590 strlen (str_list) + strlen (p->key) + 5); 591 (void) strcat (str_list, p->key); 592 break; 593 case 't': 594 str_list = 595 xrealloc (str_list, 596 (strlen (str_list) 597 + (li->tag ? strlen (li->tag) : 0) 598 + 10) 599 ); 600 (void) strcat (str_list, (li->tag ? li->tag : "")); 601 break; 602 case 'V': 603 str_list = 604 xrealloc (str_list, 605 (strlen (str_list) 606 + (li->rev_old ? strlen (li->rev_old) : 0) 607 + 10) 608 ); 609 (void) strcat (str_list, (li->rev_old 610 ? li->rev_old : "NONE")); 611 break; 612 case 'v': 613 str_list = 614 xrealloc (str_list, 615 (strlen (str_list) 616 + (li->rev_new ? strlen (li->rev_new) : 0) 617 + 10) 618 ); 619 (void) strcat (str_list, (li->rev_new 620 ? li->rev_new : "NONE")); 621 break; 622 /* All other characters, we insert an empty field (but 623 we do put in the comma separating it from other 624 fields). This way if future CVS versions add formatting 625 characters, one can write a loginfo file which at least 626 won't blow up on an old CVS. */ 627 } 628 if (*(c + 1) != '\0') 629 { 630 str_list = xrealloc (str_list, strlen (str_list) + 5); 631 (void) strcat (str_list, ","); 632 } 633 } 634 } 635 } 636 return (0); 637 } 638 639 /* 640 * Writes some stuff to the logfile "filter" and returns the status of the 641 * filter program. 642 */ 643 static int 644 logfile_write (repository, filter, message, logfp, changes) 645 char *repository; 646 char *filter; 647 char *message; 648 FILE *logfp; 649 List *changes; 650 { 651 FILE *pipefp; 652 char *prog; 653 char *cp; 654 int c; 655 int pipestatus; 656 char *fmt_percent; /* the location of the percent sign 657 that starts the format string. */ 658 659 /* The user may specify a format string as part of the filter. 660 Originally, `%s' was the only valid string. The string that 661 was substituted for it was: 662 663 <repository-name> <file1> <file2> <file3> ... 664 665 Each file was either a new directory/import (T_TITLE), or a 666 added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED) 667 file. 668 669 It is desirable to preserve that behavior so lots of commitlog 670 scripts won't die when they get this new code. At the same 671 time, we'd like to pass other information about the files (like 672 version numbers, statuses, or checkin times). 673 674 The solution is to allow a format string that allows us to 675 specify those other pieces of information. The format string 676 will be composed of `%' followed by a single format character, 677 or followed by a set of format characters surrounded by `{' and 678 `}' as separators. The format characters are: 679 680 s = file name 681 t = tag name 682 V = old version number (pre-checkin) 683 v = new version number (post-checkin) 684 685 For example, valid format strings are: 686 687 %{} 688 %s 689 %{s} 690 %{sVv} 691 %{Vvts} 692 693 There's no reason that more items couldn't be added (like 694 modification date or file status [added, modified, updated, 695 etc.]) -- the code modifications would be minimal (logmsg.c 696 (title_proc) and commit.c (check_fileproc)). 697 698 The output will be a string of tokens separated by spaces. For 699 backwards compatibility, the the first token will be the 700 repository name. The rest of the tokens will be 701 comma-delimited lists of the information requested in the 702 format string. For example, if `/u/src/master' is the 703 repository, `%{sVv}' is the format string, and three files 704 (ChangeLog, Makefile, foo.c) were modified, the output might 705 be: 706 707 /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13 708 709 Why this duplicates the old behavior when the format string is 710 `%s' is left as an exercise for the reader. */ 711 712 fmt_percent = strchr (filter, '%'); 713 if (fmt_percent) 714 { 715 int len; 716 char *srepos; 717 char *fmt_begin, *fmt_end; /* beginning and end of the 718 format string specified in 719 filter. */ 720 char *fmt_continue; /* where the string continues 721 after the format string (we 722 might skip a '}') somewhere 723 in there... */ 724 725 /* Grab the format string. */ 726 727 if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0')) 728 { 729 /* The percent stands alone. This is an error. We could 730 be treating ' ' like any other formatting character, but 731 using it as a formatting character seems like it would be 732 a mistake. */ 733 734 /* Would be nice to also be giving the line number. */ 735 error (0, 0, "loginfo: '%%' not followed by formatting character"); 736 fmt_begin = fmt_percent + 1; 737 fmt_end = fmt_begin; 738 fmt_continue = fmt_begin; 739 } 740 else if (*(fmt_percent + 1) == '{') 741 { 742 /* The percent has a set of characters following it. */ 743 744 fmt_begin = fmt_percent + 2; 745 fmt_end = strchr (fmt_begin, '}'); 746 if (fmt_end) 747 { 748 /* Skip over the '}' character. */ 749 750 fmt_continue = fmt_end + 1; 751 } 752 else 753 { 754 /* There was no close brace -- assume that format 755 string continues to the end of the line. */ 756 757 /* Would be nice to also be giving the line number. */ 758 error (0, 0, "loginfo: '}' missing"); 759 fmt_end = fmt_begin + strlen (fmt_begin); 760 fmt_continue = fmt_end; 761 } 762 } 763 else 764 { 765 /* The percent has a single character following it. FIXME: 766 %% should expand to a regular percent sign. */ 767 768 fmt_begin = fmt_percent + 1; 769 fmt_end = fmt_begin + 1; 770 fmt_continue = fmt_end; 771 } 772 773 len = fmt_end - fmt_begin; 774 str_list_format = xmalloc (len + 1); 775 strncpy (str_list_format, fmt_begin, len); 776 str_list_format[len] = '\0'; 777 778 /* Allocate an initial chunk of memory. As we build up the string 779 we will realloc it. */ 780 if (!str_list) 781 str_list = xmalloc (1); 782 str_list[0] = '\0'; 783 784 /* Add entries to the string. Don't bother looking for 785 entries if the format string is empty. */ 786 787 if (str_list_format[0] != '\0') 788 { 789 type = T_TITLE; 790 (void) walklist (changes, title_proc, NULL); 791 type = T_ADDED; 792 (void) walklist (changes, title_proc, NULL); 793 type = T_MODIFIED; 794 (void) walklist (changes, title_proc, NULL); 795 type = T_REMOVED; 796 (void) walklist (changes, title_proc, NULL); 797 } 798 799 free (str_list_format); 800 801 /* Construct the final string. */ 802 803 srepos = Short_Repository (repository); 804 805 prog = cp = xmalloc ((fmt_percent - filter) + 2 * strlen (srepos) 806 + 2 * strlen (str_list) + strlen (fmt_continue) 807 + 10); 808 (void) memcpy (cp, filter, fmt_percent - filter); 809 cp += fmt_percent - filter; 810 *cp++ = '"'; 811 cp = shell_escape (cp, srepos); 812 cp = shell_escape (cp, str_list); 813 *cp++ = '"'; 814 (void) strcpy (cp, fmt_continue); 815 816 /* To be nice, free up some memory. */ 817 818 free (str_list); 819 str_list = (char *) NULL; 820 } 821 else 822 { 823 /* There's no format string. */ 824 prog = xstrdup (filter); 825 } 826 827 if ((pipefp = run_popen (prog, "w")) == NULL) 828 { 829 if (!noexec) 830 error (0, 0, "cannot write entry to log filter: %s", prog); 831 free (prog); 832 return (1); 833 } 834 (void) fprintf (pipefp, "Update of %s\n", repository); 835 (void) fprintf (pipefp, "In directory %s:", hostname); 836 cp = xgetwd (); 837 if (cp == NULL) 838 fprintf (pipefp, "<cannot get working directory: %s>\n\n", 839 strerror (errno)); 840 else 841 { 842 fprintf (pipefp, "%s\n\n", cp); 843 free (cp); 844 } 845 846 setup_tmpfile (pipefp, "", changes); 847 (void) fprintf (pipefp, "Log Message:\n%s\n", message); 848 if (logfp != (FILE *) 0) 849 { 850 (void) fprintf (pipefp, "Status:\n"); 851 rewind (logfp); 852 while ((c = getc (logfp)) != EOF) 853 (void) putc ((char) c, pipefp); 854 } 855 free (prog); 856 pipestatus = pclose (pipefp); 857 return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0; 858 } 859 860 /* 861 * We choose to use the *last* match within the editinfo file for this 862 * repository. This allows us to have a global editinfo program for the 863 * root of some hierarchy, for example, and different ones within different 864 * sub-directories of the root (like a special checker for changes made to 865 * the "src" directory versus changes made to the "doc" or "test" 866 * directories. 867 */ 868 /* ARGSUSED */ 869 static int 870 editinfo_proc(repository, editor) 871 char *repository; 872 char *editor; 873 { 874 /* nothing to do if the last match is the same as this one */ 875 if (editinfo_editor && strcmp (editinfo_editor, editor) == 0) 876 return (0); 877 if (editinfo_editor) 878 free (editinfo_editor); 879 880 editinfo_editor = xstrdup (editor); 881 return (0); 882 } 883 884 /* This routine is calld by Parse_Info. it asigns the name of the 885 * message verification script to the global variable verify_script 886 */ 887 static int 888 verifymsg_proc (repository, script) 889 char *repository; 890 char *script; 891 { 892 if (verifymsg_script && strcmp (verifymsg_script, script) == 0) 893 return (0); 894 if (verifymsg_script) 895 free (verifymsg_script); 896 verifymsg_script = xstrdup (script); 897 return (0); 898 } 899