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