1 /* $OpenBSD: annotate.c,v 1.69 2017/06/01 08:08:24 joris 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/types.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 int force_head = 0;
36
37 struct cvs_cmd cvs_cmd_annotate = {
38 CVS_OP_ANNOTATE, CVS_USE_WDIR, "annotate",
39 { "ann", "blame" },
40 "Show last revision where each line was modified",
41 "[-flR] [-D date | -r rev] [file ...]",
42 "D:flRr:",
43 NULL,
44 cvs_annotate
45 };
46
47 struct cvs_cmd cvs_cmd_rannotate = {
48 CVS_OP_RANNOTATE, 0, "rannotate",
49 { "rann", "ra" },
50 "Show last revision where each line was modified",
51 "[-flR] [-D date | -r rev] module ...",
52 "D:flRr:",
53 NULL,
54 cvs_annotate
55 };
56
57 int
cvs_annotate(int argc,char ** argv)58 cvs_annotate(int argc, char **argv)
59 {
60 int ch, flags;
61 char *arg = ".";
62 char *dateflag = NULL;
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 if ((cvs_specified_date = date_parse(dateflag)) == -1)
73 fatal("invalid date: %s", dateflag);
74 break;
75 case 'f':
76 force_head = 1;
77 break;
78 case 'l':
79 flags &= ~CR_RECURSE_DIRS;
80 break;
81 case 'R':
82 flags |= CR_RECURSE_DIRS;
83 break;
84 case 'r':
85 cvs_specified_tag = optarg;
86 break;
87 default:
88 fatal("%s", cvs_cmdop == CVS_OP_ANNOTATE ?
89 cvs_cmd_annotate.cmd_synopsis :
90 cvs_cmd_rannotate.cmd_synopsis);
91 }
92 }
93
94 argc -= optind;
95 argv += optind;
96
97 if (cvs_cmdop == CVS_OP_RANNOTATE) {
98 if (argc == 0)
99 fatal("%s", cvs_cmd_rannotate.cmd_synopsis);
100
101 flags |= CR_REPO;
102 }
103
104 cr.enterdir = NULL;
105 cr.leavedir = NULL;
106
107 if (cvsroot_is_remote()) {
108 cvs_client_connect_to_server();
109 cr.fileproc = cvs_client_sendfile;
110
111 if (dateflag != NULL)
112 cvs_client_send_request("Argument -D%s", dateflag);
113
114 if (force_head == 1)
115 cvs_client_send_request("Argument -f");
116
117 if (!(flags & CR_RECURSE_DIRS))
118 cvs_client_send_request("Argument -l");
119
120 if (cvs_specified_tag != NULL)
121 cvs_client_send_request("Argument -r%s",
122 cvs_specified_tag);
123 } else {
124 if (cvs_cmdop == CVS_OP_RANNOTATE &&
125 chdir(current_cvsroot->cr_dir) == -1)
126 fatal("cvs_annotate: %s", strerror(errno));
127
128 cr.fileproc = cvs_annotate_local;
129 }
130
131 cr.flags = flags;
132
133 if (cvs_cmdop == CVS_OP_ANNOTATE || cvsroot_is_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 (cvsroot_is_remote()) {
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
cvs_annotate_local(struct cvs_file * cf)154 cvs_annotate_local(struct cvs_file *cf)
155 {
156 int i;
157 u_char *p;
158 char date[10], rnum[13];
159 RCSNUM *bnum, *rev;
160 struct rcs_line *line;
161 struct rcs_line **alines;
162
163 cvs_log(LP_TRACE, "cvs_annotate_local(%s)", cf->file_path);
164
165 cvs_file_classify(cf, cvs_directory_tag);
166
167 if (cf->file_rcs == NULL || cf->file_rcs->rf_head == NULL)
168 return;
169
170 if (cvs_specified_tag != NULL) {
171 if ((rev = rcs_translate_tag(cvs_specified_tag,
172 cf->file_rcs)) == NULL) {
173 if (!force_head)
174 /* Stick at weird GNU cvs, ignore error. */
175 return;
176
177 /* -f is not allowed for unknown symbols */
178 rev = rcsnum_parse(cvs_specified_tag);
179 if (rev == NULL)
180 fatal("no such tag %s", cvs_specified_tag);
181 free(rev);
182 rev = rcsnum_alloc();
183 rcsnum_cpy(cf->file_rcs->rf_head, rev, 0);
184 }
185
186 /*
187 * If this is a revision in a branch, we have to go first
188 * from HEAD to branch, then down to 1.1. After that, take
189 * annotated branch and go up to branch revision. This must
190 * be done this way due to different handling of "a" and
191 * "d" in rcs file for annotation.
192 */
193 if (!RCSNUM_ISBRANCHREV(rev)) {
194 bnum = rev;
195 } else {
196 bnum = rcsnum_alloc();
197 rcsnum_cpy(rev, bnum, 2);
198 }
199
200 rcs_rev_getlines(cf->file_rcs, bnum, &alines);
201
202 /*
203 * Go into branch and receive annotations for branch revision,
204 * with inverted "a" and "d" meaning.
205 */
206 if (bnum != rev) {
207 rcs_annotate_getlines(cf->file_rcs, rev, &alines);
208 free(bnum);
209 }
210 free(rev);
211 } else {
212 rcs_rev_getlines(cf->file_rcs, (cvs_specified_date != -1 ||
213 cvs_directory_date != -1) ? cf->file_rcsrev :
214 cf->file_rcs->rf_head, &alines);
215 }
216
217 /* Stick at weird GNU cvs, ignore error. */
218 if (alines == NULL)
219 return;
220
221 cvs_log(LP_RCS, "Annotations for %s", cf->file_path);
222 cvs_log(LP_RCS, "***************");
223
224 for (i = 0; alines[i] != NULL; i++) {
225 line = alines[i];
226
227 rcsnum_tostr(line->l_delta->rd_num, rnum, sizeof(rnum));
228 strftime(date, sizeof(date), "%d-%b-%y",
229 &(line->l_delta->rd_date));
230 if (line->l_len && line->l_line[line->l_len - 1] == '\n')
231 line->l_line[line->l_len - 1] = '\0';
232 else {
233 p = xmalloc(line->l_len + 1);
234 memcpy(p, line->l_line, line->l_len);
235 p[line->l_len] = '\0';
236
237 if (line->l_needsfree)
238 free(line->l_line);
239 line->l_line = p;
240 line->l_len++;
241 line->l_needsfree = 1;
242 }
243 cvs_printf("%-12.12s (%-8.8s %s): %s\n", rnum,
244 line->l_delta->rd_author, date, line->l_line);
245
246 if (line->l_needsfree)
247 free(line->l_line);
248 free(line);
249 }
250
251 free(alines);
252 }
253