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