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