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