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