Annotation of src/usr.bin/rcs/rlog.c, Revision 1.66
1.66 ! jcs 1: /* $OpenBSD: rlog.c,v 1.65 2011/07/14 16:38:39 sobrado 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.64 ray 230: if ((firstdate = date_parse(first)) == -1)
231: return -1;
1.62 joris 232: delim = '\0';
233: last = "\0";
234: } else {
235: while (*last && isspace(*last))
236: last++;
237: }
238:
239: if (delim == '>' && *last == '\0') {
240: flags |= RLOG_DATE_EARLIER;
1.64 ray 241: if ((firstdate = date_parse(first)) == -1)
242: return -1;
1.62 joris 243: }
244:
245: if (delim == '>' && *first == '\0' && *last != '\0') {
246: flags |= RLOG_DATE_LATER;
1.64 ray 247: if ((firstdate = date_parse(last)) == -1)
248: return -1;
1.62 joris 249: }
250:
251: if (delim == '<' && *last == '\0') {
252: flags |= RLOG_DATE_LATER;
1.64 ray 253: if ((firstdate = date_parse(first)) == -1)
254: return -1;
1.62 joris 255: }
256:
257: if (delim == '<' && *first == '\0' && *last != '\0') {
258: flags |= RLOG_DATE_EARLIER;
1.64 ray 259: if ((firstdate = date_parse(last)) == -1)
260: return -1;
1.62 joris 261: }
262:
263: if (*first != '\0' && *last != '\0') {
264: flags |= RLOG_DATE_RANGE;
265:
266: if (delim == '<') {
1.63 ray 267: firstdate = date_parse(first);
268: lastdate = date_parse(last);
1.62 joris 269: } else {
1.63 ray 270: firstdate = date_parse(last);
271: lastdate = date_parse(first);
1.62 joris 272: }
1.64 ray 273: if (firstdate == -1 || lastdate == -1)
274: return -1;
1.62 joris 275: }
276:
277: TAILQ_FOREACH(rdp, &(rcsfile->rf_delta), rd_list) {
278: rcsdate = mktime(&(rdp->rd_date));
279:
280: if (flags & RLOG_DATE_SINGLE) {
281: if (rcsdate <= firstdate) {
282: rdp->rd_flags |= RCS_RD_SELECT;
283: nrev++;
284: break;
285: }
286: }
287:
288: if (flags & RLOG_DATE_EARLIER) {
289: if (rcsdate < firstdate) {
290: rdp->rd_flags |= RCS_RD_SELECT;
291: nrev++;
292: continue;
293: }
294:
295: if (flags & RLOG_DATE_INCLUSIVE &&
296: (rcsdate <= firstdate)) {
297: rdp->rd_flags |= RCS_RD_SELECT;
298: nrev++;
299: continue;
300: }
301: }
302:
303: if (flags & RLOG_DATE_LATER) {
304: if (rcsdate > firstdate) {
305: rdp->rd_flags |= RCS_RD_SELECT;
306: nrev++;
307: continue;
308: }
309:
310: if (flags & RLOG_DATE_INCLUSIVE &&
311: (rcsdate >= firstdate)) {
312: rdp->rd_flags |= RCS_RD_SELECT;
313: nrev++;
314: continue;
315: }
316: }
317:
318: if (flags & RLOG_DATE_RANGE) {
319: if ((rcsdate > firstdate) &&
320: (rcsdate < lastdate)) {
321: rdp->rd_flags |= RCS_RD_SELECT;
322: nrev++;
323: continue;
324: }
325:
326: if (flags & RLOG_DATE_INCLUSIVE &&
327: ((rcsdate >= firstdate) &&
328: (rcsdate <= lastdate))) {
329: rdp->rd_flags |= RCS_RD_SELECT;
330: nrev++;
331: continue;
332: }
333: }
334: }
335: }
336:
337: return (nrev);
338: }
339:
1.32 xsa 340: static void
1.44 ray 341: rlog_file(const char *fname, RCSFILE *file)
1.1 joris 342: {
1.58 xsa 343: char numb[RCS_REV_BUFSZ];
1.36 ray 344: u_int nrev;
1.1 joris 345: struct rcs_sym *sym;
346: struct rcs_access *acp;
1.20 xsa 347: struct rcs_delta *rdp;
1.10 xsa 348: struct rcs_lock *lkp;
1.45 joris 349: char *workfile, *p;
1.1 joris 350:
1.36 ray 351: if (rflag == 1)
1.46 xsa 352: nrev = rcs_rev_select(file, revisions);
1.64 ray 353: else if (dflag == 1) {
354: if ((nrev = rlog_select_daterev(file, rlog_dates)) == -1)
355: errx(1, "invalid date: %s", rlog_dates);
356: } else
1.36 ray 357: nrev = file->rf_ndelta;
358:
1.45 joris 359: if ((workfile = basename(fname)) == NULL)
1.50 xsa 360: err(1, "basename");
1.45 joris 361:
362: /*
363: * In case they specified 'foo,v' as argument.
364: */
365: if ((p = strrchr(workfile, ',')) != NULL)
366: *p = '\0';
367:
1.43 ray 368: printf("\nRCS file: %s", file->rf_path);
1.45 joris 369: printf("\nWorking file: %s", workfile);
1.1 joris 370: printf("\nhead:");
371: if (file->rf_head != NULL)
372: printf(" %s", rcsnum_tostr(file->rf_head, numb, sizeof(numb)));
373:
374: printf("\nbranch:");
375: if (rcs_branch_get(file) != NULL) {
376: printf(" %s", rcsnum_tostr(rcs_branch_get(file),
377: numb, sizeof(numb)));
378: }
379:
380: printf("\nlocks: %s", (file->rf_flags & RCS_SLOCK) ? "strict" : "");
1.10 xsa 381: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list)
382: printf("\n\t%s: %s", lkp->rl_name,
383: rcsnum_tostr(lkp->rl_num, numb, sizeof(numb)));
1.1 joris 384: printf("\naccess list:\n");
385: TAILQ_FOREACH(acp, &(file->rf_access), ra_list)
386: printf("\t%s\n", acp->ra_name);
387:
388: if (Nflag == 0) {
389: printf("symbolic names:\n");
390: TAILQ_FOREACH(sym, &(file->rf_symbols), rs_list) {
391: printf("\t%s: %s\n", sym->rs_name,
392: rcsnum_tostr(sym->rs_num, numb, sizeof(numb)));
393: }
394: }
395:
396: printf("keyword substitution: %s\n",
397: file->rf_expand == NULL ? "kv" : file->rf_expand);
398:
1.20 xsa 399: printf("total revisions: %u", file->rf_ndelta);
400:
1.41 deraadt 401: if (file->rf_head != NULL && hflag == 0 && tflag == 0)
1.36 ray 402: printf(";\tselected revisions: %u", nrev);
1.20 xsa 403:
404: printf("\n");
405:
1.1 joris 406:
1.41 deraadt 407: if (hflag == 0 || tflag == 1)
1.13 xsa 408: printf("description:\n%s", file->rf_desc);
1.1 joris 409:
1.42 ray 410: if (hflag == 0 && tflag == 0 &&
411: !(lflag == 1 && TAILQ_EMPTY(&file->rf_locks))) {
1.36 ray 412: TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
413: /*
414: * if selections are enabled verify that entry is
1.39 niallo 415: * selected.
1.36 ray 416: */
1.62 joris 417: if ((rflag == 0 && dflag == 0)
418: || (rdp->rd_flags & RCS_RD_SELECT))
1.36 ray 419: rlog_rev_print(rdp);
420: }
1.20 xsa 421: }
1.1 joris 422:
423: printf("%s\n", REVEND);
1.16 xsa 424: }
425:
426: static void
1.20 xsa 427: rlog_rev_print(struct rcs_delta *rdp)
1.16 xsa 428: {
1.20 xsa 429: int i, found;
1.40 joris 430: struct tm t;
1.58 xsa 431: char *author, numb[RCS_REV_BUFSZ], *fmt, timeb[RCS_TIME_BUFSZ];
1.51 joris 432: struct rcs_argvector *largv, *sargv, *wargv;
1.60 xsa 433: struct rcs_branch *rb;
434: struct rcs_delta *nrdp;
1.20 xsa 435:
436: i = found = 0;
437: author = NULL;
438:
1.23 xsa 439: /* -l[lockers] */
440: if (lflag == 1) {
1.42 ray 441: if (rdp->rd_locker != NULL)
442: found++;
1.23 xsa 443:
444: if (llist != NULL) {
445: /* if locker is empty, no need to go further. */
446: if (rdp->rd_locker == NULL)
447: return;
1.51 joris 448: largv = rcs_strsplit(llist, ",");
1.34 pat 449: for (i = 0; largv->argv[i] != NULL; i++) {
450: if (strcmp(rdp->rd_locker, largv->argv[i])
451: == 0) {
1.23 xsa 452: found++;
453: break;
454: }
455: found = 0;
456: }
1.51 joris 457: rcs_argv_destroy(largv);
1.23 xsa 458: }
459: }
1.40 joris 460:
1.20 xsa 461: /* -sstates */
462: if (slist != NULL) {
1.51 joris 463: sargv = rcs_strsplit(slist, ",");
1.34 pat 464: for (i = 0; sargv->argv[i] != NULL; i++) {
465: if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) {
1.20 xsa 466: found++;
467: break;
468: }
469: found = 0;
470: }
1.51 joris 471: rcs_argv_destroy(sargv);
1.20 xsa 472: }
1.40 joris 473:
1.20 xsa 474: /* -w[logins] */
475: if (wflag == 1) {
476: if (wlist != NULL) {
1.51 joris 477: wargv = rcs_strsplit(wlist, ",");
1.34 pat 478: for (i = 0; wargv->argv[i] != NULL; i++) {
479: if (strcmp(rdp->rd_author, wargv->argv[i])
480: == 0) {
1.20 xsa 481: found++;
482: break;
483: }
484: found = 0;
485: }
1.51 joris 486: rcs_argv_destroy(wargv);
1.20 xsa 487: } else {
488: if ((author = getlogin()) == NULL)
1.50 xsa 489: err(1, "getlogin");
1.16 xsa 490:
1.20 xsa 491: if (strcmp(rdp->rd_author, author) == 0)
492: found++;
493: }
494: }
1.16 xsa 495:
1.20 xsa 496: /* XXX dirty... */
1.41 deraadt 497: if ((((slist != NULL && wflag == 1) ||
498: (slist != NULL && lflag == 1) ||
499: (lflag == 1 && wflag == 1)) && found < 2) ||
500: (((slist != NULL && lflag == 1 && wflag == 1) ||
501: (slist != NULL || lflag == 1 || wflag == 1)) && found == 0))
1.20 xsa 502: return;
503:
504: printf("%s\n", REVSEP);
505:
506: rcsnum_tostr(rdp->rd_num, numb, sizeof(numb));
507:
1.22 xsa 508: printf("revision %s", numb);
509: if (rdp->rd_locker != NULL)
510: printf("\tlocked by: %s;", rdp->rd_locker);
1.40 joris 511:
512: if (timezone_flag != NULL) {
513: rcs_set_tz(timezone_flag, rdp, &t);
1.53 xsa 514: fmt = "%Y-%m-%d %H:%M:%S%z";
1.40 joris 515: } else {
516: t = rdp->rd_date;
517: fmt = "%Y/%m/%d %H:%M:%S";
518: }
519:
1.60 xsa 520: (void)strftime(timeb, sizeof(timeb), fmt, &t);
1.40 joris 521:
1.60 xsa 522: printf("\ndate: %s; author: %s; state: %s;", timeb, rdp->rd_author,
1.41 deraadt 523: rdp->rd_state);
1.60 xsa 524:
525: /*
526: * If we are a branch revision, the diff of this revision is stored
527: * in place.
528: * Otherwise, it is stored in the previous revision as a reversed diff.
529: */
530: if (RCSNUM_ISBRANCHREV(rdp->rd_num))
531: nrdp = rdp;
532: else
533: nrdp = TAILQ_NEXT(rdp, rd_list);
1.65 sobrado 534:
1.60 xsa 535: /*
536: * We do not write diff stats for the first revision of the default
537: * branch, since it was not a diff but a full text.
538: */
539: if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) {
540: int added, removed;
1.61 xsa 541:
1.60 xsa 542: rcs_delta_stats(nrdp, &added, &removed);
543: if (RCSNUM_ISBRANCHREV(rdp->rd_num))
1.66 ! jcs 544: printf(" lines: +%d -%d;", added, removed);
1.60 xsa 545: else
1.66 ! jcs 546: printf(" lines: +%d -%d;", removed, added);
1.61 xsa 547: }
1.66 ! jcs 548:
! 549: if (rdp->rd_commitid != NULL)
! 550: printf(" commitid: %s;", rdp->rd_commitid);
! 551:
1.61 xsa 552: printf("\n");
1.65 sobrado 553:
1.61 xsa 554: if (!TAILQ_EMPTY(&(rdp->rd_branches))) {
555: printf("branches:");
556: TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
557: RCSNUM *branch;
558: branch = rcsnum_revtobr(rb->rb_num);
559: (void)rcsnum_tostr(branch, numb, sizeof(numb));
560: printf(" %s;", numb);
561: rcsnum_free(branch);
1.60 xsa 562: }
563: printf("\n");
1.61 xsa 564: }
1.40 joris 565:
1.20 xsa 566: printf("%s", rdp->rd_log);
1.1 joris 567: }