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
rlog_usage(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
rlog_main(int argc,char ** argv)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
rlog_select_daterev(RCSFILE * rcsfile,char * date)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
rlog_file(const char * fname,RCSFILE * file)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
rlog_rev_print(struct rcs_delta * rdp)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