xref: /openbsd/usr.bin/rcs/rlog.c (revision 4cfece93)
1 /*	$OpenBSD: rlog.c,v 1.74 2016/10/16 13:35:51 okan 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 <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 #include <unistd.h>
36 
37 #include "rcsprog.h"
38 #include "diff.h"
39 
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 *);
47 static void	rlog_file(const char *, RCSFILE *);
48 static void	rlog_rev_print(struct rcs_delta *);
49 
50 #define RLOG_OPTSTRING	"d:E:hLl::NqRr::S:s:TtVw::x::z::"
51 
52 static int dflag, hflag, Lflag, lflag, rflag, tflag, Nflag, wflag;
53 static char *llist = NULL;
54 static char *slist = NULL;
55 static char *wlist = NULL;
56 static char *revisions = NULL;
57 static char *rlog_dates = NULL;
58 static char *revsep = "----------------------------";
59 static char *revend = "====================================================="
60     "========================";
61 
62 __dead void
63 rlog_usage(void)
64 {
65 	fprintf(stderr,
66 	    "usage: rlog [-bhLNRtV] [-ddates] [-Eendsep] [-l[lockers]] "
67 	    "[-r[revs]]\n"
68 	    "            [-Srevsep] [-sstates] [-w[logins]] [-xsuffixes] "
69 	    "[-ztz] file ...\n");
70 
71 	exit(1);
72 }
73 
74 int
75 rlog_main(int argc, char **argv)
76 {
77 	RCSFILE *file;
78 	int Rflag;
79 	int i, ch, fd, status;
80 	char fpath[PATH_MAX];
81 
82 	rcsnum_flags |= RCSNUM_NO_MAGIC;
83 	hflag = Rflag = rflag = status = 0;
84 	while ((ch = rcs_getopt(argc, argv, RLOG_OPTSTRING)) != -1) {
85 		switch (ch) {
86 		case 'd':
87 			dflag = 1;
88 			rlog_dates = rcs_optarg;
89 			break;
90 		case 'E':
91 			revend = rcs_optarg;
92 			break;
93 		case 'h':
94 			hflag = 1;
95 			break;
96 		case 'L':
97 			Lflag = 1;
98 			break;
99 		case 'l':
100 			lflag = 1;
101 			llist = rcs_optarg;
102 			break;
103 		case 'N':
104 			Nflag = 1;
105 			break;
106 		case 'q':
107 			/*
108 			 * kept for compatibility
109 			 */
110 			break;
111 		case 'R':
112 			Rflag = 1;
113 			break;
114 		case 'r':
115 			rflag = 1;
116 			revisions = rcs_optarg;
117 			break;
118 		case 'S':
119 			revsep = rcs_optarg;
120 			break;
121 		case 's':
122 			slist = rcs_optarg;
123 			break;
124 		case 'T':
125 			/*
126 			 * kept for compatibility
127 			 */
128 			break;
129 		case 't':
130 			tflag = 1;
131 			break;
132 		case 'V':
133 			printf("%s\n", rcs_version);
134 			exit(0);
135 		case 'w':
136 			wflag = 1;
137 			wlist = rcs_optarg;
138 			break;
139 		case 'x':
140 			/* Use blank extension if none given. */
141 			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
142 			break;
143 		case 'z':
144 			timezone_flag = rcs_optarg;
145 			break;
146 		default:
147 			(usage)();
148 		}
149 	}
150 
151 	argc -= rcs_optind;
152 	argv += rcs_optind;
153 
154 	if (argc == 0) {
155 		warnx("no input file");
156 		(usage)();
157 	}
158 
159 	if (hflag == 1 && tflag == 1) {
160 		warnx("warning: -t overrides -h.");
161 		hflag = 0;
162 	}
163 
164 	for (i = 0; i < argc; i++) {
165 		fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
166 		if (fd < 0) {
167 			warn("%s", fpath);
168 			status = 1;
169 			continue;
170 		}
171 
172 		if ((file = rcs_open(fpath, fd,
173 		    RCS_READ|RCS_PARSE_FULLY)) == NULL) {
174 			status = 1;
175 			continue;
176 		}
177 
178 		if (Lflag == 1 && TAILQ_EMPTY(&(file->rf_locks))) {
179 			rcs_close(file);
180 			continue;
181 		}
182 
183 		if (Rflag == 1) {
184 			printf("%s\n", fpath);
185 			rcs_close(file);
186 			continue;
187 		}
188 
189 		rlog_file(argv[i], file);
190 
191 		rcs_close(file);
192 	}
193 
194 	return (status);
195 }
196 
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;
238 			if ((firstdate = date_parse(first)) == -1)
239 				return -1;
240 			delim = '\0';
241 			last = "\0";
242 		} else {
243 			while (*last && isspace((unsigned char)*last))
244 				last++;
245 		}
246 
247 		if (delim == '>' && *last == '\0') {
248 			flags |= RLOG_DATE_EARLIER;
249 			if ((firstdate = date_parse(first)) == -1)
250 				return -1;
251 		}
252 
253 		if (delim == '>' && *first == '\0' && *last != '\0') {
254 			flags |= RLOG_DATE_LATER;
255 			if ((firstdate = date_parse(last)) == -1)
256 				return -1;
257 		}
258 
259 		if (delim == '<' && *last == '\0') {
260 			flags |= RLOG_DATE_LATER;
261 			if ((firstdate = date_parse(first)) == -1)
262 				return -1;
263 		}
264 
265 		if (delim == '<' && *first == '\0' && *last != '\0') {
266 			flags |= RLOG_DATE_EARLIER;
267 			if ((firstdate = date_parse(last)) == -1)
268 				return -1;
269 		}
270 
271 		if (*first != '\0' && *last != '\0') {
272 			flags |= RLOG_DATE_RANGE;
273 
274 			if (delim == '<') {
275 				firstdate = date_parse(first);
276 				lastdate = date_parse(last);
277 			} else {
278 				firstdate = date_parse(last);
279 				lastdate = date_parse(first);
280 			}
281 			if (firstdate == -1 || lastdate == -1)
282 				return -1;
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 
348 static void
349 rlog_file(const char *fname, RCSFILE *file)
350 {
351 	char numb[RCS_REV_BUFSZ];
352 	u_int nrev;
353 	struct rcs_sym *sym;
354 	struct rcs_access *acp;
355 	struct rcs_delta *rdp;
356 	struct rcs_lock *lkp;
357 	char *workfile, *p;
358 
359 	if (rflag == 1)
360 		nrev = rcs_rev_select(file, revisions);
361 	else if (dflag == 1) {
362 		if ((nrev = rlog_select_daterev(file, rlog_dates)) == (u_int)-1)
363 			errx(1, "invalid date: %s", rlog_dates);
364 	} else
365 		nrev = file->rf_ndelta;
366 
367 	if ((workfile = basename(fname)) == NULL)
368 		err(1, "basename");
369 
370 	/*
371 	 * In case they specified 'foo,v' as argument.
372 	 */
373 	if ((p = strrchr(workfile, ',')) != NULL)
374 		*p = '\0';
375 
376 	printf("\nRCS file: %s", file->rf_path);
377 	printf("\nWorking file: %s", workfile);
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" : "");
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)));
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 
407 	printf("total revisions: %u", file->rf_ndelta);
408 
409 	if (file->rf_head != NULL && hflag == 0 && tflag == 0)
410 		printf(";\tselected revisions: %u", nrev);
411 
412 	printf("\n");
413 
414 
415 	if (hflag == 0 || tflag == 1)
416 		printf("description:\n%s", file->rf_desc);
417 
418 	if (hflag == 0 && tflag == 0 &&
419 	    !(lflag == 1 && TAILQ_EMPTY(&file->rf_locks))) {
420 		TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
421 			/*
422 			 * if selections are enabled verify that entry is
423 			 * selected.
424 			 */
425 			if ((rflag == 0 && dflag == 0)
426 			    || (rdp->rd_flags & RCS_RD_SELECT))
427 				rlog_rev_print(rdp);
428 		}
429 	}
430 
431 	printf("%s\n", revend);
432 }
433 
434 static void
435 rlog_rev_print(struct rcs_delta *rdp)
436 {
437 	int i, found;
438 	struct tm t;
439 	char *author, numb[RCS_REV_BUFSZ], *fmt, timeb[RCS_TIME_BUFSZ];
440 	struct rcs_argvector *largv, *sargv, *wargv;
441 	struct rcs_branch *rb;
442 	struct rcs_delta *nrdp;
443 
444 	found = 0;
445 	author = NULL;
446 
447 	/* -l[lockers] */
448 	if (lflag == 1) {
449 		if (rdp->rd_locker != NULL)
450 			found++;
451 
452 		if (llist != NULL) {
453 			/* if locker is empty, no need to go further. */
454 			if (rdp->rd_locker == NULL)
455 				return;
456 			largv = rcs_strsplit(llist, ",");
457 			for (i = 0; largv->argv[i] != NULL; i++) {
458 				if (strcmp(rdp->rd_locker, largv->argv[i])
459 				    == 0) {
460 					found++;
461 					break;
462 				}
463 				found = 0;
464 			}
465 			rcs_argv_destroy(largv);
466 		}
467 	}
468 
469 	/* -sstates */
470 	if (slist != NULL) {
471 		sargv = rcs_strsplit(slist, ",");
472 		for (i = 0; sargv->argv[i] != NULL; i++) {
473 			if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) {
474 				found++;
475 				break;
476 			}
477 			found = 0;
478 		}
479 		rcs_argv_destroy(sargv);
480 	}
481 
482 	/* -w[logins] */
483 	if (wflag == 1) {
484 		if (wlist != NULL) {
485 			wargv = rcs_strsplit(wlist, ",");
486 			for (i = 0; wargv->argv[i] != NULL; i++) {
487 				if (strcmp(rdp->rd_author, wargv->argv[i])
488 				    == 0) {
489 					found++;
490 					break;
491 				}
492 				found = 0;
493 			}
494 			rcs_argv_destroy(wargv);
495 		} else {
496 			if ((author = getlogin()) == NULL)
497 				err(1, "getlogin");
498 
499 			if (strcmp(rdp->rd_author, author) == 0)
500 				found++;
501 		}
502 	}
503 
504 	/* XXX dirty... */
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))
510 		return;
511 
512 	printf("%s\n", revsep);
513 
514 	rcsnum_tostr(rdp->rd_num, numb, sizeof(numb));
515 
516 	printf("revision %s", numb);
517 	if (rdp->rd_locker != NULL)
518 		printf("\tlocked by: %s;", rdp->rd_locker);
519 
520 	if (timezone_flag != NULL) {
521 		rcs_set_tz(timezone_flag, rdp, &t);
522 		fmt = "%Y-%m-%d %H:%M:%S%z";
523 	} else {
524 		t = rdp->rd_date;
525 		fmt = "%Y/%m/%d %H:%M:%S";
526 	}
527 
528 	(void)strftime(timeb, sizeof(timeb), fmt, &t);
529 
530 	printf("\ndate: %s;  author: %s;  state: %s;", timeb, rdp->rd_author,
531 	    rdp->rd_state);
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);
542 
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;
549 
550 		rcs_delta_stats(nrdp, &added, &removed);
551 		if (RCSNUM_ISBRANCHREV(rdp->rd_num))
552 			printf("  lines: +%d -%d;", added, removed);
553 		else
554 			printf("  lines: +%d -%d;", removed, added);
555 	}
556 
557 	if (rdp->rd_commitid != NULL)
558 		printf("  commitid: %s;", rdp->rd_commitid);
559 
560 	printf("\n");
561 
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);
570 		}
571 		printf("\n");
572 	}
573 
574 	printf("%s", rdp->rd_log);
575 }
576