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