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