1 /* $OpenBSD: getlog.c,v 1.100 2016/10/15 22:20:17 millert Exp $ */ 2 /* 3 * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org> 4 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <unistd.h> 20 #include <stdlib.h> 21 #include <string.h> 22 #include <errno.h> 23 #include <ctype.h> 24 25 #include "cvs.h" 26 #include "remote.h" 27 28 #define L_HEAD 0x01 29 #define L_HEAD_DESCR 0x02 30 #define L_NAME 0x04 31 #define L_NOTAGS 0x08 32 #define L_LOGINS 0x10 33 #define L_STATES 0x20 34 35 #define LDATE_LATER 0x01 36 #define LDATE_EARLIER 0x02 37 #define LDATE_SINGLE 0x04 38 #define LDATE_RANGE 0x08 39 #define LDATE_INCLUSIVE 0x10 40 41 void cvs_log_local(struct cvs_file *); 42 static void log_rev_print(struct rcs_delta *); 43 static char *push_date(char *dest, const char *); 44 static u_int date_select(RCSFILE *, char *); 45 46 int runflags = 0; 47 char *logrev = NULL; 48 char *logdate = NULL; 49 char *slist = NULL; 50 char *wlist = NULL; 51 52 struct cvs_cmd cvs_cmd_log = { 53 CVS_OP_LOG, CVS_USE_WDIR, "log", 54 { "lo" }, 55 "Print out history information for files", 56 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 57 "bd:hlNRr:s:tw:", 58 NULL, 59 cvs_getlog 60 }; 61 62 struct cvs_cmd cvs_cmd_rlog = { 63 CVS_OP_RLOG, 0, "rlog", 64 { "rlo" }, 65 "Print out history information for files", 66 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 67 "bd:hlNRr:s:tw:", 68 NULL, 69 cvs_getlog 70 }; 71 72 int 73 cvs_getlog(int argc, char **argv) 74 { 75 int ch, flags, i; 76 char *arg = "."; 77 struct cvs_recursion cr; 78 79 flags = CR_RECURSE_DIRS; 80 81 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_LOG ? 82 cvs_cmd_log.cmd_opts : cvs_cmd_rlog.cmd_opts)) != -1) { 83 switch (ch) { 84 case 'd': 85 logdate = push_date(logdate, optarg); 86 break; 87 case 'h': 88 runflags |= L_HEAD; 89 break; 90 case 'l': 91 flags &= ~CR_RECURSE_DIRS; 92 break; 93 case 'N': 94 runflags |= L_NOTAGS; 95 break; 96 case 'R': 97 runflags |= L_NAME; 98 break; 99 case 'r': 100 logrev = optarg; 101 break; 102 case 's': 103 runflags |= L_STATES; 104 slist = optarg; 105 break; 106 case 't': 107 runflags |= L_HEAD_DESCR; 108 break; 109 case 'w': 110 runflags |= L_LOGINS; 111 wlist = optarg; 112 break; 113 default: 114 fatal("%s", cvs_cmdop == CVS_OP_LOG ? 115 cvs_cmd_log.cmd_synopsis : 116 cvs_cmd_rlog.cmd_synopsis); 117 } 118 } 119 120 argc -= optind; 121 argv += optind; 122 123 if (cvs_cmdop == CVS_OP_RLOG) { 124 flags |= CR_REPO; 125 126 if (argc == 0) 127 return 0; 128 129 for (i = 0; i < argc; i++) 130 if (argv[i][0] == '/') 131 fatal("Absolute path name is invalid: %s", 132 argv[i]); 133 } 134 135 cr.enterdir = NULL; 136 cr.leavedir = NULL; 137 138 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 139 cvs_client_connect_to_server(); 140 cr.fileproc = cvs_client_sendfile; 141 142 if (logdate != NULL) 143 cvs_client_send_request("Argument -d%s", logdate); 144 145 if (runflags & L_HEAD) 146 cvs_client_send_request("Argument -h"); 147 148 if (!(flags & CR_RECURSE_DIRS)) 149 cvs_client_send_request("Argument -l"); 150 151 if (runflags & L_NOTAGS) 152 cvs_client_send_request("Argument -N"); 153 154 if (runflags & L_NAME) 155 cvs_client_send_request("Argument -R"); 156 157 if (logrev != NULL) 158 cvs_client_send_request("Argument -r%s", logrev); 159 160 if (runflags & L_STATES) 161 cvs_client_send_request("Argument -s%s", slist); 162 163 if (runflags & L_HEAD_DESCR) 164 cvs_client_send_request("Argument -t"); 165 166 if (runflags & L_LOGINS) 167 cvs_client_send_request("Argument -w%s", wlist); 168 } else { 169 if (cvs_cmdop == CVS_OP_RLOG && 170 chdir(current_cvsroot->cr_dir) == -1) 171 fatal("cvs_getlog: %s", strerror(errno)); 172 173 cr.fileproc = cvs_log_local; 174 } 175 176 cr.flags = flags; 177 178 if (cvs_cmdop == CVS_OP_LOG || 179 current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 180 if (argc > 0) 181 cvs_file_run(argc, argv, &cr); 182 else 183 cvs_file_run(1, &arg, &cr); 184 } 185 186 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 187 cvs_client_send_files(argv, argc); 188 cvs_client_senddir("."); 189 190 cvs_client_send_request((cvs_cmdop == CVS_OP_RLOG) ? 191 "rlog" : "log"); 192 193 cvs_client_get_responses(); 194 } 195 196 return (0); 197 } 198 199 void 200 cvs_log_local(struct cvs_file *cf) 201 { 202 u_int nrev; 203 RCSNUM *rev; 204 struct rcs_sym *sym; 205 struct rcs_lock *lkp; 206 struct rcs_delta *rdp; 207 struct rcs_access *acp; 208 char numb[CVS_REV_BUFSZ]; 209 210 cvs_log(LP_TRACE, "cvs_log_local(%s)", cf->file_path); 211 212 cvs_file_classify(cf, cvs_directory_tag); 213 214 if (cf->file_type == CVS_DIR) { 215 if (verbosity > 1) 216 cvs_log(LP_ERR, "Logging %s", cf->file_path); 217 return; 218 } 219 220 if (cf->file_rcs == NULL) { 221 return; 222 } else if (cf->file_status == FILE_ADDED) { 223 if (verbosity > 0) 224 cvs_log(LP_ERR, "%s has been added, but not committed", 225 cf->file_path); 226 return; 227 } 228 229 if (runflags & L_NAME) { 230 cvs_printf("%s\n", cf->file_rpath); 231 return; 232 } 233 234 if (logrev != NULL) 235 nrev = cvs_revision_select(cf->file_rcs, logrev); 236 else if (logdate != NULL) { 237 if ((nrev = date_select(cf->file_rcs, logdate)) == (u_int)-1) { 238 cvs_log(LP_ERR, "invalid date: %s", logdate); 239 return; 240 } 241 } else 242 nrev = cf->file_rcs->rf_ndelta; 243 244 cvs_printf("\nRCS file: %s", cf->file_rpath); 245 246 if (cvs_cmdop != CVS_OP_RLOG) 247 cvs_printf("\nWorking file: %s", cf->file_path); 248 249 cvs_printf("\nhead:"); 250 if (cf->file_rcs->rf_head != NULL) 251 cvs_printf(" %s", rcsnum_tostr(cf->file_rcs->rf_head, 252 numb, sizeof(numb))); 253 254 cvs_printf("\nbranch:"); 255 if (rcs_branch_get(cf->file_rcs) != NULL) { 256 cvs_printf(" %s", rcsnum_tostr(rcs_branch_get(cf->file_rcs), 257 numb, sizeof(numb))); 258 } 259 260 cvs_printf("\nlocks: %s", (cf->file_rcs->rf_flags & RCS_SLOCK) 261 ? "strict" : ""); 262 TAILQ_FOREACH(lkp, &(cf->file_rcs->rf_locks), rl_list) 263 cvs_printf("\n\t%s: %s", lkp->rl_name, 264 rcsnum_tostr(lkp->rl_num, numb, sizeof(numb))); 265 266 cvs_printf("\naccess list:\n"); 267 TAILQ_FOREACH(acp, &(cf->file_rcs->rf_access), ra_list) 268 cvs_printf("\t%s\n", acp->ra_name); 269 270 if (!(runflags & L_NOTAGS)) { 271 cvs_printf("symbolic names:\n"); 272 TAILQ_FOREACH(sym, &(cf->file_rcs->rf_symbols), rs_list) { 273 rev = rcsnum_alloc(); 274 rcsnum_cpy(sym->rs_num, rev, 0); 275 if (RCSNUM_ISBRANCH(sym->rs_num)) 276 rcsnum_addmagic(rev); 277 278 cvs_printf("\t%s: %s\n", sym->rs_name, 279 rcsnum_tostr(rev, numb, sizeof(numb))); 280 free(rev); 281 } 282 } 283 284 cvs_printf("keyword substitution: %s\n", 285 cf->file_rcs->rf_expand == NULL ? "kv" : cf->file_rcs->rf_expand); 286 287 cvs_printf("total revisions: %u", cf->file_rcs->rf_ndelta); 288 289 if (cf->file_rcs->rf_head != NULL && 290 !(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) 291 cvs_printf(";\tselected revisions: %u", nrev); 292 293 cvs_printf("\n"); 294 295 if (!(runflags & L_HEAD) || (runflags & L_HEAD_DESCR)) 296 cvs_printf("description:\n%s", cf->file_rcs->rf_desc); 297 298 if (!(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) { 299 TAILQ_FOREACH(rdp, &(cf->file_rcs->rf_delta), rd_list) { 300 /* 301 * if selections are enabled verify that entry is 302 * selected. 303 */ 304 if ((logrev == NULL && logdate == NULL) || 305 (rdp->rd_flags & RCS_RD_SELECT)) 306 log_rev_print(rdp); 307 } 308 } 309 310 cvs_printf("%s\n", LOG_REVEND); 311 } 312 313 static void 314 log_rev_print(struct rcs_delta *rdp) 315 { 316 int i, found; 317 char numb[CVS_REV_BUFSZ], timeb[CVS_TIME_BUFSZ]; 318 struct cvs_argvector *sargv, *wargv; 319 struct rcs_branch *rb; 320 struct rcs_delta *nrdp; 321 322 i = found = 0; 323 324 /* -s states */ 325 if (runflags & L_STATES) { 326 sargv = cvs_strsplit(slist, ","); 327 for (i = 0; sargv->argv[i] != NULL; i++) { 328 if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) { 329 found++; 330 break; 331 } 332 found = 0; 333 } 334 cvs_argv_destroy(sargv); 335 } 336 337 /* -w[logins] */ 338 if (runflags & L_LOGINS) { 339 wargv = cvs_strsplit(wlist, ","); 340 for (i = 0; wargv->argv[i] != NULL; i++) { 341 if (strcmp(rdp->rd_author, wargv->argv[i]) == 0) { 342 found++; 343 break; 344 } 345 found = 0; 346 } 347 cvs_argv_destroy(wargv); 348 } 349 350 if ((runflags & (L_STATES|L_LOGINS)) && found == 0) 351 return; 352 353 cvs_printf("%s\n", LOG_REVSEP); 354 355 rcsnum_tostr(rdp->rd_num, numb, sizeof(numb)); 356 cvs_printf("revision %s", numb); 357 358 strftime(timeb, sizeof(timeb), "%Y/%m/%d %H:%M:%S", &rdp->rd_date); 359 cvs_printf("\ndate: %s; author: %s; state: %s;", 360 timeb, rdp->rd_author, rdp->rd_state); 361 362 /* 363 * If we are a branch revision, the diff of this revision is stored 364 * in place. 365 * Otherwise, it is stored in the previous revision as a reversed diff. 366 */ 367 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 368 nrdp = rdp; 369 else 370 nrdp = TAILQ_NEXT(rdp, rd_list); 371 372 /* 373 * We do not write diff stats for the first revision of the default 374 * branch, since it was not a diff but a full text. 375 */ 376 if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) { 377 int added, removed; 378 rcs_delta_stats(nrdp, &added, &removed); 379 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 380 cvs_printf(" lines: +%d -%d;", added, removed); 381 else 382 cvs_printf(" lines: +%d -%d;", removed, added); 383 } 384 385 if (rdp->rd_commitid != NULL) 386 printf(" commitid: %s;", rdp->rd_commitid); 387 388 cvs_printf("\n"); 389 390 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 391 cvs_printf("branches:"); 392 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 393 RCSNUM *branch; 394 branch = rcsnum_revtobr(rb->rb_num); 395 rcsnum_tostr(branch, numb, sizeof(numb)); 396 cvs_printf(" %s;", numb); 397 free(branch); 398 } 399 cvs_printf("\n"); 400 } 401 402 cvs_printf("%s", rdp->rd_log); 403 } 404 405 static char * 406 push_date(char *dest, const char *src) 407 { 408 size_t len; 409 410 if (dest == NULL) 411 return (xstrdup(src)); 412 413 /* 2 = ; and '\0' */ 414 len = strlen(dest) + strlen(src) + 2; 415 416 dest[strlen(dest)] = ';'; 417 dest = xreallocarray(dest, len, 1); 418 strlcat(dest, src, len); 419 return (dest); 420 } 421 422 static u_int 423 date_select(RCSFILE *file, char *date) 424 { 425 int i, nrev, flags; 426 struct rcs_delta *rdp; 427 struct cvs_argvector *args; 428 char *first, *last, delim; 429 time_t firstdate, lastdate, rcsdate; 430 431 nrev = 0; 432 args = cvs_strsplit(date, ";"); 433 434 for (i = 0; args->argv[i] != NULL; i++) { 435 flags = 0; 436 firstdate = lastdate = -1; 437 438 first = args->argv[i]; 439 last = strchr(args->argv[i], '<'); 440 if (last != NULL) { 441 delim = *last; 442 *last++ = '\0'; 443 444 if (*last == '=') { 445 last++; 446 flags |= LDATE_INCLUSIVE; 447 } 448 } else { 449 last = strchr(args->argv[i], '>'); 450 if (last != NULL) { 451 delim = *last; 452 *last++ = '\0'; 453 454 if (*last == '=') { 455 last++; 456 flags |= LDATE_INCLUSIVE; 457 } 458 } 459 } 460 461 if (last == NULL) { 462 flags |= LDATE_SINGLE; 463 if ((firstdate = date_parse(first)) == -1) 464 return -1; 465 delim = '\0'; 466 last = "\0"; 467 } else { 468 while (*last && isspace((unsigned char)*last)) 469 last++; 470 } 471 472 if (delim == '>' && *last == '\0') { 473 flags |= LDATE_EARLIER; 474 if ((firstdate = date_parse(first)) == -1) 475 return -1; 476 } 477 478 if (delim == '>' && *first == '\0' && *last != '\0') { 479 flags |= LDATE_LATER; 480 if ((firstdate = date_parse(last)) == -1) 481 return -1; 482 } 483 484 if (delim == '<' && *last == '\0') { 485 flags |= LDATE_LATER; 486 if ((firstdate = date_parse(first)) == -1) 487 return -1; 488 } 489 490 if (delim == '<' && *first == '\0' && *last != '\0') { 491 flags |= LDATE_EARLIER; 492 if ((firstdate = date_parse(last)) == -1) 493 return -1; 494 } 495 496 if (*first != '\0' && *last != '\0') { 497 flags |= LDATE_RANGE; 498 499 if (delim == '<') { 500 firstdate = date_parse(first); 501 lastdate = date_parse(last); 502 } else { 503 firstdate = date_parse(last); 504 lastdate = date_parse(first); 505 } 506 if (firstdate == -1 || lastdate == -1) 507 return -1; 508 } 509 510 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 511 rcsdate = mktime(&(rdp->rd_date)); 512 513 if (flags & LDATE_SINGLE) { 514 if (rcsdate <= firstdate) { 515 rdp->rd_flags |= RCS_RD_SELECT; 516 nrev++; 517 break; 518 } 519 } 520 521 if (flags & LDATE_EARLIER) { 522 if (rcsdate < firstdate) { 523 rdp->rd_flags |= RCS_RD_SELECT; 524 nrev++; 525 continue; 526 } 527 528 if (flags & LDATE_INCLUSIVE && 529 (rcsdate <= firstdate)) { 530 rdp->rd_flags |= RCS_RD_SELECT; 531 nrev++; 532 continue; 533 } 534 } 535 536 if (flags & LDATE_LATER) { 537 if (rcsdate > firstdate) { 538 rdp->rd_flags |= RCS_RD_SELECT; 539 nrev++; 540 continue; 541 } 542 543 if (flags & LDATE_INCLUSIVE && 544 (rcsdate >= firstdate)) { 545 rdp->rd_flags |= RCS_RD_SELECT; 546 nrev++; 547 continue; 548 } 549 } 550 551 if (flags & LDATE_RANGE) { 552 if ((rcsdate > firstdate) && 553 (rcsdate < lastdate)) { 554 rdp->rd_flags |= RCS_RD_SELECT; 555 nrev++; 556 continue; 557 } 558 559 if (flags & LDATE_INCLUSIVE && 560 ((rcsdate >= firstdate) && 561 (rcsdate <= lastdate))) { 562 rdp->rd_flags |= RCS_RD_SELECT; 563 nrev++; 564 continue; 565 } 566 } 567 } 568 } 569 570 cvs_argv_destroy(args); 571 572 return (nrev); 573 } 574