1 /* $OpenBSD: rlog.c,v 1.75 2020/10/15 19:47:46 naddy Exp $ */ 2 /* 3 * Copyright (c) 2005, 2009 Joris Vink <joris@openbsd.org> 4 * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <ctype.h> 29 #include <err.h> 30 #include <libgen.h> 31 #include <limits.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <unistd.h> 37 38 #include "rcsprog.h" 39 #include "diff.h" 40 41 #define RLOG_DATE_LATER 0x01 42 #define RLOG_DATE_EARLIER 0x02 43 #define RLOG_DATE_SINGLE 0x04 44 #define RLOG_DATE_RANGE 0x08 45 #define RLOG_DATE_INCLUSIVE 0x10 46 47 static int rlog_select_daterev(RCSFILE *, char *); 48 static void rlog_file(const char *, RCSFILE *); 49 static void rlog_rev_print(struct rcs_delta *); 50 51 #define RLOG_OPTSTRING "d:E:hLl::NqRr::S:s:TtVw::x::z::" 52 53 static int dflag, hflag, Lflag, lflag, rflag, tflag, Nflag, wflag; 54 static char *llist = NULL; 55 static char *slist = NULL; 56 static char *wlist = NULL; 57 static char *revisions = NULL; 58 static char *rlog_dates = NULL; 59 static char *revsep = "----------------------------"; 60 static char *revend = "=====================================================" 61 "========================"; 62 63 __dead void 64 rlog_usage(void) 65 { 66 fprintf(stderr, 67 "usage: rlog [-bhLNRtV] [-ddates] [-Eendsep] [-l[lockers]] " 68 "[-r[revs]]\n" 69 " [-Srevsep] [-sstates] [-w[logins]] [-xsuffixes] " 70 "[-ztz] file ...\n"); 71 72 exit(1); 73 } 74 75 int 76 rlog_main(int argc, char **argv) 77 { 78 RCSFILE *file; 79 int Rflag; 80 int i, ch, fd, status; 81 char fpath[PATH_MAX]; 82 83 rcsnum_flags |= RCSNUM_NO_MAGIC; 84 hflag = Rflag = rflag = status = 0; 85 while ((ch = rcs_getopt(argc, argv, RLOG_OPTSTRING)) != -1) { 86 switch (ch) { 87 case 'd': 88 dflag = 1; 89 rlog_dates = rcs_optarg; 90 break; 91 case 'E': 92 revend = rcs_optarg; 93 break; 94 case 'h': 95 hflag = 1; 96 break; 97 case 'L': 98 Lflag = 1; 99 break; 100 case 'l': 101 lflag = 1; 102 llist = rcs_optarg; 103 break; 104 case 'N': 105 Nflag = 1; 106 break; 107 case 'q': 108 /* 109 * kept for compatibility 110 */ 111 break; 112 case 'R': 113 Rflag = 1; 114 break; 115 case 'r': 116 rflag = 1; 117 revisions = rcs_optarg; 118 break; 119 case 'S': 120 revsep = rcs_optarg; 121 break; 122 case 's': 123 slist = rcs_optarg; 124 break; 125 case 'T': 126 /* 127 * kept for compatibility 128 */ 129 break; 130 case 't': 131 tflag = 1; 132 break; 133 case 'V': 134 printf("%s\n", rcs_version); 135 exit(0); 136 case 'w': 137 wflag = 1; 138 wlist = rcs_optarg; 139 break; 140 case 'x': 141 /* Use blank extension if none given. */ 142 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 143 break; 144 case 'z': 145 timezone_flag = rcs_optarg; 146 break; 147 default: 148 (usage)(); 149 } 150 } 151 152 argc -= rcs_optind; 153 argv += rcs_optind; 154 155 if (argc == 0) { 156 warnx("no input file"); 157 (usage)(); 158 } 159 160 if (hflag == 1 && tflag == 1) { 161 warnx("warning: -t overrides -h."); 162 hflag = 0; 163 } 164 165 for (i = 0; i < argc; i++) { 166 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 167 if (fd < 0) { 168 warn("%s", fpath); 169 status = 1; 170 continue; 171 } 172 173 if ((file = rcs_open(fpath, fd, 174 RCS_READ|RCS_PARSE_FULLY)) == NULL) { 175 status = 1; 176 continue; 177 } 178 179 if (Lflag == 1 && TAILQ_EMPTY(&(file->rf_locks))) { 180 rcs_close(file); 181 continue; 182 } 183 184 if (Rflag == 1) { 185 printf("%s\n", fpath); 186 rcs_close(file); 187 continue; 188 } 189 190 rlog_file(argv[i], file); 191 192 rcs_close(file); 193 } 194 195 return (status); 196 } 197 198 static int 199 rlog_select_daterev(RCSFILE *rcsfile, char *date) 200 { 201 int i, nrev, flags; 202 struct rcs_delta *rdp; 203 struct rcs_argvector *args; 204 char *first, *last, delim; 205 time_t firstdate, lastdate, rcsdate; 206 207 nrev = 0; 208 args = rcs_strsplit(date, ";"); 209 210 for (i = 0; args->argv[i] != NULL; i++) { 211 flags = 0; 212 firstdate = lastdate = -1; 213 214 first = args->argv[i]; 215 last = strchr(args->argv[i], '<'); 216 if (last != NULL) { 217 delim = *last; 218 *last++ = '\0'; 219 220 if (*last == '=') { 221 last++; 222 flags |= RLOG_DATE_INCLUSIVE; 223 } 224 } else { 225 last = strchr(args->argv[i], '>'); 226 if (last != NULL) { 227 delim = *last; 228 *last++ = '\0'; 229 230 if (*last == '=') { 231 last++; 232 flags |= RLOG_DATE_INCLUSIVE; 233 } 234 } 235 } 236 237 if (last == NULL) { 238 flags |= RLOG_DATE_SINGLE; 239 if ((firstdate = date_parse(first)) == -1) 240 return -1; 241 delim = '\0'; 242 last = "\0"; 243 } else { 244 while (*last && isspace((unsigned char)*last)) 245 last++; 246 } 247 248 if (delim == '>' && *last == '\0') { 249 flags |= RLOG_DATE_EARLIER; 250 if ((firstdate = date_parse(first)) == -1) 251 return -1; 252 } 253 254 if (delim == '>' && *first == '\0' && *last != '\0') { 255 flags |= RLOG_DATE_LATER; 256 if ((firstdate = date_parse(last)) == -1) 257 return -1; 258 } 259 260 if (delim == '<' && *last == '\0') { 261 flags |= RLOG_DATE_LATER; 262 if ((firstdate = date_parse(first)) == -1) 263 return -1; 264 } 265 266 if (delim == '<' && *first == '\0' && *last != '\0') { 267 flags |= RLOG_DATE_EARLIER; 268 if ((firstdate = date_parse(last)) == -1) 269 return -1; 270 } 271 272 if (*first != '\0' && *last != '\0') { 273 flags |= RLOG_DATE_RANGE; 274 275 if (delim == '<') { 276 firstdate = date_parse(first); 277 lastdate = date_parse(last); 278 } else { 279 firstdate = date_parse(last); 280 lastdate = date_parse(first); 281 } 282 if (firstdate == -1 || lastdate == -1) 283 return -1; 284 } 285 286 TAILQ_FOREACH(rdp, &(rcsfile->rf_delta), rd_list) { 287 rcsdate = mktime(&(rdp->rd_date)); 288 289 if (flags & RLOG_DATE_SINGLE) { 290 if (rcsdate <= firstdate) { 291 rdp->rd_flags |= RCS_RD_SELECT; 292 nrev++; 293 break; 294 } 295 } 296 297 if (flags & RLOG_DATE_EARLIER) { 298 if (rcsdate < firstdate) { 299 rdp->rd_flags |= RCS_RD_SELECT; 300 nrev++; 301 continue; 302 } 303 304 if (flags & RLOG_DATE_INCLUSIVE && 305 (rcsdate <= firstdate)) { 306 rdp->rd_flags |= RCS_RD_SELECT; 307 nrev++; 308 continue; 309 } 310 } 311 312 if (flags & RLOG_DATE_LATER) { 313 if (rcsdate > firstdate) { 314 rdp->rd_flags |= RCS_RD_SELECT; 315 nrev++; 316 continue; 317 } 318 319 if (flags & RLOG_DATE_INCLUSIVE && 320 (rcsdate >= firstdate)) { 321 rdp->rd_flags |= RCS_RD_SELECT; 322 nrev++; 323 continue; 324 } 325 } 326 327 if (flags & RLOG_DATE_RANGE) { 328 if ((rcsdate > firstdate) && 329 (rcsdate < lastdate)) { 330 rdp->rd_flags |= RCS_RD_SELECT; 331 nrev++; 332 continue; 333 } 334 335 if (flags & RLOG_DATE_INCLUSIVE && 336 ((rcsdate >= firstdate) && 337 (rcsdate <= lastdate))) { 338 rdp->rd_flags |= RCS_RD_SELECT; 339 nrev++; 340 continue; 341 } 342 } 343 } 344 } 345 346 return (nrev); 347 } 348 349 static void 350 rlog_file(const char *fname, RCSFILE *file) 351 { 352 char fnamebuf[PATH_MAX], numb[RCS_REV_BUFSZ]; 353 u_int nrev; 354 struct rcs_sym *sym; 355 struct rcs_access *acp; 356 struct rcs_delta *rdp; 357 struct rcs_lock *lkp; 358 char *workfile, *p; 359 360 if (rflag == 1) 361 nrev = rcs_rev_select(file, revisions); 362 else if (dflag == 1) { 363 if ((nrev = rlog_select_daterev(file, rlog_dates)) == (u_int)-1) 364 errx(1, "invalid date: %s", rlog_dates); 365 } else 366 nrev = file->rf_ndelta; 367 368 if (strlcpy(fnamebuf, fname, sizeof(fnamebuf)) >= sizeof(fnamebuf)) 369 errx(1, "rlog_file: truncation"); 370 371 if ((workfile = basename(fnamebuf)) == NULL) 372 err(1, "basename"); 373 374 /* 375 * In case they specified 'foo,v' as argument. 376 */ 377 if ((p = strrchr(workfile, ',')) != NULL) 378 *p = '\0'; 379 380 printf("\nRCS file: %s", file->rf_path); 381 printf("\nWorking file: %s", workfile); 382 printf("\nhead:"); 383 if (file->rf_head != NULL) 384 printf(" %s", rcsnum_tostr(file->rf_head, numb, sizeof(numb))); 385 386 printf("\nbranch:"); 387 if (rcs_branch_get(file) != NULL) { 388 printf(" %s", rcsnum_tostr(rcs_branch_get(file), 389 numb, sizeof(numb))); 390 } 391 392 printf("\nlocks: %s", (file->rf_flags & RCS_SLOCK) ? "strict" : ""); 393 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) 394 printf("\n\t%s: %s", lkp->rl_name, 395 rcsnum_tostr(lkp->rl_num, numb, sizeof(numb))); 396 printf("\naccess list:\n"); 397 TAILQ_FOREACH(acp, &(file->rf_access), ra_list) 398 printf("\t%s\n", acp->ra_name); 399 400 if (Nflag == 0) { 401 printf("symbolic names:\n"); 402 TAILQ_FOREACH(sym, &(file->rf_symbols), rs_list) { 403 printf("\t%s: %s\n", sym->rs_name, 404 rcsnum_tostr(sym->rs_num, numb, sizeof(numb))); 405 } 406 } 407 408 printf("keyword substitution: %s\n", 409 file->rf_expand == NULL ? "kv" : file->rf_expand); 410 411 printf("total revisions: %u", file->rf_ndelta); 412 413 if (file->rf_head != NULL && hflag == 0 && tflag == 0) 414 printf(";\tselected revisions: %u", nrev); 415 416 printf("\n"); 417 418 419 if (hflag == 0 || tflag == 1) 420 printf("description:\n%s", file->rf_desc); 421 422 if (hflag == 0 && tflag == 0 && 423 !(lflag == 1 && TAILQ_EMPTY(&file->rf_locks))) { 424 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 425 /* 426 * if selections are enabled verify that entry is 427 * selected. 428 */ 429 if ((rflag == 0 && dflag == 0) 430 || (rdp->rd_flags & RCS_RD_SELECT)) 431 rlog_rev_print(rdp); 432 } 433 } 434 435 printf("%s\n", revend); 436 } 437 438 static void 439 rlog_rev_print(struct rcs_delta *rdp) 440 { 441 int i, found; 442 struct tm t; 443 char *author, numb[RCS_REV_BUFSZ], *fmt, timeb[RCS_TIME_BUFSZ]; 444 struct rcs_argvector *largv, *sargv, *wargv; 445 struct rcs_branch *rb; 446 struct rcs_delta *nrdp; 447 448 found = 0; 449 author = NULL; 450 451 /* -l[lockers] */ 452 if (lflag == 1) { 453 if (rdp->rd_locker != NULL) 454 found++; 455 456 if (llist != NULL) { 457 /* if locker is empty, no need to go further. */ 458 if (rdp->rd_locker == NULL) 459 return; 460 largv = rcs_strsplit(llist, ","); 461 for (i = 0; largv->argv[i] != NULL; i++) { 462 if (strcmp(rdp->rd_locker, largv->argv[i]) 463 == 0) { 464 found++; 465 break; 466 } 467 found = 0; 468 } 469 rcs_argv_destroy(largv); 470 } 471 } 472 473 /* -sstates */ 474 if (slist != NULL) { 475 sargv = rcs_strsplit(slist, ","); 476 for (i = 0; sargv->argv[i] != NULL; i++) { 477 if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) { 478 found++; 479 break; 480 } 481 found = 0; 482 } 483 rcs_argv_destroy(sargv); 484 } 485 486 /* -w[logins] */ 487 if (wflag == 1) { 488 if (wlist != NULL) { 489 wargv = rcs_strsplit(wlist, ","); 490 for (i = 0; wargv->argv[i] != NULL; i++) { 491 if (strcmp(rdp->rd_author, wargv->argv[i]) 492 == 0) { 493 found++; 494 break; 495 } 496 found = 0; 497 } 498 rcs_argv_destroy(wargv); 499 } else { 500 if ((author = getlogin()) == NULL) 501 err(1, "getlogin"); 502 503 if (strcmp(rdp->rd_author, author) == 0) 504 found++; 505 } 506 } 507 508 /* XXX dirty... */ 509 if ((((slist != NULL && wflag == 1) || 510 (slist != NULL && lflag == 1) || 511 (lflag == 1 && wflag == 1)) && found < 2) || 512 (((slist != NULL && lflag == 1 && wflag == 1) || 513 (slist != NULL || lflag == 1 || wflag == 1)) && found == 0)) 514 return; 515 516 printf("%s\n", revsep); 517 518 rcsnum_tostr(rdp->rd_num, numb, sizeof(numb)); 519 520 printf("revision %s", numb); 521 if (rdp->rd_locker != NULL) 522 printf("\tlocked by: %s;", rdp->rd_locker); 523 524 if (timezone_flag != NULL) { 525 rcs_set_tz(timezone_flag, rdp, &t); 526 fmt = "%Y-%m-%d %H:%M:%S%z"; 527 } else { 528 t = rdp->rd_date; 529 fmt = "%Y/%m/%d %H:%M:%S"; 530 } 531 532 (void)strftime(timeb, sizeof(timeb), fmt, &t); 533 534 printf("\ndate: %s; author: %s; state: %s;", timeb, rdp->rd_author, 535 rdp->rd_state); 536 537 /* 538 * If we are a branch revision, the diff of this revision is stored 539 * in place. 540 * Otherwise, it is stored in the previous revision as a reversed diff. 541 */ 542 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 543 nrdp = rdp; 544 else 545 nrdp = TAILQ_NEXT(rdp, rd_list); 546 547 /* 548 * We do not write diff stats for the first revision of the default 549 * branch, since it was not a diff but a full text. 550 */ 551 if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) { 552 int added, removed; 553 554 rcs_delta_stats(nrdp, &added, &removed); 555 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 556 printf(" lines: +%d -%d;", added, removed); 557 else 558 printf(" lines: +%d -%d;", removed, added); 559 } 560 561 if (rdp->rd_commitid != NULL) 562 printf(" commitid: %s;", rdp->rd_commitid); 563 564 printf("\n"); 565 566 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 567 printf("branches:"); 568 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 569 RCSNUM *branch; 570 branch = rcsnum_revtobr(rb->rb_num); 571 (void)rcsnum_tostr(branch, numb, sizeof(numb)); 572 printf(" %s;", numb); 573 rcsnum_free(branch); 574 } 575 printf("\n"); 576 } 577 578 printf("%s", rdp->rd_log); 579 } 580