1*5238b68eStobias /* $OpenBSD: annotate.c,v 1.57 2008/03/02 19:14:10 tobias Exp $ */ 2e0d97c9bSxsa /* 35e4c4390Stobias * Copyright (c) 2007 Tobias Stoeckmann <tobias@openbsd.org> 4efaa2accStobias * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org> 5e0d97c9bSxsa * 6e0d97c9bSxsa * Permission to use, copy, modify, and distribute this software for any 7e0d97c9bSxsa * purpose with or without fee is hereby granted, provided that the above 8e0d97c9bSxsa * copyright notice and this permission notice appear in all copies. 9e0d97c9bSxsa * 10e0d97c9bSxsa * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11e0d97c9bSxsa * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12e0d97c9bSxsa * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13e0d97c9bSxsa * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14e0d97c9bSxsa * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15e0d97c9bSxsa * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16e0d97c9bSxsa * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17e0d97c9bSxsa */ 18e0d97c9bSxsa 191f8531bdSotto #include <sys/param.h> 201f8531bdSotto #include <sys/dirent.h> 215e4c4390Stobias 22e9658789Stobias #include <errno.h> 235e4c4390Stobias #include <stdlib.h> 245e4c4390Stobias #include <string.h> 255e4c4390Stobias #include <time.h> 261f8531bdSotto #include <unistd.h> 27e0d97c9bSxsa 28e0d97c9bSxsa #include "cvs.h" 29e0d97c9bSxsa #include "remote.h" 30e0d97c9bSxsa 31e0d97c9bSxsa void cvs_annotate_local(struct cvs_file *); 32e0d97c9bSxsa 3337cbc181Stobias extern char *cvs_specified_tag; 3437cbc181Stobias 355466b72fStobias static char *dateflag; 36e0d97c9bSxsa static int force_head = 0; 37e0d97c9bSxsa 38e0d97c9bSxsa struct cvs_cmd cvs_cmd_annotate = { 39f331ff59Stobias CVS_OP_ANNOTATE, CVS_USE_WDIR, "annotate", 40e0d97c9bSxsa { "ann", "blame" }, 41e0d97c9bSxsa "Show last revision where each line was modified", 42e0d97c9bSxsa "[-flR] [-D date | -r rev] [file ...]", 43e0d97c9bSxsa "D:flRr:", 44e0d97c9bSxsa NULL, 45e0d97c9bSxsa cvs_annotate 46e0d97c9bSxsa }; 47e0d97c9bSxsa 48e9658789Stobias struct cvs_cmd cvs_cmd_rannotate = { 49e9658789Stobias CVS_OP_RANNOTATE, 0, "rannotate", 50e9658789Stobias { "rann", "ra" }, 51e9658789Stobias "Show last revision where each line was modified", 52b92e16f1Sxsa "[-flR] [-D date | -r rev] module ...", 53e9658789Stobias "D:flRr:", 54e9658789Stobias NULL, 55e9658789Stobias cvs_annotate 56e9658789Stobias }; 57e9658789Stobias 58e0d97c9bSxsa int 59e0d97c9bSxsa cvs_annotate(int argc, char **argv) 60e0d97c9bSxsa { 61e0d97c9bSxsa int ch, flags; 62e0d97c9bSxsa char *arg = "."; 63e0d97c9bSxsa struct cvs_recursion cr; 64e0d97c9bSxsa 65e0d97c9bSxsa flags = CR_RECURSE_DIRS; 66e0d97c9bSxsa 67e9d83458Stobias while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_ANNOTATE ? 68e9d83458Stobias cvs_cmd_annotate.cmd_opts : cvs_cmd_rannotate.cmd_opts)) != -1) { 69e0d97c9bSxsa switch (ch) { 70e0d97c9bSxsa case 'D': 715466b72fStobias dateflag = optarg; 725466b72fStobias cvs_specified_date = cvs_date_parse(dateflag); 73e0d97c9bSxsa break; 74e0d97c9bSxsa case 'f': 75e0d97c9bSxsa force_head = 1; 76e0d97c9bSxsa break; 77e0d97c9bSxsa case 'l': 78e0d97c9bSxsa flags &= ~CR_RECURSE_DIRS; 79e0d97c9bSxsa break; 80e0d97c9bSxsa case 'R': 81bcf22459Stobias flags |= CR_RECURSE_DIRS; 82e0d97c9bSxsa break; 83e0d97c9bSxsa case 'r': 84efaa2accStobias cvs_specified_tag = optarg; 85e0d97c9bSxsa break; 86e0d97c9bSxsa default: 87aeaaf4a6Stobias fatal("%s", cvs_cmdop == CVS_OP_ANNOTATE ? 88aeaaf4a6Stobias cvs_cmd_annotate.cmd_synopsis : 89aeaaf4a6Stobias cvs_cmd_rannotate.cmd_synopsis); 90e0d97c9bSxsa } 91e0d97c9bSxsa } 92e0d97c9bSxsa 93e0d97c9bSxsa argc -= optind; 94e0d97c9bSxsa argv += optind; 95e0d97c9bSxsa 96*5238b68eStobias if (cvs_cmdop == CVS_OP_RANNOTATE) { 97*5238b68eStobias if (argc == 0) 98*5238b68eStobias fatal("%s", cvs_cmd_rannotate.cmd_synopsis); 99*5238b68eStobias 100e9658789Stobias flags |= CR_REPO; 101*5238b68eStobias } 102e9658789Stobias 103e0d97c9bSxsa cr.enterdir = NULL; 104e0d97c9bSxsa cr.leavedir = NULL; 105e0d97c9bSxsa 106e0d97c9bSxsa if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 10780f6ca9bSjoris cvs_client_connect_to_server(); 108e0d97c9bSxsa cr.fileproc = cvs_client_sendfile; 109e0d97c9bSxsa 1105466b72fStobias if (dateflag != NULL) 1115466b72fStobias cvs_client_send_request("Argument -D%s", dateflag); 1125466b72fStobias 113e0d97c9bSxsa if (force_head == 1) 114e0d97c9bSxsa cvs_client_send_request("Argument -f"); 115e0d97c9bSxsa 116e0d97c9bSxsa if (!(flags & CR_RECURSE_DIRS)) 117e0d97c9bSxsa cvs_client_send_request("Argument -l"); 118e0d97c9bSxsa 119efaa2accStobias if (cvs_specified_tag != NULL) 120efaa2accStobias cvs_client_send_request("Argument -r%s", 121efaa2accStobias cvs_specified_tag); 122e0d97c9bSxsa } else { 123e9658789Stobias if (cvs_cmdop == CVS_OP_RANNOTATE && 124e9658789Stobias chdir(current_cvsroot->cr_dir) == -1) 125e9658789Stobias fatal("cvs_annotate: %s", strerror(errno)); 126e9658789Stobias 127e0d97c9bSxsa cr.fileproc = cvs_annotate_local; 128e0d97c9bSxsa } 129e0d97c9bSxsa 130e0d97c9bSxsa cr.flags = flags; 131e0d97c9bSxsa 132e9658789Stobias if (cvs_cmdop == CVS_OP_ANNOTATE || 133e9658789Stobias current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 134e0d97c9bSxsa if (argc > 0) 135e0d97c9bSxsa cvs_file_run(argc, argv, &cr); 136e0d97c9bSxsa else 137e0d97c9bSxsa cvs_file_run(1, &arg, &cr); 138e9658789Stobias } 139e0d97c9bSxsa 140e0d97c9bSxsa if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 141e0d97c9bSxsa cvs_client_send_files(argv, argc); 142e0d97c9bSxsa cvs_client_senddir("."); 143e9658789Stobias 144e9658789Stobias cvs_client_send_request((cvs_cmdop == CVS_OP_RANNOTATE) ? 145e9658789Stobias "rannotate" : "annotate"); 146e9658789Stobias 147e0d97c9bSxsa cvs_client_get_responses(); 148e0d97c9bSxsa } 149e0d97c9bSxsa 150e0d97c9bSxsa return (0); 151e0d97c9bSxsa } 152e0d97c9bSxsa 153e0d97c9bSxsa void 154e0d97c9bSxsa cvs_annotate_local(struct cvs_file *cf) 155e0d97c9bSxsa { 1565e4c4390Stobias int i; 1575e4c4390Stobias char date[10], rnum[13], *p; 15837cbc181Stobias RCSNUM *bnum, *rev; 159efaa2accStobias struct cvs_line *line; 1605e4c4390Stobias struct cvs_line **alines; 1615e4c4390Stobias 162e0d97c9bSxsa cvs_log(LP_TRACE, "cvs_annotate_local(%s)", cf->file_path); 163e0d97c9bSxsa 16451ef6581Sjoris cvs_file_classify(cf, cvs_directory_tag); 165e0d97c9bSxsa 1665e4c4390Stobias if (cf->file_status == FILE_UNKNOWN || cf->file_status == FILE_UNLINK || 1675e4c4390Stobias cf->file_type != CVS_FILE) 168e0d97c9bSxsa return; 169e0d97c9bSxsa 17037cbc181Stobias if (cvs_specified_tag != NULL) { 171f1f4c0cfStobias if ((rev = rcs_translate_tag(cvs_specified_tag, 172f1f4c0cfStobias cf->file_rcs)) == NULL) { 173f1f4c0cfStobias if (!force_head) 174f1f4c0cfStobias /* Stick at weird GNU cvs, ignore error. */ 175f1f4c0cfStobias return; 17637cbc181Stobias 177b03d8731Stobias /* -f is not allowed for unknown symbols */ 178b03d8731Stobias rev = rcsnum_parse(cvs_specified_tag); 179b03d8731Stobias if (rev == NULL) 180b03d8731Stobias fatal("no such tag %s", cvs_specified_tag); 181b03d8731Stobias rcsnum_free(rev); 182f1f4c0cfStobias rev = rcsnum_alloc(); 183f1f4c0cfStobias rcsnum_cpy(cf->file_rcs->rf_head, rev, 0); 184f1f4c0cfStobias } 1855e4c4390Stobias 18637cbc181Stobias /* 18737cbc181Stobias * If this is a revision in a branch, we have to go first 18837cbc181Stobias * from HEAD to branch, then down to 1.1. After that, take 18937cbc181Stobias * annotated branch and go up to branch revision. This must 19037cbc181Stobias * be done this way due to different handling of "a" and 19137cbc181Stobias * "d" in rcs file for annotation. 19237cbc181Stobias */ 19337cbc181Stobias if (!RCSNUM_ISBRANCHREV(rev)) { 19437cbc181Stobias bnum = rev; 19537cbc181Stobias } else { 19637cbc181Stobias bnum = rcsnum_alloc(); 19737cbc181Stobias rcsnum_cpy(rev, bnum, 2); 1985e4c4390Stobias } 19937cbc181Stobias 20037cbc181Stobias rcs_rev_getlines(cf->file_rcs, bnum, &alines); 20137cbc181Stobias 20237cbc181Stobias /* 20337cbc181Stobias * Go into branch and receive annotations for branch revision, 20437cbc181Stobias * with inverted "a" and "d" meaning. 20537cbc181Stobias */ 20637cbc181Stobias if (bnum != rev) { 20737cbc181Stobias rcs_annotate_getlines(cf->file_rcs, rev, &alines); 20837cbc181Stobias rcsnum_free(bnum); 2095e4c4390Stobias } 210efaa2accStobias rcsnum_free(rev); 21137cbc181Stobias } else { 2127f0c79fcSjoris rcs_rev_getlines(cf->file_rcs, (cvs_specified_date != -1) ? 213556b17e7Stobias cf->file_rcsrev : cf->file_rcs->rf_head, &alines); 2145e4c4390Stobias } 2155e4c4390Stobias 2165e4c4390Stobias /* Stick at weird GNU cvs, ignore error. */ 2175e4c4390Stobias if (alines == NULL) 2185e4c4390Stobias return; 2195e4c4390Stobias 2205e4c4390Stobias cvs_log(LP_RCS, "Annotations for %s", cf->file_path); 2215e4c4390Stobias cvs_log(LP_RCS, "***************"); 2225e4c4390Stobias 2235e4c4390Stobias for (i = 0; alines[i] != NULL; i++) { 224efaa2accStobias line = alines[i]; 2255e4c4390Stobias 226efaa2accStobias rcsnum_tostr(line->l_delta->rd_num, rnum, sizeof(rnum)); 227efaa2accStobias strftime(date, sizeof(date), "%d-%b-%y", 228efaa2accStobias &(line->l_delta->rd_date)); 229efaa2accStobias if (line->l_len && line->l_line[line->l_len - 1] == '\n') 230efaa2accStobias line->l_line[line->l_len - 1] = '\0'; 231efaa2accStobias else { 232efaa2accStobias p = xmalloc(line->l_len + 1); 233efaa2accStobias memcpy(p, line->l_line, line->l_len); 234efaa2accStobias p[line->l_len] = '\0'; 235efaa2accStobias 236efaa2accStobias if (line->l_needsfree) 237efaa2accStobias xfree(line->l_line); 238efaa2accStobias line->l_line = p; 239efaa2accStobias line->l_len++; 240efaa2accStobias line->l_needsfree = 1; 2415e4c4390Stobias } 2425e4c4390Stobias cvs_printf("%-12.12s (%-8.8s %s): %s\n", rnum, 243efaa2accStobias line->l_delta->rd_author, date, line->l_line); 2445e4c4390Stobias 245efaa2accStobias if (line->l_needsfree) 246efaa2accStobias xfree(line->l_line); 247efaa2accStobias xfree(line); 2485e4c4390Stobias } 2495e4c4390Stobias 2505e4c4390Stobias xfree(alines); 251e0d97c9bSxsa } 252