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