1 /* Print log messages and other information about RCS files.
2
3 Copyright (C) 2010-2020 Thien-Thi Nguyen
4 Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5 Copyright (C) 1982, 1988, 1989 Walter Tichy
6
7 This file is part of GNU RCS.
8
9 GNU RCS is free software: you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 GNU RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty
16 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "base.h"
24 #include <string.h>
25 #include <stdlib.h>
26 #include <errno.h> /* temporary, to support ‘read_positive_integer’,
27 before it and ‘compute_a_d’ move to grok. */
28 #include "rlog.help"
29 #include "b-complain.h"
30 #include "b-divvy.h"
31 #include "b-esds.h"
32 #include "b-excwho.h"
33 #include "b-fb.h"
34 #include "b-fro.h"
35
36 static char const ks_delims[] = ", \t\n;";
37
38 struct revrange
39 {
40 char const *beg;
41 char const *end;
42 int nfield;
43 };
44
45 struct daterange
46 {
47 char beg[DATESIZE];
48 char end[DATESIZE];
49 bool open_end;
50 };
51
52 struct date_selection
53 {
54 struct link *in; /* start - end */
55 struct link *by; /* end only */
56 };
57
58 struct criteria
59 {
60 struct link *revs, *actual;
61 /* On the first pass (option processing), push onto ‘.revs’.
62 After grokking, walk ‘.revs’ and push onto ‘.actual’. */
63
64 struct link *authors;
65 struct link *lockers;
66 struct link *states;
67 };
68
69 #define PUSH(x,ls) ls = prepend (x, ls, PLEXUS)
70
71 static bool
tokenize(char * argv,struct link ** chain)72 tokenize (char *argv, struct link **chain)
73 /* Tokenize (destructively) ‘argv’ using ‘ks_delims’.
74 Push tokens on ‘chain’ (LIFO).
75 Return true if any tokens were parsed. */
76 {
77 struct link *before = *chain;
78 char *s, *save, *token;
79
80 for (s = argv;
81 (token = strtok_r (s, ks_delims, &save));
82 s = NULL)
83 PUSH (token, *chain);
84
85 return *chain != before;
86 }
87
88 static void
cleanup(int * exitstatus)89 cleanup (int *exitstatus)
90 {
91 if (FLOW (erroneous))
92 *exitstatus = exit_failure;
93 fro_zclose (&FLOW (from));
94 }
95
96 static void
getlocker(char * argv,struct criteria * criteria)97 getlocker (char *argv, struct criteria *criteria)
98 /* Parse logins from ‘argv’; store in ‘criteria->lockers’. */
99 {
100 tokenize (argv, &criteria->lockers);
101 }
102
103 /* TODO: Move into b-fro.c, since counting lines is not sensitive to
104 "@@" presence (we can work in-place, avoiding ‘string_from_atat’). */
105
106 static long
read_positive_integer(char const ** p)107 read_positive_integer (char const **p)
108 {
109 long rv;
110 char *end;
111
112 errno = 0;
113 if (1 > (rv = strtol (*p, &end, 10)))
114 RFATAL ("non-positive integer");
115 if (ERANGE == errno)
116 RFATAL ("bad integer");
117 *p = end;
118 return rv;
119 }
120
121 static void
count_a_d(long * a,long * d,struct atat * edits)122 count_a_d (long *a, long *d, struct atat *edits)
123 {
124 struct cbuf s = string_from_atat (SINGLE, edits);
125 char const *end = s.string + s.size;
126 long *totals = zlloc (SINGLE, 2 * sizeof (long));
127
128 for (char const *p = s.string; p < end; p++)
129 {
130 bool add = ('a' == *p++);
131 long count;
132
133 /* Skip the line number. */
134 p = strchr (p, ' ');
135 count = read_positive_integer (&p);
136
137 totals[add] += count;
138 if (add)
139 /* Ignore the actual lines. */
140 while (count--)
141 {
142 size_t remaining = end - p;
143
144 if (! (p = memchr (++p, '\n', remaining)))
145 /* No final newline; skip out. */
146 goto done;
147 }
148 }
149 done:
150 *a = totals[1];
151 *d = totals[0];
152 brush_off (SINGLE, totals);
153 }
154
155 static void
putadelta(struct delta const * node,struct delta const * editscript,char const * insDelFormat)156 putadelta (struct delta const *node, struct delta const *editscript,
157 char const *insDelFormat)
158 /* Print delta ‘node’. ‘editscript’ indicates where the editscript is
159 stored; it equals ‘node’ if this node is not in trunk. */
160 {
161 FILE *out = stdout;
162 char datebuf[FULLDATESIZE];
163 bool pre5 = BE (version) < VERSION (5);
164 struct atat *log;
165
166 aprintf (out, "----------------------------\nrevision %s%s",
167 node->num,
168 pre5 ? " " : "");
169 if (node->lockedby)
170 aprintf (out, pre5 + "\tlocked by: %s;", node->lockedby);
171
172 aprintf (out, "\ndate: %s; author: %s; state: %s;",
173 date2str (node->date, datebuf), node->author, node->state);
174
175 if (editscript && editscript != REPO (tip))
176 {
177 bool trunk = node != editscript;
178 long a, d;
179
180 count_a_d (trunk ? &d : &a,
181 trunk ? &a : &d,
182 editscript->text);
183 aprintf (out, insDelFormat, a, d);
184 }
185
186 if (node->branches)
187 {
188 aputs ("\nbranches:", out);
189 for (struct wlink *ls = node->branches; ls; ls = ls->next)
190 {
191 struct delta *delta = ls->entry;
192
193 aprintf (out, " %s;", BRANCHNO (delta->num));
194 }
195 }
196
197 if (node->commitid)
198 aprintf (out, "%s commitid: %s",
199 editscript ? ";" : "",
200 node->commitid);
201
202 newline (out);
203 if ((log = node->log)
204 && log->beg + 1 < ATAT_END (log))
205 atat_display (out, log, true);
206 else
207 awrite (EMPTYLOG "\n", sizeof (EMPTYLOG), out);
208 }
209
210 static void
putrunk(const char * insDelFormat)211 putrunk (const char *insDelFormat)
212 /* Print revisions chosen, which are in trunk. */
213 {
214 for (struct delta const *ptr = REPO (tip); ptr; ptr = ptr->ilk)
215 if (ptr->selector)
216 putadelta (ptr, ptr->ilk, insDelFormat);
217 }
218
219 static struct delta const *putforest (struct wlink const *, const char *);
220
221 static void
putree(struct delta const * root,const char * insDelFormat)222 putree (struct delta const *root, const char *insDelFormat)
223 /* Print delta tree from ‘root’ (not including trunk)
224 in reverse order on each branch. */
225 {
226 while (root)
227 if (! root->branches)
228 root = root->ilk;
229 else
230 {
231 /* FIXME: This recurses deeply in the worst case. */
232 putree (root->ilk, insDelFormat);
233 root = putforest (root->branches, insDelFormat);
234 }
235 }
236
237 static void
putabranch(struct delta const * root,const char * insDelFormat)238 putabranch (struct delta const *root, const char *insDelFormat)
239 /* Print one branch from ‘root’. */
240 {
241 while (!root->selector)
242 {
243 root = root->ilk;
244 if (!root)
245 return;
246 }
247
248 /* FIXME: This recurses deeply in the worst case. */
249 if (root->ilk)
250 putabranch (root->ilk, insDelFormat);
251
252 putadelta (root, root, insDelFormat);
253 }
254
255 static struct delta const *
putforest(struct wlink const * branchroot,const char * insDelFormat)256 putforest (struct wlink const *branchroot, const char *insDelFormat)
257 /* Print branches that have the same direct ancestor ‘branchroot’. */
258 {
259 /* FIXME: This recurses deeply in the worst case. */
260 if (branchroot->next)
261 putforest (branchroot->next, insDelFormat);
262
263 putabranch (branchroot->entry, insDelFormat);
264 return branchroot->entry;
265 }
266
267 static bool
extractdelta(struct delta const * pdelta,bool lockflag,struct criteria * criteria)268 extractdelta (struct delta const *pdelta, bool lockflag,
269 struct criteria *criteria)
270 /* Return true if ‘pdelta’ matches the selection critera. */
271 {
272 struct link const *pstate;
273 struct link const *pauthor;
274 int length;
275
276 /* Only certain authors wanted. */
277 if ((pauthor = criteria->authors))
278 while (STR_DIFF (pauthor->entry, pdelta->author))
279 if (!(pauthor = pauthor->next))
280 return false;
281 /* Only certain states wanted. */
282 if ((pstate = criteria->states))
283 while (STR_DIFF (pstate->entry, pdelta->state))
284 if (!(pstate = pstate->next))
285 return false;
286 /* Only locked revisions wanted. */
287 if (lockflag && !lock_on (pdelta))
288 return false;
289 /* Only certain revs or branches wanted. */
290 for (struct link *ls = criteria->actual; ls;)
291 {
292 struct revrange const *rr = ls->entry;
293
294 length = rr->nfield;
295 if (countnumflds (pdelta->num) == length + ODDP (length)
296 && 0 <= compartial (pdelta->num, rr->beg, length)
297 && 0 <= compartial (rr->end, pdelta->num, length))
298 break;
299 if (! (ls = ls->next))
300 return false;
301 }
302 return true;
303 }
304
305 static void
exttree(struct delta * root,bool lockflag,struct criteria * criteria)306 exttree (struct delta *root, bool lockflag,
307 struct criteria *criteria)
308 /* Select revisions, starting with ‘root’. */
309 {
310 while (root)
311 {
312 root->selector = extractdelta (root, lockflag, criteria);
313 root->pretty_log.string = NULL;
314
315 if (! root->branches)
316 root = root->ilk;
317 else
318 {
319 /* FIXME: This recurses deeply in the worst case. */
320 struct wlink *ls;
321 exttree (root->ilk, lockflag, criteria);
322 for (ls = root->branches; ls->next; ls = ls->next)
323 exttree (ls->entry, lockflag, criteria);
324 root = ls->entry;
325 }
326 }
327 }
328
329 static void
getauthor(char * argv,struct criteria * criteria)330 getauthor (char *argv, struct criteria *criteria)
331 /* Parse logins from ‘argv’; store in ‘criteria->authors’.
332 If none specified, default to the user login. */
333 {
334 if (! tokenize (argv, &criteria->authors))
335 PUSH (getusername (false),
336 criteria->authors);
337 }
338
339 static void
getstate(char * argv,struct criteria * criteria)340 getstate (char *argv, struct criteria *criteria)
341 /* Parse states from ‘argv’; store in ‘criteria->states’.
342 Signal error if none specified. */
343 {
344 if (! tokenize (argv, &criteria->states))
345 PERR ("missing state attributes after -s option");
346 }
347
348 static void
trunclocks(struct criteria * criteria)349 trunclocks (struct criteria *criteria)
350 /* Truncate the list of locks to those that are held by the id's on
351 ‘criteria->lockers’. Do not truncate if ‘criteria->lockers’ empty. */
352 {
353 struct link const *plocker;
354 struct link box, *tp;
355
356 if (!criteria->lockers)
357 return;
358
359 /* Shorten locks to those contained in ‘criteria->lockers’. */
360 for (box.next = GROK (locks), tp = &box; tp->next;)
361 {
362 struct rcslock const *rl = tp->next->entry;
363
364 for (plocker = criteria->lockers;;)
365 if (STR_SAME (plocker->entry, rl->login))
366 {
367 tp = tp->next;
368 break;
369 }
370 else if (!(plocker = plocker->next))
371 {
372 tp->next = tp->next->next;
373 GROK (locks) = box.next;
374 break;
375 }
376 }
377 }
378
379 static void
recentdate(struct delta const * root,struct daterange * r)380 recentdate (struct delta const *root, struct daterange *r)
381 /* Find the delta that is closest to the cutoff date ‘pd’ among the
382 revisions selected by ‘exttree’. Successively narrow down the
383 interval given by ‘pd’, and set the ‘strtdate’ of ‘pd’ to the date
384 of the selected delta. */
385 {
386 while (root)
387 {
388 if (root->selector
389 && !DATE_LT (root->date, r->beg)
390 && !DATE_GT (root->date, r->end))
391 {
392 strncpy (r->beg, root->date, DATESIZE);
393 r->beg[DATESIZE - 1] = '\0';
394 }
395
396 struct wlink *ls = root->branches;
397 if (!ls)
398 root = root->ilk;
399 else
400 {
401 /* FIXME: This recurses deeply in the worst case. */
402 for (; ls->next; ls = ls->next)
403 recentdate (ls->entry, r);
404 root = ls->entry;
405 }
406 }
407 }
408
409 static size_t
extdate(struct delta * root,struct date_selection * datesel)410 extdate (struct delta *root, struct date_selection *datesel)
411 /* Select revisions which are in the date range specified in ‘datesel->by’
412 and ‘datesel->in’, starting at ‘root’. Return number of revisions
413 selected, including those already selected. */
414 {
415 size_t revno = 0;
416
417 for (; root; root = root->ilk)
418 {
419 if (datesel->in || datesel->by)
420 {
421 struct daterange const *r;
422 bool open_end, sel = false;
423
424 for (struct link *ls = datesel->in; ls; ls = ls->next)
425 {
426 r = ls->entry;
427 open_end = r->open_end;
428 if ((sel = ((!r->beg[0]
429 || (open_end
430 ? DATE_LT (r->beg, root->date)
431 : !DATE_GT (r->beg, root->date)))
432 &&
433 (!r->end[0]
434 || (open_end
435 ? DATE_LT (root->date, r->end)
436 : !DATE_GT (root->date, r->end))))))
437 break;
438 }
439 if (!sel)
440 {
441 for (struct link *ls = datesel->by; ls; ls = ls->next)
442 {
443 r = ls->entry;
444 if ((sel = DATE_EQ (root->date, r->beg)))
445 break;
446 }
447 if (!sel)
448 root->selector = false;
449 }
450 }
451
452 revno += root->selector;
453
454 /* FIXME: This recurses deeply in the worst case. */
455 for (struct wlink *ls = root->branches; ls; ls = ls->next)
456 revno += extdate (ls->entry, datesel);
457 }
458
459 return revno;
460 }
461
462 static void
getdatepair(char * argv,struct date_selection * datesel)463 getdatepair (char *argv, struct date_selection *datesel)
464 /* Get time range from command line and store in ‘datesel->in’ if
465 a time range specified or in ‘datesel->by’ if a time spot specified. */
466 {
467 register char c;
468 struct daterange *r;
469 char const *rawdate;
470 bool switchflag;
471
472 argv--;
473 while ((c = *++argv) == ',' || c == ' ' || c == '\t' || c == '\n'
474 || c == ';')
475 continue;
476 if (c == '\0')
477 {
478 PERR ("missing date/time after -d");
479 return;
480 }
481
482 while (c != '\0')
483 {
484 switchflag = false;
485 r = ZLLOC (1, struct daterange);
486 if (c == '<') /* <DATE */
487 {
488 c = *++argv;
489 if (!(r->open_end = c != '='))
490 c = *++argv;
491 r->beg[0] = '\0';
492 }
493 else if (c == '>') /* >DATE */
494 {
495 c = *++argv;
496 if (!(r->open_end = c != '='))
497 c = *++argv;
498 r->end[0] = '\0';
499 switchflag = true;
500 }
501 else
502 {
503 rawdate = argv;
504 while (c != '<' && c != '>' && c != ';' && c != '\0')
505 c = *++argv;
506 *argv = '\0';
507 if (c == '>')
508 switchflag = true;
509 str2date (rawdate, switchflag ? r->end : r->beg);
510 if (c == ';' || c == '\0') /* DATE */
511 {
512 memcpy (r->end, r->beg, DATESIZE);
513 PUSH (r, datesel->by);
514 goto end;
515 }
516 else /* DATE< or DATE> (see ‘switchflag’) */
517 {
518 bool eq = argv[1] == '=';
519
520 r->open_end = !eq;
521 argv += eq;
522 while ((c = *++argv) == ' ' || c == '\t' || c == '\n')
523 continue;
524 if (c == ';' || c == '\0')
525 {
526 /* Second date missing. */
527 (switchflag ? r->beg : r->end)[0] = '\0';
528 PUSH (r, datesel->in);
529 goto end;
530 }
531 }
532 }
533 rawdate = argv;
534 while (c != '>' && c != '<' && c != ';' && c != '\0')
535 c = *++argv;
536 *argv = '\0';
537 str2date (rawdate, switchflag ? r->beg : r->end);
538 PUSH (r, datesel->in);
539 end:
540 if (BE (version) < VERSION (5))
541 r->open_end = false;
542 if (c == '\0')
543 return;
544 while ((c = *++argv) == ';' || c == ' ' || c == '\t' || c == '\n')
545 continue;
546 }
547 }
548
549 static bool
checkrevpair(char const * num1,char const * num2)550 checkrevpair (char const *num1, char const *num2)
551 /* Check whether ‘num1’, ‘num2’ are a legal pair, i.e.
552 only the last field differs and have same number of
553 fields (if length <= 2, may be different if first field). */
554 {
555 int length = countnumflds (num1);
556
557 if (countnumflds (num2) != length
558 || (2 < length && compartial (num1, num2, length - 1) != 0))
559 {
560 RERR ("invalid branch or revision pair %s : %s", num1, num2);
561 return false;
562 }
563 return true;
564 }
565
566 #define ZERODATE "0.0.0.0.0.0"
567
568 static bool
getnumericrev(bool branchflag,struct criteria * criteria)569 getnumericrev (bool branchflag, struct criteria *criteria)
570 /* Get the numeric name of revisions stored in ‘criteria->revs’; store
571 them in ‘criteria->actual’. If ‘branchflag’, also add default branch. */
572 {
573 struct link *ls;
574 struct revrange *rr;
575 int n;
576 struct cbuf s, e;
577 char const *lrev;
578 struct cbuf const *rstart, *rend;
579 struct delta *tip = REPO (tip);
580 char const *defbr = GROK (branch);
581
582 criteria->actual = NULL;
583 for (ls = criteria->revs; ls; ls = ls->next)
584 {
585 struct revrange const *from = ls->entry;
586
587 n = 0;
588 rstart = &s;
589 rend = &e;
590
591 switch (from->nfield)
592 {
593
594 case 1: /* -rREV */
595 if (!fully_numeric_no_k (&s, from->beg))
596 goto freebufs;
597 rend = &s;
598 n = countnumflds (s.string);
599 if (!n && (lrev = tiprev ()))
600 {
601 s.string = lrev;
602 n = countnumflds (lrev);
603 }
604 break;
605
606 case 2: /* -rREV: */
607 if (!fully_numeric_no_k (&s, from->beg))
608 goto freebufs;
609 e.string = (2 > (n = countnumflds (s.string)))
610 ? ""
611 : SHSNIP (&e.size, s.string, strrchr (s.string, '.'));
612 break;
613
614 case 3: /* -r:REV */
615 if (!fully_numeric_no_k (&e, from->end))
616 goto freebufs;
617 if ((n = countnumflds (e.string)) < 2)
618 s.string = ".0";
619 else
620 {
621 SHACCR (e.string, strrchr (e.string, '.'));
622 accf (PLEXUS, ".0");
623 s.string = SHSTR (&s.size);
624 }
625 break;
626
627 default: /* -rREV1:REV2 */
628 if (!(fully_numeric_no_k (&s, from->beg)
629 && fully_numeric_no_k (&e, from->end)
630 && checkrevpair (s.string, e.string)))
631 goto freebufs;
632 n = countnumflds (s.string);
633 /* Swap if out of order. */
634 if (compartial (s.string, e.string, n) > 0)
635 {
636 rstart = &e;
637 rend = &s;
638 }
639 break;
640 }
641
642 if (n)
643 {
644 rr = FALLOC (struct revrange);
645 rr->nfield = n;
646 rr->beg = rstart->string;
647 rr->end = rend->string;
648 PUSH (rr, criteria->actual);
649 }
650 }
651 /* Now take care of ‘branchflag’. */
652 if (branchflag && (defbr || tip))
653 {
654 rr = FALLOC (struct revrange);
655 rr->beg = rr->end = defbr
656 ? defbr
657 : TAKE (1, tip->num);
658 rr->nfield = countnumflds (rr->beg);
659 PUSH (rr, criteria->actual);
660 }
661
662 freebufs:
663 return !ls;
664 }
665
666 static void
putrevpairs(char const * b,char const * e,bool sawsep,void * data)667 putrevpairs (char const *b, char const *e, bool sawsep, void *data)
668 /* Store a revision or branch range into ‘creteria->revs’. */
669 {
670 struct criteria *criteria = data;
671 struct revrange *rr = ZLLOC (1, struct revrange);
672
673 rr->beg = b;
674 rr->end = e;
675 rr->nfield = (!sawsep
676 ? 1 /* -rREV */
677 : (!e[0]
678 ? 2 /* -rREV: */
679 : (!b[0]
680 ? 3 /* -r:REV */
681 : 4))); /* -rREV1:REV2 */
682 PUSH (rr, criteria->revs);
683 }
684
685 DECLARE_PROGRAM (rlog, TYAG_IMMEDIATE);
686
687 static int
rlog_main(const char * cmd,int argc,char ** argv)688 rlog_main (const char *cmd, int argc, char **argv)
689 {
690 int exitstatus = EXIT_SUCCESS;
691 bool branchflag = false;
692 bool lockflag = false;
693 struct date_selection datesel = { .in = NULL, .by = NULL };
694 struct criteria criteria =
695 {
696 .revs = NULL,
697 .authors = NULL,
698 .lockers = NULL,
699 .states = NULL
700 };
701 char const *insDelFormat;
702 FILE *out;
703 char *a, **newargv;
704 char const *accessListString, *accessFormat;
705 char const *headFormat, *symbolFormat;
706 bool descflag, selectflag;
707 bool onlylockflag; /* print only files with locks */
708 bool onlyRCSflag; /* print only RCS filename */
709 bool pre5;
710 bool shownames;
711 size_t revno;
712
713 CHECK_HV (cmd);
714 gnurcs_init (&program);
715
716 descflag = selectflag = shownames = true;
717 onlylockflag = onlyRCSflag = false;
718 out = stdout;
719
720 argc = getRCSINIT (argc, argv, &newargv);
721 argv = newargv;
722 while (a = *++argv, 0 < --argc && *a++ == '-')
723 {
724 switch (*a++)
725 {
726
727 case 'L':
728 onlylockflag = true;
729 break;
730
731 case 'N':
732 shownames = false;
733 break;
734
735 case 'R':
736 onlyRCSflag = true;
737 break;
738
739 case 'l':
740 lockflag = true;
741 getlocker (a, &criteria);
742 break;
743
744 case 'b':
745 branchflag = true;
746 break;
747
748 case 'r':
749 parse_revpairs ('r', a, &criteria, putrevpairs);
750 break;
751
752 case 'd':
753 getdatepair (a, &datesel);
754 break;
755
756 case 's':
757 getstate (a, &criteria);
758 break;
759
760 case 'w':
761 getauthor (a, &criteria);
762 break;
763
764 case 'h':
765 descflag = false;
766 break;
767
768 case 't':
769 selectflag = false;
770 break;
771
772 case 'q':
773 /* This has no effect; it's here for consistency. */
774 BE (quiet) = true;
775 break;
776
777 case 'x':
778 BE (pe) = a;
779 break;
780
781 case 'z':
782 zone_set (a);
783 break;
784
785 case 'T':
786 /* Ignore -T, so that RCSINIT can contain -T. */
787 if (*a)
788 goto unknown;
789 break;
790
791 case 'V':
792 setRCSversion (*argv);
793 break;
794
795 default:
796 unknown:
797 bad_option (*argv);
798 };
799 }
800 /* (End of option processing.) */
801
802 if (!(descflag | selectflag))
803 {
804 PWARN ("-t overrides -h.");
805 descflag = true;
806 }
807
808 pre5 = BE (version) < VERSION (5);
809 if (pre5)
810 {
811 accessListString = "\naccess list: ";
812 accessFormat = " %s";
813 headFormat =
814 "\nRCS file: %s; Working file: %s\nhead: %s%s\nbranch: %s%s\nlocks: ";
815 insDelFormat = " lines added/del: %ld/%ld";
816 symbolFormat = " %s: %s;";
817 }
818 else
819 {
820 accessListString = "\naccess list:";
821 accessFormat = "\n\t%s";
822 headFormat =
823 "\nRCS file: %s\nWorking file: %s\nhead:%s%s\nbranch:%s%s\nlocks:%s";
824 insDelFormat = " lines: +%ld -%ld";
825 symbolFormat = "\n\t%s: %s";
826 }
827
828 /* Now handle all filenames. */
829 if (FLOW (erroneous))
830 cleanup (&exitstatus);
831 else if (argc < 1)
832 PFATAL ("no input file");
833 else
834 for (; 0 < argc; cleanup (&exitstatus), ++argv, --argc)
835 {
836 char const *repo_filename;
837 struct delta *tip;
838 char const *defbr;
839 bool strictly_locking;
840 int kws;
841 struct link *locks;
842
843 ffree ();
844
845 if (pairnames (argc, argv, rcsreadopen, true, false) <= 0)
846 continue;
847
848 /* ‘REPO (filename)’ contains the name of the RCS file,
849 and ‘FLOW (from)’ the file descriptor;
850 ‘MANI (filename)’ contains the name of the working file. */
851 repo_filename = REPO (filename);
852 tip = REPO (tip);
853 defbr = GROK (branch);
854 locks = GROK (locks);
855 strictly_locking = BE (strictly_locking);
856 kws = BE (kws);
857
858 /* Keep only those locks given by ‘-l’. */
859 if (lockflag)
860 trunclocks (&criteria);
861
862 /* Do nothing if ‘-L’ is given and there are no locks. */
863 if (onlylockflag && !locks)
864 continue;
865
866 if (onlyRCSflag)
867 {
868 aprintf (out, "%s\n", repo_filename);
869 continue;
870 }
871
872 if (!getnumericrev (branchflag, &criteria))
873 continue;
874
875 /* Print RCS filename, working filename and optional
876 administrative information. Could use ‘getfullRCSname’
877 here, but that is very slow. */
878 aprintf (out, headFormat, repo_filename, MANI (filename),
879 tip ? " " : "",
880 tip ? tip->num : "",
881 defbr ? " " : "",
882 defbr ? defbr : "",
883 strictly_locking ? " strict" : "");
884 format_locks (out, symbolFormat);
885 if (strictly_locking && pre5)
886 aputs (" ; strict" + (locks ? 3 : 0), out);
887
888 /* Print access list. */
889 aputs (accessListString, out);
890 for (struct link *ls = GROK (access); ls; ls = ls->next)
891 aprintf (out, accessFormat, ls->entry);
892
893 if (shownames)
894 {
895 /* Print symbolic names. */
896 aputs ("\nsymbolic names:", out);
897 format_assocs (out, symbolFormat);
898 }
899 if (pre5)
900 {
901 aputs ("\ncomment leader: \"", out);
902 awrite (REPO (log_lead).string, REPO (log_lead).size, out);
903 afputc ('\"', out);
904 }
905 if (!pre5 || kws != kwsub_kv)
906 aprintf (out, "\nkeyword substitution: %s", kwsub_string (kws));
907
908 aprintf (out, "\ntotal revisions: %zu", GROK (deltas_count));
909
910 revno = 0;
911
912 if (tip && selectflag & descflag)
913 {
914 exttree (tip, lockflag, &criteria);
915
916 /* Get most recently date of the dates pointed by ‘duelst’. */
917 for (struct link *ls = datesel.by; ls; ls = ls->next)
918 {
919 struct daterange const *incomplete = ls->entry;
920 struct daterange *r = ZLLOC (1, struct daterange);
921
922 /* Couldn't this have been done before? --ttn */
923 *r = *incomplete;
924 memcpy (r->beg, ZERODATE, sizeof ZERODATE);
925 ls->entry = r;
926 recentdate (tip, r);
927 }
928
929 revno = extdate (tip, &datesel);
930
931 aprintf (out, ";\tselected revisions: %zu", revno);
932 }
933
934 newline (out);
935 if (descflag)
936 {
937 struct atat *desc = GROK (desc);
938
939 aputs ("description:\n", out);
940 atat_display (out, desc, true);
941 }
942 if (revno)
943 {
944 putrunk (insDelFormat);
945 putree (tip, insDelFormat);
946 }
947 aputs (equal_line, out);
948 }
949 Ozclose (&out);
950 gnurcs_goodbye ();
951 return exitstatus;
952 }
953
954 static const uint8_t rlog_aka[10] =
955 {
956 2 /* count */,
957 3,'l','o','g',
958 4,'r','l','o','g'
959 };
960
961 YET_ANOTHER_COMMAND (rlog);
962
963 /*:help
964 [options] file ...
965 Options:
966 -L Ignore RCS files with no locks set.
967 -R Print the RCS file name only.
968 -h Print only the "header" information.
969 -t Like -h, but also include the description.
970 -N Omit symbolic names.
971 -b Select the default branch.
972 -dDATES Select revisions in the range DATES, with spec:
973 D -- single revision D or earlier
974 D1<D2 -- between D1 and D2, exclusive
975 D2>D1 -- likewise
976 <D, D> -- before D
977 >D, D< -- after D
978 Use <= or >= to make ranges inclusive; DATES
979 may also be a list of semicolon-separated specs.
980 -l[WHO] Select revisions locked by WHO (comma-separated list)
981 only, or by anyone if WHO is omitted.
982 -r[REVS] Select revisions in REVS, a comma-separated list of
983 range specs, one of: REV, REV:, :REV, REV1:REV2
984 -sSTATES Select revisions with state in STATES (comma-separated list).
985 -w[WHO] Select revisions checked in by WHO (comma-separated list),
986 or by the user if WHO is omitted.
987 -T No effect; included for compatibility with other commands.
988 -V Obsolete; do not use.
989 -VN Emulate RCS version N.
990 -xSUFF Specify SUFF as a slash-separated list of suffixes
991 used to identify RCS file names.
992 -zZONE Specify date output format in keyword-substitution.
993 -q No effect, included for consistency with other commands.
994 */
995
996 /* rlog.c ends here */
997