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