xref: /dragonfly/contrib/cvs-1.12/src/annotate.c (revision 6fb88001)
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 (void *callerdat, struct file_info *);
33 static int rannotate_proc (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
54 annotate (int argc, char **argv)
55 {
56     int local = 0;
57     int err = 0;
58     int c;
59 
60     is_rannotate = (strcmp(cvs_cmd_name, "rannotate") == 0);
61 
62     if (argc == -1)
63 	usage (annotate_usage);
64 
65     optind = 0;
66     while ((c = getopt (argc, argv, "+lr:D:fFR")) != -1)
67     {
68 	switch (c)
69 	{
70 	    case 'l':
71 		local = 1;
72 		break;
73 	    case 'R':
74 		local = 0;
75 		break;
76 	    case 'r':
77 		parse_tagdate (&tag, &date, optarg);
78 		break;
79 	    case 'D':
80 		if (date) free (date);
81 	        date = Make_Date (optarg);
82 		break;
83 	    case 'f':
84 	        force_tag_match = 0;
85 		break;
86 	    case 'F':
87 	        force_binary = 1;
88 		break;
89 	    case '?':
90 	    default:
91 		usage (annotate_usage);
92 		break;
93 	}
94     }
95     argc -= optind;
96     argv += optind;
97 
98 #ifdef CLIENT_SUPPORT
99     if (current_parsed_root->isremote)
100     {
101 	start_server ();
102 
103 	if (is_rannotate && !supported_request ("rannotate"))
104 	    error (1, 0, "server does not support rannotate");
105 
106 	ign_setup ();
107 
108 	if (local)
109 	    send_arg ("-l");
110 	if (!force_tag_match)
111 	    send_arg ("-f");
112 	if (force_binary)
113 	    send_arg ("-F");
114 	option_with_arg ("-r", tag);
115 	if (date)
116 	    client_senddate (date);
117 	send_arg ("--");
118 	if (is_rannotate)
119 	{
120 	    int i;
121 	    for (i = 0; i < argc; i++)
122 		send_arg (argv[i]);
123 	    send_to_server ("rannotate\012", 0);
124 	}
125 	else
126 	{
127 	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
128 	    send_file_names (argc, argv, SEND_EXPAND_WILD);
129 	    send_to_server ("annotate\012", 0);
130 	}
131 	return get_responses_and_close ();
132     }
133 #endif /* CLIENT_SUPPORT */
134 
135     if (is_rannotate)
136     {
137 	DBM *db;
138 	int i;
139 	db = open_module ();
140 	for (i = 0; i < argc; i++)
141 	{
142 	    err += do_module (db, argv[i], MISC, "Annotating", rannotate_proc,
143 			      NULL, 0, local, 0, 0, NULL);
144 	}
145 	close_module (db);
146     }
147     else
148     {
149 	err = rannotate_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0,
150 			      local, NULL, NULL);
151     }
152 
153     return err;
154 }
155 
156 
157 static int
158 rannotate_proc (int argc, char **argv, char *xwhere, char *mwhere,
159 		char *mfile, int shorten, int local, char *mname, char *msg)
160 {
161     /* Begin section which is identical to patch_proc--should this
162        be abstracted out somehow?  */
163     char *myargv[2];
164     int err = 0;
165     int which;
166     char *repository;
167     char *where;
168 
169     if (is_rannotate)
170     {
171 	repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
172 			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
173 	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
174 	where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
175 			 + 1);
176 	(void) strcpy (where, argv[0]);
177 
178 	/* if mfile isn't null, we need to set up to do only part of the module */
179 	if (mfile != NULL)
180 	{
181 	    char *cp;
182 	    char *path;
183 
184 	    /* if the portion of the module is a path, put the dir part on repos */
185 	    if ((cp = strrchr (mfile, '/')) != NULL)
186 	    {
187 		*cp = '\0';
188 		(void) strcat (repository, "/");
189 		(void) strcat (repository, mfile);
190 		(void) strcat (where, "/");
191 		(void) strcat (where, mfile);
192 		mfile = cp + 1;
193 	    }
194 
195 	    /* take care of the rest */
196 	    path = Xasprintf ("%s/%s", repository, mfile);
197 	    if (isdir (path))
198 	    {
199 		/* directory means repository gets the dir tacked on */
200 		(void) strcpy (repository, path);
201 		(void) strcat (where, "/");
202 		(void) strcat (where, mfile);
203 	    }
204 	    else
205 	    {
206 		myargv[0] = argv[0];
207 		myargv[1] = mfile;
208 		argc = 2;
209 		argv = myargv;
210 	    }
211 	    free (path);
212 	}
213 
214 	/* cd to the starting repository */
215 	if (CVS_CHDIR (repository) < 0)
216 	{
217 	    error (0, errno, "cannot chdir to %s", repository);
218 	    free (repository);
219 	    free (where);
220 	    return 1;
221 	}
222 	/* End section which is identical to patch_proc.  */
223 
224 	if (force_tag_match && tag != NULL)
225 	    which = W_REPOS | W_ATTIC;
226 	else
227 	    which = W_REPOS;
228     }
229     else
230     {
231         where = NULL;
232         which = W_LOCAL;
233         repository = "";
234     }
235 
236     if (tag != NULL && !tag_validated)
237     {
238 	tag_check_valid (tag, argc - 1, argv + 1, local, 0, repository, false);
239 	tag_validated = 1;
240     }
241 
242     err = start_recursion (annotate_fileproc, NULL, NULL, NULL, NULL,
243 			   argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
244 			   where, 1, repository);
245     if (which & W_REPOS)
246 	free (repository);
247     if (where != NULL)
248 	free (where);
249     return err;
250 }
251 
252 
253 static int
254 annotate_fileproc (void *callerdat, struct file_info *finfo)
255 {
256     char *expand, *version;
257 
258     if (finfo->rcs == NULL)
259         return 1;
260 
261     if (finfo->rcs->flags & PARTIAL)
262         RCS_reparsercsfile (finfo->rcs, NULL, NULL);
263 
264     expand = RCS_getexpand (finfo->rcs);
265     version = RCS_getversion (finfo->rcs, tag, date, force_tag_match, NULL);
266 
267     if (version == NULL)
268         return 0;
269 
270     /* Distinguish output for various files if we are processing
271        several files.  */
272     cvs_outerr ("\nAnnotations for ", 0);
273     cvs_outerr (finfo->fullname, 0);
274     cvs_outerr ("\n***************\n", 0);
275 
276     if (!force_binary && expand && expand[0] == 'b')
277     {
278         cvs_outerr ("Skipping binary file -- -F not specified.\n", 0);
279     }
280     else
281     {
282 	RCS_deltas (finfo->rcs, NULL, NULL,
283 		    version, RCS_ANNOTATE, NULL, NULL, NULL, NULL);
284     }
285     free (version);
286     return 0;
287 }
288