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