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