1/*	$OpenBSD: getlog.c,v 1.101 2017/06/01 08:08:24 joris 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 <stdlib.h>
21#include <string.h>
22#include <errno.h>
23#include <ctype.h>
24
25#include "cvs.h"
26#include "remote.h"
27
28#define L_HEAD		0x01
29#define L_HEAD_DESCR	0x02
30#define L_NAME		0x04
31#define L_NOTAGS	0x08
32#define L_LOGINS	0x10
33#define L_STATES	0x20
34
35#define LDATE_LATER	0x01
36#define LDATE_EARLIER	0x02
37#define LDATE_SINGLE	0x04
38#define LDATE_RANGE	0x08
39#define LDATE_INCLUSIVE	0x10
40
41void		 cvs_log_local(struct cvs_file *);
42static void	 log_rev_print(struct rcs_delta *);
43static char 	*push_date(char *dest, const char *);
44static u_int	 date_select(RCSFILE *, char *);
45
46int	 runflags = 0;
47char	*logrev = NULL;
48char	*logdate = NULL;
49char	*slist = NULL;
50char	*wlist = NULL;
51
52struct cvs_cmd cvs_cmd_log = {
53	CVS_OP_LOG, CVS_USE_WDIR, "log",
54	{ "lo" },
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
62struct cvs_cmd cvs_cmd_rlog = {
63	CVS_OP_RLOG, 0, "rlog",
64	{ "rlo" },
65	"Print out history information for files",
66	"[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]",
67	"bd:hlNRr:s:tw:",
68	NULL,
69	cvs_getlog
70};
71
72int
73cvs_getlog(int argc, char **argv)
74{
75	int ch, flags, i;
76	char *arg = ".";
77	struct cvs_recursion cr;
78
79	flags = CR_RECURSE_DIRS;
80
81	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_LOG ?
82	    cvs_cmd_log.cmd_opts : cvs_cmd_rlog.cmd_opts)) != -1) {
83		switch (ch) {
84		case 'd':
85			logdate = push_date(logdate, optarg);
86			break;
87		case 'h':
88			runflags |= L_HEAD;
89			break;
90		case 'l':
91			flags &= ~CR_RECURSE_DIRS;
92			break;
93		case 'N':
94			runflags |= L_NOTAGS;
95			break;
96		case 'R':
97			runflags |= L_NAME;
98			break;
99		case 'r':
100			logrev = optarg;
101			break;
102		case 's':
103			runflags |= L_STATES;
104			slist = optarg;
105			break;
106		case 't':
107			runflags |= L_HEAD_DESCR;
108			break;
109		case 'w':
110			runflags |= L_LOGINS;
111			wlist = optarg;
112			break;
113		default:
114			fatal("%s", cvs_cmdop == CVS_OP_LOG ?
115			    cvs_cmd_log.cmd_synopsis :
116			    cvs_cmd_rlog.cmd_synopsis);
117		}
118	}
119
120	argc -= optind;
121	argv += optind;
122
123	if (cvs_cmdop == CVS_OP_RLOG) {
124		flags |= CR_REPO;
125
126		if (argc == 0)
127			return 0;
128
129		for (i = 0; i < argc; i++)
130			if (argv[i][0] == '/')
131				fatal("Absolute path name is invalid: %s",
132				    argv[i]);
133	}
134
135	cr.enterdir = NULL;
136	cr.leavedir = NULL;
137
138	if (cvsroot_is_remote()) {
139		cvs_client_connect_to_server();
140		cr.fileproc = cvs_client_sendfile;
141
142		if (logdate != NULL)
143			cvs_client_send_request("Argument -d%s", logdate);
144
145		if (runflags & L_HEAD)
146			cvs_client_send_request("Argument -h");
147
148		if (!(flags & CR_RECURSE_DIRS))
149			cvs_client_send_request("Argument -l");
150
151		if (runflags & L_NOTAGS)
152			cvs_client_send_request("Argument -N");
153
154		if (runflags & L_NAME)
155			cvs_client_send_request("Argument -R");
156
157		if (logrev != NULL)
158			cvs_client_send_request("Argument -r%s", logrev);
159
160		if (runflags & L_STATES)
161			cvs_client_send_request("Argument -s%s", slist);
162
163		if (runflags & L_HEAD_DESCR)
164			cvs_client_send_request("Argument -t");
165
166		if (runflags & L_LOGINS)
167			cvs_client_send_request("Argument -w%s", wlist);
168	} else {
169		if (cvs_cmdop == CVS_OP_RLOG &&
170		    chdir(current_cvsroot->cr_dir) == -1)
171			fatal("cvs_getlog: %s", strerror(errno));
172
173		cr.fileproc = cvs_log_local;
174	}
175
176	cr.flags = flags;
177
178	if (cvs_cmdop == CVS_OP_LOG || cvsroot_is_local()) {
179		if (argc > 0)
180			cvs_file_run(argc, argv, &cr);
181		else
182			cvs_file_run(1, &arg, &cr);
183	}
184
185	if (cvsroot_is_remote()) {
186		cvs_client_send_files(argv, argc);
187		cvs_client_senddir(".");
188
189		cvs_client_send_request((cvs_cmdop == CVS_OP_RLOG) ?
190		    "rlog" : "log");
191
192		cvs_client_get_responses();
193	}
194
195	return (0);
196}
197
198void
199cvs_log_local(struct cvs_file *cf)
200{
201	u_int nrev;
202	RCSNUM *rev;
203	struct rcs_sym *sym;
204	struct rcs_lock *lkp;
205	struct rcs_delta *rdp;
206	struct rcs_access *acp;
207	char numb[CVS_REV_BUFSZ];
208
209	cvs_log(LP_TRACE, "cvs_log_local(%s)", cf->file_path);
210
211	cvs_file_classify(cf, cvs_directory_tag);
212
213	if (cf->file_type == CVS_DIR) {
214		if (verbosity > 1)
215			cvs_log(LP_ERR, "Logging %s", cf->file_path);
216		return;
217	}
218
219	if (cf->file_rcs == NULL) {
220		return;
221	} else if (cf->file_status == FILE_ADDED) {
222		if (verbosity > 0)
223			cvs_log(LP_ERR, "%s has been added, but not committed",
224			    cf->file_path);
225		return;
226	}
227
228	if (runflags & L_NAME) {
229		cvs_printf("%s\n", cf->file_rpath);
230		return;
231	}
232
233	if (logrev != NULL)
234		nrev = cvs_revision_select(cf->file_rcs, logrev);
235	else if (logdate != NULL) {
236		if ((nrev = date_select(cf->file_rcs, logdate)) == (u_int)-1) {
237			cvs_log(LP_ERR, "invalid date: %s", logdate);
238			return;
239		}
240	} else
241		nrev = cf->file_rcs->rf_ndelta;
242
243	cvs_printf("\nRCS file: %s", cf->file_rpath);
244
245	if (cvs_cmdop != CVS_OP_RLOG)
246		cvs_printf("\nWorking file: %s", cf->file_path);
247
248	cvs_printf("\nhead:");
249	if (cf->file_rcs->rf_head != NULL)
250		cvs_printf(" %s", rcsnum_tostr(cf->file_rcs->rf_head,
251		    numb, sizeof(numb)));
252
253	cvs_printf("\nbranch:");
254	if (rcs_branch_get(cf->file_rcs) != NULL) {
255		cvs_printf(" %s", rcsnum_tostr(rcs_branch_get(cf->file_rcs),
256		    numb, sizeof(numb)));
257	}
258
259	cvs_printf("\nlocks: %s", (cf->file_rcs->rf_flags & RCS_SLOCK)
260	    ? "strict" : "");
261	TAILQ_FOREACH(lkp, &(cf->file_rcs->rf_locks), rl_list)
262		cvs_printf("\n\t%s: %s", lkp->rl_name,
263		    rcsnum_tostr(lkp->rl_num, numb, sizeof(numb)));
264
265	cvs_printf("\naccess list:\n");
266	TAILQ_FOREACH(acp, &(cf->file_rcs->rf_access), ra_list)
267		cvs_printf("\t%s\n", acp->ra_name);
268
269	if (!(runflags & L_NOTAGS)) {
270		cvs_printf("symbolic names:\n");
271		TAILQ_FOREACH(sym, &(cf->file_rcs->rf_symbols), rs_list) {
272			rev = rcsnum_alloc();
273			rcsnum_cpy(sym->rs_num, rev, 0);
274			if (RCSNUM_ISBRANCH(sym->rs_num))
275				rcsnum_addmagic(rev);
276
277			cvs_printf("\t%s: %s\n", sym->rs_name,
278			    rcsnum_tostr(rev, numb, sizeof(numb)));
279			free(rev);
280		}
281	}
282
283	cvs_printf("keyword substitution: %s\n",
284	    cf->file_rcs->rf_expand == NULL ? "kv" : cf->file_rcs->rf_expand);
285
286	cvs_printf("total revisions: %u", cf->file_rcs->rf_ndelta);
287
288	if (cf->file_rcs->rf_head != NULL &&
289	    !(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR))
290		cvs_printf(";\tselected revisions: %u", nrev);
291
292	cvs_printf("\n");
293
294	if (!(runflags & L_HEAD) || (runflags & L_HEAD_DESCR))
295		cvs_printf("description:\n%s", cf->file_rcs->rf_desc);
296
297	if (!(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) {
298		TAILQ_FOREACH(rdp, &(cf->file_rcs->rf_delta), rd_list) {
299			/*
300			 * if selections are enabled verify that entry is
301			 * selected.
302			 */
303			if ((logrev == NULL && logdate == NULL) ||
304			    (rdp->rd_flags & RCS_RD_SELECT))
305				log_rev_print(rdp);
306		}
307	}
308
309	cvs_printf("%s\n", LOG_REVEND);
310}
311
312static void
313log_rev_print(struct rcs_delta *rdp)
314{
315	int i, found;
316	char numb[CVS_REV_BUFSZ], timeb[CVS_TIME_BUFSZ];
317	struct cvs_argvector *sargv, *wargv;
318	struct rcs_branch *rb;
319	struct rcs_delta *nrdp;
320
321	i = found = 0;
322
323	/* -s states */
324	if (runflags & L_STATES) {
325		sargv = cvs_strsplit(slist, ",");
326		for (i = 0; sargv->argv[i] != NULL; i++) {
327			if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) {
328				found++;
329				break;
330			}
331			found = 0;
332		}
333		cvs_argv_destroy(sargv);
334	}
335
336	/* -w[logins] */
337	if (runflags & L_LOGINS) {
338		wargv = cvs_strsplit(wlist, ",");
339		for (i = 0; wargv->argv[i] != NULL; i++) {
340			if (strcmp(rdp->rd_author, wargv->argv[i]) == 0) {
341				found++;
342				break;
343			}
344			found = 0;
345		}
346		cvs_argv_destroy(wargv);
347	}
348
349	if ((runflags & (L_STATES|L_LOGINS)) && found == 0)
350		return;
351
352	cvs_printf("%s\n", LOG_REVSEP);
353
354	rcsnum_tostr(rdp->rd_num, numb, sizeof(numb));
355	cvs_printf("revision %s", numb);
356
357	strftime(timeb, sizeof(timeb), "%Y/%m/%d %H:%M:%S", &rdp->rd_date);
358	cvs_printf("\ndate: %s;  author: %s;  state: %s;",
359	    timeb, rdp->rd_author, rdp->rd_state);
360
361	/*
362	 * If we are a branch revision, the diff of this revision is stored
363	 * in place.
364	 * Otherwise, it is stored in the previous revision as a reversed diff.
365	 */
366	if (RCSNUM_ISBRANCHREV(rdp->rd_num))
367		nrdp = rdp;
368	else
369		nrdp = TAILQ_NEXT(rdp, rd_list);
370
371	/*
372	 * We do not write diff stats for the first revision of the default
373	 * branch, since it was not a diff but a full text.
374	 */
375	if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) {
376		int added, removed;
377		rcs_delta_stats(nrdp, &added, &removed);
378		if (RCSNUM_ISBRANCHREV(rdp->rd_num))
379			cvs_printf("  lines: +%d -%d;", added, removed);
380		else
381			cvs_printf("  lines: +%d -%d;", removed, added);
382	}
383
384	if (rdp->rd_commitid != NULL)
385		printf("  commitid: %s;", rdp->rd_commitid);
386
387	cvs_printf("\n");
388
389	if (!TAILQ_EMPTY(&(rdp->rd_branches))) {
390		cvs_printf("branches:");
391		TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
392			RCSNUM *branch;
393			branch = rcsnum_revtobr(rb->rb_num);
394			rcsnum_tostr(branch, numb, sizeof(numb));
395			cvs_printf("  %s;", numb);
396			free(branch);
397		}
398		cvs_printf("\n");
399	}
400
401	cvs_printf("%s", rdp->rd_log);
402}
403
404static char *
405push_date(char *dest, const char *src)
406{
407	size_t len;
408
409	if (dest == NULL)
410		return (xstrdup(src));
411
412	/* 2 = ; and '\0' */
413	len = strlen(dest) + strlen(src) + 2;
414
415	dest[strlen(dest)] = ';';
416	dest = xreallocarray(dest, len, 1);
417	strlcat(dest, src, len);
418	return (dest);
419}
420
421static u_int
422date_select(RCSFILE *file, char *date)
423{
424	int i, nrev, flags;
425	struct rcs_delta *rdp;
426	struct cvs_argvector *args;
427	char *first, *last, delim;
428	time_t firstdate, lastdate, rcsdate;
429
430	nrev = 0;
431	args = cvs_strsplit(date, ";");
432
433	for (i = 0; args->argv[i] != NULL; i++) {
434		flags = 0;
435		firstdate = lastdate = -1;
436
437		first = args->argv[i];
438		last = strchr(args->argv[i], '<');
439		if (last != NULL) {
440			delim = *last;
441			*last++ = '\0';
442
443			if (*last == '=') {
444				last++;
445				flags |= LDATE_INCLUSIVE;
446			}
447		} else {
448			last = strchr(args->argv[i], '>');
449			if (last != NULL) {
450				delim = *last;
451				*last++ = '\0';
452
453				if (*last == '=') {
454					last++;
455					flags |= LDATE_INCLUSIVE;
456				}
457			}
458		}
459
460		if (last == NULL) {
461			flags |= LDATE_SINGLE;
462			if ((firstdate = date_parse(first)) == -1)
463				return -1;
464			delim = '\0';
465			last = "\0";
466		} else {
467			while (*last && isspace((unsigned char)*last))
468				last++;
469		}
470
471		if (delim == '>' && *last == '\0') {
472			flags |= LDATE_EARLIER;
473			if ((firstdate = date_parse(first)) == -1)
474				return -1;
475		}
476
477		if (delim == '>' && *first == '\0' && *last != '\0') {
478			flags |= LDATE_LATER;
479			if ((firstdate = date_parse(last)) == -1)
480				return -1;
481		}
482
483		if (delim == '<' && *last == '\0') {
484			flags |= LDATE_LATER;
485			if ((firstdate = date_parse(first)) == -1)
486				return -1;
487		}
488
489		if (delim == '<' && *first == '\0' && *last != '\0') {
490			flags |= LDATE_EARLIER;
491			if ((firstdate = date_parse(last)) == -1)
492				return -1;
493		}
494
495		if (*first != '\0' && *last != '\0') {
496			flags |= LDATE_RANGE;
497
498			if (delim == '<') {
499				firstdate = date_parse(first);
500				lastdate = date_parse(last);
501			} else {
502				firstdate = date_parse(last);
503				lastdate = date_parse(first);
504			}
505			if (firstdate == -1 || lastdate == -1)
506				return -1;
507		}
508
509		TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) {
510			rcsdate = mktime(&(rdp->rd_date));
511
512			if (flags & LDATE_SINGLE) {
513				if (rcsdate <= firstdate) {
514					rdp->rd_flags |= RCS_RD_SELECT;
515					nrev++;
516					break;
517				}
518			}
519
520			if (flags & LDATE_EARLIER) {
521				if (rcsdate < firstdate) {
522					rdp->rd_flags |= RCS_RD_SELECT;
523					nrev++;
524					continue;
525				}
526
527				if (flags & LDATE_INCLUSIVE &&
528				    (rcsdate <= firstdate)) {
529					rdp->rd_flags |= RCS_RD_SELECT;
530					nrev++;
531					continue;
532				}
533			}
534
535			if (flags & LDATE_LATER) {
536				if (rcsdate > firstdate) {
537					rdp->rd_flags |= RCS_RD_SELECT;
538					nrev++;
539					continue;
540				}
541
542				if (flags & LDATE_INCLUSIVE &&
543				    (rcsdate >= firstdate)) {
544					rdp->rd_flags |= RCS_RD_SELECT;
545					nrev++;
546					continue;
547				}
548			}
549
550			if (flags & LDATE_RANGE) {
551				if ((rcsdate > firstdate) &&
552				    (rcsdate < lastdate)) {
553					rdp->rd_flags |= RCS_RD_SELECT;
554					nrev++;
555					continue;
556				}
557
558				if (flags & LDATE_INCLUSIVE &&
559				    ((rcsdate >= firstdate) &&
560				    (rcsdate <= lastdate))) {
561					rdp->rd_flags |= RCS_RD_SELECT;
562					nrev++;
563					continue;
564				}
565			}
566		}
567	}
568
569	cvs_argv_destroy(args);
570
571	return (nrev);
572}
573