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