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