2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
10 * You may distribute under the terms of the GNU General Public License as
11 * specified in the README file that comes with the CVS source distribution.
13 * Print Log Information
15 * Prints the RCS "log" (rlog) information for the specified files. With no
16 * argument, prints the log information for all the files in the directory
17 * (recursive by default).
23 /* This structure holds information parsed from the -r option. */
27 /* The next -r option. */
28 struct option_revlist
*next
;
29 /* The first revision to print. This is NULL if the range is
30 :rev, or if no revision is given. */
32 /* The last revision to print. This is NULL if the range is rev:,
33 or if no revision is given. If there is no colon, first and
36 /* Nonzero if there was a trailing `.', which means to print only
37 the head revision of a branch. */
39 /* Nonzero if first and last are inclusive. */
43 /* This structure holds information derived from option_revlist given
44 a particular RCS file. */
50 /* The first numeric revision to print. */
52 /* The last numeric revision to print. */
54 /* The number of fields in these revisions (one more than
57 /* Whether first & last are to be included or excluded. */
61 /* This structure holds information parsed from the -d option. */
66 struct datelist
*next
;
67 /* The starting date. */
69 /* The ending date. */
71 /* Nonzero if the range is inclusive rather than exclusive. */
75 /* This structure is used to pass information through start_recursion. */
78 /* Nonzero if the -R option was given, meaning that only the name
79 of the RCS file should be printed. */
81 /* Nonzero if the -h option was given, meaning that only header
82 information should be printed. */
84 /* Nonzero if the -t option was given, meaning that only the
85 header and the descriptive text should be printed. */
87 /* Nonzero if the -N option was seen, meaning that tag information
88 should not be printed. */
90 /* Nonzero if the -b option was seen, meaning that only revisions
91 on the default branch should be printed. */
93 /* Nonzero if the -S option was seen, meaning that the header/name
94 should be suppressed if no revisions are selected. */
96 /* If not NULL, the value given for the -r option, which lists
97 sets of revisions to be printed. */
98 struct option_revlist
*revlist
;
99 /* If not NULL, the date pairs given for the -d option, which
100 select date ranges to print. */
101 struct datelist
*datelist
;
102 /* If not NULL, the single dates given for the -d option, which
103 select specific revisions to print based on a date. */
104 struct datelist
*singledatelist
;
105 /* If not NULL, the list of states given for the -s option, which
106 only prints revisions of given states. */
108 /* If not NULL, the list of login names given for the -w option,
109 which only prints revisions checked in by given users. */
113 /* This structure is used to pass information through walklist. */
114 struct log_data_and_rcs
116 struct log_data
*log_data
;
117 struct revlist
*revlist
;
121 static int rlog_proc (int argc
, char **argv
, char *xwhere
,
122 char *mwhere
, char *mfile
, int shorten
,
123 int local_specified
, char *mname
, char *msg
);
124 static Dtype
log_dirproc (void *callerdat
, const char *dir
,
125 const char *repository
, const char *update_dir
,
127 static int log_fileproc (void *callerdat
, struct file_info
*finfo
);
128 static struct option_revlist
*log_parse_revlist (const char *);
129 static void log_parse_date (struct log_data
*, const char *);
130 static void log_parse_list (List
**, const char *);
131 static struct revlist
*log_expand_revlist (RCSNode
*, char *,
132 struct option_revlist
*, int);
133 static void log_free_revlist (struct revlist
*);
134 static int log_version_requested (struct log_data
*, struct revlist
*,
135 RCSNode
*, RCSVers
*);
136 static int log_symbol (Node
*, void *);
137 static int log_count (Node
*, void *);
138 static int log_fix_singledate (Node
*, void *);
139 static int log_count_print (Node
*, void *);
140 static void log_tree (struct log_data
*, struct revlist
*,
141 RCSNode
*, const char *);
142 static void log_abranch (struct log_data
*, struct revlist
*,
143 RCSNode
*, const char *);
144 static void log_version (struct log_data
*, struct revlist
*,
145 RCSNode
*, RCSVers
*, int);
146 static int log_branch (Node
*, void *);
147 static int version_compare (const char *, const char *, int);
149 static struct log_data log_data
;
152 static const char *const log_usage
[] =
154 "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
155 " [-w[logins]] [files...]\n",
156 "\t-l\tLocal directory only, no recursion.\n",
157 "\t-b\tOnly list revisions on the default branch.\n",
158 "\t-h\tOnly print header.\n",
159 "\t-R\tOnly print name of RCS file.\n",
160 "\t-t\tOnly print header and descriptive text.\n",
161 "\t-N\tDo not list tags.\n",
162 "\t-S\tDo not print name/header if no revisions selected. -d, -r,\n",
163 "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
164 "\t\t-t without this option.\n",
165 "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
166 "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
167 "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n",
168 "\t rev: rev and following revisions on the same branch.\n",
169 "\t rev:: After rev on the same branch.\n",
170 "\t :rev rev and previous revisions on the same branch.\n",
171 "\t ::rev rev and previous revisions on the same branch.\n",
172 "\t rev Just rev.\n",
173 "\t branch All revisions on the branch.\n",
174 "\t branch. The last revision on the branch.\n",
175 "\t-d dates\tA semicolon-separated list of dates\n",
176 "\t \t(D1<D2 for range, D for latest before).\n",
177 "\t-s states\tOnly list revisions with specified states.\n",
178 "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
179 "(Specify the --help global option for a list of other help options)\n",
183 #ifdef CLIENT_SUPPORT
187 /* Helper function for send_arg_list. */
189 send_one (Node
*node
, void *closure
)
191 char *option
= closure
;
193 send_to_server ("Argument ", 0);
194 send_to_server (option
, 0);
195 if (strcmp (node
->key
, "@@MYSELF") == 0)
196 /* It is a bare -w option. Note that we must send it as
197 -w rather than messing with getcaller() or something (which on
198 the client will return garbage). */
201 send_to_server (node
->key
, 0);
202 send_to_server ("\012", 0);
208 /* For each element in ARG, send an argument consisting of OPTION
209 concatenated with that element. */
211 send_arg_list (char *option
, List
*arg
)
215 walklist (arg
, send_one
, option
);
223 cvslog (int argc
, char **argv
)
228 struct option_revlist
**prl
;
230 is_rlog
= (strcmp (cvs_cmd_name
, "rlog") == 0);
235 memset (&log_data
, 0, sizeof log_data
);
236 prl
= &log_data
.revlist
;
239 while ((c
= getopt (argc
, argv
, "+bd:hlNSRr::s:tw::")) != -1)
244 log_data
.default_branch
= 1;
247 log_parse_date (&log_data
, optarg
);
259 log_data
.sup_header
= 1;
262 log_data
.nameonly
= 1;
265 *prl
= log_parse_revlist (optarg
);
269 log_parse_list (&log_data
.statelist
, optarg
);
272 log_data
.long_header
= 1;
276 log_parse_list (&log_data
.authorlist
, optarg
);
278 log_parse_list (&log_data
.authorlist
, "@@MYSELF");
291 #ifdef CLIENT_SUPPORT
292 if (current_parsed_root
->isremote
)
295 struct option_revlist
*rp
;
296 char datetmp
[MAXDATELEN
];
298 /* We're the local client. Fire up the remote server. */
301 if (is_rlog
&& !supported_request ("rlog"))
302 error (1, 0, "server does not support rlog");
306 if (log_data
.default_branch
)
309 while (log_data
.datelist
!= NULL
)
311 p
= log_data
.datelist
;
312 log_data
.datelist
= p
->next
;
313 send_to_server ("Argument -d\012", 0);
314 send_to_server ("Argument ", 0);
315 date_to_internet (datetmp
, p
->start
);
316 send_to_server (datetmp
, 0);
318 send_to_server ("<=", 0);
320 send_to_server ("<", 0);
321 date_to_internet (datetmp
, p
->end
);
322 send_to_server (datetmp
, 0);
323 send_to_server ("\012", 0);
330 while (log_data
.singledatelist
!= NULL
)
332 p
= log_data
.singledatelist
;
333 log_data
.singledatelist
= p
->next
;
334 send_to_server ("Argument -d\012", 0);
335 send_to_server ("Argument ", 0);
336 date_to_internet (datetmp
, p
->end
);
337 send_to_server (datetmp
, 0);
338 send_to_server ("\012", 0);
350 if (log_data
.sup_header
)
352 if (log_data
.nameonly
)
354 if (log_data
.long_header
)
357 while (log_data
.revlist
!= NULL
)
359 rp
= log_data
.revlist
;
360 log_data
.revlist
= rp
->next
;
361 send_to_server ("Argument -r", 0);
364 if (rp
->first
!= NULL
)
365 send_to_server (rp
->first
, 0);
366 send_to_server (".", 1);
370 if (rp
->first
!= NULL
)
371 send_to_server (rp
->first
, 0);
372 send_to_server (":", 1);
374 send_to_server (":", 1);
375 if (rp
->last
!= NULL
)
376 send_to_server (rp
->last
, 0);
378 send_to_server ("\012", 0);
385 send_arg_list ("-s", log_data
.statelist
);
386 dellist (&log_data
.statelist
);
387 send_arg_list ("-w", log_data
.authorlist
);
388 dellist (&log_data
.authorlist
);
394 for (i
= 0; i
< argc
; i
++)
396 send_to_server ("rlog\012", 0);
400 send_files (argc
, argv
, local
, 0, SEND_NO_CONTENTS
);
401 send_file_names (argc
, argv
, SEND_EXPAND_WILD
);
402 send_to_server ("log\012", 0);
404 err
= get_responses_and_close ();
409 /* OK, now that we know we are local/server, we can resolve @@MYSELF
410 into our user name. */
411 if (findnode (log_data
.authorlist
, "@@MYSELF") != NULL
)
412 log_parse_list (&log_data
.authorlist
, getcaller ());
419 for (i
= 0; i
< argc
; i
++)
421 err
+= do_module (db
, argv
[i
], MISC
, "Logging", rlog_proc
,
422 NULL
, 0, local
, 0, 0, NULL
);
428 err
= rlog_proc (argc
+ 1, argv
- 1, NULL
, NULL
, NULL
, 0, local
, NULL
,
432 while (log_data
.revlist
)
434 struct option_revlist
*rl
= log_data
.revlist
->next
;
435 if (log_data
.revlist
->first
)
436 free (log_data
.revlist
->first
);
437 if (log_data
.revlist
->last
)
438 free (log_data
.revlist
->last
);
439 free (log_data
.revlist
);
440 log_data
.revlist
= rl
;
442 while (log_data
.datelist
)
444 struct datelist
*nd
= log_data
.datelist
->next
;
445 if (log_data
.datelist
->start
)
446 free (log_data
.datelist
->start
);
447 if (log_data
.datelist
->end
)
448 free (log_data
.datelist
->end
);
449 free (log_data
.datelist
);
450 log_data
.datelist
= nd
;
452 while (log_data
.singledatelist
)
454 struct datelist
*nd
= log_data
.singledatelist
->next
;
455 if (log_data
.singledatelist
->start
)
456 free (log_data
.singledatelist
->start
);
457 if (log_data
.singledatelist
->end
)
458 free (log_data
.singledatelist
->end
);
459 free (log_data
.singledatelist
);
460 log_data
.singledatelist
= nd
;
462 dellist (&log_data
.statelist
);
463 dellist (&log_data
.authorlist
);
471 rlog_proc (int argc
, char **argv
, char *xwhere
, char *mwhere
, char *mfile
,
472 int shorten
, int local
, char *mname
, char *msg
)
474 /* Begin section which is identical to patch_proc--should this
475 be abstracted out somehow? */
479 char *repository
= NULL
;
484 repository
= xmalloc (strlen (current_parsed_root
->directory
)
486 + (mfile
== NULL
? 0 : strlen (mfile
) + 1) + 2);
487 (void)sprintf (repository
, "%s/%s",
488 current_parsed_root
->directory
, argv
[0]);
489 where
= xmalloc (strlen (argv
[0])
490 + (mfile
== NULL
? 0 : strlen (mfile
) + 1)
492 (void)strcpy (where
, argv
[0]);
494 /* If mfile isn't null, we need to set up to do only part of theu
502 /* If the portion of the module is a path, put the dir part on
505 if ((cp
= strrchr (mfile
, '/')) != NULL
)
508 (void)strcat (repository
, "/");
509 (void)strcat (repository
, mfile
);
510 (void)strcat (where
, "/");
511 (void)strcat (where
, mfile
);
515 /* take care of the rest */
516 path
= Xasprintf ("%s/%s", repository
, mfile
);
519 /* directory means repository gets the dir tacked on */
520 (void)strcpy (repository
, path
);
521 (void)strcat (where
, "/");
522 (void)strcat (where
, mfile
);
534 /* cd to the starting repository */
535 if (CVS_CHDIR (repository
) < 0)
537 error (0, errno
, "cannot chdir to %s", repository
);
542 /* End section which is identical to patch_proc. */
544 which
= W_REPOS
| W_ATTIC
;
550 which
= W_LOCAL
| W_REPOS
| W_ATTIC
;
553 err
= start_recursion (log_fileproc
, NULL
, log_dirproc
,
555 argc
- 1, argv
+ 1, local
, which
, 0, CVS_LOCK_READ
,
556 where
, 1, repository
);
558 if (!(which
& W_LOCAL
)) free (repository
);
559 if (where
) free (where
);
567 * Parse a revision list specification.
569 static struct option_revlist
*
570 log_parse_revlist (const char *argstring
)
572 char *orig_copy
, *copy
;
573 struct option_revlist
*ret
, **pr
;
575 /* Unfortunately, rlog accepts -r without an argument to mean that
576 latest revision on the default branch, so we must support that
577 for compatibility. */
578 if (argstring
== NULL
)
584 /* Copy the argument into memory so that we can change it. We
585 don't want to change the argument because, at least as of this
586 writing, we will use it if we send the arguments to the server. */
587 orig_copy
= copy
= xstrdup (argstring
);
591 struct option_revlist
*r
;
593 comma
= strchr (copy
, ',');
597 r
= xmalloc (sizeof *r
);
601 r
->last
= strchr (copy
, ':');
605 r
->inclusive
= (*r
->last
!= ':');
613 if (r
->first
[0] != '\0' && r
->first
[strlen (r
->first
) - 1] == '.')
616 r
->first
[strlen (r
->first
) - 1] = '\0';
620 if (*r
->first
== '\0')
622 if (*r
->last
== '\0')
625 if (r
->first
!= NULL
)
626 r
->first
= xstrdup (r
->first
);
628 r
->last
= xstrdup (r
->last
);
643 * Parse a date specification.
646 log_parse_date (struct log_data
*log_data
, const char *argstring
)
648 char *orig_copy
, *copy
;
650 /* Copy the argument into memory so that we can change it. We
651 don't want to change the argument because, at least as of this
652 writing, we will use it if we send the arguments to the server. */
653 orig_copy
= copy
= xstrdup (argstring
);
656 struct datelist
*nd
, **pd
;
657 char *cpend
, *cp
, *ds
, *de
;
659 nd
= xmalloc (sizeof *nd
);
661 cpend
= strchr (copy
, ';');
665 pd
= &log_data
->datelist
;
668 if ((cp
= strchr (copy
, '>')) != NULL
)
679 else if ((cp
= strchr (copy
, '<')) != NULL
)
694 pd
= &log_data
->singledatelist
;
699 else if (*ds
!= '\0')
700 nd
->start
= Make_Date (ds
);
703 /* 1970 was the beginning of time, as far as get_date and
704 Make_Date are concerned. FIXME: That is true only if time_t
705 is a POSIX-style time and there is nothing in ANSI that
706 mandates that. It would be cleaner to set a flag saying
707 whether or not there is a start date. */
708 nd
->start
= Make_Date ("1/1/1970 UTC");
712 nd
->end
= Make_Date (de
);
715 /* We want to set the end date to some time sufficiently far
716 in the future to pick up all revisions that have been
717 created since the specified date and the time `cvs log'
718 completes. FIXME: The date in question only makes sense
719 if time_t is a POSIX-style time and it is 32 bits
720 and signed. We should instead be setting a flag saying
721 whether or not there is an end date. Note that using
722 something like "next week" would break the testsuite (and,
723 perhaps less importantly, loses if the clock is set grossly
725 nd
->end
= Make_Date ("2038-01-01");
740 * Parse a comma separated list of items, and add each one to *PLIST.
743 log_parse_list (List
**plist
, const char *argstring
)
752 cp
= strchr (argstring
, ',');
754 p
->key
= xstrdup (argstring
);
759 len
= cp
- argstring
;
760 p
->key
= xmalloc (len
+ 1);
761 strncpy (p
->key
, argstring
, len
);
767 if (addnode (*plist
, p
) != 0)
780 printlock_proc (Node
*lock
, void *foo
)
782 cvs_output ("\n\t", 2);
783 cvs_output (lock
->data
, 0);
784 cvs_output (": ", 2);
785 cvs_output (lock
->key
, 0);
792 * Do an rlog on a file
795 log_fileproc (void *callerdat
, struct file_info
*finfo
)
797 struct log_data
*log_data
= callerdat
;
803 struct revlist
*revlist
= NULL
;
804 struct log_data_and_rcs log_data_and_rcs
;
806 rcsfile
= finfo
->rcs
;
807 p
= findnode (finfo
->entries
, finfo
->file
);
810 Entnode
*e
= p
->data
;
811 baserev
= e
->version
;
812 if (baserev
[0] == '-') ++baserev
;
819 /* no rcs file. What *do* we know about this file? */
822 if (baserev
[0] == '0' && baserev
[1] == '\0')
825 error (0, 0, "%s has been added, but not committed",
832 error (0, 0, "nothing known about %s", finfo
->file
);
837 if (log_data
->sup_header
|| !log_data
->nameonly
)
840 /* We will need all the information in the RCS file. */
841 RCS_fully_parse (rcsfile
);
843 /* Turn any symbolic revisions in the revision list into numeric
845 revlist
= log_expand_revlist (rcsfile
, baserev
, log_data
->revlist
,
846 log_data
->default_branch
);
847 if (log_data
->sup_header
848 || (!log_data
->header
&& !log_data
->long_header
))
850 log_data_and_rcs
.log_data
= log_data
;
851 log_data_and_rcs
.revlist
= revlist
;
852 log_data_and_rcs
.rcs
= rcsfile
;
854 /* If any single dates were specified, we need to identify the
855 revisions they select. Each one selects the single
856 revision, which is otherwise selected, of that date or
857 earlier. The log_fix_singledate routine will fill in the
858 start date for each specific revision. */
859 if (log_data
->singledatelist
!= NULL
)
860 walklist (rcsfile
->versions
, log_fix_singledate
,
863 selrev
= walklist (rcsfile
->versions
, log_count_print
,
865 if (log_data
->sup_header
&& selrev
== 0)
867 log_free_revlist (revlist
);
874 if (log_data
->nameonly
)
876 cvs_output (rcsfile
->print_path
, 0);
877 cvs_output ("\n", 1);
878 log_free_revlist (revlist
);
882 /* The output here is intended to be exactly compatible with the
883 output of rlog. I'm not sure whether this code should be here
884 or in rcs.c; I put it here because it is specific to the log
885 function, even though it uses information gathered by the
886 functions in rcs.c. */
888 cvs_output ("\n", 1);
890 cvs_output ("RCS file: ", 0);
891 cvs_output (rcsfile
->print_path
, 0);
895 cvs_output ("\nWorking file: ", 0);
896 if (finfo
->update_dir
[0] != '\0')
898 cvs_output (finfo
->update_dir
, 0);
901 cvs_output (finfo
->file
, 0);
904 cvs_output ("\nhead:", 0);
905 if (rcsfile
->head
!= NULL
)
908 cvs_output (rcsfile
->head
, 0);
911 cvs_output ("\nbranch:", 0);
912 if (rcsfile
->branch
!= NULL
)
915 cvs_output (rcsfile
->branch
, 0);
918 cvs_output ("\nlocks:", 0);
919 if (rcsfile
->strict_locks
)
920 cvs_output (" strict", 0);
921 walklist (RCS_getlocks (rcsfile
), printlock_proc
, NULL
);
923 cvs_output ("\naccess list:", 0);
924 if (rcsfile
->access
!= NULL
)
928 cp
= rcsfile
->access
;
933 cvs_output ("\n\t", 2);
935 while (!isspace ((unsigned char)*cp2
) && *cp2
!= '\0')
937 cvs_output (cp
, cp2
- cp
);
939 while (isspace ((unsigned char)*cp
) && *cp
!= '\0')
944 if (!log_data
->notags
)
948 cvs_output ("\nsymbolic names:", 0);
949 syms
= RCS_symbols (rcsfile
);
950 walklist (syms
, log_symbol
, NULL
);
953 cvs_output ("\nkeyword substitution: ", 0);
954 if (rcsfile
->expand
== NULL
)
955 cvs_output ("kv", 2);
957 cvs_output (rcsfile
->expand
, 0);
959 cvs_output ("\ntotal revisions: ", 0);
960 sprintf (buf
, "%d", walklist (rcsfile
->versions
, log_count
, NULL
));
965 cvs_output (";\tselected revisions: ", 0);
966 sprintf (buf
, "%d", selrev
);
970 cvs_output ("\n", 1);
972 if (!log_data
->header
|| log_data
->long_header
)
974 cvs_output ("description:\n", 0);
975 if (rcsfile
->desc
!= NULL
)
976 cvs_output (rcsfile
->desc
, 0);
979 if (!log_data
->header
&& ! log_data
->long_header
&& rcsfile
->head
!= NULL
)
981 p
= findnode (rcsfile
->versions
, rcsfile
->head
);
983 error (1, 0, "can not find head revision in `%s'",
987 RCSVers
*vers
= p
->data
;
989 log_version (log_data
, revlist
, rcsfile
, vers
, 1);
990 if (vers
->next
== NULL
)
994 p
= findnode (rcsfile
->versions
, vers
->next
);
996 error (1, 0, "can not find next revision `%s' in `%s'",
997 vers
->next
, finfo
->fullname
);
1001 log_tree (log_data
, revlist
, rcsfile
, rcsfile
->head
);
1005 =============================================================================\n",
1008 /* Free up the new revlist and restore the old one. */
1009 log_free_revlist (revlist
);
1011 /* If singledatelist is not NULL, free up the start dates we added
1013 if (log_data
->singledatelist
!= NULL
)
1017 for (d
= log_data
->singledatelist
; d
!= NULL
; d
= d
->next
)
1019 if (d
->start
!= NULL
)
1031 * Fix up a revision list in order to compare it against versions.
1032 * Expand any symbolic revisions.
1034 static struct revlist
*
1035 log_expand_revlist (RCSNode
*rcs
, char *baserev
,
1036 struct option_revlist
*revlist
, int default_branch
)
1038 struct option_revlist
*r
;
1039 struct revlist
*ret
, **pr
;
1043 for (r
= revlist
; r
!= NULL
; r
= r
->next
)
1047 nr
= xmalloc (sizeof *nr
);
1048 nr
->inclusive
= r
->inclusive
;
1050 if (r
->first
== NULL
&& r
->last
== NULL
)
1052 /* If both first and last are NULL, it means that we want
1053 just the head of the default branch, which is RCS_head. */
1054 nr
->first
= RCS_head (rcs
);
1058 error (0, 0, "No head revision in archive `%s'.",
1065 nr
->last
= xstrdup (nr
->first
);
1066 nr
->fields
= numdots (nr
->first
) + 1;
1069 else if (r
->branchhead
)
1073 /* Print just the head of the branch. */
1074 if (isdigit ((unsigned char) r
->first
[0]))
1075 nr
->first
= RCS_getbranch (rcs
, r
->first
, 1);
1078 branch
= RCS_whatbranch (rcs
, r
->first
);
1083 nr
->first
= RCS_getbranch (rcs
, branch
, 1);
1090 error (0, 0, "warning: no branch `%s' in `%s'",
1091 r
->first
, rcs
->print_path
);
1097 nr
->last
= xstrdup (nr
->first
);
1098 nr
->fields
= numdots (nr
->first
) + 1;
1103 if (r
->first
== NULL
|| isdigit ((unsigned char) r
->first
[0]))
1104 nr
->first
= xstrdup (r
->first
);
1107 if (baserev
&& strcmp (r
->first
, TAG_BASE
) == 0)
1108 nr
->first
= xstrdup (baserev
);
1109 else if (RCS_nodeisbranch (rcs
, r
->first
))
1110 nr
->first
= RCS_whatbranch (rcs
, r
->first
);
1112 nr
->first
= RCS_gettag (rcs
, r
->first
, 1, NULL
);
1113 if (nr
->first
== NULL
&& !really_quiet
)
1115 error (0, 0, "warning: no revision `%s' in `%s'",
1116 r
->first
, rcs
->print_path
);
1120 if (r
->last
== r
->first
|| (r
->last
!= NULL
&& r
->first
!= NULL
&&
1121 strcmp (r
->last
, r
->first
) == 0))
1122 nr
->last
= xstrdup (nr
->first
);
1123 else if (r
->last
== NULL
|| isdigit ((unsigned char) r
->last
[0]))
1124 nr
->last
= xstrdup (r
->last
);
1127 if (baserev
&& strcmp (r
->last
, TAG_BASE
) == 0)
1128 nr
->last
= xstrdup (baserev
);
1129 else if (RCS_nodeisbranch (rcs
, r
->last
))
1130 nr
->last
= RCS_whatbranch (rcs
, r
->last
);
1132 nr
->last
= RCS_gettag (rcs
, r
->last
, 1, NULL
);
1133 if (nr
->last
== NULL
&& !really_quiet
)
1135 error (0, 0, "warning: no revision `%s' in `%s'",
1136 r
->last
, rcs
->print_path
);
1140 /* Process the revision numbers the same way that rlog
1141 does. This code is a bit cryptic for my tastes, but
1142 keeping the same implementation as rlog ensures a
1143 certain degree of compatibility. */
1144 if (r
->first
== NULL
&& nr
->last
!= NULL
)
1146 nr
->fields
= numdots (nr
->last
) + 1;
1148 nr
->first
= xstrdup (".0");
1153 nr
->first
= xstrdup (nr
->last
);
1154 cp
= strrchr (nr
->first
, '.');
1156 strcpy (cp
+ 1, "0");
1159 else if (r
->last
== NULL
&& nr
->first
!= NULL
)
1161 nr
->fields
= numdots (nr
->first
) + 1;
1162 nr
->last
= xstrdup (nr
->first
);
1169 cp
= strrchr (nr
->last
, '.');
1174 else if (nr
->first
== NULL
|| nr
->last
== NULL
)
1176 else if (strcmp (nr
->first
, nr
->last
) == 0)
1177 nr
->fields
= numdots (nr
->last
) + 1;
1181 int dots1
= numdots (nr
->first
);
1182 int dots2
= numdots (nr
->last
);
1183 if (dots1
> dots2
|| (dots1
== dots2
&&
1184 version_compare (nr
->first
, nr
->last
, dots1
+ 1) > 0))
1186 char *tmp
= nr
->first
;
1187 nr
->first
= nr
->last
;
1189 nr
->fields
= dots2
+ 1;
1191 dots1
= nr
->fields
- 1;
1194 nr
->fields
= dots1
+ 1;
1195 dots1
+= (nr
->fields
& 1);
1196 ord
= version_compare (nr
->first
, nr
->last
, dots1
);
1197 if (ord
> 0 || (nr
->fields
> 2 && ord
< 0))
1200 "invalid branch or revision pair %s:%s in `%s'",
1201 r
->first
, r
->last
, rcs
->print_path
);
1210 if (nr
->fields
<= dots2
&& (nr
->fields
& 1))
1212 char *p
= Xasprintf ("%s.0", nr
->first
);
1217 while (nr
->fields
<= dots2
)
1224 nr
= xmalloc (sizeof *nr
);
1226 nr
->first
= xstrdup ((*pr
)->last
);
1227 nr
->last
= xstrdup ((*pr
)->last
);
1228 nr
->fields
= (*pr
)->fields
;
1230 for (i
= 0; i
< nr
->fields
; i
++)
1231 p
= strchr (p
, '.') + 1;
1233 p
= strchr (nr
->first
+ (p
- (*pr
)->last
), '.');
1253 /* If the default branch was requested, add a revlist entry for
1254 it. This is how rlog handles this option. */
1256 && (rcs
->head
!= NULL
|| rcs
->branch
!= NULL
))
1260 nr
= xmalloc (sizeof *nr
);
1261 if (rcs
->branch
!= NULL
)
1262 nr
->first
= xstrdup (rcs
->branch
);
1267 nr
->first
= xstrdup (rcs
->head
);
1269 cp
= strrchr (nr
->first
, '.');
1273 nr
->last
= xstrdup (nr
->first
);
1274 nr
->fields
= numdots (nr
->first
) + 1;
1287 * Free a revlist created by log_expand_revlist.
1290 log_free_revlist (struct revlist
*revlist
)
1297 struct revlist
*next
;
1299 if (r
->first
!= NULL
)
1301 if (r
->last
!= NULL
)
1312 * Return nonzero if a revision should be printed, based on the
1316 log_version_requested (struct log_data
*log_data
, struct revlist
*revlist
,
1317 RCSNode
*rcs
, RCSVers
*vnode
)
1319 /* Handle the list of states from the -s option. */
1320 if (log_data
->statelist
!= NULL
1321 && findnode (log_data
->statelist
, vnode
->state
) == NULL
)
1326 /* Handle the list of authors from the -w option. */
1327 if (log_data
->authorlist
!= NULL
)
1329 if (vnode
->author
!= NULL
1330 && findnode (log_data
->authorlist
, vnode
->author
) == NULL
)
1336 /* rlog considers all the -d options together when it decides
1337 whether to print a revision, so we must be compatible. */
1338 if (log_data
->datelist
!= NULL
|| log_data
->singledatelist
!= NULL
)
1342 for (d
= log_data
->datelist
; d
!= NULL
; d
= d
->next
)
1346 cmp
= RCS_datecmp (vnode
->date
, d
->start
);
1347 if (cmp
> 0 || (cmp
== 0 && d
->inclusive
))
1349 cmp
= RCS_datecmp (vnode
->date
, d
->end
);
1350 if (cmp
< 0 || (cmp
== 0 && d
->inclusive
))
1357 /* Look through the list of specific dates. We want to
1358 select the revision with the exact date found in the
1359 start field. The commit code ensures that it is
1360 impossible to check in multiple revisions of a single
1361 file in a single second, so checking the date this way
1362 should never select more than one revision. */
1363 for (d
= log_data
->singledatelist
; d
!= NULL
; d
= d
->next
)
1365 if (d
->start
!= NULL
1366 && RCS_datecmp (vnode
->date
, d
->start
) == 0)
1377 /* If the -r or -b options were used, REVLIST will be non NULL,
1378 and we print the union of the specified revisions. */
1379 if (revlist
!= NULL
)
1385 /* This code is taken from rlog. */
1387 vfields
= numdots (v
) + 1;
1388 for (r
= revlist
; r
!= NULL
; r
= r
->next
)
1390 if (vfields
== r
->fields
+ (r
->fields
& 1) &&
1391 (r
->inclusive
? version_compare (v
, r
->first
, r
->fields
) >= 0 :
1392 version_compare (v
, r
->first
, r
->fields
) > 0)
1393 && version_compare (v
, r
->last
, r
->fields
) <= 0)
1399 /* If we get here, then the -b and/or the -r option was used,
1400 but did not match this revision, so we reject it. */
1405 /* By default, we print all revisions. */
1412 * Output a single symbol. This is called via walklist.
1416 log_symbol (Node
*p
, void *closure
)
1418 cvs_output ("\n\t", 2);
1419 cvs_output (p
->key
, 0);
1420 cvs_output (": ", 2);
1421 cvs_output (p
->data
, 0);
1428 * Count the number of entries on a list. This is called via walklist.
1432 log_count (Node
*p
, void *closure
)
1440 * Sort out a single date specification by narrowing down the date
1441 * until we find the specific selected revision.
1444 log_fix_singledate (Node
*p
, void *closure
)
1446 struct log_data_and_rcs
*data
= closure
;
1449 struct datelist
*holdsingle
, *holddate
;
1452 pv
= findnode (data
->rcs
->versions
, p
->key
);
1454 error (1, 0, "missing version `%s' in RCS file `%s'",
1455 p
->key
, data
->rcs
->print_path
);
1458 /* We are only interested if this revision passes any other tests.
1459 Temporarily clear log_data->singledatelist to avoid confusing
1460 log_version_requested. We also clear log_data->datelist,
1461 because rlog considers all the -d options together. We don't
1462 want to reject a revision because it does not match a date pair
1463 if we are going to select it on the basis of the singledate. */
1464 holdsingle
= data
->log_data
->singledatelist
;
1465 data
->log_data
->singledatelist
= NULL
;
1466 holddate
= data
->log_data
->datelist
;
1467 data
->log_data
->datelist
= NULL
;
1468 requested
= log_version_requested (data
->log_data
, data
->revlist
,
1470 data
->log_data
->singledatelist
= holdsingle
;
1471 data
->log_data
->datelist
= holddate
;
1477 /* For each single date, if this revision is before the
1478 specified date, but is closer than the previously selected
1479 revision, select it instead. */
1480 for (d
= data
->log_data
->singledatelist
; d
!= NULL
; d
= d
->next
)
1482 if (RCS_datecmp (vnode
->date
, d
->end
) <= 0
1483 && (d
->start
== NULL
1484 || RCS_datecmp (vnode
->date
, d
->start
) > 0))
1486 if (d
->start
!= NULL
)
1488 d
->start
= xstrdup (vnode
->date
);
1499 * Count the number of revisions we are going to print.
1502 log_count_print (Node
*p
, void *closure
)
1504 struct log_data_and_rcs
*data
= closure
;
1507 pv
= findnode (data
->rcs
->versions
, p
->key
);
1509 error (1, 0, "missing version `%s' in RCS file `%s'",
1510 p
->key
, data
->rcs
->print_path
);
1511 if (log_version_requested (data
->log_data
, data
->revlist
, data
->rcs
,
1521 * Print the list of changes, not including the trunk, in reverse
1522 * order for each branch.
1525 log_tree (struct log_data
*log_data
, struct revlist
*revlist
, RCSNode
*rcs
,
1531 p
= findnode (rcs
->versions
, ver
);
1533 error (1, 0, "missing version `%s' in RCS file `%s'",
1534 ver
, rcs
->print_path
);
1536 if (vnode
->next
!= NULL
)
1537 log_tree (log_data
, revlist
, rcs
, vnode
->next
);
1538 if (vnode
->branches
!= NULL
)
1540 Node
*head
, *branch
;
1542 /* We need to do the branches in reverse order. This breaks
1543 the List abstraction, but so does most of the branch
1544 manipulation in rcs.c. */
1545 head
= vnode
->branches
->list
;
1546 for (branch
= head
->prev
; branch
!= head
; branch
= branch
->prev
)
1548 log_abranch (log_data
, revlist
, rcs
, branch
->key
);
1549 log_tree (log_data
, revlist
, rcs
, branch
->key
);
1557 * Log the changes for a branch, in reverse order.
1560 log_abranch (struct log_data
*log_data
, struct revlist
*revlist
, RCSNode
*rcs
,
1566 p
= findnode (rcs
->versions
, ver
);
1568 error (1, 0, "missing version `%s' in RCS file `%s'",
1569 ver
, rcs
->print_path
);
1571 if (vnode
->next
!= NULL
)
1572 log_abranch (log_data
, revlist
, rcs
, vnode
->next
);
1573 log_version (log_data
, revlist
, rcs
, vnode
, 0);
1579 * Print the log output for a single version.
1582 log_version (struct log_data
*log_data
, struct revlist
*revlist
, RCSNode
*rcs
,
1583 RCSVers
*ver
, int trunk
)
1586 int year
, mon
, mday
, hour
, min
, sec
;
1590 if (! log_version_requested (log_data
, revlist
, rcs
, ver
))
1593 cvs_output ("----------------------------\nrevision ", 0);
1594 cvs_output (ver
->version
, 0);
1596 p
= findnode (RCS_getlocks (rcs
), ver
->version
);
1599 cvs_output ("\tlocked by: ", 0);
1600 cvs_output (p
->data
, 0);
1601 cvs_output (";", 1);
1603 cvs_output ("\n", 1);
1605 cvs_output_tagged ("text", "date: ");
1606 (void)sscanf (ver
->date
, SDATEFORM
, &year
, &mon
, &mday
, &hour
, &min
,
1610 sprintf (buf
, "%04d-%02d-%02d %02d:%02d:%02d +0000", year
, mon
, mday
,
1612 cvs_output_tagged ("date", buf
);
1614 cvs_output_tagged ("text", "; author: ");
1615 cvs_output_tagged ("text", ver
->author
);
1617 cvs_output_tagged ("text", "; state: ");
1618 cvs_output_tagged ("text", ver
->state
);
1619 cvs_output_tagged ("text", ";");
1623 padd
= findnode (ver
->other
, ";add");
1624 pdel
= findnode (ver
->other
, ";delete");
1626 else if (ver
->next
== NULL
)
1636 nextp
= findnode (rcs
->versions
, ver
->next
);
1638 error (1, 0, "missing version `%s' in `%s'", ver
->next
,
1640 nextver
= nextp
->data
;
1641 pdel
= findnode (nextver
->other
, ";add");
1642 padd
= findnode (nextver
->other
, ";delete");
1648 cvs_output_tagged ("text", " lines: +");
1649 cvs_output_tagged ("text", padd
->data
);
1650 cvs_output_tagged ("text", " -");
1651 cvs_output_tagged ("text", pdel
->data
);
1652 cvs_output_tagged ("text", ";");
1655 p
= findnode(ver
->other_delta
,"commitid");
1658 cvs_output_tagged ("text", " commitid: ");
1659 cvs_output_tagged ("text", p
->data
);
1660 cvs_output_tagged ("text", ";");
1663 cvs_output_tagged ("newline", NULL
);
1665 if (ver
->branches
!= NULL
)
1667 cvs_output ("branches:", 0);
1668 walklist (ver
->branches
, log_branch
, NULL
);
1669 cvs_output ("\n", 1);
1672 p
= findnode (ver
->other
, "log");
1673 /* The p->date == NULL case is the normal one for an empty log
1674 message (rcs-14 in sanity.sh). I don't think the case where
1675 p->data is "" can happen (getrcskey in rcs.c checks for an
1676 empty string and set the value to NULL in that case). My guess
1677 would be the p == NULL case would mean an RCS file which was
1678 missing the "log" keyword (which is invalid according to
1680 if (p
== NULL
|| p
->data
== NULL
|| *(char *)p
->data
== '\0')
1681 cvs_output ("*** empty log message ***\n", 0);
1684 /* FIXME: Technically, the log message could contain a null
1686 cvs_output (p
->data
, 0);
1687 if (((char *)p
->data
)[strlen (p
->data
) - 1] != '\n')
1688 cvs_output ("\n", 1);
1695 * Output a branch version. This is called via walklist.
1699 log_branch (Node
*p
, void *closure
)
1701 cvs_output (" ", 2);
1702 if ((numdots (p
->key
) & 1) == 0)
1703 cvs_output (p
->key
, 0);
1708 f
= xstrdup (p
->key
);
1709 cp
= strrchr (f
, '.');
1714 cvs_output (";", 1);
1721 * Print a warm fuzzy message
1725 log_dirproc (void *callerdat
, const char *dir
, const char *repository
,
1726 const char *update_dir
, List
*entries
)
1732 error (0, 0, "Logging %s", update_dir
);
1739 * Compare versions. This is taken from RCS compartial.
1742 version_compare (const char *v1
, const char *v2
, int len
)
1755 for (d1
= 0; isdigit ((unsigned char) v1
[d1
]); ++d1
)
1760 for (d2
= 0; isdigit ((unsigned char) v2
[d2
]); ++d2
)
1764 return d1
< d2
? -1 : 1;
1766 r
= memcmp (v1
, v2
, d1
);