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