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