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