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