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