1 /* $OpenBSD: getlog.c,v 1.93 2009/03/26 17:30:04 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 <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 nrev = date_select(cf->file_rcs, logdate); 237 else 238 nrev = cf->file_rcs->rf_ndelta; 239 240 cvs_printf("\nRCS file: %s", cf->file_rpath); 241 242 if (cvs_cmdop != CVS_OP_RLOG) 243 cvs_printf("\nWorking file: %s", cf->file_path); 244 245 cvs_printf("\nhead:"); 246 if (cf->file_rcs->rf_head != NULL) 247 cvs_printf(" %s", rcsnum_tostr(cf->file_rcs->rf_head, 248 numb, sizeof(numb))); 249 250 cvs_printf("\nbranch:"); 251 if (rcs_branch_get(cf->file_rcs) != NULL) { 252 cvs_printf(" %s", rcsnum_tostr(rcs_branch_get(cf->file_rcs), 253 numb, sizeof(numb))); 254 } 255 256 cvs_printf("\nlocks: %s", (cf->file_rcs->rf_flags & RCS_SLOCK) 257 ? "strict" : ""); 258 TAILQ_FOREACH(lkp, &(cf->file_rcs->rf_locks), rl_list) 259 cvs_printf("\n\t%s: %s", lkp->rl_name, 260 rcsnum_tostr(lkp->rl_num, numb, sizeof(numb))); 261 262 cvs_printf("\naccess list:\n"); 263 TAILQ_FOREACH(acp, &(cf->file_rcs->rf_access), ra_list) 264 cvs_printf("\t%s\n", acp->ra_name); 265 266 if (!(runflags & L_NOTAGS)) { 267 cvs_printf("symbolic names:\n"); 268 TAILQ_FOREACH(sym, &(cf->file_rcs->rf_symbols), rs_list) { 269 rev = rcsnum_alloc(); 270 rcsnum_cpy(sym->rs_num, rev, 0); 271 if (RCSNUM_ISBRANCH(sym->rs_num)) 272 rcsnum_addmagic(rev); 273 274 cvs_printf("\t%s: %s\n", sym->rs_name, 275 rcsnum_tostr(rev, numb, sizeof(numb))); 276 rcsnum_free(rev); 277 } 278 } 279 280 cvs_printf("keyword substitution: %s\n", 281 cf->file_rcs->rf_expand == NULL ? "kv" : cf->file_rcs->rf_expand); 282 283 cvs_printf("total revisions: %u", cf->file_rcs->rf_ndelta); 284 285 if (cf->file_rcs->rf_head != NULL && 286 !(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) 287 cvs_printf(";\tselected revisions: %u", nrev); 288 289 cvs_printf("\n"); 290 291 if (!(runflags & L_HEAD) || (runflags & L_HEAD_DESCR)) 292 cvs_printf("description:\n%s", cf->file_rcs->rf_desc); 293 294 if (!(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) { 295 TAILQ_FOREACH(rdp, &(cf->file_rcs->rf_delta), rd_list) { 296 /* 297 * if selections are enabled verify that entry is 298 * selected. 299 */ 300 if ((logrev == NULL && logdate == NULL) || 301 (rdp->rd_flags & RCS_RD_SELECT)) 302 log_rev_print(rdp); 303 } 304 } 305 306 cvs_printf("%s\n", LOG_REVEND); 307 } 308 309 static void 310 log_rev_print(struct rcs_delta *rdp) 311 { 312 int i, found; 313 char numb[CVS_REV_BUFSZ], timeb[CVS_TIME_BUFSZ]; 314 struct cvs_argvector *sargv, *wargv; 315 struct rcs_branch *rb; 316 struct rcs_delta *nrdp; 317 318 i = found = 0; 319 320 /* -s states */ 321 if (runflags & L_STATES) { 322 sargv = cvs_strsplit(slist, ","); 323 for (i = 0; sargv->argv[i] != NULL; i++) { 324 if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) { 325 found++; 326 break; 327 } 328 found = 0; 329 } 330 cvs_argv_destroy(sargv); 331 } 332 333 /* -w[logins] */ 334 if (runflags & L_LOGINS) { 335 wargv = cvs_strsplit(wlist, ","); 336 for (i = 0; wargv->argv[i] != NULL; i++) { 337 if (strcmp(rdp->rd_author, wargv->argv[i]) == 0) { 338 found++; 339 break; 340 } 341 found = 0; 342 } 343 cvs_argv_destroy(wargv); 344 } 345 346 if ((runflags & (L_STATES|L_LOGINS)) && found == 0) 347 return; 348 349 cvs_printf("%s\n", LOG_REVSEP); 350 351 rcsnum_tostr(rdp->rd_num, numb, sizeof(numb)); 352 cvs_printf("revision %s", numb); 353 354 strftime(timeb, sizeof(timeb), "%Y/%m/%d %H:%M:%S", &rdp->rd_date); 355 cvs_printf("\ndate: %s; author: %s; state: %s;", 356 timeb, rdp->rd_author, rdp->rd_state); 357 358 /* 359 * If we are a branch revision, the diff of this revision is stored 360 * in place. 361 * Otherwise, it is stored in the previous revision as a reversed diff. 362 */ 363 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 364 nrdp = rdp; 365 else 366 nrdp = TAILQ_NEXT(rdp, rd_list); 367 368 /* 369 * We do not write diff stats for the first revision of the default 370 * branch, since it was not a diff but a full text. 371 */ 372 if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) { 373 int added, removed; 374 rcs_delta_stats(nrdp, &added, &removed); 375 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 376 cvs_printf(" lines: +%d -%d", added, removed); 377 else 378 cvs_printf(" lines: +%d -%d", removed, added); 379 } 380 cvs_printf("\n"); 381 382 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 383 cvs_printf("branches:"); 384 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 385 RCSNUM *branch; 386 branch = rcsnum_revtobr(rb->rb_num); 387 rcsnum_tostr(branch, numb, sizeof(numb)); 388 cvs_printf(" %s;", numb); 389 rcsnum_free(branch); 390 } 391 cvs_printf("\n"); 392 } 393 394 cvs_printf("%s", rdp->rd_log); 395 } 396 397 static char * 398 push_date(char *dest, const char *src) 399 { 400 size_t len; 401 402 if (dest == NULL) 403 return (xstrdup(src)); 404 405 /* 2 = ; and '\0' */ 406 len = strlen(dest) + strlen(src) + 2; 407 408 dest[strlen(dest)] = ';'; 409 dest = xrealloc(dest, len, 1); 410 strlcat(dest, src, len); 411 return (dest); 412 } 413 414 static u_int 415 date_select(RCSFILE *file, char *date) 416 { 417 int i, nrev, flags; 418 struct rcs_delta *rdp; 419 struct cvs_argvector *args; 420 char *first, *last, delim; 421 time_t firstdate, lastdate, rcsdate; 422 423 nrev = 0; 424 args = cvs_strsplit(date, ";"); 425 426 for (i = 0; args->argv[i] != NULL; i++) { 427 flags = 0; 428 firstdate = lastdate = -1; 429 430 first = args->argv[i]; 431 last = strchr(args->argv[i], '<'); 432 if (last != NULL) { 433 delim = *last; 434 *last++ = '\0'; 435 436 if (*last == '=') { 437 last++; 438 flags |= LDATE_INCLUSIVE; 439 } 440 } else { 441 last = strchr(args->argv[i], '>'); 442 if (last != NULL) { 443 delim = *last; 444 *last++ = '\0'; 445 446 if (*last == '=') { 447 last++; 448 flags |= LDATE_INCLUSIVE; 449 } 450 } 451 } 452 453 if (last == NULL) { 454 flags |= LDATE_SINGLE; 455 firstdate = cvs_date_parse(first); 456 delim = '\0'; 457 last = "\0"; 458 } else { 459 while (*last && isspace(*last)) 460 last++; 461 } 462 463 if (delim == '>' && *last == '\0') { 464 flags |= LDATE_EARLIER; 465 firstdate = cvs_date_parse(first); 466 } 467 468 if (delim == '>' && *first == '\0' && *last != '\0') { 469 flags |= LDATE_LATER; 470 firstdate = cvs_date_parse(last); 471 } 472 473 if (delim == '<' && *last == '\0') { 474 flags |= LDATE_LATER; 475 firstdate = cvs_date_parse(first); 476 } 477 478 if (delim == '<' && *first == '\0' && *last != '\0') { 479 flags |= LDATE_EARLIER; 480 firstdate = cvs_date_parse(last); 481 } 482 483 if (*first != '\0' && *last != '\0') { 484 flags |= LDATE_RANGE; 485 486 if (delim == '<') { 487 firstdate = cvs_date_parse(first); 488 lastdate = cvs_date_parse(last); 489 } else { 490 firstdate = cvs_date_parse(last); 491 lastdate = cvs_date_parse(first); 492 } 493 } 494 495 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 496 rcsdate = mktime(&(rdp->rd_date)); 497 498 if (flags & LDATE_SINGLE) { 499 if (rcsdate <= firstdate) { 500 rdp->rd_flags |= RCS_RD_SELECT; 501 nrev++; 502 break; 503 } 504 } 505 506 if (flags & LDATE_EARLIER) { 507 if (rcsdate < firstdate) { 508 rdp->rd_flags |= RCS_RD_SELECT; 509 nrev++; 510 continue; 511 } 512 513 if (flags & LDATE_INCLUSIVE && 514 (rcsdate <= firstdate)) { 515 rdp->rd_flags |= RCS_RD_SELECT; 516 nrev++; 517 continue; 518 } 519 } 520 521 if (flags & LDATE_LATER) { 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_RANGE) { 537 if ((rcsdate > firstdate) && 538 (rcsdate < lastdate)) { 539 rdp->rd_flags |= RCS_RD_SELECT; 540 nrev++; 541 continue; 542 } 543 544 if (flags & LDATE_INCLUSIVE && 545 ((rcsdate >= firstdate) && 546 (rcsdate <= lastdate))) { 547 rdp->rd_flags |= RCS_RD_SELECT; 548 nrev++; 549 continue; 550 } 551 } 552 } 553 } 554 555 cvs_argv_destroy(args); 556 557 return (nrev); 558 } 559