xref: /openbsd-src/usr.bin/cvs/getlog.c (revision d874cce4b1d9fe6b41c9e4f2117a77d8a4a37b92)
1 /*	$OpenBSD: getlog.c,v 1.89 2008/06/14 04:34:08 tobias Exp $	*/
2 /*
3  * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
4  * Copyright (c) 2006 Joris Vink <joris@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 <unistd.h>
20 #include <string.h>
21 #include <errno.h>
22 
23 #include "cvs.h"
24 #include "remote.h"
25 
26 #define L_HEAD		0x01
27 #define L_HEAD_DESCR	0x02
28 #define L_NAME		0x04
29 #define L_NOTAGS	0x08
30 #define L_LOGINS	0x10
31 #define L_STATES	0x20
32 
33 void	cvs_log_local(struct cvs_file *);
34 
35 static void	log_rev_print(struct rcs_delta *);
36 
37 int	 runflags = 0;
38 char	*logrev = NULL;
39 char	*slist = NULL;
40 char	*wlist = NULL;
41 
42 struct cvs_cmd cvs_cmd_log = {
43 	CVS_OP_LOG, CVS_USE_WDIR, "log",
44 	{ "lo" },
45 	"Print out history information for files",
46 	"[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]",
47 	"bd:hlNRr:s:tw:",
48 	NULL,
49 	cvs_getlog
50 };
51 
52 struct cvs_cmd cvs_cmd_rlog = {
53 	CVS_OP_RLOG, 0, "rlog",
54 	{ "rlo" },
55 	"Print out history information for files",
56 	"[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]",
57 	"bd:hlNRr:s:tw:",
58 	NULL,
59 	cvs_getlog
60 };
61 
62 int
63 cvs_getlog(int argc, char **argv)
64 {
65 	int ch, flags, i;
66 	char *arg = ".";
67 	struct cvs_recursion cr;
68 
69 	flags = CR_RECURSE_DIRS;
70 
71 	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_LOG ?
72 	    cvs_cmd_log.cmd_opts : cvs_cmd_rlog.cmd_opts)) != -1) {
73 		switch (ch) {
74 		case 'h':
75 			runflags |= L_HEAD;
76 			break;
77 		case 'l':
78 			flags &= ~CR_RECURSE_DIRS;
79 			break;
80 		case 'N':
81 			runflags |= L_NOTAGS;
82 			break;
83 		case 'R':
84 			runflags |= L_NAME;
85 			break;
86 		case 'r':
87 			logrev = optarg;
88 			break;
89 		case 's':
90 			runflags |= L_STATES;
91 			slist = optarg;
92 			break;
93 		case 't':
94 			runflags |= L_HEAD_DESCR;
95 			break;
96 		case 'w':
97 			runflags |= L_LOGINS;
98 			wlist = optarg;
99 			break;
100 		default:
101 			fatal("%s", cvs_cmdop == CVS_OP_LOG ?
102 			    cvs_cmd_log.cmd_synopsis :
103 			    cvs_cmd_rlog.cmd_synopsis);
104 		}
105 	}
106 
107 	argc -= optind;
108 	argv += optind;
109 
110 	if (cvs_cmdop == CVS_OP_RLOG) {
111 		flags |= CR_REPO;
112 
113 		if (argc == 0)
114 			return 0;
115 
116 		for (i = 0; i < argc; i++)
117 			if (argv[i][0] == '/')
118 				fatal("Absolute path name is invalid: %s",
119 				    argv[i]);
120 	}
121 
122 	cr.enterdir = NULL;
123 	cr.leavedir = NULL;
124 
125 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
126 		cvs_client_connect_to_server();
127 		cr.fileproc = cvs_client_sendfile;
128 
129 		if (runflags & L_HEAD)
130 			cvs_client_send_request("Argument -h");
131 
132 		if (!(flags & CR_RECURSE_DIRS))
133 			cvs_client_send_request("Argument -l");
134 
135 		if (runflags & L_NOTAGS)
136 			cvs_client_send_request("Argument -N");
137 
138 		if (runflags & L_NAME)
139 			cvs_client_send_request("Argument -R");
140 
141 		if (logrev != NULL)
142 			cvs_client_send_request("Argument -r%s", logrev);
143 
144 		if (runflags & L_STATES)
145 			cvs_client_send_request("Argument -s%s", slist);
146 
147 		if (runflags & L_HEAD_DESCR)
148 			cvs_client_send_request("Argument -t");
149 
150 		if (runflags & L_LOGINS)
151 			cvs_client_send_request("Argument -w%s", wlist);
152 	} else {
153 		if (cvs_cmdop == CVS_OP_RLOG &&
154 		    chdir(current_cvsroot->cr_dir) == -1)
155 			fatal("cvs_getlog: %s", strerror(errno));
156 
157 		cr.fileproc = cvs_log_local;
158 	}
159 
160 	cr.flags = flags;
161 
162 	if (cvs_cmdop == CVS_OP_LOG ||
163 	    current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
164 		if (argc > 0)
165 			cvs_file_run(argc, argv, &cr);
166 		else
167 			cvs_file_run(1, &arg, &cr);
168 	}
169 
170 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
171 		cvs_client_send_files(argv, argc);
172 		cvs_client_senddir(".");
173 
174 		cvs_client_send_request((cvs_cmdop == CVS_OP_RLOG) ?
175 		    "rlog" : "log");
176 
177 		cvs_client_get_responses();
178 	}
179 
180 	return (0);
181 }
182 
183 void
184 cvs_log_local(struct cvs_file *cf)
185 {
186 	u_int nrev;
187 	RCSNUM *rev;
188 	struct rcs_sym *sym;
189 	struct rcs_lock *lkp;
190 	struct rcs_delta *rdp;
191 	struct rcs_access *acp;
192 	char numb[CVS_REV_BUFSZ];
193 
194 	cvs_log(LP_TRACE, "cvs_log_local(%s)", cf->file_path);
195 
196 	cvs_file_classify(cf, cvs_directory_tag);
197 
198 	if (cf->file_rcs == NULL) {
199 		return;
200 	} else if (cf->file_status == FILE_ADDED) {
201 		if (verbosity > 0)
202 			cvs_log(LP_ERR, "%s has been added, but not committed",
203 			    cf->file_path);
204 		return;
205 	}
206 
207 	if (cf->file_type == CVS_DIR) {
208 		if (verbosity > 1)
209 			cvs_log(LP_NOTICE, "Logging %s", cf->file_path);
210 		return;
211 	}
212 
213 	if (runflags & L_NAME) {
214 		cvs_printf("%s\n", cf->file_rpath);
215 		return;
216 	}
217 
218 	cvs_printf("\nRCS file: %s", cf->file_rpath);
219 
220 	if (cvs_cmdop != CVS_OP_RLOG)
221 		cvs_printf("\nWorking file: %s", cf->file_path);
222 
223 	cvs_printf("\nhead:");
224 	if (cf->file_rcs->rf_head != NULL)
225 		cvs_printf(" %s", rcsnum_tostr(cf->file_rcs->rf_head,
226 		    numb, sizeof(numb)));
227 
228 	cvs_printf("\nbranch:");
229 	if (rcs_branch_get(cf->file_rcs) != NULL) {
230 		cvs_printf(" %s", rcsnum_tostr(rcs_branch_get(cf->file_rcs),
231 		    numb, sizeof(numb)));
232 	}
233 
234 	cvs_printf("\nlocks: %s", (cf->file_rcs->rf_flags & RCS_SLOCK)
235 	    ? "strict" : "");
236 	TAILQ_FOREACH(lkp, &(cf->file_rcs->rf_locks), rl_list)
237 		cvs_printf("\n\t%s: %s", lkp->rl_name,
238 		    rcsnum_tostr(lkp->rl_num, numb, sizeof(numb)));
239 
240 	cvs_printf("\naccess list:\n");
241 	TAILQ_FOREACH(acp, &(cf->file_rcs->rf_access), ra_list)
242 		cvs_printf("\t%s\n", acp->ra_name);
243 
244 	if (!(runflags & L_NOTAGS)) {
245 		cvs_printf("symbolic names:\n");
246 		TAILQ_FOREACH(sym, &(cf->file_rcs->rf_symbols), rs_list) {
247 			rev = rcsnum_alloc();
248 			rcsnum_cpy(sym->rs_num, rev, 0);
249 			if (RCSNUM_ISBRANCH(sym->rs_num))
250 				rcsnum_addmagic(rev);
251 
252 			cvs_printf("\t%s: %s\n", sym->rs_name,
253 			    rcsnum_tostr(rev, numb, sizeof(numb)));
254 			rcsnum_free(rev);
255 		}
256 	}
257 
258 	cvs_printf("keyword substitution: %s\n",
259 	    cf->file_rcs->rf_expand == NULL ? "kv" : cf->file_rcs->rf_expand);
260 
261 	cvs_printf("total revisions: %u", cf->file_rcs->rf_ndelta);
262 
263 	if (logrev != NULL)
264 		nrev = cvs_revision_select(cf->file_rcs, logrev);
265 	else
266 		nrev = cf->file_rcs->rf_ndelta;
267 
268 	if (cf->file_rcs->rf_head != NULL &&
269 	    !(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR))
270 		cvs_printf(";\tselected revisions: %u", nrev);
271 
272 	cvs_printf("\n");
273 
274 	if (!(runflags & L_HEAD) || (runflags & L_HEAD_DESCR))
275 		cvs_printf("description:\n%s", cf->file_rcs->rf_desc);
276 
277 	if (!(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) {
278 		TAILQ_FOREACH(rdp, &(cf->file_rcs->rf_delta), rd_list) {
279 			/*
280 			 * if selections are enabled verify that entry is
281 			 * selected.
282 			 */
283 			if (logrev == NULL || (rdp->rd_flags & RCS_RD_SELECT))
284 				log_rev_print(rdp);
285 		}
286 	}
287 
288 	cvs_printf("%s\n", LOG_REVEND);
289 }
290 
291 static void
292 log_rev_print(struct rcs_delta *rdp)
293 {
294 	int i, found;
295 	char numb[CVS_REV_BUFSZ], timeb[CVS_TIME_BUFSZ];
296 	struct cvs_argvector *sargv, *wargv;
297 	struct rcs_branch *rb;
298 	struct rcs_delta *nrdp;
299 
300 	i = found = 0;
301 
302 	/* -s states */
303 	if (runflags & L_STATES) {
304 		sargv = cvs_strsplit(slist, ",");
305 		for (i = 0; sargv->argv[i] != NULL; i++) {
306 			if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) {
307 				found++;
308 				break;
309 			}
310 			found = 0;
311 		}
312 		cvs_argv_destroy(sargv);
313 	}
314 
315 	/* -w[logins] */
316 	if (runflags & L_LOGINS) {
317 		wargv = cvs_strsplit(wlist, ",");
318 		for (i = 0; wargv->argv[i] != NULL; i++) {
319 			if (strcmp(rdp->rd_author, wargv->argv[i]) == 0) {
320 				found++;
321 				break;
322 			}
323 			found = 0;
324 		}
325 		cvs_argv_destroy(wargv);
326 	}
327 
328 	if ((runflags & (L_STATES|L_LOGINS)) && found == 0)
329 		return;
330 
331 	cvs_printf("%s\n", LOG_REVSEP);
332 
333 	rcsnum_tostr(rdp->rd_num, numb, sizeof(numb));
334 	cvs_printf("revision %s", numb);
335 
336 	strftime(timeb, sizeof(timeb), "%Y/%m/%d %H:%M:%S", &rdp->rd_date);
337 	cvs_printf("\ndate: %s;  author: %s;  state: %s;",
338 	    timeb, rdp->rd_author, rdp->rd_state);
339 
340 	/*
341 	 * If we are a branch revision, the diff of this revision is stored
342 	 * in place.
343 	 * Otherwise, it is stored in the previous revision as a reversed diff.
344 	 */
345 	if (RCSNUM_ISBRANCHREV(rdp->rd_num))
346 		nrdp = rdp;
347 	else
348 		nrdp = TAILQ_NEXT(rdp, rd_list);
349 
350 	/*
351 	 * We do not write diff stats for the first revision of the default
352 	 * branch, since it was not a diff but a full text.
353 	 */
354 	if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) {
355 		int added, removed;
356 		rcs_delta_stats(nrdp, &added, &removed);
357 		if (RCSNUM_ISBRANCHREV(rdp->rd_num))
358 			cvs_printf("  lines: +%d -%d", added, removed);
359 		else
360 			cvs_printf("  lines: +%d -%d", removed, added);
361 	}
362 	cvs_printf("\n");
363 
364 	if (!TAILQ_EMPTY(&(rdp->rd_branches))) {
365 		cvs_printf("branches:");
366 		TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
367 			RCSNUM *branch;
368 			branch = rcsnum_revtobr(rb->rb_num);
369 			rcsnum_tostr(branch, numb, sizeof(numb));
370 			cvs_printf("  %s;", numb);
371 			rcsnum_free(branch);
372 		}
373 		cvs_printf("\n");
374 	}
375 
376 	cvs_printf("%s", rdp->rd_log);
377 }
378