Bug Summary

File:src/gnu/usr.bin/cvs/src/log.c
Warning:line 254, column 8
Dereference of null pointer (loaded from variable 'prl')

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name log.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/gnu/usr.bin/cvs/obj/src -resource-dir /usr/local/lib/clang/13.0.0 -D HAVE_CONFIG_H -I . -I /usr/src/gnu/usr.bin/cvs/src -I .. -I . -I /usr/src/gnu/usr.bin/cvs/lib -I /usr/src/gnu/usr.bin/cvs/diff -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/gnu/usr.bin/cvs/obj/src -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/gnu/usr.bin/cvs/src/log.c
1/*
2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
4 *
5 * You may distribute under the terms of the GNU General Public License as
6 * specified in the README file that comes with the CVS source distribution.
7 *
8 * Print Log Information
9 *
10 * Prints the RCS "log" (rlog) information for the specified files. With no
11 * argument, prints the log information for all the files in the directory
12 * (recursive by default).
13 */
14
15#include "cvs.h"
16
17/* This structure holds information parsed from the -r option. */
18
19struct option_revlist
20{
21 /* The next -r option. */
22 struct option_revlist *next;
23 /* The first revision to print. This is NULL if the range is
24 :rev, or if no revision is given. */
25 char *first;
26 /* The last revision to print. This is NULL if the range is rev:,
27 or if no revision is given. If there is no colon, first and
28 last are the same. */
29 char *last;
30 /* Nonzero if there was a trailing `.', which means to print only
31 the head revision of a branch. */
32 int branchhead;
33 /* Nonzero if first and last are inclusive. */
34 int inclusive;
35};
36
37/* This structure holds information derived from option_revlist given
38 a particular RCS file. */
39
40struct revlist
41{
42 /* The next pair. */
43 struct revlist *next;
44 /* The first numeric revision to print. */
45 char *first;
46 /* The last numeric revision to print. */
47 char *last;
48 /* The number of fields in these revisions (one more than
49 numdots). */
50 int fields;
51 /* Whether first & last are to be included or excluded. */
52 int inclusive;
53};
54
55/* This structure holds information parsed from the -d option. */
56
57struct datelist
58{
59 /* The next date. */
60 struct datelist *next;
61 /* The starting date. */
62 char *start;
63 /* The ending date. */
64 char *end;
65 /* Nonzero if the range is inclusive rather than exclusive. */
66 int inclusive;
67};
68
69/* This structure is used to pass information through start_recursion. */
70struct log_data
71{
72 /* Nonzero if the -R option was given, meaning that only the name
73 of the RCS file should be printed. */
74 int nameonly;
75 /* Nonzero if the -h option was given, meaning that only header
76 information should be printed. */
77 int header;
78 /* Nonzero if the -t option was given, meaning that only the
79 header and the descriptive text should be printed. */
80 int long_header;
81 /* Nonzero if the -N option was seen, meaning that tag information
82 should not be printed. */
83 int notags;
84 /* Nonzero if the -b option was seen, meaning that only revisions
85 on the default branch should be printed. */
86 int default_branch;
87 /* If not NULL, the value given for the -r option, which lists
88 sets of revisions to be printed. */
89 struct option_revlist *revlist;
90 /* If not NULL, the date pairs given for the -d option, which
91 select date ranges to print. */
92 struct datelist *datelist;
93 /* If not NULL, the single dates given for the -d option, which
94 select specific revisions to print based on a date. */
95 struct datelist *singledatelist;
96 /* If not NULL, the list of states given for the -s option, which
97 only prints revisions of given states. */
98 List *statelist;
99 /* If not NULL, the list of login names given for the -w option,
100 which only prints revisions checked in by given users. */
101 List *authorlist;
102};
103
104/* This structure is used to pass information through walklist. */
105struct log_data_and_rcs
106{
107 struct log_data *log_data;
108 struct revlist *revlist;
109 RCSNode *rcs;
110};
111
112static int rlog_proc PROTO((int argc, char **argv, char *xwhere,(int argc, char **argv, char *xwhere, char *mwhere, char *mfile
, int shorten, int local_specified, char *mname, char *msg)
113 char *mwhere, char *mfile, int shorten,(int argc, char **argv, char *xwhere, char *mwhere, char *mfile
, int shorten, int local_specified, char *mname, char *msg)
114 int local_specified, char *mname, char *msg))(int argc, char **argv, char *xwhere, char *mwhere, char *mfile
, int shorten, int local_specified, char *mname, char *msg)
;
115static Dtype log_dirproc PROTO ((void *callerdat, char *dir,(void *callerdat, char *dir, char *repository, char *update_dir
, List *entries)
116 char *repository, char *update_dir,(void *callerdat, char *dir, char *repository, char *update_dir
, List *entries)
117 List *entries))(void *callerdat, char *dir, char *repository, char *update_dir
, List *entries)
;
118static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo))(void *callerdat, struct file_info *finfo);
119static struct option_revlist *log_parse_revlist PROTO ((const char *))(const char *);
120static void log_parse_date PROTO ((struct log_data *, const char *))(struct log_data *, const char *);
121static void log_parse_list PROTO ((List **, const char *))(List **, const char *);
122static struct revlist *log_expand_revlist PROTO ((RCSNode *,(RCSNode *, struct option_revlist *, int)
123 struct option_revlist *,(RCSNode *, struct option_revlist *, int)
124 int))(RCSNode *, struct option_revlist *, int);
125static void log_free_revlist PROTO ((struct revlist *))(struct revlist *);
126static int log_version_requested PROTO ((struct log_data *, struct revlist *,(struct log_data *, struct revlist *, RCSNode *, RCSVers *)
127 RCSNode *, RCSVers *))(struct log_data *, struct revlist *, RCSNode *, RCSVers *);
128static int log_symbol PROTO ((Node *, void *))(Node *, void *);
129static int log_count PROTO ((Node *, void *))(Node *, void *);
130static int log_fix_singledate PROTO ((Node *, void *))(Node *, void *);
131static int log_count_print PROTO ((Node *, void *))(Node *, void *);
132static void log_tree PROTO ((struct log_data *, struct revlist *,(struct log_data *, struct revlist *, RCSNode *, const char *
)
133 RCSNode *, const char *))(struct log_data *, struct revlist *, RCSNode *, const char *
)
;
134static void log_abranch PROTO ((struct log_data *, struct revlist *,(struct log_data *, struct revlist *, RCSNode *, const char *
)
135 RCSNode *, const char *))(struct log_data *, struct revlist *, RCSNode *, const char *
)
;
136static void log_version PROTO ((struct log_data *, struct revlist *,(struct log_data *, struct revlist *, RCSNode *, RCSVers *, int
)
137 RCSNode *, RCSVers *, int))(struct log_data *, struct revlist *, RCSNode *, RCSVers *, int
)
;
138static int log_branch PROTO ((Node *, void *))(Node *, void *);
139static int version_compare PROTO ((const char *, const char *, int))(const char *, const char *, int);
140
141static struct log_data log_data;
142static int is_rlog;
143
144static const char *const log_usage[] =
145{
146 "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
147 " [-w[logins]] [files...]\n",
148 "\t-l\tLocal directory only, no recursion.\n",
149 "\t-R\tOnly print name of RCS file.\n",
150 "\t-h\tOnly print header.\n",
151 "\t-t\tOnly print header and descriptive text.\n",
152 "\t-N\tDo not list tags.\n",
153 "\t-b\tOnly list revisions on the default branch.\n",
154 "\t-r[revisions]\tSpecify revision(s) to list.\n",
155 "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
156 "\t rev1::rev2 Between rev1 and rev2, excluding rev1 and rev2.\n",
157 "\t rev: rev and following revisions on the same branch.\n",
158 "\t rev:: After rev on the same branch.\n",
159 "\t :rev rev and previous revisions on the same branch.\n",
160 "\t ::rev Before rev on the same branch.\n",
161 "\t rev Just rev.\n",
162 "\t branch All revisions on the branch.\n",
163 "\t branch. The last revision on the branch.\n",
164 "\t-d dates\tSpecify dates (D1<D2 for range, D for latest before).\n",
165 "\t-s states\tOnly list revisions with specified states.\n",
166 "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
167 "(Specify the --help global option for a list of other help options)\n",
168 NULL((void*)0)
169};
170
171#ifdef CLIENT_SUPPORT1
172
173/* Helper function for send_arg_list. */
174static int send_one PROTO ((Node *, void *))(Node *, void *);
175
176static int
177send_one (node, closure)
178 Node *node;
179 void *closure;
180{
181 char *option = (char *) closure;
182
183 send_to_server ("Argument ", 0);
184 send_to_server (option, 0);
185 if (strcmp (node->key, "@@MYSELF") == 0)
186 /* It is a bare -w option. Note that we must send it as
187 -w rather than messing with getcaller() or something (which on
188 the client will return garbage). */
189 ;
190 else
191 send_to_server (node->key, 0);
192 send_to_server ("\012", 0);
193 return 0;
194}
195
196/* For each element in ARG, send an argument consisting of OPTION
197 concatenated with that element. */
198static void send_arg_list PROTO ((char *, List *))(char *, List *);
199
200static void
201send_arg_list (option, arg)
202 char *option;
203 List *arg;
204{
205 if (arg == NULL((void*)0))
206 return;
207 walklist (arg, send_one, (void *)option);
208}
209
210#endif
211
212int
213cvslog (argc, argv)
214 int argc;
215 char **argv;
216{
217 int c;
218 int err = 0;
219 int local = 0;
220 struct option_revlist **prl;
221
222 is_rlog = (strcmp (command_name, "rlog") == 0);
223
224 if (argc == -1)
1
Assuming the condition is false
2
Taking false branch
225 usage (log_usage);
226
227 memset (&log_data, 0, sizeof log_data);
228 prl = &log_data.revlist;
229
230 optind = 0;
231 while ((c = getopt (argc, argv, "+bd:hlNRr::s:tw::")) != -1)
3
Assuming the condition is true
4
Loop condition is true. Entering loop body
8
Assuming the condition is true
9
Loop condition is true. Entering loop body
232 {
233 switch (c)
5
Control jumps to 'case 114:' at line 253
10
Control jumps to 'case 114:' at line 253
234 {
235 case 'b':
236 log_data.default_branch = 1;
237 break;
238 case 'd':
239 log_parse_date (&log_data, optarg);
240 break;
241 case 'h':
242 log_data.header = 1;
243 break;
244 case 'l':
245 local = 1;
246 break;
247 case 'N':
248 log_data.notags = 1;
249 break;
250 case 'R':
251 log_data.nameonly = 1;
252 break;
253 case 'r':
254 *prl = log_parse_revlist (optarg);
11
Dereference of null pointer (loaded from variable 'prl')
255 prl = &(*prl)->next;
6
Null pointer value stored to 'prl'
256 break;
7
Execution continues on line 231
257 case 's':
258 log_parse_list (&log_data.statelist, optarg);
259 break;
260 case 't':
261 log_data.long_header = 1;
262 break;
263 case 'w':
264 if (optarg != NULL((void*)0))
265 log_parse_list (&log_data.authorlist, optarg);
266 else
267 log_parse_list (&log_data.authorlist, "@@MYSELF");
268 break;
269 case '?':
270 default:
271 usage (log_usage);
272 break;
273 }
274 }
275 argc -= optind;
276 argv += optind;
277
278 wrap_setup ();
279
280#ifdef CLIENT_SUPPORT1
281 if (current_parsed_root->isremote)
282 {
283 struct datelist *p;
284 struct option_revlist *rp;
285 char datetmp[MAXDATELEN50];
286
287 /* We're the local client. Fire up the remote server. */
288 start_server ();
289
290 if (is_rlog && !supported_request ("rlog"))
291 error (1, 0, "server does not support rlog");
292
293 ign_setup ();
294
295 if (log_data.default_branch)
296 send_arg ("-b");
297
298 while (log_data.datelist != NULL((void*)0))
299 {
300 p = log_data.datelist;
301 log_data.datelist = p->next;
302 send_to_server ("Argument -d\012", 0);
303 send_to_server ("Argument ", 0);
304 date_to_internet (datetmp, p->start);
305 send_to_server (datetmp, 0);
306 if (p->inclusive)
307 send_to_server ("<=", 0);
308 else
309 send_to_server ("<", 0);
310 date_to_internet (datetmp, p->end);
311 send_to_server (datetmp, 0);
312 send_to_server ("\012", 0);
313 if (p->start)
314 free (p->start);
315 if (p->end)
316 free (p->end);
317 free (p);
318 }
319 while (log_data.singledatelist != NULL((void*)0))
320 {
321 p = log_data.singledatelist;
322 log_data.singledatelist = p->next;
323 send_to_server ("Argument -d\012", 0);
324 send_to_server ("Argument ", 0);
325 date_to_internet (datetmp, p->end);
326 send_to_server (datetmp, 0);
327 send_to_server ("\012", 0);
328 if (p->end)
329 free (p->end);
330 free (p);
331 }
332
333 if (log_data.header)
334 send_arg ("-h");
335 if (local)
336 send_arg("-l");
337 if (log_data.notags)
338 send_arg("-N");
339 if (log_data.nameonly)
340 send_arg("-R");
341 if (log_data.long_header)
342 send_arg("-t");
343
344 while (log_data.revlist != NULL((void*)0))
345 {
346 rp = log_data.revlist;
347 log_data.revlist = rp->next;
348 send_to_server ("Argument -r", 0);
349 if (rp->branchhead)
350 {
351 if (rp->first != NULL((void*)0))
352 send_to_server (rp->first, 0);
353 send_to_server (".", 1);
354 }
355 else
356 {
357 if (rp->first != NULL((void*)0))
358 send_to_server (rp->first, 0);
359 send_to_server (":", 1);
360 if (!rp->inclusive)
361 send_to_server (":", 1);
362 if (rp->last != NULL((void*)0))
363 send_to_server (rp->last, 0);
364 }
365 send_to_server ("\012", 0);
366 if (rp->first)
367 free (rp->first);
368 if (rp->last)
369 free (rp->last);
370 free (rp);
371 }
372 send_arg_list ("-s", log_data.statelist);
373 dellist (&log_data.statelist);
374 send_arg_list ("-w", log_data.authorlist);
375 dellist (&log_data.authorlist);
376
377 if (is_rlog)
378 {
379 int i;
380 for (i = 0; i < argc; i++)
381 send_arg (argv[i]);
382 send_to_server ("rlog\012", 0);
383 }
384 else
385 {
386 send_files (argc, argv, local, 0, SEND_NO_CONTENTS4);
387 send_file_names (argc, argv, SEND_EXPAND_WILD1);
388 send_to_server ("log\012", 0);
389 }
390 err = get_responses_and_close ();
391 return err;
392 }
393#endif
394
395 /* OK, now that we know we are local/server, we can resolve @@MYSELF
396 into our user name. */
397 if (findnode (log_data.authorlist, "@@MYSELF") != NULL((void*)0))
398 log_parse_list (&log_data.authorlist, getcaller ());
399
400 if (is_rlog)
401 {
402 DBM *db;
403 int i;
404 db = open_module ();
405 for (i = 0; i < argc; i++)
406 {
407 err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
408 (char *) NULL((void*)0), 0, 0, 0, 0, (char *) NULL((void*)0));
409 }
410 close_module (db);
411 }
412 else
413 {
414 err = rlog_proc (argc + 1, argv - 1, (char *) NULL((void*)0),
415 (char *) NULL((void*)0), (char *) NULL((void*)0), 0, 0, (char *) NULL((void*)0),
416 (char *) NULL((void*)0));
417 }
418
419 while (log_data.revlist)
420 {
421 struct option_revlist *rl = log_data.revlist->next;
422 if (log_data.revlist->first)
423 free (log_data.revlist->first);
424 if (log_data.revlist->last)
425 free (log_data.revlist->last);
426 free (log_data.revlist);
427 log_data.revlist = rl;
428 }
429 while (log_data.datelist)
430 {
431 struct datelist *nd = log_data.datelist->next;
432 if (log_data.datelist->start)
433 free (log_data.datelist->start);
434 if (log_data.datelist->end)
435 free (log_data.datelist->end);
436 free (log_data.datelist);
437 log_data.datelist = nd;
438 }
439 while (log_data.singledatelist)
440 {
441 struct datelist *nd = log_data.singledatelist->next;
442 if (log_data.singledatelist->start)
443 free (log_data.singledatelist->start);
444 if (log_data.singledatelist->end)
445 free (log_data.singledatelist->end);
446 free (log_data.singledatelist);
447 log_data.singledatelist = nd;
448 }
449 dellist (&log_data.statelist);
450 dellist (&log_data.authorlist);
451
452 return (err);
453}
454
455
456static int
457rlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
458 int argc;
459 char **argv;
460 char *xwhere;
461 char *mwhere;
462 char *mfile;
463 int shorten;
464 int local;
465 char *mname;
466 char *msg;
467{
468 /* Begin section which is identical to patch_proc--should this
469 be abstracted out somehow? */
470 char *myargv[2];
471 int err = 0;
472 int which;
473 char *repository;
474 char *where;
475
476 if (is_rlog)
477 {
478 repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
479 + (mfile == NULL((void*)0) ? 0 : strlen (mfile) + 1) + 2);
480 (void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
481 where = xmalloc (strlen (argv[0]) + (mfile == NULL((void*)0) ? 0 : strlen (mfile) + 1)
482 + 1);
483 (void) strcpy (where, argv[0]);
484
485 /* if mfile isn't null, we need to set up to do only part of the module */
486 if (mfile != NULL((void*)0))
487 {
488 char *cp;
489 char *path;
490
491 /* if the portion of the module is a path, put the dir part on repos */
492 if ((cp = strrchr (mfile, '/')) != NULL((void*)0))
493 {
494 *cp = '\0';
495 (void) strcat (repository, "/");
496 (void) strcat (repository, mfile);
497 (void) strcat (where, "/");
498 (void) strcat (where, mfile);
499 mfile = cp + 1;
500 }
501
502 /* take care of the rest */
503 path = xmalloc (strlen (repository) + strlen (mfile) + 5);
504 (void) sprintf (path, "%s/%s", repository, mfile);
505 if (isdir (path))
506 {
507 /* directory means repository gets the dir tacked on */
508 (void) strcpy (repository, path);
509 (void) strcat (where, "/");
510 (void) strcat (where, mfile);
511 }
512 else
513 {
514 myargv[0] = argv[0];
515 myargv[1] = mfile;
516 argc = 2;
517 argv = myargv;
518 }
519 free (path);
520 }
521
522 /* cd to the starting repository */
523 if ( CVS_CHDIRchdir (repository) < 0)
524 {
525 error (0, errno(*__errno()), "cannot chdir to %s", repository);
526 free (repository);
527 return (1);
528 }
529 free (repository);
530 /* End section which is identical to patch_proc. */
531
532 which = W_REPOS0x02 | W_ATTIC0x04;
533 }
534 else
535 {
536 where = NULL((void*)0);
537 which = W_LOCAL0x01 | W_REPOS0x02 | W_ATTIC0x04;
538 }
539
540 err = start_recursion (log_fileproc, (FILESDONEPROC) NULL((void*)0), log_dirproc,
541 (DIRLEAVEPROC) NULL((void*)0), (void *) &log_data,
542 argc - 1, argv + 1, local, which, 0, 1,
543 where, 1);
544 return err;
545}
546
547
548/*
549 * Parse a revision list specification.
550 */
551
552static struct option_revlist *
553log_parse_revlist (argstring)
554 const char *argstring;
555{
556 char *orig_copy, *copy;
557 struct option_revlist *ret, **pr;
558
559 /* Unfortunately, rlog accepts -r without an argument to mean that
560 latest revision on the default branch, so we must support that
561 for compatibility. */
562 if (argstring == NULL((void*)0))
563 argstring = "";
564
565 ret = NULL((void*)0);
566 pr = &ret;
567
568 /* Copy the argument into memory so that we can change it. We
569 don't want to change the argument because, at least as of this
570 writing, we will use it if we send the arguments to the server. */
571 orig_copy = copy = xstrdup (argstring);
572 while (copy != NULL((void*)0))
573 {
574 char *comma;
575 struct option_revlist *r;
576
577 comma = strchr (copy, ',');
578 if (comma != NULL((void*)0))
579 *comma++ = '\0';
580
581 r = (struct option_revlist *) xmalloc (sizeof *r);
582 r->next = NULL((void*)0);
583 r->first = copy;
584 r->branchhead = 0;
585 r->last = strchr (copy, ':');
586 if (r->last != NULL((void*)0))
587 {
588 *r->last++ = '\0';
589 r->inclusive = (*r->last != ':');
590 if (!r->inclusive)
591 r->last++;
592 }
593 else
594 {
595 r->last = r->first;
596 r->inclusive = 1;
597 if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
598 {
599 r->branchhead = 1;
600 r->first[strlen (r->first) - 1] = '\0';
601 }
602 }
603
604 if (*r->first == '\0')
605 r->first = NULL((void*)0);
606 if (*r->last == '\0')
607 r->last = NULL((void*)0);
608
609 if (r->first != NULL((void*)0))
610 r->first = xstrdup (r->first);
611 if (r->last != NULL((void*)0))
612 r->last = xstrdup (r->last);
613
614 *pr = r;
615 pr = &r->next;
616
617 copy = comma;
618 }
619
620 free (orig_copy);
621 return ret;
622}
623
624/*
625 * Parse a date specification.
626 */
627static void
628log_parse_date (log_data, argstring)
629 struct log_data *log_data;
630 const char *argstring;
631{
632 char *orig_copy, *copy;
633
634 /* Copy the argument into memory so that we can change it. We
635 don't want to change the argument because, at least as of this
636 writing, we will use it if we send the arguments to the server. */
637 orig_copy = copy = xstrdup (argstring);
638 while (copy != NULL((void*)0))
639 {
640 struct datelist *nd, **pd;
641 char *cpend, *cp, *ds, *de;
642
643 nd = (struct datelist *) xmalloc (sizeof *nd);
644
645 cpend = strchr (copy, ';');
646 if (cpend != NULL((void*)0))
647 *cpend++ = '\0';
648
649 pd = &log_data->datelist;
650 nd->inclusive = 0;
651
652 if ((cp = strchr (copy, '>')) != NULL((void*)0))
653 {
654 *cp++ = '\0';
655 if (*cp == '=')
656 {
657 ++cp;
658 nd->inclusive = 1;
659 }
660 ds = cp;
661 de = copy;
662 }
663 else if ((cp = strchr (copy, '<')) != NULL((void*)0))
664 {
665 *cp++ = '\0';
666 if (*cp == '=')
667 {
668 ++cp;
669 nd->inclusive = 1;
670 }
671 ds = copy;
672 de = cp;
673 }
674 else
675 {
676 ds = NULL((void*)0);
677 de = copy;
678 pd = &log_data->singledatelist;
679 }
680
681 if (ds == NULL((void*)0))
682 nd->start = NULL((void*)0);
683 else if (*ds != '\0')
684 nd->start = Make_Date (ds);
685 else
686 {
687 /* 1970 was the beginning of time, as far as get_date and
688 Make_Date are concerned. FIXME: That is true only if time_t
689 is a POSIX-style time and there is nothing in ANSI that
690 mandates that. It would be cleaner to set a flag saying
691 whether or not there is a start date. */
692 nd->start = Make_Date ("1/1/1970 UTC");
693 }
694
695 if (*de != '\0')
696 nd->end = Make_Date (de);
697 else
698 {
699 /* We want to set the end date to some time sufficiently far
700 in the future to pick up all revisions that have been
701 created since the specified date and the time `cvs log'
702 completes. FIXME: The date in question only makes sense
703 if time_t is a POSIX-style time and it is 32 bits
704 and signed. We should instead be setting a flag saying
705 whether or not there is an end date. Note that using
706 something like "next week" would break the testsuite (and,
707 perhaps less importantly, loses if the clock is set grossly
708 wrong). */
709 nd->end = Make_Date ("2038-01-01");
710 }
711
712 nd->next = *pd;
713 *pd = nd;
714
715 copy = cpend;
716 }
717
718 free (orig_copy);
719}
720
721/*
722 * Parse a comma separated list of items, and add each one to *PLIST.
723 */
724static void
725log_parse_list (plist, argstring)
726 List **plist;
727 const char *argstring;
728{
729 while (1)
730 {
731 Node *p;
732 char *cp;
733
734 p = getnode ();
735
736 cp = strchr (argstring, ',');
737 if (cp == NULL((void*)0))
738 p->key = xstrdup (argstring);
739 else
740 {
741 size_t len;
742
743 len = cp - argstring;
744 p->key = xmalloc (len + 1);
745 strncpy (p->key, argstring, len);
746 p->key[len + 1] = '\0';
747 }
748
749 if (*plist == NULL((void*)0))
750 *plist = getlist ();
751 if (addnode (*plist, p) != 0)
752 freenode (p);
753
754 if (cp == NULL((void*)0))
755 break;
756
757 argstring = cp + 1;
758 }
759}
760
761static int printlock_proc PROTO ((Node *, void *))(Node *, void *);
762
763static int
764printlock_proc (lock, foo)
765 Node *lock;
766 void *foo;
767{
768 cvs_output ("\n\t", 2);
769 cvs_output (lock->data, 0);
770 cvs_output (": ", 2);
771 cvs_output (lock->key, 0);
772 return 0;
773}
774
775/*
776 * Do an rlog on a file
777 */
778static int
779log_fileproc (callerdat, finfo)
780 void *callerdat;
781 struct file_info *finfo;
782{
783 struct log_data *log_data = (struct log_data *) callerdat;
784 Node *p;
785 RCSNode *rcsfile;
786 char buf[50];
787 struct revlist *revlist;
788 struct log_data_and_rcs log_data_and_rcs;
789
790 if ((rcsfile = finfo->rcs) == NULL((void*)0))
791 {
792 /* no rcs file. What *do* we know about this file? */
793 p = findnode (finfo->entries, finfo->file);
794 if (p != NULL((void*)0))
795 {
796 Entnode *e;
797
798 e = (Entnode *) p->data;
799 if (e->version[0] == '0' && e->version[1] == '\0')
800 {
801 if (!really_quiet)
802 error (0, 0, "%s has been added, but not committed",
803 finfo->file);
804 return(0);
805 }
806 }
807
808 if (!really_quiet)
809 error (0, 0, "nothing known about %s", finfo->file);
810
811 return (1);
812 }
813
814 if (log_data->nameonly)
815 {
816 cvs_output (rcsfile->path, 0);
817 cvs_output ("\n", 1);
818 return 0;
819 }
820
821 /* We will need all the information in the RCS file. */
822 RCS_fully_parse (rcsfile);
823
824 /* Turn any symbolic revisions in the revision list into numeric
825 revisions. */
826 revlist = log_expand_revlist (rcsfile, log_data->revlist,
827 log_data->default_branch);
828
829 /* The output here is intended to be exactly compatible with the
830 output of rlog. I'm not sure whether this code should be here
831 or in rcs.c; I put it here because it is specific to the log
832 function, even though it uses information gathered by the
833 functions in rcs.c. */
834
835 cvs_output ("\n", 1);
836
837 cvs_output ("RCS file: ", 0);
838 cvs_output (rcsfile->path, 0);
839
840 if (!is_rlog)
841 {
842 cvs_output ("\nWorking file: ", 0);
843 if (finfo->update_dir[0] != '\0')
844 {
845 cvs_output (finfo->update_dir, 0);
846 cvs_output ("/", 0);
847 }
848 cvs_output (finfo->file, 0);
849 }
850
851 cvs_output ("\nhead:", 0);
852 if (rcsfile->head != NULL((void*)0))
853 {
854 cvs_output (" ", 1);
855 cvs_output (rcsfile->head, 0);
856 }
857
858 cvs_output ("\nbranch:", 0);
859 if (rcsfile->branch != NULL((void*)0))
860 {
861 cvs_output (" ", 1);
862 cvs_output (rcsfile->branch, 0);
863 }
864
865 cvs_output ("\nlocks:", 0);
866 if (rcsfile->strict_locks)
867 cvs_output (" strict", 0);
868 walklist (RCS_getlocks (rcsfile), printlock_proc, NULL((void*)0));
869
870 cvs_output ("\naccess list:", 0);
871 if (rcsfile->access != NULL((void*)0))
872 {
873 const char *cp;
874
875 cp = rcsfile->access;
876 while (*cp != '\0')
877 {
878 const char *cp2;
879
880 cvs_output ("\n\t", 2);
881 cp2 = cp;
882 while (! isspace ((unsigned char) *cp2) && *cp2 != '\0')
883 ++cp2;
884 cvs_output (cp, cp2 - cp);
885 cp = cp2;
886 while (isspace ((unsigned char) *cp) && *cp != '\0')
887 ++cp;
888 }
889 }
890
891 if (! log_data->notags)
892 {
893 List *syms;
894
895 cvs_output ("\nsymbolic names:", 0);
896 syms = RCS_symbols (rcsfile);
897 walklist (syms, log_symbol, NULL((void*)0));
898 }
899
900 cvs_output ("\nkeyword substitution: ", 0);
901 if (rcsfile->expand == NULL((void*)0))
902 cvs_output ("kv", 2);
903 else
904 cvs_output (rcsfile->expand, 0);
905
906 cvs_output ("\ntotal revisions: ", 0);
907 sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL((void*)0)));
908 cvs_output (buf, 0);
909
910 if (! log_data->header && ! log_data->long_header)
911 {
912 cvs_output (";\tselected revisions: ", 0);
913
914 log_data_and_rcs.log_data = log_data;
915 log_data_and_rcs.revlist = revlist;
916 log_data_and_rcs.rcs = rcsfile;
917
918 /* If any single dates were specified, we need to identify the
919 revisions they select. Each one selects the single
920 revision, which is otherwise selected, of that date or
921 earlier. The log_fix_singledate routine will fill in the
922 start date for each specific revision. */
923 if (log_data->singledatelist != NULL((void*)0))
924 walklist (rcsfile->versions, log_fix_singledate,
925 (void *) &log_data_and_rcs);
926
927 sprintf (buf, "%d", walklist (rcsfile->versions, log_count_print,
928 (void *) &log_data_and_rcs));
929 cvs_output (buf, 0);
930 }
931
932 cvs_output ("\n", 1);
933
934 if (! log_data->header || log_data->long_header)
935 {
936 cvs_output ("description:\n", 0);
937 if (rcsfile->desc != NULL((void*)0))
938 cvs_output (rcsfile->desc, 0);
939 }
940
941 if (! log_data->header && ! log_data->long_header && rcsfile->head != NULL((void*)0))
942 {
943 p = findnode (rcsfile->versions, rcsfile->head);
944 if (p == NULL((void*)0))
945 error (1, 0, "can not find head revision in `%s'",
946 finfo->fullname);
947 while (p != NULL((void*)0))
948 {
949 RCSVers *vers;
950
951 vers = (RCSVers *) p->data;
952 log_version (log_data, revlist, rcsfile, vers, 1);
953 if (vers->next == NULL((void*)0))
954 p = NULL((void*)0);
955 else
956 {
957 p = findnode (rcsfile->versions, vers->next);
958 if (p == NULL((void*)0))
959 error (1, 0, "can not find next revision `%s' in `%s'",
960 vers->next, finfo->fullname);
961 }
962 }
963
964 log_tree (log_data, revlist, rcsfile, rcsfile->head);
965 }
966
967 cvs_output("\
968=============================================================================\n",
969 0);
970
971 /* Free up the new revlist and restore the old one. */
972 log_free_revlist (revlist);
973
974 /* If singledatelist is not NULL, free up the start dates we added
975 to it. */
976 if (log_data->singledatelist != NULL((void*)0))
977 {
978 struct datelist *d;
979
980 for (d = log_data->singledatelist; d != NULL((void*)0); d = d->next)
981 {
982 if (d->start != NULL((void*)0))
983 free (d->start);
984 d->start = NULL((void*)0);
985 }
986 }
987
988 return 0;
989}
990
991/*
992 * Fix up a revision list in order to compare it against versions.
993 * Expand any symbolic revisions.
994 */
995static struct revlist *
996log_expand_revlist (rcs, revlist, default_branch)
997 RCSNode *rcs;
998 struct option_revlist *revlist;
999 int default_branch;
1000{
1001 struct option_revlist *r;
1002 struct revlist *ret, **pr;
1003
1004 ret = NULL((void*)0);
1005 pr = &ret;
1006 for (r = revlist; r != NULL((void*)0); r = r->next)
1007 {
1008 struct revlist *nr;
1009
1010 nr = (struct revlist *) xmalloc (sizeof *nr);
1011 nr->inclusive = r->inclusive;
1012
1013 if (r->first == NULL((void*)0) && r->last == NULL((void*)0))
1014 {
1015 /* If both first and last are NULL, it means that we want
1016 just the head of the default branch, which is RCS_head. */
1017 nr->first = RCS_head (rcs);
1018 nr->last = xstrdup (nr->first);
1019 nr->fields = numdots (nr->first) + 1;
1020 }
1021 else if (r->branchhead)
1022 {
1023 char *branch;
1024
1025 /* Print just the head of the branch. */
1026 if (isdigit ((unsigned char) r->first[0]))
1027 nr->first = RCS_getbranch (rcs, r->first, 1);
1028 else
1029 {
1030 branch = RCS_whatbranch (rcs, r->first);
1031 if (branch == NULL((void*)0))
1032 {
1033 error (0, 0, "warning: `%s' is not a branch in `%s'",
1034 r->first, rcs->path);
1035 free (nr);
1036 continue;
1037 }
1038 nr->first = RCS_getbranch (rcs, branch, 1);
1039 free (branch);
1040 }
1041 if (nr->first == NULL((void*)0))
1042 {
1043 error (0, 0, "warning: no revision `%s' in `%s'",
1044 r->first, rcs->path);
1045 free (nr);
1046 continue;
1047 }
1048 nr->last = xstrdup (nr->first);
1049 nr->fields = numdots (nr->first) + 1;
1050 }
1051 else
1052 {
1053 if (r->first == NULL((void*)0) || isdigit ((unsigned char) r->first[0]))
1054 nr->first = xstrdup (r->first);
1055 else
1056 {
1057 if (RCS_nodeisbranch (rcs, r->first))
1058 nr->first = RCS_whatbranch (rcs, r->first);
1059 else
1060 nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL((void*)0));
1061 if (nr->first == NULL((void*)0))
1062 {
1063 error (0, 0, "warning: no revision `%s' in `%s'",
1064 r->first, rcs->path);
1065 free (nr);
1066 continue;
1067 }
1068 }
1069
1070 if (r->last == r->first)
1071 nr->last = xstrdup (nr->first);
1072 else if (r->last == NULL((void*)0) || isdigit ((unsigned char) r->last[0]))
1073 nr->last = xstrdup (r->last);
1074 else
1075 {
1076 if (RCS_nodeisbranch (rcs, r->last))
1077 nr->last = RCS_whatbranch (rcs, r->last);
1078 else
1079 nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL((void*)0));
1080 if (nr->last == NULL((void*)0))
1081 {
1082 error (0, 0, "warning: no revision `%s' in `%s'",
1083 r->last, rcs->path);
1084 if (nr->first != NULL((void*)0))
1085 free (nr->first);
1086 free (nr);
1087 continue;
1088 }
1089 }
1090
1091 /* Process the revision numbers the same way that rlog
1092 does. This code is a bit cryptic for my tastes, but
1093 keeping the same implementation as rlog ensures a
1094 certain degree of compatibility. */
1095 if (r->first == NULL((void*)0))
1096 {
1097 nr->fields = numdots (nr->last) + 1;
1098 if (nr->fields < 2)
1099 nr->first = xstrdup (".0");
1100 else
1101 {
1102 char *cp;
1103
1104 nr->first = xstrdup (nr->last);
1105 cp = strrchr (nr->first, '.');
1106 strcpy (cp, ".0");
1107 }
1108 }
1109 else if (r->last == NULL((void*)0))
1110 {
1111 nr->fields = numdots (nr->first) + 1;
1112 nr->last = xstrdup (nr->first);
1113 if (nr->fields < 2)
1114 nr->last[0] = '\0';
1115 else
1116 {
1117 char *cp;
1118
1119 cp = strrchr (nr->last, '.');
1120 *cp = '\0';
1121 }
1122 }
1123 else
1124 {
1125 nr->fields = numdots (nr->first) + 1;
1126 if (nr->fields != numdots (nr->last) + 1
1127 || (nr->fields > 2
1128 && version_compare (nr->first, nr->last,
1129 nr->fields - 1) != 0))
1130 {
1131 error (0, 0,
1132 "invalid branch or revision pair %s:%s in `%s'",
1133 r->first, r->last, rcs->path);
1134 free (nr->first);
1135 free (nr->last);
1136 free (nr);
1137 continue;
1138 }
1139 if (version_compare (nr->first, nr->last, nr->fields) > 0)
1140 {
1141 char *tmp;
1142
1143 tmp = nr->first;
1144 nr->first = nr->last;
1145 nr->last = tmp;
1146 }
1147 }
1148 }
1149
1150 nr->next = NULL((void*)0);
1151 *pr = nr;
1152 pr = &nr->next;
1153 }
1154
1155 /* If the default branch was requested, add a revlist entry for
1156 it. This is how rlog handles this option. */
1157 if (default_branch
1158 && (rcs->head != NULL((void*)0) || rcs->branch != NULL((void*)0)))
1159 {
1160 struct revlist *nr;
1161
1162 nr = (struct revlist *) xmalloc (sizeof *nr);
1163 if (rcs->branch != NULL((void*)0))
1164 nr->first = xstrdup (rcs->branch);
1165 else
1166 {
1167 char *cp;
1168
1169 nr->first = xstrdup (rcs->head);
1170 cp = strrchr (nr->first, '.');
1171 *cp = '\0';
1172 }
1173 nr->last = xstrdup (nr->first);
1174 nr->fields = numdots (nr->first) + 1;
1175 nr->inclusive = 1;
1176
1177 nr->next = NULL((void*)0);
1178 *pr = nr;
1179 }
1180
1181 return ret;
1182}
1183
1184/*
1185 * Free a revlist created by log_expand_revlist.
1186 */
1187static void
1188log_free_revlist (revlist)
1189 struct revlist *revlist;
1190{
1191 struct revlist *r;
1192
1193 r = revlist;
1194 while (r != NULL((void*)0))
1195 {
1196 struct revlist *next;
1197
1198 if (r->first != NULL((void*)0))
1199 free (r->first);
1200 if (r->last != NULL((void*)0))
1201 free (r->last);
1202 next = r->next;
1203 free (r);
1204 r = next;
1205 }
1206}
1207
1208/*
1209 * Return nonzero if a revision should be printed, based on the
1210 * options provided.
1211 */
1212static int
1213log_version_requested (log_data, revlist, rcs, vnode)
1214 struct log_data *log_data;
1215 struct revlist *revlist;
1216 RCSNode *rcs;
1217 RCSVers *vnode;
1218{
1219 /* Handle the list of states from the -s option. */
1220 if (log_data->statelist != NULL((void*)0)
1221 && findnode (log_data->statelist, vnode->state) == NULL((void*)0))
1222 {
1223 return 0;
1224 }
1225
1226 /* Handle the list of authors from the -w option. */
1227 if (log_data->authorlist != NULL((void*)0))
1228 {
1229 if (vnode->author != NULL((void*)0)
1230 && findnode (log_data->authorlist, vnode->author) == NULL((void*)0))
1231 {
1232 return 0;
1233 }
1234 }
1235
1236 /* rlog considers all the -d options together when it decides
1237 whether to print a revision, so we must be compatible. */
1238 if (log_data->datelist != NULL((void*)0) || log_data->singledatelist != NULL((void*)0))
1239 {
1240 struct datelist *d;
1241
1242 for (d = log_data->datelist; d != NULL((void*)0); d = d->next)
1243 {
1244 int cmp;
1245
1246 cmp = RCS_datecmp (vnode->date, d->start);
1247 if (cmp > 0 || (cmp == 0 && d->inclusive))
1248 {
1249 cmp = RCS_datecmp (vnode->date, d->end);
1250 if (cmp < 0 || (cmp == 0 && d->inclusive))
1251 break;
1252 }
1253 }
1254
1255 if (d == NULL((void*)0))
1256 {
1257 /* Look through the list of specific dates. We want to
1258 select the revision with the exact date found in the
1259 start field. The commit code ensures that it is
1260 impossible to check in multiple revisions of a single
1261 file in a single second, so checking the date this way
1262 should never select more than one revision. */
1263 for (d = log_data->singledatelist; d != NULL((void*)0); d = d->next)
1264 {
1265 if (d->start != NULL((void*)0)
1266 && RCS_datecmp (vnode->date, d->start) == 0)
1267 {
1268 break;
1269 }
1270 }
1271
1272 if (d == NULL((void*)0))
1273 return 0;
1274 }
1275 }
1276
1277 /* If the -r or -b options were used, REVLIST will be non NULL,
1278 and we print the union of the specified revisions. */
1279 if (revlist != NULL((void*)0))
1280 {
1281 char *v;
1282 int vfields;
1283 struct revlist *r;
1284
1285 /* This code is taken from rlog. */
1286 v = vnode->version;
1287 vfields = numdots (v) + 1;
1288 for (r = revlist; r != NULL((void*)0); r = r->next)
1289 {
1290 if (vfields == r->fields + (r->fields & 1) &&
1291 (r->inclusive ?
1292 version_compare (v, r->first, r->fields) >= 0
1293 && version_compare (v, r->last, r->fields) <= 0 :
1294 version_compare (v, r->first, r->fields) > 0
1295 && version_compare (v, r->last, r->fields) < 0))
1296 {
1297 return 1;
1298 }
1299 }
1300
1301 /* If we get here, then the -b and/or the -r option was used,
1302 but did not match this revision, so we reject it. */
1303
1304 return 0;
1305 }
1306
1307 /* By default, we print all revisions. */
1308 return 1;
1309}
1310
1311/*
1312 * Output a single symbol. This is called via walklist.
1313 */
1314/*ARGSUSED*/
1315static int
1316log_symbol (p, closure)
1317 Node *p;
1318 void *closure;
1319{
1320 cvs_output ("\n\t", 2);
1321 cvs_output (p->key, 0);
1322 cvs_output (": ", 2);
1323 cvs_output (p->data, 0);
1324 return 0;
1325}
1326
1327/*
1328 * Count the number of entries on a list. This is called via walklist.
1329 */
1330/*ARGSUSED*/
1331static int
1332log_count (p, closure)
1333 Node *p;
1334 void *closure;
1335{
1336 return 1;
1337}
1338
1339/*
1340 * Sort out a single date specification by narrowing down the date
1341 * until we find the specific selected revision.
1342 */
1343static int
1344log_fix_singledate (p, closure)
1345 Node *p;
1346 void *closure;
1347{
1348 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
1349 Node *pv;
1350 RCSVers *vnode;
1351 struct datelist *holdsingle, *holddate;
1352 int requested;
1353
1354 pv = findnode (data->rcs->versions, p->key);
1355 if (pv == NULL((void*)0))
1356 error (1, 0, "missing version `%s' in RCS file `%s'",
1357 p->key, data->rcs->path);
1358 vnode = (RCSVers *) pv->data;
1359
1360 /* We are only interested if this revision passes any other tests.
1361 Temporarily clear log_data->singledatelist to avoid confusing
1362 log_version_requested. We also clear log_data->datelist,
1363 because rlog considers all the -d options together. We don't
1364 want to reject a revision because it does not match a date pair
1365 if we are going to select it on the basis of the singledate. */
1366 holdsingle = data->log_data->singledatelist;
1367 data->log_data->singledatelist = NULL((void*)0);
1368 holddate = data->log_data->datelist;
1369 data->log_data->datelist = NULL((void*)0);
1370 requested = log_version_requested (data->log_data, data->revlist,
1371 data->rcs, vnode);
1372 data->log_data->singledatelist = holdsingle;
1373 data->log_data->datelist = holddate;
1374
1375 if (requested)
1376 {
1377 struct datelist *d;
1378
1379 /* For each single date, if this revision is before the
1380 specified date, but is closer than the previously selected
1381 revision, select it instead. */
1382 for (d = data->log_data->singledatelist; d != NULL((void*)0); d = d->next)
1383 {
1384 if (RCS_datecmp (vnode->date, d->end) <= 0
1385 && (d->start == NULL((void*)0)
1386 || RCS_datecmp (vnode->date, d->start) > 0))
1387 {
1388 if (d->start != NULL((void*)0))
1389 free (d->start);
1390 d->start = xstrdup (vnode->date);
1391 }
1392 }
1393 }
1394
1395 return 0;
1396}
1397
1398/*
1399 * Count the number of revisions we are going to print.
1400 */
1401static int
1402log_count_print (p, closure)
1403 Node *p;
1404 void *closure;
1405{
1406 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
1407 Node *pv;
1408
1409 pv = findnode (data->rcs->versions, p->key);
1410 if (pv == NULL((void*)0))
1411 error (1, 0, "missing version `%s' in RCS file `%s'",
1412 p->key, data->rcs->path);
1413 if (log_version_requested (data->log_data, data->revlist, data->rcs,
1414 (RCSVers *) pv->data))
1415 return 1;
1416 else
1417 return 0;
1418}
1419
1420/*
1421 * Print the list of changes, not including the trunk, in reverse
1422 * order for each branch.
1423 */
1424static void
1425log_tree (log_data, revlist, rcs, ver)
1426 struct log_data *log_data;
1427 struct revlist *revlist;
1428 RCSNode *rcs;
1429 const char *ver;
1430{
1431 Node *p;
1432 RCSVers *vnode;
1433
1434 p = findnode (rcs->versions, ver);
1435 if (p == NULL((void*)0))
1436 error (1, 0, "missing version `%s' in RCS file `%s'",
1437 ver, rcs->path);
1438 vnode = (RCSVers *) p->data;
1439 if (vnode->next != NULL((void*)0))
1440 log_tree (log_data, revlist, rcs, vnode->next);
1441 if (vnode->branches != NULL((void*)0))
1442 {
1443 Node *head, *branch;
1444
1445 /* We need to do the branches in reverse order. This breaks
1446 the List abstraction, but so does most of the branch
1447 manipulation in rcs.c. */
1448 head = vnode->branches->list;
1449 for (branch = head->prev; branch != head; branch = branch->prev)
1450 {
1451 log_abranch (log_data, revlist, rcs, branch->key);
1452 log_tree (log_data, revlist, rcs, branch->key);
1453 }
1454 }
1455}
1456
1457/*
1458 * Log the changes for a branch, in reverse order.
1459 */
1460static void
1461log_abranch (log_data, revlist, rcs, ver)
1462 struct log_data *log_data;
1463 struct revlist *revlist;
1464 RCSNode *rcs;
1465 const char *ver;
1466{
1467 Node *p;
1468 RCSVers *vnode;
1469
1470 p = findnode (rcs->versions, ver);
1471 if (p == NULL((void*)0))
1472 error (1, 0, "missing version `%s' in RCS file `%s'",
1473 ver, rcs->path);
1474 vnode = (RCSVers *) p->data;
1475 if (vnode->next != NULL((void*)0))
1476 log_abranch (log_data, revlist, rcs, vnode->next);
1477 log_version (log_data, revlist, rcs, vnode, 0);
1478}
1479
1480/*
1481 * Print the log output for a single version.
1482 */
1483static void
1484log_version (log_data, revlist, rcs, ver, trunk)
1485 struct log_data *log_data;
1486 struct revlist *revlist;
1487 RCSNode *rcs;
1488 RCSVers *ver;
1489 int trunk;
1490{
1491 Node *p;
1492 int year, mon, mday, hour, min, sec;
1493 char buf[100];
1494 Node *padd, *pdel;
1495
1496 if (! log_version_requested (log_data, revlist, rcs, ver))
1497 return;
1498
1499 cvs_output ("----------------------------\nrevision ", 0);
1500 cvs_output (ver->version, 0);
1501
1502 p = findnode (RCS_getlocks (rcs), ver->version);
1503 if (p != NULL((void*)0))
1504 {
1505 cvs_output ("\tlocked by: ", 0);
1506 cvs_output (p->data, 0);
1507 cvs_output (";", 1);
1508 }
1509
1510 cvs_output ("\ndate: ", 0);
1511 (void) sscanf (ver->date, SDATEFORM"%d.%d.%d.%d.%d.%d", &year, &mon, &mday, &hour, &min,
1512 &sec);
1513 if (year < 1900)
1514 year += 1900;
1515 sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
1516 hour, min, sec);
1517 cvs_output (buf, 0);
1518
1519 cvs_output ("; author: ", 0);
1520 cvs_output (ver->author, 0);
1521
1522 cvs_output ("; state: ", 0);
1523 cvs_output (ver->state, 0);
1524 cvs_output (";", 1);
1525
1526 if (! trunk)
1527 {
1528 padd = findnode (ver->other, ";add");
1529 pdel = findnode (ver->other, ";delete");
1530 }
1531 else if (ver->next == NULL((void*)0))
1532 {
1533 padd = NULL((void*)0);
1534 pdel = NULL((void*)0);
1535 }
1536 else
1537 {
1538 Node *nextp;
1539 RCSVers *nextver;
1540
1541 nextp = findnode (rcs->versions, ver->next);
1542 if (nextp == NULL((void*)0))
1543 error (1, 0, "missing version `%s' in `%s'", ver->next,
1544 rcs->path);
1545 nextver = (RCSVers *) nextp->data;
1546 pdel = findnode (nextver->other, ";add");
1547 padd = findnode (nextver->other, ";delete");
1548 }
1549
1550 if (padd != NULL((void*)0))
1551 {
1552 cvs_output (" lines: +", 0);
1553 cvs_output (padd->data, 0);
1554 cvs_output (" -", 2);
1555 cvs_output (pdel->data, 0);
1556 cvs_output (";", 1);
1557 }
1558
1559 p = findnode (ver->other_delta,"commitid");
1560 if (p != NULL((void*)0) && p->data)
1561 {
1562 cvs_output (" commitid: ", 12);
1563 cvs_output (p->data, 0);
1564 cvs_output (";", 1);
1565 }
1566
1567 if (ver->branches != NULL((void*)0))
1568 {
1569 cvs_output ("\nbranches:", 0);
1570 walklist (ver->branches, log_branch, (void *) NULL((void*)0));
1571 }
1572
1573 cvs_output ("\n", 1);
1574
1575 p = findnode (ver->other, "log");
1576 /* The p->date == NULL case is the normal one for an empty log
1577 message (rcs-14 in sanity.sh). I don't think the case where
1578 p->data is "" can happen (getrcskey in rcs.c checks for an
1579 empty string and set the value to NULL in that case). My guess
1580 would be the p == NULL case would mean an RCS file which was
1581 missing the "log" keyword (which is illegal according to
1582 rcsfile.5). */
1583 if (p == NULL((void*)0) || p->data == NULL((void*)0) || p->data[0] == '\0')
1584 cvs_output ("*** empty log message ***\n", 0);
1585 else
1586 {
1587 /* FIXME: Technically, the log message could contain a null
1588 byte. */
1589 cvs_output (p->data, 0);
1590 if (p->data[strlen (p->data) - 1] != '\n')
1591 cvs_output ("\n", 1);
1592 }
1593}
1594
1595/*
1596 * Output a branch version. This is called via walklist.
1597 */
1598/*ARGSUSED*/
1599static int
1600log_branch (p, closure)
1601 Node *p;
1602 void *closure;
1603{
1604 cvs_output (" ", 2);
1605 if ((numdots (p->key) & 1) == 0)
1606 cvs_output (p->key, 0);
1607 else
1608 {
1609 char *f, *cp;
1610
1611 f = xstrdup (p->key);
1612 cp = strrchr (f, '.');
1613 *cp = '\0';
1614 cvs_output (f, 0);
1615 free (f);
1616 }
1617 cvs_output (";", 1);
1618 return 0;
1619}
1620
1621/*
1622 * Print a warm fuzzy message
1623 */
1624/* ARGSUSED */
1625static Dtype
1626log_dirproc (callerdat, dir, repository, update_dir, entries)
1627 void *callerdat;
1628 char *dir;
1629 char *repository;
1630 char *update_dir;
1631 List *entries;
1632{
1633 if (!isdir (dir))
1634 return (R_SKIP_ALL);
1635
1636 if (!quiet)
1637 error (0, 0, "Logging %s", update_dir);
1638 return (R_PROCESS);
1639}
1640
1641/*
1642 * Compare versions. This is taken from RCS compartial.
1643 */
1644static int
1645version_compare (v1, v2, len)
1646 const char *v1;
1647 const char *v2;
1648 int len;
1649{
1650 while (1)
1651 {
1652 int d1, d2, r;
1653
1654 if (*v1 == '\0')
1655 return 1;
1656 if (*v2 == '\0')
1657 return -1;
1658
1659 while (*v1 == '0')
1660 ++v1;
1661 for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
1662 ;
1663
1664 while (*v2 == '0')
1665 ++v2;
1666 for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
1667 ;
1668
1669 if (d1 != d2)
1670 return d1 < d2 ? -1 : 1;
1671
1672 r = memcmp (v1, v2, d1);
1673 if (r != 0)
1674 return r;
1675
1676 --len;
1677 if (len == 0)
1678 return 0;
1679
1680 v1 += d1;
1681 v2 += d1;
1682
1683 if (*v1 == '.')
1684 ++v1;
1685 if (*v2 == '.')
1686 ++v2;
1687 }
1688}