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