1 /* $OpenBSD: annotate.c,v 1.60 2008/06/14 04:34:08 tobias Exp $ */ 2 /* 3 * Copyright (c) 2007 Tobias Stoeckmann <tobias@openbsd.org> 4 * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/dirent.h> 21 22 #include <errno.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <time.h> 26 #include <unistd.h> 27 28 #include "cvs.h" 29 #include "remote.h" 30 31 void cvs_annotate_local(struct cvs_file *); 32 33 extern char *cvs_specified_tag; 34 35 static char *dateflag; 36 static int force_head = 0; 37 38 struct cvs_cmd cvs_cmd_annotate = { 39 CVS_OP_ANNOTATE, CVS_USE_WDIR, "annotate", 40 { "ann", "blame" }, 41 "Show last revision where each line was modified", 42 "[-flR] [-D date | -r rev] [file ...]", 43 "D:flRr:", 44 NULL, 45 cvs_annotate 46 }; 47 48 struct cvs_cmd cvs_cmd_rannotate = { 49 CVS_OP_RANNOTATE, 0, "rannotate", 50 { "rann", "ra" }, 51 "Show last revision where each line was modified", 52 "[-flR] [-D date | -r rev] module ...", 53 "D:flRr:", 54 NULL, 55 cvs_annotate 56 }; 57 58 int 59 cvs_annotate(int argc, char **argv) 60 { 61 int ch, flags; 62 char *arg = "."; 63 struct cvs_recursion cr; 64 65 flags = CR_RECURSE_DIRS; 66 67 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_ANNOTATE ? 68 cvs_cmd_annotate.cmd_opts : cvs_cmd_rannotate.cmd_opts)) != -1) { 69 switch (ch) { 70 case 'D': 71 dateflag = optarg; 72 cvs_specified_date = cvs_date_parse(dateflag); 73 break; 74 case 'f': 75 force_head = 1; 76 break; 77 case 'l': 78 flags &= ~CR_RECURSE_DIRS; 79 break; 80 case 'R': 81 flags |= CR_RECURSE_DIRS; 82 break; 83 case 'r': 84 cvs_specified_tag = optarg; 85 break; 86 default: 87 fatal("%s", cvs_cmdop == CVS_OP_ANNOTATE ? 88 cvs_cmd_annotate.cmd_synopsis : 89 cvs_cmd_rannotate.cmd_synopsis); 90 } 91 } 92 93 argc -= optind; 94 argv += optind; 95 96 if (cvs_cmdop == CVS_OP_RANNOTATE) { 97 if (argc == 0) 98 fatal("%s", cvs_cmd_rannotate.cmd_synopsis); 99 100 flags |= CR_REPO; 101 } 102 103 cr.enterdir = NULL; 104 cr.leavedir = NULL; 105 106 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 107 cvs_client_connect_to_server(); 108 cr.fileproc = cvs_client_sendfile; 109 110 if (dateflag != NULL) 111 cvs_client_send_request("Argument -D%s", dateflag); 112 113 if (force_head == 1) 114 cvs_client_send_request("Argument -f"); 115 116 if (!(flags & CR_RECURSE_DIRS)) 117 cvs_client_send_request("Argument -l"); 118 119 if (cvs_specified_tag != NULL) 120 cvs_client_send_request("Argument -r%s", 121 cvs_specified_tag); 122 } else { 123 if (cvs_cmdop == CVS_OP_RANNOTATE && 124 chdir(current_cvsroot->cr_dir) == -1) 125 fatal("cvs_annotate: %s", strerror(errno)); 126 127 cr.fileproc = cvs_annotate_local; 128 } 129 130 cr.flags = flags; 131 132 if (cvs_cmdop == CVS_OP_ANNOTATE || 133 current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 134 if (argc > 0) 135 cvs_file_run(argc, argv, &cr); 136 else 137 cvs_file_run(1, &arg, &cr); 138 } 139 140 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 141 cvs_client_send_files(argv, argc); 142 cvs_client_senddir("."); 143 144 cvs_client_send_request((cvs_cmdop == CVS_OP_RANNOTATE) ? 145 "rannotate" : "annotate"); 146 147 cvs_client_get_responses(); 148 } 149 150 return (0); 151 } 152 153 void 154 cvs_annotate_local(struct cvs_file *cf) 155 { 156 int i; 157 char date[10], rnum[13], *p; 158 RCSNUM *bnum, *rev; 159 struct cvs_line *line; 160 struct cvs_line **alines; 161 162 cvs_log(LP_TRACE, "cvs_annotate_local(%s)", cf->file_path); 163 164 cvs_file_classify(cf, cvs_directory_tag); 165 166 if (cf->file_rcs == NULL || cf->file_rcs->rf_head == NULL) 167 return; 168 169 if (cvs_specified_tag != NULL) { 170 if ((rev = rcs_translate_tag(cvs_specified_tag, 171 cf->file_rcs)) == NULL) { 172 if (!force_head) 173 /* Stick at weird GNU cvs, ignore error. */ 174 return; 175 176 /* -f is not allowed for unknown symbols */ 177 rev = rcsnum_parse(cvs_specified_tag); 178 if (rev == NULL) 179 fatal("no such tag %s", cvs_specified_tag); 180 rcsnum_free(rev); 181 rev = rcsnum_alloc(); 182 rcsnum_cpy(cf->file_rcs->rf_head, rev, 0); 183 } 184 185 /* 186 * If this is a revision in a branch, we have to go first 187 * from HEAD to branch, then down to 1.1. After that, take 188 * annotated branch and go up to branch revision. This must 189 * be done this way due to different handling of "a" and 190 * "d" in rcs file for annotation. 191 */ 192 if (!RCSNUM_ISBRANCHREV(rev)) { 193 bnum = rev; 194 } else { 195 bnum = rcsnum_alloc(); 196 rcsnum_cpy(rev, bnum, 2); 197 } 198 199 rcs_rev_getlines(cf->file_rcs, bnum, &alines); 200 201 /* 202 * Go into branch and receive annotations for branch revision, 203 * with inverted "a" and "d" meaning. 204 */ 205 if (bnum != rev) { 206 rcs_annotate_getlines(cf->file_rcs, rev, &alines); 207 rcsnum_free(bnum); 208 } 209 rcsnum_free(rev); 210 } else { 211 rcs_rev_getlines(cf->file_rcs, (cvs_specified_date != -1 || 212 cvs_directory_date != -1) ? cf->file_rcsrev : 213 cf->file_rcs->rf_head, &alines); 214 } 215 216 /* Stick at weird GNU cvs, ignore error. */ 217 if (alines == NULL) 218 return; 219 220 cvs_log(LP_RCS, "Annotations for %s", cf->file_path); 221 cvs_log(LP_RCS, "***************"); 222 223 for (i = 0; alines[i] != NULL; i++) { 224 line = alines[i]; 225 226 rcsnum_tostr(line->l_delta->rd_num, rnum, sizeof(rnum)); 227 strftime(date, sizeof(date), "%d-%b-%y", 228 &(line->l_delta->rd_date)); 229 if (line->l_len && line->l_line[line->l_len - 1] == '\n') 230 line->l_line[line->l_len - 1] = '\0'; 231 else { 232 p = xmalloc(line->l_len + 1); 233 memcpy(p, line->l_line, line->l_len); 234 p[line->l_len] = '\0'; 235 236 if (line->l_needsfree) 237 xfree(line->l_line); 238 line->l_line = p; 239 line->l_len++; 240 line->l_needsfree = 1; 241 } 242 cvs_printf("%-12.12s (%-8.8s %s): %s\n", rnum, 243 line->l_delta->rd_author, date, line->l_line); 244 245 if (line->l_needsfree) 246 xfree(line->l_line); 247 xfree(line); 248 } 249 250 xfree(alines); 251 } 252