1 /* $OpenBSD: getlog.c,v 1.95 2010/07/30 21:47:18 ray 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 <string.h> 21 #include <errno.h> 22 #include <ctype.h> 23 24 #include "cvs.h" 25 #include "remote.h" 26 27 #define L_HEAD 0x01 28 #define L_HEAD_DESCR 0x02 29 #define L_NAME 0x04 30 #define L_NOTAGS 0x08 31 #define L_LOGINS 0x10 32 #define L_STATES 0x20 33 34 #define LDATE_LATER 0x01 35 #define LDATE_EARLIER 0x02 36 #define LDATE_SINGLE 0x04 37 #define LDATE_RANGE 0x08 38 #define LDATE_INCLUSIVE 0x10 39 40 void cvs_log_local(struct cvs_file *); 41 static void log_rev_print(struct rcs_delta *); 42 static char *push_date(char *dest, const char *); 43 static u_int date_select(RCSFILE *, char *); 44 45 int runflags = 0; 46 char *logrev = NULL; 47 char *logdate = NULL; 48 char *slist = NULL; 49 char *wlist = NULL; 50 51 struct cvs_cmd cvs_cmd_log = { 52 CVS_OP_LOG, CVS_USE_WDIR, "log", 53 { "lo" }, 54 "Print out history information for files", 55 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 56 "bd:hlNRr:s:tw:", 57 NULL, 58 cvs_getlog 59 }; 60 61 struct cvs_cmd cvs_cmd_rlog = { 62 CVS_OP_RLOG, 0, "rlog", 63 { "rlo" }, 64 "Print out history information for files", 65 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 66 "bd:hlNRr:s:tw:", 67 NULL, 68 cvs_getlog 69 }; 70 71 int 72 cvs_getlog(int argc, char **argv) 73 { 74 int ch, flags, i; 75 char *arg = "."; 76 struct cvs_recursion cr; 77 78 flags = CR_RECURSE_DIRS; 79 80 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_LOG ? 81 cvs_cmd_log.cmd_opts : cvs_cmd_rlog.cmd_opts)) != -1) { 82 switch (ch) { 83 case 'd': 84 logdate = push_date(logdate, optarg); 85 break; 86 case 'h': 87 runflags |= L_HEAD; 88 break; 89 case 'l': 90 flags &= ~CR_RECURSE_DIRS; 91 break; 92 case 'N': 93 runflags |= L_NOTAGS; 94 break; 95 case 'R': 96 runflags |= L_NAME; 97 break; 98 case 'r': 99 logrev = optarg; 100 break; 101 case 's': 102 runflags |= L_STATES; 103 slist = optarg; 104 break; 105 case 't': 106 runflags |= L_HEAD_DESCR; 107 break; 108 case 'w': 109 runflags |= L_LOGINS; 110 wlist = optarg; 111 break; 112 default: 113 fatal("%s", cvs_cmdop == CVS_OP_LOG ? 114 cvs_cmd_log.cmd_synopsis : 115 cvs_cmd_rlog.cmd_synopsis); 116 } 117 } 118 119 argc -= optind; 120 argv += optind; 121 122 if (cvs_cmdop == CVS_OP_RLOG) { 123 flags |= CR_REPO; 124 125 if (argc == 0) 126 return 0; 127 128 for (i = 0; i < argc; i++) 129 if (argv[i][0] == '/') 130 fatal("Absolute path name is invalid: %s", 131 argv[i]); 132 } 133 134 cr.enterdir = NULL; 135 cr.leavedir = NULL; 136 137 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 138 cvs_client_connect_to_server(); 139 cr.fileproc = cvs_client_sendfile; 140 141 if (logdate != NULL) 142 cvs_client_send_request("Argument -d%s", logdate); 143 144 if (runflags & L_HEAD) 145 cvs_client_send_request("Argument -h"); 146 147 if (!(flags & CR_RECURSE_DIRS)) 148 cvs_client_send_request("Argument -l"); 149 150 if (runflags & L_NOTAGS) 151 cvs_client_send_request("Argument -N"); 152 153 if (runflags & L_NAME) 154 cvs_client_send_request("Argument -R"); 155 156 if (logrev != NULL) 157 cvs_client_send_request("Argument -r%s", logrev); 158 159 if (runflags & L_STATES) 160 cvs_client_send_request("Argument -s%s", slist); 161 162 if (runflags & L_HEAD_DESCR) 163 cvs_client_send_request("Argument -t"); 164 165 if (runflags & L_LOGINS) 166 cvs_client_send_request("Argument -w%s", wlist); 167 } else { 168 if (cvs_cmdop == CVS_OP_RLOG && 169 chdir(current_cvsroot->cr_dir) == -1) 170 fatal("cvs_getlog: %s", strerror(errno)); 171 172 cr.fileproc = cvs_log_local; 173 } 174 175 cr.flags = flags; 176 177 if (cvs_cmdop == CVS_OP_LOG || 178 current_cvsroot->cr_method == CVS_METHOD_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 (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 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 198 void 199 cvs_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)) == -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 rcsnum_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 312 static void 313 log_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 cvs_printf("\n"); 384 385 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 386 cvs_printf("branches:"); 387 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 388 RCSNUM *branch; 389 branch = rcsnum_revtobr(rb->rb_num); 390 rcsnum_tostr(branch, numb, sizeof(numb)); 391 cvs_printf(" %s;", numb); 392 rcsnum_free(branch); 393 } 394 cvs_printf("\n"); 395 } 396 397 cvs_printf("%s", rdp->rd_log); 398 } 399 400 static char * 401 push_date(char *dest, const char *src) 402 { 403 size_t len; 404 405 if (dest == NULL) 406 return (xstrdup(src)); 407 408 /* 2 = ; and '\0' */ 409 len = strlen(dest) + strlen(src) + 2; 410 411 dest[strlen(dest)] = ';'; 412 dest = xrealloc(dest, len, 1); 413 strlcat(dest, src, len); 414 return (dest); 415 } 416 417 static u_int 418 date_select(RCSFILE *file, char *date) 419 { 420 int i, nrev, flags; 421 struct rcs_delta *rdp; 422 struct cvs_argvector *args; 423 char *first, *last, delim; 424 time_t firstdate, lastdate, rcsdate; 425 426 nrev = 0; 427 args = cvs_strsplit(date, ";"); 428 429 for (i = 0; args->argv[i] != NULL; i++) { 430 flags = 0; 431 firstdate = lastdate = -1; 432 433 first = args->argv[i]; 434 last = strchr(args->argv[i], '<'); 435 if (last != NULL) { 436 delim = *last; 437 *last++ = '\0'; 438 439 if (*last == '=') { 440 last++; 441 flags |= LDATE_INCLUSIVE; 442 } 443 } else { 444 last = strchr(args->argv[i], '>'); 445 if (last != NULL) { 446 delim = *last; 447 *last++ = '\0'; 448 449 if (*last == '=') { 450 last++; 451 flags |= LDATE_INCLUSIVE; 452 } 453 } 454 } 455 456 if (last == NULL) { 457 flags |= LDATE_SINGLE; 458 if ((firstdate = date_parse(first)) == -1) 459 return -1; 460 delim = '\0'; 461 last = "\0"; 462 } else { 463 while (*last && isspace(*last)) 464 last++; 465 } 466 467 if (delim == '>' && *last == '\0') { 468 flags |= LDATE_EARLIER; 469 if ((firstdate = date_parse(first)) == -1) 470 return -1; 471 } 472 473 if (delim == '>' && *first == '\0' && *last != '\0') { 474 flags |= LDATE_LATER; 475 if ((firstdate = date_parse(last)) == -1) 476 return -1; 477 } 478 479 if (delim == '<' && *last == '\0') { 480 flags |= LDATE_LATER; 481 if ((firstdate = date_parse(first)) == -1) 482 return -1; 483 } 484 485 if (delim == '<' && *first == '\0' && *last != '\0') { 486 flags |= LDATE_EARLIER; 487 if ((firstdate = date_parse(last)) == -1) 488 return -1; 489 } 490 491 if (*first != '\0' && *last != '\0') { 492 flags |= LDATE_RANGE; 493 494 if (delim == '<') { 495 firstdate = date_parse(first); 496 lastdate = date_parse(last); 497 } else { 498 firstdate = date_parse(last); 499 lastdate = date_parse(first); 500 } 501 if (firstdate == -1 || lastdate == -1) 502 return -1; 503 } 504 505 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 506 rcsdate = mktime(&(rdp->rd_date)); 507 508 if (flags & LDATE_SINGLE) { 509 if (rcsdate <= firstdate) { 510 rdp->rd_flags |= RCS_RD_SELECT; 511 nrev++; 512 break; 513 } 514 } 515 516 if (flags & LDATE_EARLIER) { 517 if (rcsdate < firstdate) { 518 rdp->rd_flags |= RCS_RD_SELECT; 519 nrev++; 520 continue; 521 } 522 523 if (flags & LDATE_INCLUSIVE && 524 (rcsdate <= firstdate)) { 525 rdp->rd_flags |= RCS_RD_SELECT; 526 nrev++; 527 continue; 528 } 529 } 530 531 if (flags & LDATE_LATER) { 532 if (rcsdate > firstdate) { 533 rdp->rd_flags |= RCS_RD_SELECT; 534 nrev++; 535 continue; 536 } 537 538 if (flags & LDATE_INCLUSIVE && 539 (rcsdate >= firstdate)) { 540 rdp->rd_flags |= RCS_RD_SELECT; 541 nrev++; 542 continue; 543 } 544 } 545 546 if (flags & LDATE_RANGE) { 547 if ((rcsdate > firstdate) && 548 (rcsdate < lastdate)) { 549 rdp->rd_flags |= RCS_RD_SELECT; 550 nrev++; 551 continue; 552 } 553 554 if (flags & LDATE_INCLUSIVE && 555 ((rcsdate >= firstdate) && 556 (rcsdate <= lastdate))) { 557 rdp->rd_flags |= RCS_RD_SELECT; 558 nrev++; 559 continue; 560 } 561 } 562 } 563 } 564 565 cvs_argv_destroy(args); 566 567 return (nrev); 568 } 569