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