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