1*ce7279d8Sjsg /* $OpenBSD: history.c,v 1.46 2024/05/21 05:00:48 jsg Exp $ */
23901dfa5Sjoris /*
33901dfa5Sjoris * Copyright (c) 2007 Joris Vink <joris@openbsd.org>
43901dfa5Sjoris *
53901dfa5Sjoris * Permission to use, copy, modify, and distribute this software for any
63901dfa5Sjoris * purpose with or without fee is hereby granted, provided that the above
73901dfa5Sjoris * copyright notice and this permission notice appear in all copies.
83901dfa5Sjoris *
93901dfa5Sjoris * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
103901dfa5Sjoris * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
113901dfa5Sjoris * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
123901dfa5Sjoris * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
133901dfa5Sjoris * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
143901dfa5Sjoris * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
153901dfa5Sjoris * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
163901dfa5Sjoris */
173901dfa5Sjoris
183901dfa5Sjoris #include <sys/stat.h>
193901dfa5Sjoris
203901dfa5Sjoris #include <ctype.h>
213901dfa5Sjoris #include <errno.h>
22b35edee1Stobias #include <fcntl.h>
233901dfa5Sjoris #include <pwd.h>
243901dfa5Sjoris #include <stdlib.h>
253901dfa5Sjoris #include <string.h>
2628fb0957Sokan #include <time.h>
273901dfa5Sjoris #include <unistd.h>
283901dfa5Sjoris
293901dfa5Sjoris #include "cvs.h"
303901dfa5Sjoris #include "remote.h"
313901dfa5Sjoris
32062b8fd7Stobias static void history_compress(char *, const char *);
33062b8fd7Stobias
343901dfa5Sjoris struct cvs_cmd cvs_cmd_history = {
35f331ff59Stobias CVS_OP_HISTORY, CVS_USE_WDIR, "history",
36eb5e8157Sxsa { "hi", "his" }, /* omghi2you */
372a04ffe6Stobias "Display history of actions done in the base repository",
383901dfa5Sjoris "[-ac]",
393901dfa5Sjoris "ac",
403901dfa5Sjoris NULL,
413901dfa5Sjoris cvs_history
423901dfa5Sjoris };
433901dfa5Sjoris
443901dfa5Sjoris /* keep in sync with the defines for history stuff in cvs.h */
453901dfa5Sjoris const char historytab[] = {
463901dfa5Sjoris 'T',
473901dfa5Sjoris 'O',
483901dfa5Sjoris 'E',
493901dfa5Sjoris 'F',
503901dfa5Sjoris 'W',
513901dfa5Sjoris 'U',
523901dfa5Sjoris 'G',
533901dfa5Sjoris 'C',
543901dfa5Sjoris 'M',
553901dfa5Sjoris 'A',
563901dfa5Sjoris 'R',
575a781597Sray '\0'
583901dfa5Sjoris };
593901dfa5Sjoris
603901dfa5Sjoris #define HISTORY_ALL_USERS 0x01
613901dfa5Sjoris #define HISTORY_DISPLAY_ARCHIVED 0x02
623901dfa5Sjoris
633901dfa5Sjoris void
cvs_history_add(int type,struct cvs_file * cf,const char * argument)643901dfa5Sjoris cvs_history_add(int type, struct cvs_file *cf, const char *argument)
653901dfa5Sjoris {
6628b0a253Stobias BUF *buf;
673901dfa5Sjoris FILE *fp;
68062b8fd7Stobias RCSNUM *hrev;
69062b8fd7Stobias size_t len;
70b35edee1Stobias int fd;
71062b8fd7Stobias char *cwd, *p, *rev;
72b9fc9a72Sderaadt char revbuf[CVS_REV_BUFSZ], repo[PATH_MAX], fpath[PATH_MAX];
7328b0a253Stobias char timebuf[CVS_TIME_BUFSZ];
7428b0a253Stobias struct tm datetm;
753901dfa5Sjoris
763901dfa5Sjoris if (cvs_nolog == 1)
773901dfa5Sjoris return;
783901dfa5Sjoris
793901dfa5Sjoris if (cvs_cmdop == CVS_OP_CHECKOUT || cvs_cmdop == CVS_OP_EXPORT) {
803901dfa5Sjoris if (type != CVS_HISTORY_CHECKOUT &&
813901dfa5Sjoris type != CVS_HISTORY_EXPORT)
823901dfa5Sjoris return;
833901dfa5Sjoris }
843901dfa5Sjoris
853901dfa5Sjoris cvs_log(LP_TRACE, "cvs_history_add(`%c', `%s', `%s')",
863901dfa5Sjoris historytab[type], (cf != NULL) ? cf->file_name : "", argument);
873901dfa5Sjoris
883901dfa5Sjoris /* construct repository field */
893901dfa5Sjoris if (cvs_cmdop != CVS_OP_CHECKOUT && cvs_cmdop != CVS_OP_EXPORT) {
903c84fc2eStobias cvs_get_repository_name((cf != NULL) ? cf->file_wd : ".",
913c84fc2eStobias repo, sizeof(repo));
923901dfa5Sjoris } else {
93062b8fd7Stobias cvs_get_repository_name(argument, repo, sizeof(repo));
94062b8fd7Stobias }
95062b8fd7Stobias
96062b8fd7Stobias if (cvs_server_active == 1) {
97062b8fd7Stobias cwd = "<remote>";
98062b8fd7Stobias } else {
99062b8fd7Stobias if (getcwd(fpath, sizeof(fpath)) == NULL)
100062b8fd7Stobias fatal("cvs_history_add: getcwd: %s", strerror(errno));
101062b8fd7Stobias p = fpath;
102062b8fd7Stobias if (cvs_cmdop == CVS_OP_CHECKOUT ||
103062b8fd7Stobias cvs_cmdop == CVS_OP_EXPORT) {
104062b8fd7Stobias if (strlcat(fpath, "/", sizeof(fpath)) >=
105062b8fd7Stobias sizeof(fpath) || strlcat(fpath, argument,
106062b8fd7Stobias sizeof(fpath)) >= sizeof(fpath))
107062b8fd7Stobias fatal("cvs_history_add: string truncation");
108062b8fd7Stobias }
109062b8fd7Stobias if (cvs_homedir != NULL && cvs_homedir[0] != '\0') {
110062b8fd7Stobias len = strlen(cvs_homedir);
111062b8fd7Stobias if (strncmp(cvs_homedir, fpath, len) == 0 &&
112062b8fd7Stobias fpath[len] == '/') {
113062b8fd7Stobias p += len - 1;
114062b8fd7Stobias *p = '~';
115062b8fd7Stobias }
116062b8fd7Stobias }
117062b8fd7Stobias
118062b8fd7Stobias history_compress(p, repo);
119062b8fd7Stobias cwd = xstrdup(p);
1203901dfa5Sjoris }
1213901dfa5Sjoris
1223901dfa5Sjoris /* construct revision field */
1233901dfa5Sjoris revbuf[0] = '\0';
12428b0a253Stobias rev = revbuf;
1253901dfa5Sjoris switch (type) {
1263901dfa5Sjoris case CVS_HISTORY_TAG:
1273901dfa5Sjoris strlcpy(revbuf, argument, sizeof(revbuf));
1283901dfa5Sjoris break;
1293901dfa5Sjoris case CVS_HISTORY_CHECKOUT:
1303901dfa5Sjoris case CVS_HISTORY_EXPORT:
13128b0a253Stobias /*
1327bb3ddb0Sray * buf_alloc uses xcalloc(), so we are safe even
13328b0a253Stobias * if neither cvs_specified_tag nor cvs_specified_date
13428b0a253Stobias * have been supplied.
13528b0a253Stobias */
1367bb3ddb0Sray buf = buf_alloc(128);
13728b0a253Stobias if (cvs_specified_tag != NULL) {
1387bb3ddb0Sray buf_puts(buf, cvs_specified_tag);
13928b0a253Stobias if (cvs_specified_date != -1)
1407bb3ddb0Sray buf_putc(buf, ':');
14128b0a253Stobias }
14228b0a253Stobias if (cvs_specified_date != -1) {
14328b0a253Stobias gmtime_r(&cvs_specified_date, &datetm);
14428b0a253Stobias strftime(timebuf, sizeof(timebuf),
14528b0a253Stobias "%Y.%m.%d.%H.%M.%S", &datetm);
1467bb3ddb0Sray buf_puts(buf, timebuf);
14728b0a253Stobias }
1487bb3ddb0Sray rev = buf_release(buf);
1493901dfa5Sjoris break;
1503901dfa5Sjoris case CVS_HISTORY_UPDATE_MERGED:
1513901dfa5Sjoris case CVS_HISTORY_UPDATE_MERGED_ERR:
1523901dfa5Sjoris case CVS_HISTORY_COMMIT_MODIFIED:
1533901dfa5Sjoris case CVS_HISTORY_COMMIT_ADDED:
1543901dfa5Sjoris case CVS_HISTORY_COMMIT_REMOVED:
1553901dfa5Sjoris case CVS_HISTORY_UPDATE_CO:
156062b8fd7Stobias if ((hrev = rcs_head_get(cf->file_rcs)) == NULL)
157062b8fd7Stobias fatal("cvs_history_add: rcs_head_get failed");
158062b8fd7Stobias rcsnum_tostr(hrev, revbuf, sizeof(revbuf));
15953ce2177Sfcambus free(hrev);
1603901dfa5Sjoris break;
1613901dfa5Sjoris }
1623901dfa5Sjoris
1633901dfa5Sjoris (void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
1643901dfa5Sjoris current_cvsroot->cr_dir, CVS_PATH_HISTORY);
1653901dfa5Sjoris
166b35edee1Stobias if ((fd = open(fpath, O_WRONLY|O_APPEND)) == -1) {
167b35edee1Stobias if (errno != ENOENT)
168b35edee1Stobias cvs_log(LP_ERR, "failed to open history file");
169b35edee1Stobias } else {
170b35edee1Stobias if ((fp = fdopen(fd, "a")) != NULL) {
17142354d6eSzhuk fprintf(fp, "%c%08llx|%s|%s|%s|%s|%s\n",
17242354d6eSzhuk historytab[type], (long long)time(NULL),
17342354d6eSzhuk getlogin(), cwd, repo, rev,
17442354d6eSzhuk (cf != NULL) ? cf->file_name : argument);
1753901dfa5Sjoris (void)fclose(fp);
1763901dfa5Sjoris } else {
1773901dfa5Sjoris cvs_log(LP_ERR, "failed to add entry to history file");
178b35edee1Stobias (void)close(fd);
179b35edee1Stobias }
1803901dfa5Sjoris }
1813901dfa5Sjoris
18228b0a253Stobias if (rev != revbuf)
183397ddb8aSnicm free(rev);
1844f5c3994Sjoris if (cvs_server_active != 1)
185397ddb8aSnicm free(cwd);
1863901dfa5Sjoris }
1873901dfa5Sjoris
188062b8fd7Stobias static void
history_compress(char * wdir,const char * repo)189062b8fd7Stobias history_compress(char *wdir, const char *repo)
190062b8fd7Stobias {
191062b8fd7Stobias char *p;
192062b8fd7Stobias const char *q;
193062b8fd7Stobias size_t repo_len, wdir_len;
194062b8fd7Stobias
195062b8fd7Stobias repo_len = strlen(repo);
196062b8fd7Stobias wdir_len = strlen(wdir);
197062b8fd7Stobias
198062b8fd7Stobias p = wdir + wdir_len;
199062b8fd7Stobias q = repo + repo_len;
200062b8fd7Stobias
201062b8fd7Stobias while (p >= wdir && q >= repo) {
202062b8fd7Stobias if (*p != *q)
203062b8fd7Stobias break;
204062b8fd7Stobias p--;
205062b8fd7Stobias q--;
206062b8fd7Stobias }
207062b8fd7Stobias p++;
208062b8fd7Stobias q++;
209062b8fd7Stobias
210062b8fd7Stobias /* if it's not worth the effort, skip compression */
211062b8fd7Stobias if (repo + repo_len - q < 3)
212062b8fd7Stobias return;
213062b8fd7Stobias
214062b8fd7Stobias (void)xsnprintf(p, strlen(p) + 1, "*%zx", q - repo);
215062b8fd7Stobias }
216062b8fd7Stobias
2173901dfa5Sjoris int
cvs_history(int argc,char ** argv)2183901dfa5Sjoris cvs_history(int argc, char **argv)
2193901dfa5Sjoris {
2203901dfa5Sjoris int ch, flags;
2213901dfa5Sjoris
2223901dfa5Sjoris flags = 0;
2233901dfa5Sjoris
2243901dfa5Sjoris while ((ch = getopt(argc, argv, cvs_cmd_history.cmd_opts)) != -1) {
2253901dfa5Sjoris switch (ch) {
2263901dfa5Sjoris case 'a':
2273901dfa5Sjoris flags |= HISTORY_ALL_USERS;
2283901dfa5Sjoris break;
2293901dfa5Sjoris case 'c':
2303901dfa5Sjoris flags |= HISTORY_DISPLAY_ARCHIVED;
2313901dfa5Sjoris break;
2323901dfa5Sjoris default:
2333901dfa5Sjoris fatal("%s", cvs_cmd_history.cmd_synopsis);
2343901dfa5Sjoris }
2353901dfa5Sjoris }
2363901dfa5Sjoris
2373901dfa5Sjoris argc -= optind;
2383901dfa5Sjoris argv += optind;
2393901dfa5Sjoris
2403901dfa5Sjoris return (0);
2413901dfa5Sjoris }
242