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
24static int force_tag_match = 1;
25static int force_binary = 0;
26static char *tag = NULL;
27static int tag_validated;
28static char *date = NULL;
29
30static int is_rannotate;
31
32static int annotate_fileproc PROTO ((void *callerdat, struct file_info *));
33static 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
37static 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
53int
54annotate (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
159static int
160rannotate_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
265static int
266annotate_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