1 /* $OpenBSD: rlog.c,v 1.61 2008/02/02 19:26:24 xsa Exp $ */ 2 /* 3 * Copyright (c) 2005 Joris Vink <joris@openbsd.org> 4 * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <err.h> 29 #include <libgen.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include "rcsprog.h" 36 #include "diff.h" 37 38 static void rlog_file(const char *, RCSFILE *); 39 static void rlog_rev_print(struct rcs_delta *); 40 41 #define RLOG_OPTSTRING "hLl::NqRr::s:TtVw::x::z::" 42 #define REVSEP "----------------------------" 43 #define REVEND \ 44 "=============================================================================" 45 46 static int hflag, Lflag, lflag, rflag, tflag, Nflag, wflag; 47 static char *llist = NULL; 48 static char *slist = NULL; 49 static char *wlist = NULL; 50 static char *revisions = NULL; 51 52 void 53 rlog_usage(void) 54 { 55 fprintf(stderr, 56 "usage: rlog [-bhLNRtV] [-ddates] [-l[lockers]] [-r[revs]]\n" 57 " [-sstates] [-w[logins]] [-xsuffixes]\n" 58 " [-ztz] file ...\n"); 59 } 60 61 int 62 rlog_main(int argc, char **argv) 63 { 64 RCSFILE *file; 65 int Rflag; 66 int i, ch, fd, status; 67 char fpath[MAXPATHLEN]; 68 69 rcsnum_flags |= RCSNUM_NO_MAGIC; 70 hflag = Rflag = rflag = status = 0; 71 while ((ch = rcs_getopt(argc, argv, RLOG_OPTSTRING)) != -1) { 72 switch (ch) { 73 case 'h': 74 hflag = 1; 75 break; 76 case 'L': 77 Lflag = 1; 78 break; 79 case 'l': 80 lflag = 1; 81 llist = rcs_optarg; 82 break; 83 case 'N': 84 Nflag = 1; 85 break; 86 case 'q': 87 /* 88 * kept for compatibility 89 */ 90 break; 91 case 'R': 92 Rflag = 1; 93 break; 94 case 'r': 95 rflag = 1; 96 revisions = rcs_optarg; 97 break; 98 case 's': 99 slist = rcs_optarg; 100 break; 101 case 'T': 102 /* 103 * kept for compatibility 104 */ 105 break; 106 case 't': 107 tflag = 1; 108 break; 109 case 'V': 110 printf("%s\n", rcs_version); 111 exit(0); 112 case 'w': 113 wflag = 1; 114 wlist = rcs_optarg; 115 break; 116 case 'x': 117 /* Use blank extension if none given. */ 118 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 119 break; 120 case 'z': 121 timezone_flag = rcs_optarg; 122 break; 123 default: 124 (usage()); 125 exit(1); 126 } 127 } 128 129 argc -= rcs_optind; 130 argv += rcs_optind; 131 132 if (argc == 0) { 133 warnx("no input file"); 134 (usage)(); 135 exit(1); 136 } 137 138 if (hflag == 1 && tflag == 1) { 139 warnx("warning: -t overrides -h."); 140 hflag = 0; 141 } 142 143 for (i = 0; i < argc; i++) { 144 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 145 if (fd < 0) { 146 warn("%s", fpath); 147 status = 1; 148 continue; 149 } 150 151 if ((file = rcs_open(fpath, fd, 152 RCS_READ|RCS_PARSE_FULLY)) == NULL) { 153 status = 1; 154 continue; 155 } 156 157 if (Lflag == 1 && TAILQ_EMPTY(&(file->rf_locks))) { 158 rcs_close(file); 159 continue; 160 } 161 162 if (Rflag == 1) { 163 printf("%s\n", fpath); 164 rcs_close(file); 165 continue; 166 } 167 168 rlog_file(argv[i], file); 169 170 rcs_close(file); 171 } 172 173 return (status); 174 } 175 176 static void 177 rlog_file(const char *fname, RCSFILE *file) 178 { 179 char numb[RCS_REV_BUFSZ]; 180 u_int nrev; 181 struct rcs_sym *sym; 182 struct rcs_access *acp; 183 struct rcs_delta *rdp; 184 struct rcs_lock *lkp; 185 char *workfile, *p; 186 187 if (rflag == 1) 188 nrev = rcs_rev_select(file, revisions); 189 else 190 nrev = file->rf_ndelta; 191 192 if ((workfile = basename(fname)) == NULL) 193 err(1, "basename"); 194 195 /* 196 * In case they specified 'foo,v' as argument. 197 */ 198 if ((p = strrchr(workfile, ',')) != NULL) 199 *p = '\0'; 200 201 printf("\nRCS file: %s", file->rf_path); 202 printf("\nWorking file: %s", workfile); 203 printf("\nhead:"); 204 if (file->rf_head != NULL) 205 printf(" %s", rcsnum_tostr(file->rf_head, numb, sizeof(numb))); 206 207 printf("\nbranch:"); 208 if (rcs_branch_get(file) != NULL) { 209 printf(" %s", rcsnum_tostr(rcs_branch_get(file), 210 numb, sizeof(numb))); 211 } 212 213 printf("\nlocks: %s", (file->rf_flags & RCS_SLOCK) ? "strict" : ""); 214 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) 215 printf("\n\t%s: %s", lkp->rl_name, 216 rcsnum_tostr(lkp->rl_num, numb, sizeof(numb))); 217 printf("\naccess list:\n"); 218 TAILQ_FOREACH(acp, &(file->rf_access), ra_list) 219 printf("\t%s\n", acp->ra_name); 220 221 if (Nflag == 0) { 222 printf("symbolic names:\n"); 223 TAILQ_FOREACH(sym, &(file->rf_symbols), rs_list) { 224 printf("\t%s: %s\n", sym->rs_name, 225 rcsnum_tostr(sym->rs_num, numb, sizeof(numb))); 226 } 227 } 228 229 printf("keyword substitution: %s\n", 230 file->rf_expand == NULL ? "kv" : file->rf_expand); 231 232 printf("total revisions: %u", file->rf_ndelta); 233 234 if (file->rf_head != NULL && hflag == 0 && tflag == 0) 235 printf(";\tselected revisions: %u", nrev); 236 237 printf("\n"); 238 239 240 if (hflag == 0 || tflag == 1) 241 printf("description:\n%s", file->rf_desc); 242 243 if (hflag == 0 && tflag == 0 && 244 !(lflag == 1 && TAILQ_EMPTY(&file->rf_locks))) { 245 TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { 246 /* 247 * if selections are enabled verify that entry is 248 * selected. 249 */ 250 if (rflag == 0 || (rdp->rd_flags & RCS_RD_SELECT)) 251 rlog_rev_print(rdp); 252 } 253 } 254 255 printf("%s\n", REVEND); 256 } 257 258 static void 259 rlog_rev_print(struct rcs_delta *rdp) 260 { 261 int i, found; 262 struct tm t; 263 char *author, numb[RCS_REV_BUFSZ], *fmt, timeb[RCS_TIME_BUFSZ]; 264 struct rcs_argvector *largv, *sargv, *wargv; 265 struct rcs_branch *rb; 266 struct rcs_delta *nrdp; 267 268 i = found = 0; 269 author = NULL; 270 271 /* -l[lockers] */ 272 if (lflag == 1) { 273 if (rdp->rd_locker != NULL) 274 found++; 275 276 if (llist != NULL) { 277 /* if locker is empty, no need to go further. */ 278 if (rdp->rd_locker == NULL) 279 return; 280 largv = rcs_strsplit(llist, ","); 281 for (i = 0; largv->argv[i] != NULL; i++) { 282 if (strcmp(rdp->rd_locker, largv->argv[i]) 283 == 0) { 284 found++; 285 break; 286 } 287 found = 0; 288 } 289 rcs_argv_destroy(largv); 290 } 291 } 292 293 /* -sstates */ 294 if (slist != NULL) { 295 sargv = rcs_strsplit(slist, ","); 296 for (i = 0; sargv->argv[i] != NULL; i++) { 297 if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) { 298 found++; 299 break; 300 } 301 found = 0; 302 } 303 rcs_argv_destroy(sargv); 304 } 305 306 /* -w[logins] */ 307 if (wflag == 1) { 308 if (wlist != NULL) { 309 wargv = rcs_strsplit(wlist, ","); 310 for (i = 0; wargv->argv[i] != NULL; i++) { 311 if (strcmp(rdp->rd_author, wargv->argv[i]) 312 == 0) { 313 found++; 314 break; 315 } 316 found = 0; 317 } 318 rcs_argv_destroy(wargv); 319 } else { 320 if ((author = getlogin()) == NULL) 321 err(1, "getlogin"); 322 323 if (strcmp(rdp->rd_author, author) == 0) 324 found++; 325 } 326 } 327 328 /* XXX dirty... */ 329 if ((((slist != NULL && wflag == 1) || 330 (slist != NULL && lflag == 1) || 331 (lflag == 1 && wflag == 1)) && found < 2) || 332 (((slist != NULL && lflag == 1 && wflag == 1) || 333 (slist != NULL || lflag == 1 || wflag == 1)) && found == 0)) 334 return; 335 336 printf("%s\n", REVSEP); 337 338 rcsnum_tostr(rdp->rd_num, numb, sizeof(numb)); 339 340 printf("revision %s", numb); 341 if (rdp->rd_locker != NULL) 342 printf("\tlocked by: %s;", rdp->rd_locker); 343 344 if (timezone_flag != NULL) { 345 rcs_set_tz(timezone_flag, rdp, &t); 346 fmt = "%Y-%m-%d %H:%M:%S%z"; 347 } else { 348 t = rdp->rd_date; 349 fmt = "%Y/%m/%d %H:%M:%S"; 350 } 351 352 (void)strftime(timeb, sizeof(timeb), fmt, &t); 353 354 printf("\ndate: %s; author: %s; state: %s;", timeb, rdp->rd_author, 355 rdp->rd_state); 356 357 /* 358 * If we are a branch revision, the diff of this revision is stored 359 * in place. 360 * Otherwise, it is stored in the previous revision as a reversed diff. 361 */ 362 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 363 nrdp = rdp; 364 else 365 nrdp = TAILQ_NEXT(rdp, rd_list); 366 367 /* 368 * We do not write diff stats for the first revision of the default 369 * branch, since it was not a diff but a full text. 370 */ 371 if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) { 372 int added, removed; 373 374 rcs_delta_stats(nrdp, &added, &removed); 375 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 376 printf(" lines: +%d -%d", added, removed); 377 else 378 printf(" lines: +%d -%d", removed, added); 379 } 380 printf("\n"); 381 382 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 383 printf("branches:"); 384 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 385 RCSNUM *branch; 386 branch = rcsnum_revtobr(rb->rb_num); 387 (void)rcsnum_tostr(branch, numb, sizeof(numb)); 388 printf(" %s;", numb); 389 rcsnum_free(branch); 390 } 391 printf("\n"); 392 } 393 394 printf("%s", rdp->rd_log); 395 } 396