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