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