1/*	$OpenBSD: history.c,v 1.46 2024/05/21 05:00:48 jsg Exp $	*/
2/*
3 * Copyright (c) 2007 Joris Vink <joris@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/stat.h>
19
20#include <ctype.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <pwd.h>
24#include <stdlib.h>
25#include <string.h>
26#include <time.h>
27#include <unistd.h>
28
29#include "cvs.h"
30#include "remote.h"
31
32static void	history_compress(char *, const char *);
33
34struct cvs_cmd		cvs_cmd_history = {
35	CVS_OP_HISTORY, CVS_USE_WDIR, "history",
36	{ "hi", "his" },			/* omghi2you */
37	"Display history of actions done in the base repository",
38	"[-ac]",
39	"ac",
40	NULL,
41	cvs_history
42};
43
44/* keep in sync with the defines for history stuff in cvs.h */
45const char historytab[] = {
46	'T',
47	'O',
48	'E',
49	'F',
50	'W',
51	'U',
52	'G',
53	'C',
54	'M',
55	'A',
56	'R',
57	'\0'
58};
59
60#define HISTORY_ALL_USERS		0x01
61#define HISTORY_DISPLAY_ARCHIVED	0x02
62
63void
64cvs_history_add(int type, struct cvs_file *cf, const char *argument)
65{
66	BUF *buf;
67	FILE *fp;
68	RCSNUM *hrev;
69	size_t len;
70	int fd;
71	char *cwd, *p, *rev;
72	char revbuf[CVS_REV_BUFSZ], repo[PATH_MAX], fpath[PATH_MAX];
73	char timebuf[CVS_TIME_BUFSZ];
74	struct tm datetm;
75
76	if (cvs_nolog == 1)
77		return;
78
79	if (cvs_cmdop == CVS_OP_CHECKOUT || cvs_cmdop == CVS_OP_EXPORT) {
80		if (type != CVS_HISTORY_CHECKOUT &&
81		    type != CVS_HISTORY_EXPORT)
82			return;
83	}
84
85	cvs_log(LP_TRACE, "cvs_history_add(`%c', `%s', `%s')",
86	    historytab[type], (cf != NULL) ? cf->file_name : "", argument);
87
88	/* construct repository field */
89	if (cvs_cmdop != CVS_OP_CHECKOUT && cvs_cmdop != CVS_OP_EXPORT) {
90		cvs_get_repository_name((cf != NULL) ? cf->file_wd : ".",
91		    repo, sizeof(repo));
92	} else {
93		cvs_get_repository_name(argument, repo, sizeof(repo));
94	}
95
96	if (cvs_server_active == 1) {
97		cwd = "<remote>";
98	} else {
99		if (getcwd(fpath, sizeof(fpath)) == NULL)
100			fatal("cvs_history_add: getcwd: %s", strerror(errno));
101		p = fpath;
102		if (cvs_cmdop == CVS_OP_CHECKOUT ||
103		    cvs_cmdop == CVS_OP_EXPORT) {
104			if (strlcat(fpath, "/", sizeof(fpath)) >=
105			    sizeof(fpath) || strlcat(fpath, argument,
106			    sizeof(fpath)) >= sizeof(fpath))
107				fatal("cvs_history_add: string truncation");
108		}
109		if (cvs_homedir != NULL && cvs_homedir[0] != '\0') {
110			len = strlen(cvs_homedir);
111			if (strncmp(cvs_homedir, fpath, len) == 0 &&
112			    fpath[len] == '/') {
113				p += len - 1;
114				*p = '~';
115			}
116		}
117
118		history_compress(p, repo);
119		cwd = xstrdup(p);
120	}
121
122	/* construct revision field */
123	revbuf[0] = '\0';
124	rev = revbuf;
125	switch (type) {
126	case CVS_HISTORY_TAG:
127		strlcpy(revbuf, argument, sizeof(revbuf));
128		break;
129	case CVS_HISTORY_CHECKOUT:
130	case CVS_HISTORY_EXPORT:
131		/*
132		 * buf_alloc uses xcalloc(), so we are safe even
133		 * if neither cvs_specified_tag nor cvs_specified_date
134		 * have been supplied.
135		 */
136		buf = buf_alloc(128);
137		if (cvs_specified_tag != NULL) {
138			buf_puts(buf, cvs_specified_tag);
139			if (cvs_specified_date != -1)
140				buf_putc(buf, ':');
141		}
142		if (cvs_specified_date != -1) {
143			gmtime_r(&cvs_specified_date, &datetm);
144			strftime(timebuf, sizeof(timebuf),
145			    "%Y.%m.%d.%H.%M.%S", &datetm);
146			buf_puts(buf, timebuf);
147		}
148		rev = buf_release(buf);
149		break;
150	case CVS_HISTORY_UPDATE_MERGED:
151	case CVS_HISTORY_UPDATE_MERGED_ERR:
152	case CVS_HISTORY_COMMIT_MODIFIED:
153	case CVS_HISTORY_COMMIT_ADDED:
154	case CVS_HISTORY_COMMIT_REMOVED:
155	case CVS_HISTORY_UPDATE_CO:
156		if ((hrev = rcs_head_get(cf->file_rcs)) == NULL)
157			fatal("cvs_history_add: rcs_head_get failed");
158		rcsnum_tostr(hrev, revbuf, sizeof(revbuf));
159		free(hrev);
160		break;
161	}
162
163	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
164	    current_cvsroot->cr_dir, CVS_PATH_HISTORY);
165
166	if ((fd = open(fpath, O_WRONLY|O_APPEND)) == -1) {
167		if (errno != ENOENT)
168			cvs_log(LP_ERR, "failed to open history file");
169	} else {
170		if ((fp = fdopen(fd, "a")) != NULL) {
171			fprintf(fp, "%c%08llx|%s|%s|%s|%s|%s\n",
172			    historytab[type], (long long)time(NULL),
173			    getlogin(), cwd, repo, rev,
174			    (cf != NULL) ? cf->file_name : argument);
175			(void)fclose(fp);
176		} else {
177			cvs_log(LP_ERR, "failed to add entry to history file");
178			(void)close(fd);
179		}
180	}
181
182	if (rev != revbuf)
183		free(rev);
184	if (cvs_server_active != 1)
185		free(cwd);
186}
187
188static void
189history_compress(char *wdir, const char *repo)
190{
191	char *p;
192	const char *q;
193	size_t repo_len, wdir_len;
194
195	repo_len = strlen(repo);
196	wdir_len = strlen(wdir);
197
198	p = wdir + wdir_len;
199	q = repo + repo_len;
200
201	while (p >= wdir && q >= repo) {
202		if (*p != *q)
203			break;
204		p--;
205		q--;
206	}
207	p++;
208	q++;
209
210	/* if it's not worth the effort, skip compression */
211	if (repo + repo_len - q < 3)
212		return;
213
214	(void)xsnprintf(p, strlen(p) + 1, "*%zx", q - repo);
215}
216
217int
218cvs_history(int argc, char **argv)
219{
220	int ch, flags;
221
222	flags = 0;
223
224	while ((ch = getopt(argc, argv, cvs_cmd_history.cmd_opts)) != -1) {
225		switch (ch) {
226		case 'a':
227			flags |= HISTORY_ALL_USERS;
228			break;
229		case 'c':
230			flags |= HISTORY_DISPLAY_ARCHIVED;
231			break;
232		default:
233			fatal("%s", cvs_cmd_history.cmd_synopsis);
234		}
235	}
236
237	argc -= optind;
238	argv += optind;
239
240	return (0);
241}
242