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
32 static void history_compress(char *, const char *);
33
34 struct 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 */
45 const 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
63 void
cvs_history_add(int type,struct cvs_file * cf,const char * argument)64 cvs_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
188 static void
history_compress(char * wdir,const char * repo)189 history_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
217 int
cvs_history(int argc,char ** argv)218 cvs_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