xref: /openbsd/gnu/usr.bin/cvs/src/annotate.c (revision 7b36286a)
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  * Show last revision where each line modified
9  *
10  * Prints the specified files with each line annotated with the revision
11  * number where it was last modified.  With no argument, annotates all
12  * all the files in the directory (recursive by default).
13  */
14 
15 #include "cvs.h"
16 
17 /* Options from the command line.  */
18 
19 static int force_tag_match = 1;
20 static char *tag = NULL;
21 static int tag_validated;
22 static char *date = NULL;
23 
24 static int is_rannotate;
25 
26 static int annotate_fileproc PROTO ((void *callerdat, struct file_info *));
27 static int rannotate_proc PROTO((int argc, char **argv, char *xwhere,
28 				 char *mwhere, char *mfile, int shorten,
29 				 int local, char *mname, char *msg));
30 
31 static const char *const annotate_usage[] =
32 {
33     "Usage: %s %s [-lRf] [-r rev] [-D date] [files...]\n",
34     "\t-l\tLocal directory only, no recursion.\n",
35     "\t-R\tProcess directories recursively.\n",
36     "\t-f\tUse head revision if tag/date not found.\n",
37     "\t-r rev\tAnnotate file as of specified revision/tag.\n",
38     "\t-D date\tAnnotate file as of specified date.\n",
39     "(Specify the --help global option for a list of other help options)\n",
40     NULL
41 };
42 
43 /* Command to show the revision, date, and author where each line of a
44    file was modified.  */
45 
46 int
47 annotate (argc, argv)
48     int argc;
49     char **argv;
50 {
51     int local = 0;
52     int err = 0;
53     int c;
54 
55     is_rannotate = (strcmp(command_name, "rannotate") == 0);
56 
57     if (argc == -1)
58 	usage (annotate_usage);
59 
60     optind = 0;
61     while ((c = getopt (argc, argv, "+lr:D:fR")) != -1)
62     {
63 	switch (c)
64 	{
65 	    case 'l':
66 		local = 1;
67 		break;
68 	    case 'R':
69 		local = 0;
70 		break;
71 	    case 'r':
72 	        tag = optarg;
73 		break;
74 	    case 'D':
75 	        date = Make_Date (optarg);
76 		break;
77 	    case 'f':
78 	        force_tag_match = 0;
79 		break;
80 	    case '?':
81 	    default:
82 		usage (annotate_usage);
83 		break;
84 	}
85     }
86     argc -= optind;
87     argv += optind;
88 
89 #ifdef CLIENT_SUPPORT
90     if (current_parsed_root->isremote)
91     {
92 	start_server ();
93 
94 	if (is_rannotate && !supported_request ("rannotate"))
95 	    error (1, 0, "server does not support rannotate");
96 
97 	ign_setup ();
98 
99 	if (local)
100 	    send_arg ("-l");
101 	if (!force_tag_match)
102 	    send_arg ("-f");
103 	option_with_arg ("-r", tag);
104 	if (date)
105 	    client_senddate (date);
106 	if (is_rannotate)
107 	{
108 	    int i;
109 	    for (i = 0; i < argc; i++)
110 		send_arg (argv[i]);
111 	    send_to_server ("rannotate\012", 0);
112 	}
113 	else
114 	{
115 	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
116 	    send_file_names (argc, argv, SEND_EXPAND_WILD);
117 	    send_to_server ("annotate\012", 0);
118 	}
119 	return get_responses_and_close ();
120     }
121 #endif /* CLIENT_SUPPORT */
122 
123     if (is_rannotate)
124     {
125 	DBM *db;
126 	int i;
127 	db = open_module ();
128 	for (i = 0; i < argc; i++)
129 	{
130 	    err += do_module (db, argv[i], MISC, "Annotating", rannotate_proc,
131 			     (char *) NULL, 0, 0, 0, 0, (char *) NULL);
132 	}
133 	close_module (db);
134     }
135     else
136     {
137 	err = rannotate_proc (argc + 1, argv - 1, (char *) NULL,
138 			 (char *) NULL, (char *) NULL, 0, 0, (char *) NULL,
139 			 (char *) NULL);
140     }
141 
142     return err;
143 }
144 
145 
146 static int
147 rannotate_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
148     int argc;
149     char **argv;
150     char *xwhere;
151     char *mwhere;
152     char *mfile;
153     int shorten;
154     int local;
155     char *mname;
156     char *msg;
157 {
158     /* Begin section which is identical to patch_proc--should this
159        be abstracted out somehow?  */
160     char *myargv[2];
161     int err = 0;
162     int which;
163     char *repository;
164     char *where;
165 
166     if (is_rannotate)
167     {
168 	repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
169 			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
170 	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
171 	where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
172 			 + 1);
173 	(void) strcpy (where, argv[0]);
174 
175 	/* if mfile isn't null, we need to set up to do only part of the module */
176 	if (mfile != NULL)
177 	{
178 	    char *cp;
179 	    char *path;
180 
181 	    /* if the portion of the module is a path, put the dir part on repos */
182 	    if ((cp = strrchr (mfile, '/')) != NULL)
183 	    {
184 		*cp = '\0';
185 		(void) strcat (repository, "/");
186 		(void) strcat (repository, mfile);
187 		(void) strcat (where, "/");
188 		(void) strcat (where, mfile);
189 		mfile = cp + 1;
190 	    }
191 
192 	    /* take care of the rest */
193 	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
194 	    (void) sprintf (path, "%s/%s", repository, mfile);
195 	    if (isdir (path))
196 	    {
197 		/* directory means repository gets the dir tacked on */
198 		(void) strcpy (repository, path);
199 		(void) strcat (where, "/");
200 		(void) strcat (where, mfile);
201 	    }
202 	    else
203 	    {
204 		myargv[0] = argv[0];
205 		myargv[1] = mfile;
206 		argc = 2;
207 		argv = myargv;
208 	    }
209 	    free (path);
210 	}
211 
212 	/* cd to the starting repository */
213 	if ( CVS_CHDIR (repository) < 0)
214 	{
215 	    error (0, errno, "cannot chdir to %s", repository);
216 	    free (repository);
217 	    return (1);
218 	}
219 	free (repository);
220 	/* End section which is identical to patch_proc.  */
221 
222 	if (force_tag_match && tag != NULL)
223 	    which = W_REPOS | W_ATTIC;
224 	else
225 	    which = W_REPOS;
226 	repository = NULL;
227     }
228     else
229     {
230         where = NULL;
231         which = W_LOCAL;
232         repository = "";
233     }
234 
235     if (tag != NULL && !tag_validated)
236     {
237 	tag_check_valid (tag, argc - 1, argv + 1, local, 0, repository);
238 	tag_validated = 1;
239     }
240 
241     err = start_recursion (annotate_fileproc, (FILESDONEPROC) NULL,
242 			   (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
243 			   argc - 1, argv + 1, local, which, 0, 1,
244 			   where, 1);
245     return err;
246 }
247 
248 
249 static int
250 annotate_fileproc (callerdat, finfo)
251     void *callerdat;
252     struct file_info *finfo;
253 {
254     char *version;
255 
256     if (finfo->rcs == NULL)
257         return (1);
258 
259     if (finfo->rcs->flags & PARTIAL)
260         RCS_reparsercsfile (finfo->rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
261 
262     version = RCS_getversion (finfo->rcs, tag, date, force_tag_match,
263 			      (int *) NULL);
264     if (version == NULL)
265         return 0;
266 
267     /* Distinguish output for various files if we are processing
268        several files.  */
269     cvs_outerr ("Annotations for ", 0);
270     cvs_outerr (finfo->fullname, 0);
271     cvs_outerr ("\n***************\n", 0);
272 
273     RCS_deltas (finfo->rcs, (FILE *) NULL, (struct rcsbuffer *) NULL,
274 		version, RCS_ANNOTATE, NULL, NULL, NULL, NULL);
275     free (version);
276     return 0;
277 }
278