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