1/* $OpenBSD: getlog.c,v 1.101 2017/06/01 08:08:24 joris 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 41void cvs_log_local(struct cvs_file *); 42static void log_rev_print(struct rcs_delta *); 43static char *push_date(char *dest, const char *); 44static u_int date_select(RCSFILE *, char *); 45 46int runflags = 0; 47char *logrev = NULL; 48char *logdate = NULL; 49char *slist = NULL; 50char *wlist = NULL; 51 52struct 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 62struct 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 72int 73cvs_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 (cvsroot_is_remote()) { 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 || cvsroot_is_local()) { 179 if (argc > 0) 180 cvs_file_run(argc, argv, &cr); 181 else 182 cvs_file_run(1, &arg, &cr); 183 } 184 185 if (cvsroot_is_remote()) { 186 cvs_client_send_files(argv, argc); 187 cvs_client_senddir("."); 188 189 cvs_client_send_request((cvs_cmdop == CVS_OP_RLOG) ? 190 "rlog" : "log"); 191 192 cvs_client_get_responses(); 193 } 194 195 return (0); 196} 197 198void 199cvs_log_local(struct cvs_file *cf) 200{ 201 u_int nrev; 202 RCSNUM *rev; 203 struct rcs_sym *sym; 204 struct rcs_lock *lkp; 205 struct rcs_delta *rdp; 206 struct rcs_access *acp; 207 char numb[CVS_REV_BUFSZ]; 208 209 cvs_log(LP_TRACE, "cvs_log_local(%s)", cf->file_path); 210 211 cvs_file_classify(cf, cvs_directory_tag); 212 213 if (cf->file_type == CVS_DIR) { 214 if (verbosity > 1) 215 cvs_log(LP_ERR, "Logging %s", cf->file_path); 216 return; 217 } 218 219 if (cf->file_rcs == NULL) { 220 return; 221 } else if (cf->file_status == FILE_ADDED) { 222 if (verbosity > 0) 223 cvs_log(LP_ERR, "%s has been added, but not committed", 224 cf->file_path); 225 return; 226 } 227 228 if (runflags & L_NAME) { 229 cvs_printf("%s\n", cf->file_rpath); 230 return; 231 } 232 233 if (logrev != NULL) 234 nrev = cvs_revision_select(cf->file_rcs, logrev); 235 else if (logdate != NULL) { 236 if ((nrev = date_select(cf->file_rcs, logdate)) == (u_int)-1) { 237 cvs_log(LP_ERR, "invalid date: %s", logdate); 238 return; 239 } 240 } else 241 nrev = cf->file_rcs->rf_ndelta; 242 243 cvs_printf("\nRCS file: %s", cf->file_rpath); 244 245 if (cvs_cmdop != CVS_OP_RLOG) 246 cvs_printf("\nWorking file: %s", cf->file_path); 247 248 cvs_printf("\nhead:"); 249 if (cf->file_rcs->rf_head != NULL) 250 cvs_printf(" %s", rcsnum_tostr(cf->file_rcs->rf_head, 251 numb, sizeof(numb))); 252 253 cvs_printf("\nbranch:"); 254 if (rcs_branch_get(cf->file_rcs) != NULL) { 255 cvs_printf(" %s", rcsnum_tostr(rcs_branch_get(cf->file_rcs), 256 numb, sizeof(numb))); 257 } 258 259 cvs_printf("\nlocks: %s", (cf->file_rcs->rf_flags & RCS_SLOCK) 260 ? "strict" : ""); 261 TAILQ_FOREACH(lkp, &(cf->file_rcs->rf_locks), rl_list) 262 cvs_printf("\n\t%s: %s", lkp->rl_name, 263 rcsnum_tostr(lkp->rl_num, numb, sizeof(numb))); 264 265 cvs_printf("\naccess list:\n"); 266 TAILQ_FOREACH(acp, &(cf->file_rcs->rf_access), ra_list) 267 cvs_printf("\t%s\n", acp->ra_name); 268 269 if (!(runflags & L_NOTAGS)) { 270 cvs_printf("symbolic names:\n"); 271 TAILQ_FOREACH(sym, &(cf->file_rcs->rf_symbols), rs_list) { 272 rev = rcsnum_alloc(); 273 rcsnum_cpy(sym->rs_num, rev, 0); 274 if (RCSNUM_ISBRANCH(sym->rs_num)) 275 rcsnum_addmagic(rev); 276 277 cvs_printf("\t%s: %s\n", sym->rs_name, 278 rcsnum_tostr(rev, numb, sizeof(numb))); 279 free(rev); 280 } 281 } 282 283 cvs_printf("keyword substitution: %s\n", 284 cf->file_rcs->rf_expand == NULL ? "kv" : cf->file_rcs->rf_expand); 285 286 cvs_printf("total revisions: %u", cf->file_rcs->rf_ndelta); 287 288 if (cf->file_rcs->rf_head != NULL && 289 !(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) 290 cvs_printf(";\tselected revisions: %u", nrev); 291 292 cvs_printf("\n"); 293 294 if (!(runflags & L_HEAD) || (runflags & L_HEAD_DESCR)) 295 cvs_printf("description:\n%s", cf->file_rcs->rf_desc); 296 297 if (!(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) { 298 TAILQ_FOREACH(rdp, &(cf->file_rcs->rf_delta), rd_list) { 299 /* 300 * if selections are enabled verify that entry is 301 * selected. 302 */ 303 if ((logrev == NULL && logdate == NULL) || 304 (rdp->rd_flags & RCS_RD_SELECT)) 305 log_rev_print(rdp); 306 } 307 } 308 309 cvs_printf("%s\n", LOG_REVEND); 310} 311 312static void 313log_rev_print(struct rcs_delta *rdp) 314{ 315 int i, found; 316 char numb[CVS_REV_BUFSZ], timeb[CVS_TIME_BUFSZ]; 317 struct cvs_argvector *sargv, *wargv; 318 struct rcs_branch *rb; 319 struct rcs_delta *nrdp; 320 321 i = found = 0; 322 323 /* -s states */ 324 if (runflags & L_STATES) { 325 sargv = cvs_strsplit(slist, ","); 326 for (i = 0; sargv->argv[i] != NULL; i++) { 327 if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) { 328 found++; 329 break; 330 } 331 found = 0; 332 } 333 cvs_argv_destroy(sargv); 334 } 335 336 /* -w[logins] */ 337 if (runflags & L_LOGINS) { 338 wargv = cvs_strsplit(wlist, ","); 339 for (i = 0; wargv->argv[i] != NULL; i++) { 340 if (strcmp(rdp->rd_author, wargv->argv[i]) == 0) { 341 found++; 342 break; 343 } 344 found = 0; 345 } 346 cvs_argv_destroy(wargv); 347 } 348 349 if ((runflags & (L_STATES|L_LOGINS)) && found == 0) 350 return; 351 352 cvs_printf("%s\n", LOG_REVSEP); 353 354 rcsnum_tostr(rdp->rd_num, numb, sizeof(numb)); 355 cvs_printf("revision %s", numb); 356 357 strftime(timeb, sizeof(timeb), "%Y/%m/%d %H:%M:%S", &rdp->rd_date); 358 cvs_printf("\ndate: %s; author: %s; state: %s;", 359 timeb, rdp->rd_author, rdp->rd_state); 360 361 /* 362 * If we are a branch revision, the diff of this revision is stored 363 * in place. 364 * Otherwise, it is stored in the previous revision as a reversed diff. 365 */ 366 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 367 nrdp = rdp; 368 else 369 nrdp = TAILQ_NEXT(rdp, rd_list); 370 371 /* 372 * We do not write diff stats for the first revision of the default 373 * branch, since it was not a diff but a full text. 374 */ 375 if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) { 376 int added, removed; 377 rcs_delta_stats(nrdp, &added, &removed); 378 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 379 cvs_printf(" lines: +%d -%d;", added, removed); 380 else 381 cvs_printf(" lines: +%d -%d;", removed, added); 382 } 383 384 if (rdp->rd_commitid != NULL) 385 printf(" commitid: %s;", rdp->rd_commitid); 386 387 cvs_printf("\n"); 388 389 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 390 cvs_printf("branches:"); 391 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 392 RCSNUM *branch; 393 branch = rcsnum_revtobr(rb->rb_num); 394 rcsnum_tostr(branch, numb, sizeof(numb)); 395 cvs_printf(" %s;", numb); 396 free(branch); 397 } 398 cvs_printf("\n"); 399 } 400 401 cvs_printf("%s", rdp->rd_log); 402} 403 404static char * 405push_date(char *dest, const char *src) 406{ 407 size_t len; 408 409 if (dest == NULL) 410 return (xstrdup(src)); 411 412 /* 2 = ; and '\0' */ 413 len = strlen(dest) + strlen(src) + 2; 414 415 dest[strlen(dest)] = ';'; 416 dest = xreallocarray(dest, len, 1); 417 strlcat(dest, src, len); 418 return (dest); 419} 420 421static u_int 422date_select(RCSFILE *file, char *date) 423{ 424 int i, nrev, flags; 425 struct rcs_delta *rdp; 426 struct cvs_argvector *args; 427 char *first, *last, delim; 428 time_t firstdate, lastdate, rcsdate; 429 430 nrev = 0; 431 args = cvs_strsplit(date, ";"); 432 433 for (i = 0; args->argv[i] != NULL; i++) { 434 flags = 0; 435 firstdate = lastdate = -1; 436 437 first = args->argv[i]; 438 last = strchr(args->argv[i], '<'); 439 if (last != NULL) { 440 delim = *last; 441 *last++ = '\0'; 442 443 if (*last == '=') { 444 last++; 445 flags |= LDATE_INCLUSIVE; 446 } 447 } else { 448 last = strchr(args->argv[i], '>'); 449 if (last != NULL) { 450 delim = *last; 451 *last++ = '\0'; 452 453 if (*last == '=') { 454 last++; 455 flags |= LDATE_INCLUSIVE; 456 } 457 } 458 } 459 460 if (last == NULL) { 461 flags |= LDATE_SINGLE; 462 if ((firstdate = date_parse(first)) == -1) 463 return -1; 464 delim = '\0'; 465 last = "\0"; 466 } else { 467 while (*last && isspace((unsigned char)*last)) 468 last++; 469 } 470 471 if (delim == '>' && *last == '\0') { 472 flags |= LDATE_EARLIER; 473 if ((firstdate = date_parse(first)) == -1) 474 return -1; 475 } 476 477 if (delim == '>' && *first == '\0' && *last != '\0') { 478 flags |= LDATE_LATER; 479 if ((firstdate = date_parse(last)) == -1) 480 return -1; 481 } 482 483 if (delim == '<' && *last == '\0') { 484 flags |= LDATE_LATER; 485 if ((firstdate = date_parse(first)) == -1) 486 return -1; 487 } 488 489 if (delim == '<' && *first == '\0' && *last != '\0') { 490 flags |= LDATE_EARLIER; 491 if ((firstdate = date_parse(last)) == -1) 492 return -1; 493 } 494 495 if (*first != '\0' && *last != '\0') { 496 flags |= LDATE_RANGE; 497 498 if (delim == '<') { 499 firstdate = date_parse(first); 500 lastdate = date_parse(last); 501 } else { 502 firstdate = date_parse(last); 503 lastdate = date_parse(first); 504 } 505 if (firstdate == -1 || lastdate == -1) 506 return -1; 507 } 508 509 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 510 rcsdate = mktime(&(rdp->rd_date)); 511 512 if (flags & LDATE_SINGLE) { 513 if (rcsdate <= firstdate) { 514 rdp->rd_flags |= RCS_RD_SELECT; 515 nrev++; 516 break; 517 } 518 } 519 520 if (flags & LDATE_EARLIER) { 521 if (rcsdate < firstdate) { 522 rdp->rd_flags |= RCS_RD_SELECT; 523 nrev++; 524 continue; 525 } 526 527 if (flags & LDATE_INCLUSIVE && 528 (rcsdate <= firstdate)) { 529 rdp->rd_flags |= RCS_RD_SELECT; 530 nrev++; 531 continue; 532 } 533 } 534 535 if (flags & LDATE_LATER) { 536 if (rcsdate > firstdate) { 537 rdp->rd_flags |= RCS_RD_SELECT; 538 nrev++; 539 continue; 540 } 541 542 if (flags & LDATE_INCLUSIVE && 543 (rcsdate >= firstdate)) { 544 rdp->rd_flags |= RCS_RD_SELECT; 545 nrev++; 546 continue; 547 } 548 } 549 550 if (flags & LDATE_RANGE) { 551 if ((rcsdate > firstdate) && 552 (rcsdate < lastdate)) { 553 rdp->rd_flags |= RCS_RD_SELECT; 554 nrev++; 555 continue; 556 } 557 558 if (flags & LDATE_INCLUSIVE && 559 ((rcsdate >= firstdate) && 560 (rcsdate <= lastdate))) { 561 rdp->rd_flags |= RCS_RD_SELECT; 562 nrev++; 563 continue; 564 } 565 } 566 } 567 } 568 569 cvs_argv_destroy(args); 570 571 return (nrev); 572} 573