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