1 /* $OpenBSD: getlog.c,v 1.89 2008/06/14 04:34:08 tobias Exp $ */ 2 /* 3 * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org> 4 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <unistd.h> 20 #include <string.h> 21 #include <errno.h> 22 23 #include "cvs.h" 24 #include "remote.h" 25 26 #define L_HEAD 0x01 27 #define L_HEAD_DESCR 0x02 28 #define L_NAME 0x04 29 #define L_NOTAGS 0x08 30 #define L_LOGINS 0x10 31 #define L_STATES 0x20 32 33 void cvs_log_local(struct cvs_file *); 34 35 static void log_rev_print(struct rcs_delta *); 36 37 int runflags = 0; 38 char *logrev = NULL; 39 char *slist = NULL; 40 char *wlist = NULL; 41 42 struct cvs_cmd cvs_cmd_log = { 43 CVS_OP_LOG, CVS_USE_WDIR, "log", 44 { "lo" }, 45 "Print out history information for files", 46 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 47 "bd:hlNRr:s:tw:", 48 NULL, 49 cvs_getlog 50 }; 51 52 struct cvs_cmd cvs_cmd_rlog = { 53 CVS_OP_RLOG, 0, "rlog", 54 { "rlo" }, 55 "Print out history information for files", 56 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 57 "bd:hlNRr:s:tw:", 58 NULL, 59 cvs_getlog 60 }; 61 62 int 63 cvs_getlog(int argc, char **argv) 64 { 65 int ch, flags, i; 66 char *arg = "."; 67 struct cvs_recursion cr; 68 69 flags = CR_RECURSE_DIRS; 70 71 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_LOG ? 72 cvs_cmd_log.cmd_opts : cvs_cmd_rlog.cmd_opts)) != -1) { 73 switch (ch) { 74 case 'h': 75 runflags |= L_HEAD; 76 break; 77 case 'l': 78 flags &= ~CR_RECURSE_DIRS; 79 break; 80 case 'N': 81 runflags |= L_NOTAGS; 82 break; 83 case 'R': 84 runflags |= L_NAME; 85 break; 86 case 'r': 87 logrev = optarg; 88 break; 89 case 's': 90 runflags |= L_STATES; 91 slist = optarg; 92 break; 93 case 't': 94 runflags |= L_HEAD_DESCR; 95 break; 96 case 'w': 97 runflags |= L_LOGINS; 98 wlist = optarg; 99 break; 100 default: 101 fatal("%s", cvs_cmdop == CVS_OP_LOG ? 102 cvs_cmd_log.cmd_synopsis : 103 cvs_cmd_rlog.cmd_synopsis); 104 } 105 } 106 107 argc -= optind; 108 argv += optind; 109 110 if (cvs_cmdop == CVS_OP_RLOG) { 111 flags |= CR_REPO; 112 113 if (argc == 0) 114 return 0; 115 116 for (i = 0; i < argc; i++) 117 if (argv[i][0] == '/') 118 fatal("Absolute path name is invalid: %s", 119 argv[i]); 120 } 121 122 cr.enterdir = NULL; 123 cr.leavedir = NULL; 124 125 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 126 cvs_client_connect_to_server(); 127 cr.fileproc = cvs_client_sendfile; 128 129 if (runflags & L_HEAD) 130 cvs_client_send_request("Argument -h"); 131 132 if (!(flags & CR_RECURSE_DIRS)) 133 cvs_client_send_request("Argument -l"); 134 135 if (runflags & L_NOTAGS) 136 cvs_client_send_request("Argument -N"); 137 138 if (runflags & L_NAME) 139 cvs_client_send_request("Argument -R"); 140 141 if (logrev != NULL) 142 cvs_client_send_request("Argument -r%s", logrev); 143 144 if (runflags & L_STATES) 145 cvs_client_send_request("Argument -s%s", slist); 146 147 if (runflags & L_HEAD_DESCR) 148 cvs_client_send_request("Argument -t"); 149 150 if (runflags & L_LOGINS) 151 cvs_client_send_request("Argument -w%s", wlist); 152 } else { 153 if (cvs_cmdop == CVS_OP_RLOG && 154 chdir(current_cvsroot->cr_dir) == -1) 155 fatal("cvs_getlog: %s", strerror(errno)); 156 157 cr.fileproc = cvs_log_local; 158 } 159 160 cr.flags = flags; 161 162 if (cvs_cmdop == CVS_OP_LOG || 163 current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 164 if (argc > 0) 165 cvs_file_run(argc, argv, &cr); 166 else 167 cvs_file_run(1, &arg, &cr); 168 } 169 170 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 171 cvs_client_send_files(argv, argc); 172 cvs_client_senddir("."); 173 174 cvs_client_send_request((cvs_cmdop == CVS_OP_RLOG) ? 175 "rlog" : "log"); 176 177 cvs_client_get_responses(); 178 } 179 180 return (0); 181 } 182 183 void 184 cvs_log_local(struct cvs_file *cf) 185 { 186 u_int nrev; 187 RCSNUM *rev; 188 struct rcs_sym *sym; 189 struct rcs_lock *lkp; 190 struct rcs_delta *rdp; 191 struct rcs_access *acp; 192 char numb[CVS_REV_BUFSZ]; 193 194 cvs_log(LP_TRACE, "cvs_log_local(%s)", cf->file_path); 195 196 cvs_file_classify(cf, cvs_directory_tag); 197 198 if (cf->file_rcs == NULL) { 199 return; 200 } else if (cf->file_status == FILE_ADDED) { 201 if (verbosity > 0) 202 cvs_log(LP_ERR, "%s has been added, but not committed", 203 cf->file_path); 204 return; 205 } 206 207 if (cf->file_type == CVS_DIR) { 208 if (verbosity > 1) 209 cvs_log(LP_NOTICE, "Logging %s", cf->file_path); 210 return; 211 } 212 213 if (runflags & L_NAME) { 214 cvs_printf("%s\n", cf->file_rpath); 215 return; 216 } 217 218 cvs_printf("\nRCS file: %s", cf->file_rpath); 219 220 if (cvs_cmdop != CVS_OP_RLOG) 221 cvs_printf("\nWorking file: %s", cf->file_path); 222 223 cvs_printf("\nhead:"); 224 if (cf->file_rcs->rf_head != NULL) 225 cvs_printf(" %s", rcsnum_tostr(cf->file_rcs->rf_head, 226 numb, sizeof(numb))); 227 228 cvs_printf("\nbranch:"); 229 if (rcs_branch_get(cf->file_rcs) != NULL) { 230 cvs_printf(" %s", rcsnum_tostr(rcs_branch_get(cf->file_rcs), 231 numb, sizeof(numb))); 232 } 233 234 cvs_printf("\nlocks: %s", (cf->file_rcs->rf_flags & RCS_SLOCK) 235 ? "strict" : ""); 236 TAILQ_FOREACH(lkp, &(cf->file_rcs->rf_locks), rl_list) 237 cvs_printf("\n\t%s: %s", lkp->rl_name, 238 rcsnum_tostr(lkp->rl_num, numb, sizeof(numb))); 239 240 cvs_printf("\naccess list:\n"); 241 TAILQ_FOREACH(acp, &(cf->file_rcs->rf_access), ra_list) 242 cvs_printf("\t%s\n", acp->ra_name); 243 244 if (!(runflags & L_NOTAGS)) { 245 cvs_printf("symbolic names:\n"); 246 TAILQ_FOREACH(sym, &(cf->file_rcs->rf_symbols), rs_list) { 247 rev = rcsnum_alloc(); 248 rcsnum_cpy(sym->rs_num, rev, 0); 249 if (RCSNUM_ISBRANCH(sym->rs_num)) 250 rcsnum_addmagic(rev); 251 252 cvs_printf("\t%s: %s\n", sym->rs_name, 253 rcsnum_tostr(rev, numb, sizeof(numb))); 254 rcsnum_free(rev); 255 } 256 } 257 258 cvs_printf("keyword substitution: %s\n", 259 cf->file_rcs->rf_expand == NULL ? "kv" : cf->file_rcs->rf_expand); 260 261 cvs_printf("total revisions: %u", cf->file_rcs->rf_ndelta); 262 263 if (logrev != NULL) 264 nrev = cvs_revision_select(cf->file_rcs, logrev); 265 else 266 nrev = cf->file_rcs->rf_ndelta; 267 268 if (cf->file_rcs->rf_head != NULL && 269 !(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) 270 cvs_printf(";\tselected revisions: %u", nrev); 271 272 cvs_printf("\n"); 273 274 if (!(runflags & L_HEAD) || (runflags & L_HEAD_DESCR)) 275 cvs_printf("description:\n%s", cf->file_rcs->rf_desc); 276 277 if (!(runflags & L_HEAD) && !(runflags & L_HEAD_DESCR)) { 278 TAILQ_FOREACH(rdp, &(cf->file_rcs->rf_delta), rd_list) { 279 /* 280 * if selections are enabled verify that entry is 281 * selected. 282 */ 283 if (logrev == NULL || (rdp->rd_flags & RCS_RD_SELECT)) 284 log_rev_print(rdp); 285 } 286 } 287 288 cvs_printf("%s\n", LOG_REVEND); 289 } 290 291 static void 292 log_rev_print(struct rcs_delta *rdp) 293 { 294 int i, found; 295 char numb[CVS_REV_BUFSZ], timeb[CVS_TIME_BUFSZ]; 296 struct cvs_argvector *sargv, *wargv; 297 struct rcs_branch *rb; 298 struct rcs_delta *nrdp; 299 300 i = found = 0; 301 302 /* -s states */ 303 if (runflags & L_STATES) { 304 sargv = cvs_strsplit(slist, ","); 305 for (i = 0; sargv->argv[i] != NULL; i++) { 306 if (strcmp(rdp->rd_state, sargv->argv[i]) == 0) { 307 found++; 308 break; 309 } 310 found = 0; 311 } 312 cvs_argv_destroy(sargv); 313 } 314 315 /* -w[logins] */ 316 if (runflags & L_LOGINS) { 317 wargv = cvs_strsplit(wlist, ","); 318 for (i = 0; wargv->argv[i] != NULL; i++) { 319 if (strcmp(rdp->rd_author, wargv->argv[i]) == 0) { 320 found++; 321 break; 322 } 323 found = 0; 324 } 325 cvs_argv_destroy(wargv); 326 } 327 328 if ((runflags & (L_STATES|L_LOGINS)) && found == 0) 329 return; 330 331 cvs_printf("%s\n", LOG_REVSEP); 332 333 rcsnum_tostr(rdp->rd_num, numb, sizeof(numb)); 334 cvs_printf("revision %s", numb); 335 336 strftime(timeb, sizeof(timeb), "%Y/%m/%d %H:%M:%S", &rdp->rd_date); 337 cvs_printf("\ndate: %s; author: %s; state: %s;", 338 timeb, rdp->rd_author, rdp->rd_state); 339 340 /* 341 * If we are a branch revision, the diff of this revision is stored 342 * in place. 343 * Otherwise, it is stored in the previous revision as a reversed diff. 344 */ 345 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 346 nrdp = rdp; 347 else 348 nrdp = TAILQ_NEXT(rdp, rd_list); 349 350 /* 351 * We do not write diff stats for the first revision of the default 352 * branch, since it was not a diff but a full text. 353 */ 354 if (nrdp != NULL && rdp->rd_num->rn_len == nrdp->rd_num->rn_len) { 355 int added, removed; 356 rcs_delta_stats(nrdp, &added, &removed); 357 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) 358 cvs_printf(" lines: +%d -%d", added, removed); 359 else 360 cvs_printf(" lines: +%d -%d", removed, added); 361 } 362 cvs_printf("\n"); 363 364 if (!TAILQ_EMPTY(&(rdp->rd_branches))) { 365 cvs_printf("branches:"); 366 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 367 RCSNUM *branch; 368 branch = rcsnum_revtobr(rb->rb_num); 369 rcsnum_tostr(branch, numb, sizeof(numb)); 370 cvs_printf(" %s;", numb); 371 rcsnum_free(branch); 372 } 373 cvs_printf("\n"); 374 } 375 376 cvs_printf("%s", rdp->rd_log); 377 } 378