xref: /openbsd/usr.bin/cvs/history.c (revision d415bd75)
1 /*	$OpenBSD: history.c,v 1.45 2017/07/20 13:39:11 okan 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 
32 void	cvs_history_local(struct cvs_file *);
33 
34 static void	history_compress(char *, const char *);
35 
36 struct cvs_cmd		cvs_cmd_history = {
37 	CVS_OP_HISTORY, CVS_USE_WDIR, "history",
38 	{ "hi", "his" },			/* omghi2you */
39 	"Display history of actions done in the base repository",
40 	"[-ac]",
41 	"ac",
42 	NULL,
43 	cvs_history
44 };
45 
46 /* keep in sync with the defines for history stuff in cvs.h */
47 const char historytab[] = {
48 	'T',
49 	'O',
50 	'E',
51 	'F',
52 	'W',
53 	'U',
54 	'G',
55 	'C',
56 	'M',
57 	'A',
58 	'R',
59 	'\0'
60 };
61 
62 #define HISTORY_ALL_USERS		0x01
63 #define HISTORY_DISPLAY_ARCHIVED	0x02
64 
65 void
66 cvs_history_add(int type, struct cvs_file *cf, const char *argument)
67 {
68 	BUF *buf;
69 	FILE *fp;
70 	RCSNUM *hrev;
71 	size_t len;
72 	int fd;
73 	char *cwd, *p, *rev;
74 	char revbuf[CVS_REV_BUFSZ], repo[PATH_MAX], fpath[PATH_MAX];
75 	char timebuf[CVS_TIME_BUFSZ];
76 	struct tm datetm;
77 
78 	if (cvs_nolog == 1)
79 		return;
80 
81 	if (cvs_cmdop == CVS_OP_CHECKOUT || cvs_cmdop == CVS_OP_EXPORT) {
82 		if (type != CVS_HISTORY_CHECKOUT &&
83 		    type != CVS_HISTORY_EXPORT)
84 			return;
85 	}
86 
87 	cvs_log(LP_TRACE, "cvs_history_add(`%c', `%s', `%s')",
88 	    historytab[type], (cf != NULL) ? cf->file_name : "", argument);
89 
90 	/* construct repository field */
91 	if (cvs_cmdop != CVS_OP_CHECKOUT && cvs_cmdop != CVS_OP_EXPORT) {
92 		cvs_get_repository_name((cf != NULL) ? cf->file_wd : ".",
93 		    repo, sizeof(repo));
94 	} else {
95 		cvs_get_repository_name(argument, repo, sizeof(repo));
96 	}
97 
98 	if (cvs_server_active == 1) {
99 		cwd = "<remote>";
100 	} else {
101 		if (getcwd(fpath, sizeof(fpath)) == NULL)
102 			fatal("cvs_history_add: getcwd: %s", strerror(errno));
103 		p = fpath;
104 		if (cvs_cmdop == CVS_OP_CHECKOUT ||
105 		    cvs_cmdop == CVS_OP_EXPORT) {
106 			if (strlcat(fpath, "/", sizeof(fpath)) >=
107 			    sizeof(fpath) || strlcat(fpath, argument,
108 			    sizeof(fpath)) >= sizeof(fpath))
109 				fatal("cvs_history_add: string truncation");
110 		}
111 		if (cvs_homedir != NULL && cvs_homedir[0] != '\0') {
112 			len = strlen(cvs_homedir);
113 			if (strncmp(cvs_homedir, fpath, len) == 0 &&
114 			    fpath[len] == '/') {
115 				p += len - 1;
116 				*p = '~';
117 			}
118 		}
119 
120 		history_compress(p, repo);
121 		cwd = xstrdup(p);
122 	}
123 
124 	/* construct revision field */
125 	revbuf[0] = '\0';
126 	rev = revbuf;
127 	switch (type) {
128 	case CVS_HISTORY_TAG:
129 		strlcpy(revbuf, argument, sizeof(revbuf));
130 		break;
131 	case CVS_HISTORY_CHECKOUT:
132 	case CVS_HISTORY_EXPORT:
133 		/*
134 		 * buf_alloc uses xcalloc(), so we are safe even
135 		 * if neither cvs_specified_tag nor cvs_specified_date
136 		 * have been supplied.
137 		 */
138 		buf = buf_alloc(128);
139 		if (cvs_specified_tag != NULL) {
140 			buf_puts(buf, cvs_specified_tag);
141 			if (cvs_specified_date != -1)
142 				buf_putc(buf, ':');
143 		}
144 		if (cvs_specified_date != -1) {
145 			gmtime_r(&cvs_specified_date, &datetm);
146 			strftime(timebuf, sizeof(timebuf),
147 			    "%Y.%m.%d.%H.%M.%S", &datetm);
148 			buf_puts(buf, timebuf);
149 		}
150 		rev = buf_release(buf);
151 		break;
152 	case CVS_HISTORY_UPDATE_MERGED:
153 	case CVS_HISTORY_UPDATE_MERGED_ERR:
154 	case CVS_HISTORY_COMMIT_MODIFIED:
155 	case CVS_HISTORY_COMMIT_ADDED:
156 	case CVS_HISTORY_COMMIT_REMOVED:
157 	case CVS_HISTORY_UPDATE_CO:
158 		if ((hrev = rcs_head_get(cf->file_rcs)) == NULL)
159 			fatal("cvs_history_add: rcs_head_get failed");
160 		rcsnum_tostr(hrev, revbuf, sizeof(revbuf));
161 		free(hrev);
162 		break;
163 	}
164 
165 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
166 	    current_cvsroot->cr_dir, CVS_PATH_HISTORY);
167 
168 	if ((fd = open(fpath, O_WRONLY|O_APPEND)) == -1) {
169 		if (errno != ENOENT)
170 			cvs_log(LP_ERR, "failed to open history file");
171 	} else {
172 		if ((fp = fdopen(fd, "a")) != NULL) {
173 			fprintf(fp, "%c%08llx|%s|%s|%s|%s|%s\n",
174 			    historytab[type], (long long)time(NULL),
175 			    getlogin(), cwd, repo, rev,
176 			    (cf != NULL) ? cf->file_name : argument);
177 			(void)fclose(fp);
178 		} else {
179 			cvs_log(LP_ERR, "failed to add entry to history file");
180 			(void)close(fd);
181 		}
182 	}
183 
184 	if (rev != revbuf)
185 		free(rev);
186 	if (cvs_server_active != 1)
187 		free(cwd);
188 }
189 
190 static void
191 history_compress(char *wdir, const char *repo)
192 {
193 	char *p;
194 	const char *q;
195 	size_t repo_len, wdir_len;
196 
197 	repo_len = strlen(repo);
198 	wdir_len = strlen(wdir);
199 
200 	p = wdir + wdir_len;
201 	q = repo + repo_len;
202 
203 	while (p >= wdir && q >= repo) {
204 		if (*p != *q)
205 			break;
206 		p--;
207 		q--;
208 	}
209 	p++;
210 	q++;
211 
212 	/* if it's not worth the effort, skip compression */
213 	if (repo + repo_len - q < 3)
214 		return;
215 
216 	(void)xsnprintf(p, strlen(p) + 1, "*%zx", q - repo);
217 }
218 
219 int
220 cvs_history(int argc, char **argv)
221 {
222 	int ch, flags;
223 
224 	flags = 0;
225 
226 	while ((ch = getopt(argc, argv, cvs_cmd_history.cmd_opts)) != -1) {
227 		switch (ch) {
228 		case 'a':
229 			flags |= HISTORY_ALL_USERS;
230 			break;
231 		case 'c':
232 			flags |= HISTORY_DISPLAY_ARCHIVED;
233 			break;
234 		default:
235 			fatal("%s", cvs_cmd_history.cmd_synopsis);
236 		}
237 	}
238 
239 	argc -= optind;
240 	argv += optind;
241 
242 	return (0);
243 }
244