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