GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/rcs/rlog.c Lines: 98 281 34.9 %
Date: 2017-11-07 Branches: 59 226 26.1 %

Line Branch Exec Source
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
26
	char fpath[PATH_MAX];
81
82
13
	rcsnum_flags |= RCSNUM_NO_MAGIC;
83
13
	hflag = Rflag = rflag = status = 0;
84
28
	while ((ch = rcs_getopt(argc, argv, RLOG_OPTSTRING)) != -1) {
85




4
		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
2
			lflag = 1;
101
2
			llist = rcs_optarg;
102
2
			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
13
	argc -= rcs_optind;
152
13
	argv += rcs_optind;
153
154
13
	if (argc == 0) {
155
		warnx("no input file");
156
		(usage)();
157
	}
158
159
13
	if (hflag == 1 && tflag == 1) {
160
		warnx("warning: -t overrides -h.");
161
		hflag = 0;
162
	}
163
164
52
	for (i = 0; i < argc; i++) {
165
13
		fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
166
13
		if (fd < 0) {
167
			warn("%s", fpath);
168
			status = 1;
169
			continue;
170
		}
171
172
26
		if ((file = rcs_open(fpath, fd,
173
13
		    RCS_READ|RCS_PARSE_FULLY)) == NULL) {
174
			status = 1;
175
			continue;
176
		}
177
178

13
		if (Lflag == 1 && TAILQ_EMPTY(&(file->rf_locks))) {
179
			rcs_close(file);
180
			continue;
181
		}
182
183
13
		if (Rflag == 1) {
184
			printf("%s\n", fpath);
185
			rcs_close(file);
186
			continue;
187
		}
188
189
13
		rlog_file(argv[i], file);
190
191
13
		rcs_close(file);
192
13
	}
193
194
13
	return (status);
195
13
}
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
26
	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
13
	if (rflag == 1)
360
		nrev = rcs_rev_select(file, revisions);
361
13
	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
13
		nrev = file->rf_ndelta;
366
367
13
	if ((workfile = basename(fname)) == NULL)
368
		err(1, "basename");
369
370
	/*
371
	 * In case they specified 'foo,v' as argument.
372
	 */
373
13
	if ((p = strrchr(workfile, ',')) != NULL)
374
		*p = '\0';
375
376
13
	printf("\nRCS file: %s", file->rf_path);
377
13
	printf("\nWorking file: %s", workfile);
378
13
	printf("\nhead:");
379
13
	if (file->rf_head != NULL)
380
8
		printf(" %s", rcsnum_tostr(file->rf_head, numb, sizeof(numb)));
381
382
13
	printf("\nbranch:");
383
13
	if (rcs_branch_get(file) != NULL) {
384
		printf(" %s", rcsnum_tostr(rcs_branch_get(file),
385
		    numb, sizeof(numb)));
386
	}
387
388
13
	printf("\nlocks: %s", (file->rf_flags & RCS_SLOCK) ? "strict" : "");
389
34
	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list)
390
8
		printf("\n\t%s: %s", lkp->rl_name,
391
4
		    rcsnum_tostr(lkp->rl_num, numb, sizeof(numb)));
392
13
	printf("\naccess list:\n");
393
50
	TAILQ_FOREACH(acp, &(file->rf_access), ra_list)
394
12
		printf("\t%s\n", acp->ra_name);
395
396
13
	if (Nflag == 0) {
397
13
		printf("symbolic names:\n");
398
26
		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
13
	printf("keyword substitution: %s\n",
405
26
	    file->rf_expand == NULL ? "kv" : file->rf_expand);
406
407
13
	printf("total revisions: %u", file->rf_ndelta);
408
409
13
	if (file->rf_head != NULL && hflag == 0 && tflag == 0)
410
8
		printf(";\tselected revisions: %u", nrev);
411
412
13
	printf("\n");
413
414
415
13
	if (hflag == 0 || tflag == 1)
416
13
		printf("description:\n%s", file->rf_desc);
417
418

15
	if (hflag == 0 && tflag == 0 &&
419
15
	    !(lflag == 1 && TAILQ_EMPTY(&file->rf_locks))) {
420
50
		TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
421
			/*
422
			 * if selections are enabled verify that entry is
423
			 * selected.
424
			 */
425

13
			if ((rflag == 0 && dflag == 0)
426
			    || (rdp->rd_flags & RCS_RD_SELECT))
427
13
				rlog_rev_print(rdp);
428
		}
429
	}
430
431
13
	printf("%s\n", revend);
432
13
}
433
434
static void
435
rlog_rev_print(struct rcs_delta *rdp)
436
{
437
	int i, found;
438
26
	struct tm t;
439
13
	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
13
	if (lflag == 1) {
449
1
		if (rdp->rd_locker != NULL)
450
1
			found++;
451
452
1
		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
13
	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
13
	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
13
	if ((((slist != NULL && wflag == 1) ||
506
13
	    (slist != NULL && lflag == 1) ||
507

13
	    (lflag == 1 && wflag == 1)) && found < 2) ||
508
26
	    (((slist != NULL && lflag == 1 && wflag == 1) ||
509
13
	    (slist != NULL || lflag == 1 || wflag == 1)) && found == 0))
510
		return;
511
512
13
	printf("%s\n", revsep);
513
514
13
	rcsnum_tostr(rdp->rd_num, numb, sizeof(numb));
515
516
13
	printf("revision %s", numb);
517
13
	if (rdp->rd_locker != NULL)
518
4
		printf("\tlocked by: %s;", rdp->rd_locker);
519
520
13
	if (timezone_flag != NULL) {
521
		rcs_set_tz(timezone_flag, rdp, &t);
522
		fmt = "%Y-%m-%d %H:%M:%S%z";
523
	} else {
524
13
		t = rdp->rd_date;
525
		fmt = "%Y/%m/%d %H:%M:%S";
526
	}
527
528
13
	(void)strftime(timeb, sizeof(timeb), fmt, &t);
529
530
26
	printf("\ndate: %s;  author: %s;  state: %s;", timeb, rdp->rd_author,
531
13
	    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

26
	if (RCSNUM_ISBRANCHREV(rdp->rd_num))
539
		nrdp = rdp;
540
	else
541
13
		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

19
	if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) {
548
6
		int added, removed;
549
550
6
		rcs_delta_stats(nrdp, &added, &removed);
551

12
		if (RCSNUM_ISBRANCHREV(rdp->rd_num))
552
			printf("  lines: +%d -%d;", added, removed);
553
		else
554
6
			printf("  lines: +%d -%d;", removed, added);
555
6
	}
556
557
13
	if (rdp->rd_commitid != NULL)
558
		printf("  commitid: %s;", rdp->rd_commitid);
559
560
13
	printf("\n");
561
562
13
	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
13
	printf("%s", rdp->rd_log);
575
26
}