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