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