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