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