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