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