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