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