rcsgen.c revision 10
1/* 2 * RCS revision generation 3 */ 4 5/* Copyright (C) 1982, 1988, 1989 Walter Tichy 6 Copyright 1990, 1991 by Paul Eggert 7 Distributed under license by the Free Software Foundation, Inc. 8 9This file is part of RCS. 10 11RCS is free software; you can redistribute it and/or modify 12it under the terms of the GNU General Public License as published by 13the Free Software Foundation; either version 2, or (at your option) 14any later version. 15 16RCS is distributed in the hope that it will be useful, 17but WITHOUT ANY WARRANTY; without even the implied warranty of 18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19GNU General Public License for more details. 20 21You should have received a copy of the GNU General Public License 22along with RCS; see the file COPYING. If not, write to 23the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 24 25Report problems and direct all questions to: 26 27 rcs-bugs@cs.purdue.edu 28 29*/ 30 31 32 33/* $Log: rcsgen.c,v $ 34 * Revision 5.10 1991/10/07 17:32:46 eggert 35 * Fix log bugs, e.g. ci -t/dev/null when has_mmap. 36 * 37 * Revision 5.9 1991/09/10 22:15:46 eggert 38 * Fix test for redirected stdin. 39 * 40 * Revision 5.8 1991/08/19 03:13:55 eggert 41 * Add piece tables. Tune. 42 * 43 * Revision 5.7 1991/04/21 11:58:24 eggert 44 * Add MS-DOS support. 45 * 46 * Revision 5.6 1990/12/27 19:54:26 eggert 47 * Fix bug: rcs -t inserted \n, making RCS file grow. 48 * 49 * Revision 5.5 1990/12/04 05:18:45 eggert 50 * Use -I for prompts and -q for diagnostics. 51 * 52 * Revision 5.4 1990/11/01 05:03:47 eggert 53 * Add -I and new -t behavior. Permit arbitrary data in logs. 54 * 55 * Revision 5.3 1990/09/21 06:12:43 hammer 56 * made putdesc() treat stdin the same whether or not it was from a terminal 57 * by making it recognize that a single '.' was then end of the 58 * description always 59 * 60 * Revision 5.2 1990/09/04 08:02:25 eggert 61 * Fix `co -p1.1 -ko' bug. Standardize yes-or-no procedure. 62 * 63 * Revision 5.1 1990/08/29 07:14:01 eggert 64 * Clean old log messages too. 65 * 66 * Revision 5.0 1990/08/22 08:12:52 eggert 67 * Remove compile-time limits; use malloc instead. 68 * Ansify and Posixate. 69 * 70 * Revision 4.7 89/05/01 15:12:49 narten 71 * changed copyright header to reflect current distribution rules 72 * 73 * Revision 4.6 88/08/28 14:59:10 eggert 74 * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin() 75 * 76 * Revision 4.5 87/12/18 11:43:25 narten 77 * additional lint cleanups, and a bug fix from the 4.3BSD version that 78 * keeps "ci" from sticking a '\377' into the description if you run it 79 * with a zero-length file as the description. (Guy Harris) 80 * 81 * Revision 4.4 87/10/18 10:35:10 narten 82 * Updating version numbers. Changes relative to 1.1 actually relative to 83 * 4.2 84 * 85 * Revision 1.3 87/09/24 13:59:51 narten 86 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 87 * warnings) 88 * 89 * Revision 1.2 87/03/27 14:22:27 jenkins 90 * Port to suns 91 * 92 * Revision 4.2 83/12/02 23:01:39 wft 93 * merged 4.1 and 3.3.1.1 (clearerr(stdin)). 94 * 95 * Revision 4.1 83/05/10 16:03:33 wft 96 * Changed putamin() to abort if trying to reread redirected stdin. 97 * Fixed getdesc() to output a prompt on initial newline. 98 * 99 * Revision 3.3.1.1 83/10/19 04:21:51 lepreau 100 * Added clearerr(stdin) for re-reading description from stdin. 101 * 102 * Revision 3.3 82/11/28 21:36:49 wft 103 * 4.2 prerelease 104 * 105 * Revision 3.3 82/11/28 21:36:49 wft 106 * Replaced ferror() followed by fclose() with ffclose(). 107 * Putdesc() now suppresses the prompts if stdin 108 * is not a terminal. A pointer to the current log message is now 109 * inserted into the corresponding delta, rather than leaving it in a 110 * global variable. 111 * 112 * Revision 3.2 82/10/18 21:11:26 wft 113 * I added checks for write errors during editing, and improved 114 * the prompt on putdesc(). 115 * 116 * Revision 3.1 82/10/13 15:55:09 wft 117 * corrected type of variables assigned to by getc (char --> int) 118 */ 119 120 121 122 123#include "rcsbase.h" 124 125libId(genId, "$Id: rcsgen.c,v 5.10 1991/10/07 17:32:46 eggert Exp $") 126 127int interactiveflag; /* Should we act as if stdin is a tty? */ 128struct buf curlogbuf; /* buffer for current log message */ 129 130enum stringwork { enter, copy, edit, expand, edit_expand }; 131static void scandeltatext P((struct hshentry*,enum stringwork,int)); 132 133 134 135 136 char const * 137buildrevision(deltas, target, outfile, expandflag) 138 struct hshentries const *deltas; 139 struct hshentry *target; 140 FILE *outfile; 141 int expandflag; 142/* Function: Generates the revision given by target 143 * by retrieving all deltas given by parameter deltas and combining them. 144 * If outfile is set, the revision is output to it, 145 * otherwise written into a temporary file. 146 * Temporary files are allocated by maketemp(). 147 * if expandflag is set, keyword expansion is performed. 148 * Return nil if outfile is set, the name of the temporary file otherwise. 149 * 150 * Algorithm: Copy initial revision unchanged. Then edit all revisions but 151 * the last one into it, alternating input and output files (resultfile and 152 * editfile). The last revision is then edited in, performing simultaneous 153 * keyword substitution (this saves one extra pass). 154 * All this simplifies if only one revision needs to be generated, 155 * or no keyword expansion is necessary, or if output goes to stdout. 156 */ 157{ 158 if (deltas->first == target) { 159 /* only latest revision to generate */ 160 openfcopy(outfile); 161 scandeltatext(target, expandflag?expand:copy, true); 162 if (outfile) 163 return 0; 164 else { 165 Ozclose(&fcopy); 166 return(resultfile); 167 } 168 } else { 169 /* several revisions to generate */ 170 /* Get initial revision without keyword expansion. */ 171 scandeltatext(deltas->first, enter, false); 172 while ((deltas=deltas->rest)->rest) { 173 /* do all deltas except last one */ 174 scandeltatext(deltas->first, edit, false); 175 } 176 if (expandflag || outfile) { 177 /* first, get to beginning of file*/ 178 finishedit((struct hshentry *)nil, outfile, false); 179 } 180 scandeltatext(deltas->first, expandflag?edit_expand:edit, true); 181 finishedit( 182 expandflag ? deltas->first : (struct hshentry*)nil, 183 outfile, true 184 ); 185 if (outfile) 186 return 0; 187 Ozclose(&fcopy); 188 return resultfile; 189 } 190} 191 192 193 194 static void 195scandeltatext(delta, func, needlog) 196 struct hshentry * delta; 197 enum stringwork func; 198 int needlog; 199/* Function: Scans delta text nodes up to and including the one given 200 * by delta. For the one given by delta, the log message is saved into 201 * delta->log if needlog is set; func specifies how to handle the text. 202 * Assumes the initial lexeme must be read in first. 203 * Does not advance nexttok after it is finished. 204 */ 205{ 206 struct hshentry const *nextdelta; 207 struct cbuf cb; 208 209 for (;;) { 210 if (eoflex()) 211 fatserror("can't find delta for revision %s", delta->num); 212 nextlex(); 213 if (!(nextdelta=getnum())) { 214 fatserror("delta number corrupted"); 215 } 216 getkeystring(Klog); 217 if (needlog && delta==nextdelta) { 218 cb = savestring(&curlogbuf); 219 delta->log = cleanlogmsg(curlogbuf.string, cb.size); 220 } else {readstring(); 221 } 222 nextlex(); 223 while (nexttok==ID && strcmp(NextString,Ktext)!=0) 224 ignorephrase(); 225 getkeystring(Ktext); 226 227 if (delta==nextdelta) 228 break; 229 readstring(); /* skip over it */ 230 231 } 232 switch (func) { 233 case enter: enterstring(); break; 234 case copy: copystring(); break; 235 case expand: xpandstring(delta); break; 236 case edit: editstring((struct hshentry *)nil); break; 237 case edit_expand: editstring(delta); break; 238 } 239} 240 241 struct cbuf 242cleanlogmsg(m, s) 243 char *m; 244 size_t s; 245{ 246 register char *t = m; 247 register char const *f = t; 248 struct cbuf r; 249 while (s) { 250 --s; 251 if ((*t++ = *f++) == '\n') 252 while (m < --t) 253 if (t[-1]!=' ' && t[-1]!='\t') { 254 *t++ = '\n'; 255 break; 256 } 257 } 258 while (m < t && (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n')) 259 --t; 260 r.string = m; 261 r.size = t - m; 262 return r; 263} 264 265 266int ttystdin() 267{ 268 static int initialized; 269 if (!initialized) { 270 if (!interactiveflag) 271 interactiveflag = isatty(STDIN_FILENO); 272 initialized = true; 273 } 274 return interactiveflag; 275} 276 277 int 278getcstdin() 279{ 280 register FILE *in; 281 register int c; 282 283 in = stdin; 284 if (feof(in) && ttystdin()) 285 clearerr(in); 286 c = getc(in); 287 if (c < 0) { 288 testIerror(in); 289 if (feof(in) && ttystdin()) 290 afputc('\n',stderr); 291 } 292 return c; 293} 294 295#if has_prototypes 296 int 297yesorno(int default_answer, char const *question, ...) 298#else 299 /*VARARGS2*/ int 300 yesorno(default_answer, question, va_alist) 301 int default_answer; char const *question; va_dcl 302#endif 303{ 304 va_list args; 305 register int c, r; 306 if (!quietflag && ttystdin()) { 307 oflush(); 308 vararg_start(args, question); 309 fvfprintf(stderr, question, args); 310 va_end(args); 311 eflush(); 312 r = c = getcstdin(); 313 while (c!='\n' && !feof(stdin)) 314 c = getcstdin(); 315 if (r=='y' || r=='Y') 316 return true; 317 if (r=='n' || r=='N') 318 return false; 319 } 320 return default_answer; 321} 322 323 324 void 325putdesc(textflag, textfile) 326 int textflag; 327 char *textfile; 328/* Function: puts the descriptive text into file frewrite. 329 * if finptr && !textflag, the text is copied from the old description. 330 * Otherwise, if the textfile!=nil, the text is read from that 331 * file, or from stdin, if textfile==nil. 332 * A textfile with a leading '-' is treated as a string, not a file name. 333 * If finptr, the old descriptive text is discarded. 334 * Always clears foutptr. 335 */ 336{ 337 static struct buf desc; 338 static struct cbuf desclean; 339 340 register FILE *txt; 341 register int c; 342 register FILE * frew; 343 register char *p; 344 register size_t s; 345 char const *plim; 346 347 frew = frewrite; 348 if (finptr && !textflag) { 349 /* copy old description */ 350 aprintf(frew, "\n\n%s%c", Kdesc, nextc); 351 foutptr = frewrite; 352 getdesc(false); 353 foutptr = 0; 354 } else { 355 foutptr = 0; 356 /* get new description */ 357 if (finptr) { 358 /*skip old description*/ 359 getdesc(false); 360 } 361 aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM); 362 if (!textfile) 363 desclean = getsstdin( 364 "t-", "description", 365 "NOTE: This is NOT the log message!\n", &desc 366 ); 367 else if (!desclean.string) { 368 if (*textfile == '-') { 369 p = textfile + 1; 370 s = strlen(p); 371 } else { 372 if (!(txt = fopen(textfile, "r"))) 373 efaterror(textfile); 374 bufalloc(&desc, 1); 375 p = desc.string; 376 plim = p + desc.size; 377 for (;;) { 378 if ((c=getc(txt)) < 0) { 379 testIerror(txt); 380 if (feof(txt)) 381 break; 382 } 383 if (plim <= p) 384 p = bufenlarge(&desc, &plim); 385 *p++ = c; 386 } 387 if (fclose(txt) != 0) 388 Ierror(); 389 s = p - desc.string; 390 p = desc.string; 391 } 392 desclean = cleanlogmsg(p, s); 393 } 394 putstring(frew, false, desclean, true); 395 aputc('\n', frew); 396 } 397} 398 399 struct cbuf 400getsstdin(option, name, note, buf) 401 char const *option, *name, *note; 402 struct buf *buf; 403{ 404 register int c; 405 register char *p; 406 register size_t i; 407 register int tty = ttystdin(); 408 409 if (tty) 410 aprintf(stderr, 411 "enter %s, terminated with single '.' or end of file:\n%s>> ", 412 name, note 413 ); 414 else if (feof(stdin)) 415 faterror("can't reread redirected stdin for %s; use -%s<%s>", 416 name, option, name 417 ); 418 419 for ( 420 i = 0, p = 0; 421 c = getcstdin(), !feof(stdin); 422 bufrealloc(buf, i+1), p = buf->string, p[i++] = c 423 ) 424 if (c == '\n') 425 if (i && p[i-1]=='.' && (i==1 || p[i-2]=='\n')) { 426 /* Remove trailing '.'. */ 427 --i; 428 break; 429 } else if (tty) 430 aputs(">> ", stderr); 431 return cleanlogmsg(p, i); 432} 433