logmsg.c revision 26151
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 12static int find_type PROTO((Node * p, void *closure)); 13static int fmt_proc PROTO((Node * p, void *closure)); 14static int logfile_write PROTO((char *repository, char *filter, 15 char *message, FILE * logfp, List * changes)); 16static int rcsinfo_proc PROTO((char *repository, char *template)); 17static int title_proc PROTO((Node * p, void *closure)); 18static int update_logfile_proc PROTO((char *repository, char *filter)); 19static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes)); 20static int editinfo_proc PROTO((char *repository, char *template)); 21static int verifymsg_proc PROTO((char *repository, char *script)); 22 23static FILE *fp; 24static char *str_list; 25static char *str_list_format; /* The format for str_list's contents. */ 26static char *editinfo_editor; 27static char *verifymsg_script; 28static 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. */ 34static char *prefix; 35static int col; 36static char *tag; 37static void 38setup_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 */ 91static int 92find_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 */ 110static int 111fmt_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 */ 171void 172do_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 char *p; 186 187 if (noexec || reuse_log_message) 188 return; 189 190 /* Abort creation of temp file if no editor is defined */ 191 if (strcmp (Editor, "") == 0 && !editinfo_editor) 192 error(1, 0, "no editor defined, must use -e or -m"); 193 194 195 /* Create a temporary file */ 196 fname = cvs_temp_name (); 197 again: 198 if ((fp = CVS_FOPEN (fname, "w+")) == NULL) 199 error (1, 0, "cannot create temporary file %s", fname); 200 201 if (*messagep) 202 { 203 (void) fprintf (fp, "%s", *messagep); 204 205 if ((*messagep)[0] == '\0' || 206 (*messagep)[strlen (*messagep) - 1] != '\n') 207 (void) fprintf (fp, "\n"); 208 } 209 else 210 (void) fprintf (fp, "\n"); 211 212 if (repository != NULL) 213 /* tack templates on if necessary */ 214 (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1); 215 else 216 { 217 FILE *tfp; 218 char buf[1024]; 219 char *p; 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 n = fread (buf, 1, sizeof buf, tfp); 235 nwrite = n; 236 p = buf; 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 (client_active) 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 ("%s", 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 p = *messagep; 319 while (1) 320 { 321 line_length = getline (&line, &line_chars_allocated, fp); 322 if (line_length == -1) 323 { 324 if (ferror (fp)) 325 error (0, errno, "warning: cannot read %s", fname); 326 break; 327 } 328 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0) 329 continue; 330 (void) strcpy (p, line); 331 p += line_length; 332 } 333 } 334 if (fclose (fp) < 0) 335 error (0, errno, "warning: cannot close %s", fname); 336 337 if (pre_stbuf.st_mtime == post_stbuf.st_mtime || 338 *messagep == NULL || 339 strcmp (*messagep, "\n") == 0) 340 { 341 for (;;) 342 { 343 (void) printf ("\nLog message unchanged or not specified\n"); 344 (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n"); 345 (void) printf ("Action: (continue) "); 346 (void) fflush (stdout); 347 line_length = getline (&line, &line_chars_allocated, stdin); 348 if (line_length <= 0 349 || *line == '\n' || *line == 'c' || *line == 'C') 350 break; 351 if (*line == 'a' || *line == 'A') 352 { 353 if (unlink_file (fname) < 0) 354 error (0, errno, "warning: cannot remove temp file %s", fname); 355 error (1, 0, "aborted by user"); 356 } 357 if (*line == 'e' || *line == 'E') 358 goto again; 359 if (*line == '!') 360 { 361 reuse_log_message = 1; 362 break; 363 } 364 (void) printf ("Unknown input\n"); 365 } 366 } 367 if (line) 368 free (line); 369 if (unlink_file (fname) < 0) 370 error (0, errno, "warning: cannot remove temp file %s", fname); 371 free (fname); 372} 373 374/* Runs the user-defined verification script as part of the commit or import 375 process. This verification is meant to be run whether or not the user 376 included the -m atribute. unlike the do_editor function, this is 377 independant of the running of an editor for getting a message. 378 */ 379void 380do_verify (messagep, repository) 381 char **messagep; 382 char *repository; 383{ 384 FILE *fp; 385 char *fname; 386 int retcode = 0; 387 388 char *line; 389 int line_length; 390 size_t line_chars_allocated; 391 char *p; 392 struct stat stbuf; 393 394#ifdef CLIENT_SUPPORT 395 if (client_active) 396 /* The verification will happen on the server. */ 397 return; 398#endif 399 400 /* FIXME? Do we really want to skip this on noexec? What do we do 401 for the other administrative files? */ 402 if (noexec) 403 return; 404 405 /* If there's no message, then we have nothing to verify. Can this 406 case happen? And if so why would we print a message? */ 407 if (*messagep == NULL) 408 { 409 cvs_output ("No message to verify\n", 0); 410 return; 411 } 412 413 /* Get a temp filename, open a temporary file, write the message to the 414 temp file, and close the file. */ 415 416 fname = cvs_temp_name (); 417 418 fp = fopen (fname, "w"); 419 if (fp == NULL) 420 { 421 error (1, errno, "cannot create temporary file %s", fname); 422 return; 423 } 424 else 425 { 426 fprintf (fp, "%s", *messagep); 427 if ((*messagep)[0] == '\0' || 428 (*messagep)[strlen (*messagep) - 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 ("%s", verifymsg_script); 444 run_arg (fname); 445 if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 446 RUN_NORMAL | RUN_SIGIGNORE)) != 0) 447 error (1, retcode == -1 ? errno : 0, 448 "Message verification failed"); 449 } 450 451 /* put the entire message back into the *messagep variable */ 452 453 fp = open_file (fname, "r"); 454 if (fp == NULL) 455 { 456 error (1, errno, "cannot open temporary file %s", fname); 457 return; 458 } 459 460 if (*messagep) 461 free (*messagep); 462 463 if ( CVS_STAT (fname, &stbuf) != 0) 464 error (1, errno, "cannot find size of temp file %s", fname); 465 466 if (stbuf.st_size == 0) 467 *messagep = NULL; 468 else 469 { 470 /* On NT, we might read less than st_size bytes, but we won't 471 read more. So this works. */ 472 *messagep = (char *) xmalloc (stbuf.st_size + 1); 473 *messagep[0] = '\0'; 474 } 475 476 line = NULL; 477 line_chars_allocated = 0; 478 479 if (*messagep) 480 { 481 p = *messagep; 482 while (1) 483 { 484 line_length = getline (&line, &line_chars_allocated, fp); 485 if (line_length == -1) 486 { 487 if (ferror (fp)) 488 error (0, errno, "warning: cannot read %s", fname); 489 break; 490 } 491 if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0) 492 continue; 493 (void) strcpy (p, line); 494 p += line_length; 495 } 496 } 497 if (fclose (fp) < 0) 498 error (0, errno, "warning: cannot close %s", fname); 499 500 /* Close and delete the temp file */ 501 502 unlink_file (fname); 503 free (fname); 504 } 505} 506 507/* 508 * callback proc for Parse_Info for rcsinfo templates this routine basically 509 * copies the matching template onto the end of the tempfile we are setting 510 * up 511 */ 512/* ARGSUSED */ 513static int 514rcsinfo_proc (repository, template) 515 char *repository; 516 char *template; 517{ 518 static char *last_template; 519 FILE *tfp; 520 521 /* nothing to do if the last one included is the same as this one */ 522 if (last_template && strcmp (last_template, template) == 0) 523 return (0); 524 if (last_template) 525 free (last_template); 526 last_template = xstrdup (template); 527 528 if ((tfp = CVS_FOPEN (template, "r")) != NULL) 529 { 530 char *line = NULL; 531 size_t line_chars_allocated = 0; 532 533 while (getline (&line, &line_chars_allocated, tfp) >= 0) 534 (void) fputs (line, fp); 535 if (ferror (tfp)) 536 error (0, errno, "warning: cannot read %s", template); 537 if (fclose (tfp) < 0) 538 error (0, errno, "warning: cannot close %s", template); 539 if (line) 540 free (line); 541 return (0); 542 } 543 else 544 { 545 error (0, errno, "Couldn't open rcsinfo template file %s", template); 546 return (1); 547 } 548} 549 550/* 551 * Uses setup_tmpfile() to pass the updated message on directly to any 552 * logfile programs that have a regular expression match for the checked in 553 * directory in the source repository. The log information is fed into the 554 * specified program as standard input. 555 */ 556static FILE *logfp; 557static char *message; 558static List *changes; 559 560void 561Update_Logfile (repository, xmessage, xlogfp, xchanges) 562 char *repository; 563 char *xmessage; 564 FILE *xlogfp; 565 List *xchanges; 566{ 567 /* nothing to do if the list is empty */ 568 if (xchanges == NULL || xchanges->list->next == xchanges->list) 569 return; 570 571 /* set up static vars for update_logfile_proc */ 572 message = xmessage; 573 logfp = xlogfp; 574 changes = xchanges; 575 576 /* call Parse_Info to do the actual logfile updates */ 577 (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1); 578} 579 580/* 581 * callback proc to actually do the logfile write from Update_Logfile 582 */ 583static int 584update_logfile_proc (repository, filter) 585 char *repository; 586 char *filter; 587{ 588 return (logfile_write (repository, filter, message, logfp, changes)); 589} 590 591/* 592 * concatenate each filename/version onto str_list 593 */ 594static int 595title_proc (p, closure) 596 Node *p; 597 void *closure; 598{ 599 struct logfile_info *li; 600 char *c; 601 602 li = (struct logfile_info *) p->data; 603 if (li->type == type) 604 { 605 /* Until we decide on the correct logging solution when we add 606 directories or perform imports, T_TITLE nodes will only 607 tack on the name provided, regardless of the format string. 608 You can verify that this assumption is safe by checking the 609 code in add.c (add_directory) and import.c (import). */ 610 611 str_list = xrealloc (str_list, strlen (str_list) + 5); 612 (void) strcat (str_list, " "); 613 614 if (li->type == T_TITLE) 615 { 616 str_list = xrealloc (str_list, 617 strlen (str_list) + strlen (p->key) + 5); 618 (void) strcat (str_list, p->key); 619 } 620 else 621 { 622 /* All other nodes use the format string. */ 623 624 for (c = str_list_format; *c != '\0'; c++) 625 { 626 switch (*c) 627 { 628 case 's': 629 str_list = 630 xrealloc (str_list, 631 strlen (str_list) + strlen (p->key) + 5); 632 (void) strcat (str_list, p->key); 633 break; 634 case 'V': 635 str_list = 636 xrealloc (str_list, 637 (strlen (str_list) 638 + (li->rev_old ? strlen (li->rev_old) : 0) 639 + 10) 640 ); 641 (void) strcat (str_list, (li->rev_old 642 ? li->rev_old : "NONE")); 643 break; 644 case 'v': 645 str_list = 646 xrealloc (str_list, 647 (strlen (str_list) 648 + (li->rev_new ? strlen (li->rev_new) : 0) 649 + 10) 650 ); 651 (void) strcat (str_list, (li->rev_new 652 ? li->rev_new : "NONE")); 653 break; 654 /* All other characters, we insert an empty field (but 655 we do put in the comma separating it from other 656 fields). This way if future CVS versions add formatting 657 characters, one can write a loginfo file which at least 658 won't blow up on an old CVS. */ 659 } 660 if (*(c + 1) != '\0') 661 { 662 str_list = xrealloc (str_list, strlen (str_list) + 5); 663 (void) strcat (str_list, ","); 664 } 665 } 666 } 667 } 668 return (0); 669} 670 671/* 672 * Writes some stuff to the logfile "filter" and returns the status of the 673 * filter program. 674 */ 675static int 676logfile_write (repository, filter, message, logfp, changes) 677 char *repository; 678 char *filter; 679 char *message; 680 FILE *logfp; 681 List *changes; 682{ 683 FILE *pipefp; 684 char *prog; 685 char *cp; 686 int c; 687 int pipestatus; 688 char *fmt_percent; /* the location of the percent sign 689 that starts the format string. */ 690 691 /* The user may specify a format string as part of the filter. 692 Originally, `%s' was the only valid string. The string that 693 was substituted for it was: 694 695 <repository-name> <file1> <file2> <file3> ... 696 697 Each file was either a new directory/import (T_TITLE), or a 698 added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED) 699 file. 700 701 It is desirable to preserve that behavior so lots of commitlog 702 scripts won't die when they get this new code. At the same 703 time, we'd like to pass other information about the files (like 704 version numbers, statuses, or checkin times). 705 706 The solution is to allow a format string that allows us to 707 specify those other pieces of information. The format string 708 will be composed of `%' followed by a single format character, 709 or followed by a set of format characters surrounded by `{' and 710 `}' as separators. The format characters are: 711 712 s = file name 713 V = old version number (pre-checkin) 714 v = new version number (post-checkin) 715 716 For example, valid format strings are: 717 718 %{} 719 %s 720 %{s} 721 %{sVv} 722 723 There's no reason that more items couldn't be added (like 724 modification date or file status [added, modified, updated, 725 etc.]) -- the code modifications would be minimal (logmsg.c 726 (title_proc) and commit.c (check_fileproc)). 727 728 The output will be a string of tokens separated by spaces. For 729 backwards compatibility, the the first token will be the 730 repository name. The rest of the tokens will be 731 comma-delimited lists of the information requested in the 732 format string. For example, if `/u/src/master' is the 733 repository, `%{sVv}' is the format string, and three files 734 (ChangeLog, Makefile, foo.c) were modified, the output might 735 be: 736 737 /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13 738 739 Why this duplicates the old behavior when the format string is 740 `%s' is left as an exercise for the reader. */ 741 742 fmt_percent = strchr (filter, '%'); 743 if (fmt_percent) 744 { 745 int len; 746 char *srepos; 747 char *fmt_begin, *fmt_end; /* beginning and end of the 748 format string specified in 749 filter. */ 750 char *fmt_continue; /* where the string continues 751 after the format string (we 752 might skip a '}') somewhere 753 in there... */ 754 755 /* Grab the format string. */ 756 757 if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0')) 758 { 759 /* The percent stands alone. This is an error. We could 760 be treating ' ' like any other formatting character, but 761 using it as a formatting character seems like it would be 762 a mistake. */ 763 764 /* Would be nice to also be giving the line number. */ 765 error (0, 0, "loginfo: '%%' not followed by formatting character"); 766 fmt_begin = fmt_percent + 1; 767 fmt_end = fmt_begin; 768 fmt_continue = fmt_begin; 769 } 770 else if (*(fmt_percent + 1) == '{') 771 { 772 /* The percent has a set of characters following it. */ 773 774 fmt_begin = fmt_percent + 2; 775 fmt_end = strchr (fmt_begin, '}'); 776 if (fmt_end) 777 { 778 /* Skip over the '}' character. */ 779 780 fmt_continue = fmt_end + 1; 781 } 782 else 783 { 784 /* There was no close brace -- assume that format 785 string continues to the end of the line. */ 786 787 /* Would be nice to also be giving the line number. */ 788 error (0, 0, "loginfo: '}' missing"); 789 fmt_end = fmt_begin + strlen (fmt_begin); 790 fmt_continue = fmt_end; 791 } 792 } 793 else 794 { 795 /* The percent has a single character following it. FIXME: 796 %% should expand to a regular percent sign. */ 797 798 fmt_begin = fmt_percent + 1; 799 fmt_end = fmt_begin + 1; 800 fmt_continue = fmt_end; 801 } 802 803 len = fmt_end - fmt_begin; 804 str_list_format = xmalloc (sizeof (char) * (len + 1)); 805 strncpy (str_list_format, fmt_begin, len); 806 str_list_format[len] = '\0'; 807 808 /* Allocate an initial chunk of memory. As we build up the string 809 we will realloc it. */ 810 if (!str_list) 811 str_list = xmalloc (1); 812 str_list[0] = '\0'; 813 814 /* Add entries to the string. Don't bother looking for 815 entries if the format string is empty. */ 816 817 if (str_list_format[0] != '\0') 818 { 819 type = T_TITLE; 820 (void) walklist (changes, title_proc, NULL); 821 type = T_ADDED; 822 (void) walklist (changes, title_proc, NULL); 823 type = T_MODIFIED; 824 (void) walklist (changes, title_proc, NULL); 825 type = T_REMOVED; 826 (void) walklist (changes, title_proc, NULL); 827 } 828 829 free (str_list_format); 830 831 /* Construct the final string. */ 832 833 srepos = Short_Repository (repository); 834 835 prog = xmalloc ((fmt_percent - filter) + strlen (srepos) 836 + strlen (str_list) + strlen (fmt_continue) 837 + 10); 838 (void) strncpy (prog, filter, fmt_percent - filter); 839 prog[fmt_percent - filter] = '\0'; 840 (void) strcat (prog, "'"); 841 (void) strcat (prog, srepos); 842 (void) strcat (prog, str_list); 843 (void) strcat (prog, "'"); 844 (void) strcat (prog, fmt_continue); 845 846 /* To be nice, free up some memory. */ 847 848 free (str_list); 849 str_list = (char *) NULL; 850 } 851 else 852 { 853 /* There's no format string. */ 854 prog = xstrdup (filter); 855 } 856 857 if ((pipefp = run_popen (prog, "w")) == NULL) 858 { 859 if (!noexec) 860 error (0, 0, "cannot write entry to log filter: %s", prog); 861 free (prog); 862 return (1); 863 } 864 (void) fprintf (pipefp, "Update of %s\n", repository); 865 (void) fprintf (pipefp, "In directory %s:", hostname); 866 cp = xgetwd (); 867 if (cp == NULL) 868 fprintf (pipefp, "<cannot get working directory: %s>\n\n", 869 strerror (errno)); 870 else 871 { 872 fprintf (pipefp, "%s\n\n", cp); 873 free (cp); 874 } 875 876 setup_tmpfile (pipefp, "", changes); 877 (void) fprintf (pipefp, "Log Message:\n%s\n", message); 878 if (logfp != (FILE *) 0) 879 { 880 (void) fprintf (pipefp, "Status:\n"); 881 rewind (logfp); 882 while ((c = getc (logfp)) != EOF) 883 (void) putc ((char) c, pipefp); 884 } 885 free (prog); 886 pipestatus = pclose (pipefp); 887 return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0; 888} 889 890/* 891 * We choose to use the *last* match within the editinfo file for this 892 * repository. This allows us to have a global editinfo program for the 893 * root of some hierarchy, for example, and different ones within different 894 * sub-directories of the root (like a special checker for changes made to 895 * the "src" directory versus changes made to the "doc" or "test" 896 * directories. 897 */ 898/* ARGSUSED */ 899static int 900editinfo_proc(repository, editor) 901 char *repository; 902 char *editor; 903{ 904 /* nothing to do if the last match is the same as this one */ 905 if (editinfo_editor && strcmp (editinfo_editor, editor) == 0) 906 return (0); 907 if (editinfo_editor) 908 free (editinfo_editor); 909 910 editinfo_editor = xstrdup (editor); 911 return (0); 912} 913 914/* This routine is calld by Parse_Info. it asigns the name of the 915 * message verification script to the global variable verify_script 916 */ 917static int 918verifymsg_proc (repository, script) 919 char *repository; 920 char *script; 921{ 922 if (verifymsg_script && strcmp (verifymsg_script, script) == 0) 923 return (0); 924 if (verifymsg_script) 925 free (verifymsg_script); 926 verifymsg_script = xstrdup (script); 927 return (0); 928} 929