1 /*
2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
4 *
5 * You may distribute under the terms of the GNU General Public License as
6 * specified in the README file that comes with the CVS source distribution.
7 *
8 * Show last revision where each line modified
9 *
10 * Prints the specified files with each line annotated with the revision
11 * number where it was last modified. With no argument, annotates all
12 * all the files in the directory (recursive by default).
13 */
14
15 #include "cvs.h"
16
17 /* Options from the command line. */
18
19 static int force_tag_match = 1;
20 static char *tag = NULL;
21 static int tag_validated;
22 static char *date = NULL;
23
24 static int is_rannotate;
25
26 static int annotate_fileproc PROTO ((void *callerdat, struct file_info *));
27 static int rannotate_proc PROTO((int argc, char **argv, char *xwhere,
28 char *mwhere, char *mfile, int shorten,
29 int local, char *mname, char *msg));
30
31 static const char *const annotate_usage[] =
32 {
33 "Usage: %s %s [-lRf] [-r rev] [-D date] [files...]\n",
34 "\t-l\tLocal directory only, no recursion.\n",
35 "\t-R\tProcess directories recursively.\n",
36 "\t-f\tUse head revision if tag/date not found.\n",
37 "\t-r rev\tAnnotate file as of specified revision/tag.\n",
38 "\t-D date\tAnnotate file as of specified date.\n",
39 "(Specify the --help global option for a list of other help options)\n",
40 NULL
41 };
42
43 /* Command to show the revision, date, and author where each line of a
44 file was modified. */
45
46 int
annotate(argc,argv)47 annotate (argc, argv)
48 int argc;
49 char **argv;
50 {
51 int local = 0;
52 int err = 0;
53 int c;
54
55 is_rannotate = (strcmp(command_name, "rannotate") == 0);
56
57 if (argc == -1)
58 usage (annotate_usage);
59
60 optind = 0;
61 while ((c = getopt (argc, argv, "+lr:D:fR")) != -1)
62 {
63 switch (c)
64 {
65 case 'l':
66 local = 1;
67 break;
68 case 'R':
69 local = 0;
70 break;
71 case 'r':
72 tag = optarg;
73 break;
74 case 'D':
75 date = Make_Date (optarg);
76 break;
77 case 'f':
78 force_tag_match = 0;
79 break;
80 case '?':
81 default:
82 usage (annotate_usage);
83 break;
84 }
85 }
86 argc -= optind;
87 argv += optind;
88
89 #ifdef CLIENT_SUPPORT
90 if (current_parsed_root->isremote)
91 {
92 start_server ();
93
94 if (is_rannotate && !supported_request ("rannotate"))
95 error (1, 0, "server does not support rannotate");
96
97 ign_setup ();
98
99 if (local)
100 send_arg ("-l");
101 if (!force_tag_match)
102 send_arg ("-f");
103 option_with_arg ("-r", tag);
104 if (date)
105 client_senddate (date);
106 if (is_rannotate)
107 {
108 int i;
109 for (i = 0; i < argc; i++)
110 send_arg (argv[i]);
111 send_to_server ("rannotate\012", 0);
112 }
113 else
114 {
115 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
116 send_file_names (argc, argv, SEND_EXPAND_WILD);
117 send_to_server ("annotate\012", 0);
118 }
119 return get_responses_and_close ();
120 }
121 #endif /* CLIENT_SUPPORT */
122
123 if (is_rannotate)
124 {
125 DBM *db;
126 int i;
127 db = open_module ();
128 for (i = 0; i < argc; i++)
129 {
130 err += do_module (db, argv[i], MISC, "Annotating", rannotate_proc,
131 (char *) NULL, 0, 0, 0, 0, (char *) NULL);
132 }
133 close_module (db);
134 }
135 else
136 {
137 err = rannotate_proc (argc + 1, argv - 1, (char *) NULL,
138 (char *) NULL, (char *) NULL, 0, 0, (char *) NULL,
139 (char *) NULL);
140 }
141
142 return err;
143 }
144
145
146 static int
rannotate_proc(argc,argv,xwhere,mwhere,mfile,shorten,local,mname,msg)147 rannotate_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
148 int argc;
149 char **argv;
150 char *xwhere;
151 char *mwhere;
152 char *mfile;
153 int shorten;
154 int local;
155 char *mname;
156 char *msg;
157 {
158 /* Begin section which is identical to patch_proc--should this
159 be abstracted out somehow? */
160 char *myargv[2];
161 int err = 0;
162 int which;
163 char *repository;
164 char *where;
165
166 if (is_rannotate)
167 {
168 repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
169 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
170 (void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
171 where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
172 + 1);
173 (void) strcpy (where, argv[0]);
174
175 /* if mfile isn't null, we need to set up to do only part of the module */
176 if (mfile != NULL)
177 {
178 char *cp;
179 char *path;
180
181 /* if the portion of the module is a path, put the dir part on repos */
182 if ((cp = strrchr (mfile, '/')) != NULL)
183 {
184 *cp = '\0';
185 (void) strcat (repository, "/");
186 (void) strcat (repository, mfile);
187 (void) strcat (where, "/");
188 (void) strcat (where, mfile);
189 mfile = cp + 1;
190 }
191
192 /* take care of the rest */
193 path = xmalloc (strlen (repository) + strlen (mfile) + 5);
194 (void) sprintf (path, "%s/%s", repository, mfile);
195 if (isdir (path))
196 {
197 /* directory means repository gets the dir tacked on */
198 (void) strcpy (repository, path);
199 (void) strcat (where, "/");
200 (void) strcat (where, mfile);
201 }
202 else
203 {
204 myargv[0] = argv[0];
205 myargv[1] = mfile;
206 argc = 2;
207 argv = myargv;
208 }
209 free (path);
210 }
211
212 /* cd to the starting repository */
213 if ( CVS_CHDIR (repository) < 0)
214 {
215 error (0, errno, "cannot chdir to %s", repository);
216 free (repository);
217 return (1);
218 }
219 free (repository);
220 /* End section which is identical to patch_proc. */
221
222 if (force_tag_match && tag != NULL)
223 which = W_REPOS | W_ATTIC;
224 else
225 which = W_REPOS;
226 repository = NULL;
227 }
228 else
229 {
230 where = NULL;
231 which = W_LOCAL;
232 repository = "";
233 }
234
235 if (tag != NULL && !tag_validated)
236 {
237 tag_check_valid (tag, argc - 1, argv + 1, local, 0, repository);
238 tag_validated = 1;
239 }
240
241 err = start_recursion (annotate_fileproc, (FILESDONEPROC) NULL,
242 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
243 argc - 1, argv + 1, local, which, 0, 1,
244 where, 1);
245 return err;
246 }
247
248
249 static int
annotate_fileproc(callerdat,finfo)250 annotate_fileproc (callerdat, finfo)
251 void *callerdat;
252 struct file_info *finfo;
253 {
254 char *version;
255
256 if (finfo->rcs == NULL)
257 return (1);
258
259 if (finfo->rcs->flags & PARTIAL)
260 RCS_reparsercsfile (finfo->rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
261
262 version = RCS_getversion (finfo->rcs, tag, date, force_tag_match,
263 (int *) NULL);
264 if (version == NULL)
265 return 0;
266
267 /* Distinguish output for various files if we are processing
268 several files. */
269 cvs_outerr ("Annotations for ", 0);
270 cvs_outerr (finfo->fullname, 0);
271 cvs_outerr ("\n***************\n", 0);
272
273 RCS_deltas (finfo->rcs, (FILE *) NULL, (struct rcsbuffer *) NULL,
274 version, RCS_ANNOTATE, NULL, NULL, NULL, NULL);
275 free (version);
276 return 0;
277 }
278