1*4dcde513Sjoris /* $OpenBSD: annotate.c,v 1.69 2017/06/01 08:08:24 joris 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
19b9fc9a72Sderaadt #include <sys/types.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
35e0d97c9bSxsa static int force_head = 0;
36e0d97c9bSxsa
37e0d97c9bSxsa struct cvs_cmd cvs_cmd_annotate = {
38f331ff59Stobias CVS_OP_ANNOTATE, CVS_USE_WDIR, "annotate",
39e0d97c9bSxsa { "ann", "blame" },
40e0d97c9bSxsa "Show last revision where each line was modified",
41e0d97c9bSxsa "[-flR] [-D date | -r rev] [file ...]",
42e0d97c9bSxsa "D:flRr:",
43e0d97c9bSxsa NULL,
44e0d97c9bSxsa cvs_annotate
45e0d97c9bSxsa };
46e0d97c9bSxsa
47e9658789Stobias struct cvs_cmd cvs_cmd_rannotate = {
48e9658789Stobias CVS_OP_RANNOTATE, 0, "rannotate",
49e9658789Stobias { "rann", "ra" },
50e9658789Stobias "Show last revision where each line was modified",
51b92e16f1Sxsa "[-flR] [-D date | -r rev] module ...",
52e9658789Stobias "D:flRr:",
53e9658789Stobias NULL,
54e9658789Stobias cvs_annotate
55e9658789Stobias };
56e9658789Stobias
57e0d97c9bSxsa int
cvs_annotate(int argc,char ** argv)58e0d97c9bSxsa cvs_annotate(int argc, char **argv)
59e0d97c9bSxsa {
60e0d97c9bSxsa int ch, flags;
61e0d97c9bSxsa char *arg = ".";
6263c546bcSnicm char *dateflag = NULL;
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;
72092db204Sray if ((cvs_specified_date = date_parse(dateflag)) == -1)
73092db204Sray fatal("invalid date: %s", dateflag);
74e0d97c9bSxsa break;
75e0d97c9bSxsa case 'f':
76e0d97c9bSxsa force_head = 1;
77e0d97c9bSxsa break;
78e0d97c9bSxsa case 'l':
79e0d97c9bSxsa flags &= ~CR_RECURSE_DIRS;
80e0d97c9bSxsa break;
81e0d97c9bSxsa case 'R':
82bcf22459Stobias flags |= CR_RECURSE_DIRS;
83e0d97c9bSxsa break;
84e0d97c9bSxsa case 'r':
85efaa2accStobias cvs_specified_tag = optarg;
86e0d97c9bSxsa break;
87e0d97c9bSxsa default:
88aeaaf4a6Stobias fatal("%s", cvs_cmdop == CVS_OP_ANNOTATE ?
89aeaaf4a6Stobias cvs_cmd_annotate.cmd_synopsis :
90aeaaf4a6Stobias cvs_cmd_rannotate.cmd_synopsis);
91e0d97c9bSxsa }
92e0d97c9bSxsa }
93e0d97c9bSxsa
94e0d97c9bSxsa argc -= optind;
95e0d97c9bSxsa argv += optind;
96e0d97c9bSxsa
975238b68eStobias if (cvs_cmdop == CVS_OP_RANNOTATE) {
985238b68eStobias if (argc == 0)
995238b68eStobias fatal("%s", cvs_cmd_rannotate.cmd_synopsis);
1005238b68eStobias
101e9658789Stobias flags |= CR_REPO;
1025238b68eStobias }
103e9658789Stobias
104e0d97c9bSxsa cr.enterdir = NULL;
105e0d97c9bSxsa cr.leavedir = NULL;
106e0d97c9bSxsa
107*4dcde513Sjoris if (cvsroot_is_remote()) {
10880f6ca9bSjoris cvs_client_connect_to_server();
109e0d97c9bSxsa cr.fileproc = cvs_client_sendfile;
110e0d97c9bSxsa
1115466b72fStobias if (dateflag != NULL)
1125466b72fStobias cvs_client_send_request("Argument -D%s", dateflag);
1135466b72fStobias
114e0d97c9bSxsa if (force_head == 1)
115e0d97c9bSxsa cvs_client_send_request("Argument -f");
116e0d97c9bSxsa
117e0d97c9bSxsa if (!(flags & CR_RECURSE_DIRS))
118e0d97c9bSxsa cvs_client_send_request("Argument -l");
119e0d97c9bSxsa
120efaa2accStobias if (cvs_specified_tag != NULL)
121efaa2accStobias cvs_client_send_request("Argument -r%s",
122efaa2accStobias cvs_specified_tag);
123e0d97c9bSxsa } else {
124e9658789Stobias if (cvs_cmdop == CVS_OP_RANNOTATE &&
125e9658789Stobias chdir(current_cvsroot->cr_dir) == -1)
126e9658789Stobias fatal("cvs_annotate: %s", strerror(errno));
127e9658789Stobias
128e0d97c9bSxsa cr.fileproc = cvs_annotate_local;
129e0d97c9bSxsa }
130e0d97c9bSxsa
131e0d97c9bSxsa cr.flags = flags;
132e0d97c9bSxsa
133*4dcde513Sjoris if (cvs_cmdop == CVS_OP_ANNOTATE || cvsroot_is_local()) {
134e0d97c9bSxsa if (argc > 0)
135e0d97c9bSxsa cvs_file_run(argc, argv, &cr);
136e0d97c9bSxsa else
137e0d97c9bSxsa cvs_file_run(1, &arg, &cr);
138e9658789Stobias }
139e0d97c9bSxsa
140*4dcde513Sjoris if (cvsroot_is_remote()) {
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
cvs_annotate_local(struct cvs_file * cf)154e0d97c9bSxsa cvs_annotate_local(struct cvs_file *cf)
155e0d97c9bSxsa {
1565e4c4390Stobias int i;
157fd858170Sfcambus u_char *p;
158fd858170Sfcambus char date[10], rnum[13];
15937cbc181Stobias RCSNUM *bnum, *rev;
1607bb3ddb0Sray struct rcs_line *line;
1617bb3ddb0Sray struct rcs_line **alines;
1625e4c4390Stobias
163e0d97c9bSxsa cvs_log(LP_TRACE, "cvs_annotate_local(%s)", cf->file_path);
164e0d97c9bSxsa
16551ef6581Sjoris cvs_file_classify(cf, cvs_directory_tag);
166e0d97c9bSxsa
167e28eda4eStobias if (cf->file_rcs == NULL || cf->file_rcs->rf_head == NULL)
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);
18153ce2177Sfcambus 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);
20853ce2177Sfcambus free(bnum);
2095e4c4390Stobias }
21053ce2177Sfcambus free(rev);
21137cbc181Stobias } else {
212b87788a5Stobias rcs_rev_getlines(cf->file_rcs, (cvs_specified_date != -1 ||
213b87788a5Stobias cvs_directory_date != -1) ? cf->file_rcsrev :
214b87788a5Stobias cf->file_rcs->rf_head, &alines);
2155e4c4390Stobias }
2165e4c4390Stobias
2175e4c4390Stobias /* Stick at weird GNU cvs, ignore error. */
2185e4c4390Stobias if (alines == NULL)
2195e4c4390Stobias return;
2205e4c4390Stobias
2215e4c4390Stobias cvs_log(LP_RCS, "Annotations for %s", cf->file_path);
2225e4c4390Stobias cvs_log(LP_RCS, "***************");
2235e4c4390Stobias
2245e4c4390Stobias for (i = 0; alines[i] != NULL; i++) {
225efaa2accStobias line = alines[i];
2265e4c4390Stobias
227efaa2accStobias rcsnum_tostr(line->l_delta->rd_num, rnum, sizeof(rnum));
228efaa2accStobias strftime(date, sizeof(date), "%d-%b-%y",
229efaa2accStobias &(line->l_delta->rd_date));
230efaa2accStobias if (line->l_len && line->l_line[line->l_len - 1] == '\n')
231efaa2accStobias line->l_line[line->l_len - 1] = '\0';
232efaa2accStobias else {
233efaa2accStobias p = xmalloc(line->l_len + 1);
234efaa2accStobias memcpy(p, line->l_line, line->l_len);
235efaa2accStobias p[line->l_len] = '\0';
236efaa2accStobias
237efaa2accStobias if (line->l_needsfree)
238397ddb8aSnicm free(line->l_line);
239efaa2accStobias line->l_line = p;
240efaa2accStobias line->l_len++;
241efaa2accStobias line->l_needsfree = 1;
2425e4c4390Stobias }
2435e4c4390Stobias cvs_printf("%-12.12s (%-8.8s %s): %s\n", rnum,
244efaa2accStobias line->l_delta->rd_author, date, line->l_line);
2455e4c4390Stobias
246efaa2accStobias if (line->l_needsfree)
247397ddb8aSnicm free(line->l_line);
248397ddb8aSnicm free(line);
2495e4c4390Stobias }
2505e4c4390Stobias
251397ddb8aSnicm free(alines);
252e0d97c9bSxsa }
253