Annotation of src/usr.bin/rcs/ci.c, Revision 1.156
1.156 ! xsa 1: /* $OpenBSD: ci.c,v 1.155 2006/04/21 14:18:26 xsa Exp $ */
1.1 niallo 2: /*
1.98 niallo 3: * Copyright (c) 2005, 2006 Niall O'Higgins <niallo@openbsd.org>
1.1 niallo 4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
1.143 niallo 7: * modification, are permitted provided that the following conditions
1.1 niallo 8: * are met:
9: *
1.143 niallo 10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
1.1 niallo 12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
1.91 xsa 27: #include "includes.h"
1.1 niallo 28:
1.92 xsa 29: #include "rcsprog.h"
1.4 niallo 30: #include "diff.h"
1.9 niallo 31:
1.145 ray 32: #define CI_OPTSTRING "d::f::I::i::j::k::l::M::m::N:n:qr::s:Tt:u::Vw:x::z::"
1.107 deraadt 33: #define DATE_NOW -1
34: #define DATE_MTIME -2
35:
36: #define KW_ID "Id"
37: #define KW_AUTHOR "Author"
38: #define KW_DATE "Date"
39: #define KW_STATE "State"
40: #define KW_REVISION "Revision"
41:
42: #define KW_TYPE_ID 1
43: #define KW_TYPE_AUTHOR 2
44: #define KW_TYPE_DATE 3
45: #define KW_TYPE_STATE 4
46: #define KW_TYPE_REVISION 5
47:
48: #define KW_NUMTOKS_ID 10
49: #define KW_NUMTOKS_AUTHOR 3
50: #define KW_NUMTOKS_DATE 4
51: #define KW_NUMTOKS_STATE 3
52: #define KW_NUMTOKS_REVISION 3
1.58 niallo 53:
1.115 niallo 54: #define RCSNUM_ZERO_ENDING(x) (x->rn_id[x->rn_len - 1] == 0)
55:
1.98 niallo 56: extern struct rcs_kw rcs_expkw[];
57:
1.58 niallo 58: struct checkin_params {
59: int flags, openflags;
60: mode_t fmode;
61: time_t date;
62: RCSFILE *file;
63: RCSNUM *frev, *newrev;
64: char fpath[MAXPATHLEN], *rcs_msg, *username, *deltatext, *filename;
1.151 ray 65: char *author, *description, *state, *symbol;
1.58 niallo 66: };
67:
1.94 niallo 68: static int checkin_attach_symbol(struct checkin_params *);
69: static int checkin_checklock(struct checkin_params *);
1.68 xsa 70: static char *checkin_diff_file(struct checkin_params *);
71: static char *checkin_getdesc(void);
1.154 xsa 72: static char *checkin_getlogmsg(RCSNUM *, RCSNUM *, int);
1.68 xsa 73: static int checkin_init(struct checkin_params *);
1.107 deraadt 74: static int checkin_keywordscan(char *, RCSNUM **, time_t *, char **,
1.98 niallo 75: char **);
1.107 deraadt 76: static int checkin_keywordtype(char *);
1.94 niallo 77: static int checkin_mtimedate(struct checkin_params *);
1.107 deraadt 78: static void checkin_parsekeyword(char *, RCSNUM **, time_t *, char **,
1.98 niallo 79: char **);
1.94 niallo 80: static int checkin_update(struct checkin_params *);
81: static void checkin_revert(struct checkin_params *);
1.4 niallo 82:
1.1 niallo 83: void
84: checkin_usage(void)
85: {
86: fprintf(stderr,
1.119 xsa 87: "usage: ci [-jMNqV] [-d[date]] [-f[rev]] [-I[rev]] [-i[rev]]\n"
88: " [-j[rev]] [-k[rev]] [-l[rev]] [-M[rev]] [-mmsg]\n"
89: " [-Nsymbol] [-nsymbol] [-r[rev]] [-sstate] [-tfile|str]\n"
90: " [-u[rev]] [-wusername] [-xsuffixes] [-ztz] file ...\n");
1.58 niallo 91: }
92:
1.1 niallo 93: /*
94: * checkin_main()
95: *
96: * Handler for the `ci' program.
97: * Returns 0 on success, or >0 on error.
98: */
99: int
100: checkin_main(int argc, char **argv)
101: {
1.58 niallo 102: int i, ch, status;
1.139 ray 103: char *rev_str;
1.58 niallo 104: struct checkin_params pb;
1.1 niallo 105:
1.58 niallo 106: pb.date = DATE_NOW;
107: pb.file = NULL;
1.98 niallo 108: pb.rcs_msg = pb.username = pb.author = pb.state = NULL;
1.134 niallo 109: pb.symbol = pb.description = pb.deltatext = NULL;
1.58 niallo 110: pb.newrev = NULL;
1.128 niallo 111: pb.flags = status = 0;
112: pb.fmode = S_IRUSR|S_IRGRP|S_IROTH;
1.120 joris 113: pb.flags = INTERACTIVE;
1.90 niallo 114: pb.openflags = RCS_RDWR|RCS_CREATE|RCS_PARSE_FULLY;
1.139 ray 115: rev_str = NULL;
1.9 niallo 116:
1.58 niallo 117: while ((ch = rcs_getopt(argc, argv, CI_OPTSTRING)) != -1) {
1.1 niallo 118: switch (ch) {
1.20 niallo 119: case 'd':
1.25 niallo 120: if (rcs_optarg == NULL)
1.58 niallo 121: pb.date = DATE_MTIME;
1.87 xsa 122: else if ((pb.date = cvs_date_parse(rcs_optarg)) <= 0)
123: fatal("invalid date");
1.20 niallo 124: break;
1.29 niallo 125: case 'f':
1.139 ray 126: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 127: pb.flags |= FORCE;
1.29 niallo 128: break;
1.119 xsa 129: case 'I':
1.139 ray 130: rcs_setrevstr(&rev_str, rcs_optarg);
1.119 xsa 131: pb.flags |= INTERACTIVE;
132: break;
1.58 niallo 133: case 'i':
1.139 ray 134: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 135: pb.openflags |= RCS_CREATE;
1.66 niallo 136: pb.flags |= CI_INIT;
1.58 niallo 137: break;
138: case 'j':
1.139 ray 139: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 140: pb.openflags &= ~RCS_CREATE;
1.66 niallo 141: pb.flags &= ~CI_INIT;
1.58 niallo 142: break;
1.98 niallo 143: case 'k':
1.139 ray 144: rcs_setrevstr(&rev_str, rcs_optarg);
1.98 niallo 145: pb.flags |= CI_KEYWORDSCAN;
146: break;
1.30 niallo 147: case 'l':
1.139 ray 148: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 149: pb.flags |= CO_LOCK;
1.54 niallo 150: break;
151: case 'M':
1.139 ray 152: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 153: pb.flags |= CO_REVDATE;
1.30 niallo 154: break;
1.1 niallo 155: case 'm':
1.58 niallo 156: pb.rcs_msg = rcs_optarg;
1.87 xsa 157: if (pb.rcs_msg == NULL)
158: fatal("missing message for -m option");
1.58 niallo 159: pb.flags &= ~INTERACTIVE;
1.3 joris 160: break;
1.42 niallo 161: case 'N':
1.58 niallo 162: pb.flags |= CI_SYMFORCE;
1.152 ray 163: /* FALLTHROUGH */
1.38 niallo 164: case 'n':
1.151 ray 165: if (pb.symbol != NULL)
166: xfree(pb.symbol);
1.84 joris 167: pb.symbol = xstrdup(rcs_optarg);
1.87 xsa 168: if (rcs_sym_check(pb.symbol) != 1)
169: fatal("invalid symbol `%s'", pb.symbol);
1.38 niallo 170: break;
1.3 joris 171: case 'q':
1.154 xsa 172: pb.flags |= QUIET;
1.1 niallo 173: break;
1.30 niallo 174: case 'r':
1.139 ray 175: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 176: pb.flags |= CI_DEFAULT;
1.9 niallo 177: break;
1.51 niallo 178: case 's':
1.58 niallo 179: pb.state = rcs_optarg;
1.87 xsa 180: if (rcs_state_check(pb.state) < 0)
181: fatal("invalid state `%s'", pb.state);
1.67 xsa 182: break;
183: case 'T':
184: pb.flags |= PRESERVETIME;
1.51 niallo 185: break;
1.80 niallo 186: case 't':
1.151 ray 187: if (pb.description != NULL)
188: xfree(pb.description);
1.84 joris 189: pb.description = xstrdup(rcs_optarg);
1.80 niallo 190: break;
1.9 niallo 191: case 'u':
1.139 ray 192: rcs_setrevstr(&rev_str, rcs_optarg);
1.58 niallo 193: pb.flags |= CO_UNLOCK;
1.9 niallo 194: break;
1.30 niallo 195: case 'V':
196: printf("%s\n", rcs_version);
197: exit(0);
1.121 ray 198: /* NOTREACHED */
1.31 niallo 199: case 'w':
1.151 ray 200: if (pb.author != NULL)
201: xfree(pb.author);
1.84 joris 202: pb.author = xstrdup(rcs_optarg);
1.64 xsa 203: break;
204: case 'x':
1.125 ray 205: /* Use blank extension if none given. */
206: rcs_suffixes = rcs_optarg ? rcs_optarg : "";
1.110 joris 207: break;
208: case 'z':
209: timezone_flag = rcs_optarg;
1.31 niallo 210: break;
1.1 niallo 211: default:
212: (usage)();
213: exit(1);
214: }
215: }
216:
1.24 joris 217: argc -= rcs_optind;
218: argv += rcs_optind;
219:
1.1 niallo 220: if (argc == 0) {
1.155 xsa 221: warnx("no input file");
1.1 niallo 222: (usage)();
223: exit(1);
224: }
225:
1.86 xsa 226: if ((pb.username = getlogin()) == NULL)
227: fatal("getlogin failed");
1.31 niallo 228:
229:
1.1 niallo 230: for (i = 0; i < argc; i++) {
1.58 niallo 231: pb.filename = argv[i];
1.1 niallo 232:
1.58 niallo 233: /*
234: * Test for existence of ,v file. If we are expected to
235: * create one, set NEWFILE flag.
236: */
1.156 ! xsa 237: if (rcs_statfile(pb.filename, pb.fpath,
! 238: sizeof(pb.fpath), pb.flags) < 0) {
1.71 niallo 239: if (pb.openflags & RCS_CREATE)
240: pb.flags |= NEWFILE;
241: else {
1.155 xsa 242: warnx("No existing RCS file");
1.71 niallo 243: status = 1;
244: continue;
245: }
1.66 niallo 246: } else {
247: if (pb.flags & CI_INIT) {
1.155 xsa 248: warnx("%s already exists", pb.fpath);
1.66 niallo 249: status = 1;
250: continue;
251: }
1.58 niallo 252: pb.openflags &= ~RCS_CREATE;
1.66 niallo 253: }
1.63 niallo 254: /*
255: * If we are to create a new ,v file, we must decide where it
256: * should go.
257: */
258: if (pb.flags & NEWFILE) {
1.117 ray 259: char *fpath = rcs_choosefile(pb.filename);
1.63 niallo 260: if (fpath == NULL) {
261: status = 1;
262: continue;
263: }
264: strlcpy(pb.fpath, fpath, sizeof(pb.fpath));
1.84 joris 265: xfree(fpath);
1.63 niallo 266: }
267:
1.58 niallo 268: pb.file = rcs_open(pb.fpath, pb.openflags, pb.fmode);
269:
1.87 xsa 270: if (pb.file == NULL)
271: fatal("failed to open rcsfile '%s'", pb.fpath);
1.29 niallo 272:
1.154 xsa 273: if (!(pb.flags & QUIET))
1.58 niallo 274: printf("%s <-- %s\n", pb.fpath, pb.filename);
1.139 ray 275:
276: /* XXX - Should we rcsnum_free(pb.newrev)? */
277: if (rev_str != NULL)
1.141 ray 278: if ((pb.newrev = rcs_getrevnum(rev_str, pb.file)) ==
279: NULL)
280: fatal("invalid revision: %s", rev_str);
1.107 deraadt 281:
1.148 niallo 282: if (!(pb.flags & NEWFILE))
283: pb.flags |= CI_SKIPDESC;
1.142 joris 284: /* XXX - support for commiting to a file without revisions */
285: if (pb.file->rf_ndelta == 0) {
286: pb.flags |= NEWFILE;
287: pb.file->rf_flags |= RCS_CREATE;
288: }
289:
1.149 ray 290: if (pb.flags & NEWFILE) {
291: if (checkin_init(&pb) == -1)
292: status = 1;
293: } else {
294: if (checkin_update(&pb) == -1)
295: status = 1;
296: }
1.114 niallo 297:
298: /* reset NEWFILE flag */
299: pb.flags &= ~NEWFILE;
1.129 niallo 300:
301: rcs_close(pb.file);
1.4 niallo 302: }
303:
1.154 xsa 304: if (!(pb.flags & QUIET) && status == 0)
1.93 xsa 305: printf("done\n");
306:
1.16 niallo 307: return (status);
1.4 niallo 308: }
309:
1.59 niallo 310: /*
311: * checkin_diff_file()
312: *
1.65 xsa 313: * Generate the diff between the working file and a revision.
1.59 niallo 314: * Returns pointer to a char array on success, NULL on failure.
315: */
1.4 niallo 316: static char *
1.58 niallo 317: checkin_diff_file(struct checkin_params *pb)
1.4 niallo 318: {
319: char path1[MAXPATHLEN], path2[MAXPATHLEN];
320: BUF *b1, *b2, *b3;
321: char rbuf[64], *deltatext;
322:
1.112 joris 323: b1 = b2 = b3 = NULL;
324: deltatext = NULL;
1.58 niallo 325: rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf));
1.4 niallo 326:
1.58 niallo 327: if ((b1 = cvs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL) {
1.155 xsa 328: warnx("failed to load file: `%s'", pb->filename);
1.112 joris 329: goto out;
1.1 niallo 330: }
331:
1.58 niallo 332: if ((b2 = rcs_getrev(pb->file, pb->frev)) == NULL) {
1.155 xsa 333: warnx("failed to load revision");
1.112 joris 334: goto out;
1.4 niallo 335: }
336:
1.57 xsa 337: if ((b3 = cvs_buf_alloc((size_t)128, BUF_AUTOEXT)) == NULL) {
1.155 xsa 338: warnx("failed to allocated buffer for diff");
1.112 joris 339: goto out;
1.4 niallo 340: }
341:
1.50 xsa 342: strlcpy(path1, rcs_tmpdir, sizeof(path1));
343: strlcat(path1, "/diff1.XXXXXXXXXX", sizeof(path1));
1.123 niallo 344: cvs_buf_write_stmp(b1, path1, 0600);
1.112 joris 345:
1.4 niallo 346: cvs_buf_free(b1);
1.112 joris 347: b1 = NULL;
1.4 niallo 348:
1.50 xsa 349: strlcpy(path2, rcs_tmpdir, sizeof(path2));
350: strlcat(path2, "/diff2.XXXXXXXXXX", sizeof(path2));
1.123 niallo 351: cvs_buf_write_stmp(b2, path2, 0600);
1.112 joris 352:
1.4 niallo 353: cvs_buf_free(b2);
1.112 joris 354: b2 = NULL;
1.4 niallo 355:
1.5 niallo 356: diff_format = D_RCSDIFF;
1.4 niallo 357: cvs_diffreg(path1, path2, b3);
358:
359: cvs_buf_putc(b3, '\0');
360: deltatext = (char *)cvs_buf_release(b3);
1.112 joris 361: b3 = NULL;
362:
363: out:
364: if (b1 != NULL)
365: cvs_buf_free(b1);
366: if (b2 != NULL)
367: cvs_buf_free(b2);
368: if (b3 != NULL)
369: cvs_buf_free(b3);
1.4 niallo 370:
371: return (deltatext);
1.6 niallo 372: }
373:
374: /*
1.65 xsa 375: * checkin_getlogmsg()
1.59 niallo 376: *
1.6 niallo 377: * Get log message from user interactively.
1.59 niallo 378: * Returns pointer to a char array on success, NULL on failure.
1.6 niallo 379: */
380: static char *
1.154 xsa 381: checkin_getlogmsg(RCSNUM *rev, RCSNUM *rev2, int flags)
1.6 niallo 382: {
1.58 niallo 383: char *rcs_msg, nrev[16], prev[16];
1.153 ray 384: const char *prompt =
385: "enter log message, terminated with a single '.' or end of file:\n";
1.6 niallo 386: RCSNUM *tmprev;
387:
1.7 niallo 388: rcs_msg = NULL;
1.6 niallo 389: tmprev = rcsnum_alloc();
390: rcsnum_cpy(rev, tmprev, 16);
1.9 niallo 391: rcsnum_tostr(tmprev, prev, sizeof(prev));
1.12 niallo 392: if (rev2 == NULL)
393: rcsnum_tostr(rcsnum_inc(tmprev), nrev, sizeof(nrev));
394: else
395: rcsnum_tostr(rev2, nrev, sizeof(nrev));
1.6 niallo 396: rcsnum_free(tmprev);
397:
1.154 xsa 398: if (!(flags & QUIET))
1.47 niallo 399: printf("new revision: %s; previous revision: %s\n", nrev,
400: prev);
1.32 joris 401:
1.153 ray 402: rcs_msg = rcs_prompt(prompt);
403:
1.58 niallo 404: return (rcs_msg);
405: }
406:
407:
408: /*
409: * checkin_getdesc()
410: *
411: * Get file description interactively.
1.59 niallo 412: * Returns pointer to a char array on success, NULL on failure.
1.58 niallo 413: */
414: static char *
415: checkin_getdesc()
416: {
417: char *description;
1.153 ray 418: const char *prompt =
419: "enter description, terminated with single '.' or end of file:\n"
420: "NOTE: This is NOT the log message!\n";
421:
422: description = rcs_prompt(prompt);
1.58 niallo 423:
424: return (description);
425: }
426:
427: /*
1.63 niallo 428: * checkin_update()
429: *
430: * Do a checkin to an existing RCS file.
431: *
432: * On success, return 0. On error return -1.
433: */
434: static int
435: checkin_update(struct checkin_params *pb)
436: {
1.149 ray 437: char *filec, numb1[64], numb2[64];
1.128 niallo 438: struct stat st;
1.63 niallo 439: BUF *bp;
440:
1.149 ray 441: filec = NULL;
442:
1.82 joris 443: /*
444: * XXX this is wrong, we need to get the revision the user
1.85 xsa 445: * has the lock for. So we can decide if we want to create a
1.82 joris 446: * branch or not. (if it's not current HEAD we need to branch).
447: */
1.63 niallo 448: pb->frev = pb->file->rf_head;
449:
1.126 niallo 450: /* Load file contents */
1.149 ray 451: if ((bp = cvs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL)
452: goto fail;
1.126 niallo 453:
454: cvs_buf_putc(bp, '\0');
455: filec = (char *)cvs_buf_release(bp);
456:
1.115 niallo 457: /* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
1.147 deraadt 458: if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev))
1.115 niallo 459: pb->newrev = rcsnum_inc(pb->newrev);
460:
1.82 joris 461: if (checkin_checklock(pb) < 0)
1.126 niallo 462: goto fail;
1.82 joris 463:
464: /* If revision passed on command line is less than HEAD, bail.
465: * XXX only applies to ci -r1.2 foo for example if HEAD is > 1.2 and
466: * there is no lock set for the user.
467: */
1.147 deraadt 468: if (pb->newrev != NULL &&
469: rcsnum_cmp(pb->newrev, pb->frev, 0) > 0) {
1.155 xsa 470: warnx("%s: revision %s too low; must be higher than %s",
1.77 xsa 471: pb->file->rf_path,
472: rcsnum_tostr(pb->newrev, numb1, sizeof(numb1)),
473: rcsnum_tostr(pb->frev, numb2, sizeof(numb2)));
1.126 niallo 474: goto fail;
1.63 niallo 475: }
476:
1.104 niallo 477: /*
478: * Set the date of the revision to be the last modification
479: * time of the working file if -d has no argument.
480: */
1.147 deraadt 481: if (pb->date == DATE_MTIME &&
482: (checkin_mtimedate(pb) < 0))
1.126 niallo 483: goto fail;
1.104 niallo 484:
485: /* Date from argv/mtime must be more recent than HEAD */
486: if (pb->date != DATE_NOW) {
487: time_t head_date = rcs_rev_getdate(pb->file, pb->frev);
488: if (pb->date <= head_date) {
1.113 xsa 489: char dbuf1[256], dbuf2[256], *fmt;
490: struct tm *t, *t_head;
491:
492: fmt = "%Y/%m/%d %H:%M:%S";
493:
494: t = localtime(&pb->date);
495: strftime(dbuf1, sizeof(dbuf1), fmt, t);
496: t_head = localtime(&head_date);
497: strftime(dbuf2, sizeof(dbuf2), fmt, t_head);
498:
499: fatal("%s: Date %s preceeds %s in revision %s.",
500: pb->file->rf_path, dbuf1, dbuf2,
1.104 niallo 501: rcsnum_tostr(pb->frev, numb2, sizeof(numb2)));
502: }
503: }
504:
1.73 xsa 505: /* Get RCS patch */
1.63 niallo 506: if ((pb->deltatext = checkin_diff_file(pb)) == NULL) {
1.155 xsa 507: warnx("failed to get diff");
1.126 niallo 508: goto fail;
1.63 niallo 509: }
510:
511: /*
512: * If -f is not specified and there are no differences, tell
513: * the user and revert to latest version.
514: */
515: if (!(pb->flags & FORCE) && (strlen(pb->deltatext) < 1)) {
516: checkin_revert(pb);
1.126 niallo 517: goto fail;
1.63 niallo 518: }
519:
1.73 xsa 520: /* If no log message specified, get it interactively. */
1.63 niallo 521: if (pb->flags & INTERACTIVE)
1.154 xsa 522: pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev,
523: pb->flags);
1.63 niallo 524:
1.82 joris 525: if (rcs_lock_remove(pb->file, pb->username, pb->frev) < 0) {
1.63 niallo 526: if (rcs_errno != RCS_ERR_NOENT)
1.155 xsa 527: warnx("failed to remove lock");
1.82 joris 528: else if (!(pb->flags & CO_LOCK))
1.155 xsa 529: warnx("previous revision was not locked; "
1.82 joris 530: "ignoring -l option");
1.63 niallo 531: }
532:
1.73 xsa 533: /* Current head revision gets the RCS patch as rd_text */
1.87 xsa 534: if (rcs_deltatext_set(pb->file, pb->frev, pb->deltatext) == -1)
535: fatal("failed to set new rd_text for head rev");
1.63 niallo 536:
1.73 xsa 537: /* Now add our new revision */
1.63 niallo 538: if (rcs_rev_add(pb->file,
539: (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev),
1.82 joris 540: pb->rcs_msg, pb->date, pb->author) != 0) {
1.155 xsa 541: warnx("failed to add new revision");
1.126 niallo 542: goto fail;
1.63 niallo 543: }
544:
545: /*
546: * If we are checking in to a non-default (ie user-specified)
547: * revision, set head to this revision.
548: */
1.131 xsa 549: if (pb->newrev != NULL) {
550: if (rcs_head_set(pb->file, pb->newrev) < 0)
551: fatal("rcs_head_set failed");
552: } else
1.63 niallo 553: pb->newrev = pb->file->rf_head;
554:
1.73 xsa 555: /* New head revision has to contain entire file; */
1.107 deraadt 556: if (rcs_deltatext_set(pb->file, pb->frev, filec) == -1)
1.87 xsa 557: fatal("failed to set new head revision");
1.63 niallo 558:
1.73 xsa 559: /* Attach a symbolic name to this revision if specified. */
1.147 deraadt 560: if (pb->symbol != NULL &&
561: (checkin_attach_symbol(pb) < 0))
1.126 niallo 562: goto fail;
1.63 niallo 563:
1.73 xsa 564: /* Set the state of this revision if specified. */
1.63 niallo 565: if (pb->state != NULL)
566: (void)rcs_state_set(pb->file, pb->newrev, pb->state);
567:
1.128 niallo 568: /* Maintain RCSFILE permissions */
569: if (stat(pb->filename, &st) == -1)
570: goto fail;
571:
572: /* Strip all the write bits */
573: pb->file->rf_mode = st.st_mode &
574: (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
575:
1.84 joris 576: xfree(pb->deltatext);
577: xfree(filec);
1.63 niallo 578: (void)unlink(pb->filename);
1.140 deraadt 579:
1.128 niallo 580: /* Write out RCSFILE before calling checkout_rev() */
581: rcs_write(pb->file);
1.63 niallo 582:
1.73 xsa 583: /* Do checkout if -u or -l are specified. */
1.147 deraadt 584: if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) &&
585: !(pb->flags & CI_DEFAULT))
1.63 niallo 586: checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags,
1.89 joris 587: pb->username, pb->author, NULL, NULL);
1.63 niallo 588:
589: if (pb->flags & INTERACTIVE) {
1.84 joris 590: xfree(pb->rcs_msg);
1.63 niallo 591: pb->rcs_msg = NULL;
592: }
593: return (0);
1.126 niallo 594:
595: fail:
1.149 ray 596: if (filec != NULL)
597: xfree(filec);
1.134 niallo 598: if (pb->deltatext != NULL)
599: xfree(pb->deltatext);
1.126 niallo 600: return (-1);
1.63 niallo 601: }
602:
603: /*
1.58 niallo 604: * checkin_init()
1.65 xsa 605: *
1.58 niallo 606: * Does an initial check in, just enough to create the new ,v file
1.63 niallo 607: * On success, return 0. On error return -1.
1.58 niallo 608: */
1.63 niallo 609: static int
1.58 niallo 610: checkin_init(struct checkin_params *pb)
611: {
1.80 niallo 612: BUF *bp, *dp;
1.93 xsa 613: char *filec, numb[64];
1.115 niallo 614: int fetchlog = 0;
1.80 niallo 615: const char *rcs_desc;
1.128 niallo 616: struct stat st;
1.58 niallo 617:
1.149 ray 618: filec = NULL;
619:
1.115 niallo 620: /* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
1.147 deraadt 621: if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) {
1.115 niallo 622: pb->frev = rcsnum_alloc();
623: rcsnum_cpy(pb->newrev, pb->frev, 0);
624: pb->newrev = rcsnum_inc(pb->newrev);
625: fetchlog = 1;
626: }
627:
1.73 xsa 628: /* Load file contents */
1.149 ray 629: if ((bp = cvs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL)
630: goto fail;
1.58 niallo 631:
1.124 xsa 632: cvs_buf_putc(bp, '\0');
1.58 niallo 633: filec = (char *)cvs_buf_release(bp);
634:
1.98 niallo 635: /* Get default values from working copy if -k specified */
636: if (pb->flags & CI_KEYWORDSCAN)
637: checkin_keywordscan(filec, &pb->newrev, &pb->date, &pb->state,
638: &pb->author);
639:
1.148 niallo 640: if (pb->flags & CI_SKIPDESC)
1.142 joris 641: goto skipdesc;
642:
1.73 xsa 643: /* Get description from user */
1.80 niallo 644: if (pb->description == NULL)
645: rcs_desc = (const char *)checkin_getdesc();
646: else {
647: if (*pb->description == '-') {
648: pb->description++;
1.81 niallo 649: rcs_desc = (const char *)pb->description;
650: } else {
1.80 niallo 651: dp = cvs_buf_load(pb->description, BUF_AUTOEXT);
652: if (dp == NULL) {
1.155 xsa 653: warnx("failed to load description file '%s'",
1.80 niallo 654: pb->description);
1.126 niallo 655: goto fail;
1.80 niallo 656: }
1.124 xsa 657: cvs_buf_putc(dp, '\0');
1.80 niallo 658: rcs_desc = (const char *)cvs_buf_release(dp);
659: }
660: }
1.58 niallo 661: rcs_desc_set(pb->file, rcs_desc);
1.142 joris 662:
663: skipdesc:
1.58 niallo 664:
1.115 niallo 665: /*
666: * If the user had specified a zero-ending revision number e.g. 4
667: * emulate odd GNU behaviour and fetch log message.
668: */
669: if (fetchlog == 1) {
1.154 xsa 670: pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev,
671: pb->flags);
1.115 niallo 672: rcsnum_free(pb->frev);
673: }
1.133 joris 674:
1.97 niallo 675: /*
676: * Set the date of the revision to be the last modification
677: * time of the working file if -d has no argument.
678: */
1.147 deraadt 679: if (pb->date == DATE_MTIME &&
680: (checkin_mtimedate(pb) < 0))
1.126 niallo 681: goto fail;
1.97 niallo 682:
1.73 xsa 683: /* Now add our new revision */
1.96 niallo 684: if (rcs_rev_add(pb->file,
685: (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev),
1.107 deraadt 686: (pb->rcs_msg == NULL ? "Initial revision" : pb->rcs_msg),
1.103 niallo 687: pb->date, pb->author) != 0) {
1.155 xsa 688: warnx("failed to add new revision");
1.126 niallo 689: goto fail;
1.63 niallo 690: }
1.133 joris 691:
1.63 niallo 692: /*
693: * If we are checking in to a non-default (ie user-specified)
694: * revision, set head to this revision.
695: */
1.131 xsa 696: if (pb->newrev != NULL) {
697: if (rcs_head_set(pb->file, pb->newrev) < 0)
698: fatal("rcs_head_set failed");
699: } else
1.63 niallo 700: pb->newrev = pb->file->rf_head;
701:
1.73 xsa 702: /* New head revision has to contain entire file; */
1.66 niallo 703: if (rcs_deltatext_set(pb->file, pb->file->rf_head, filec) == -1) {
1.155 xsa 704: warnx("failed to set new head revision");
1.126 niallo 705: goto fail;
1.58 niallo 706: }
1.133 joris 707:
1.73 xsa 708: /* Attach a symbolic name to this revision if specified. */
1.147 deraadt 709: if (pb->symbol != NULL &&
710: (checkin_attach_symbol(pb) < 0))
1.126 niallo 711: goto fail;
1.66 niallo 712:
1.73 xsa 713: /* Set the state of this revision if specified. */
1.66 niallo 714: if (pb->state != NULL)
715: (void)rcs_state_set(pb->file, pb->newrev, pb->state);
716:
1.128 niallo 717: /* Inherit RCSFILE permissions from file being checked in */
718: if (stat(pb->filename, &st) == -1)
719: goto fail;
720: /* Strip all the write bits */
721: pb->file->rf_mode = st.st_mode &
722: (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH);
723:
1.84 joris 724: xfree(filec);
1.66 niallo 725: (void)unlink(pb->filename);
726:
1.128 niallo 727: /* Write out RCSFILE before calling checkout_rev() */
728: rcs_write(pb->file);
729:
1.73 xsa 730: /* Do checkout if -u or -l are specified. */
1.147 deraadt 731: if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) &&
732: !(pb->flags & CI_DEFAULT)) {
1.66 niallo 733: checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags,
1.89 joris 734: pb->username, pb->author, NULL, NULL);
1.128 niallo 735: }
736:
1.63 niallo 737:
1.154 xsa 738: if (!(pb->flags & QUIET)) {
1.119 xsa 739: fprintf(stderr, "initial revision: %s\n",
740: rcsnum_tostr(pb->newrev, numb, sizeof(numb)));
741: }
1.93 xsa 742:
1.63 niallo 743: return (0);
1.126 niallo 744: fail:
1.149 ray 745: if (filec != NULL)
746: xfree(filec);
1.126 niallo 747: return (-1);
1.58 niallo 748: }
749:
1.59 niallo 750: /*
751: * checkin_attach_symbol()
752: *
753: * Attempt to attach the specified symbol to the revision.
754: * On success, return 0. On error return -1.
755: */
1.58 niallo 756: static int
757: checkin_attach_symbol(struct checkin_params *pb)
758: {
759: char rbuf[16];
760: int ret;
1.154 xsa 761: if (!(pb->flags & QUIET))
1.58 niallo 762: printf("symbol: %s\n", pb->symbol);
1.130 xsa 763: if (pb->flags & CI_SYMFORCE) {
764: if (rcs_sym_remove(pb->file, pb->symbol) < 0) {
765: if (rcs_errno != RCS_ERR_NOENT) {
1.155 xsa 766: warnx("problem removing symbol: %s",
767: pb->symbol);
1.130 xsa 768: return (-1);
769: }
770: }
771: }
1.147 deraadt 772: if ((ret = rcs_sym_add(pb->file, pb->symbol, pb->newrev) == -1) &&
773: (rcs_errno == RCS_ERR_DUPENT)) {
1.58 niallo 774: rcsnum_tostr(rcs_sym_getrev(pb->file, pb->symbol),
775: rbuf, sizeof(rbuf));
1.155 xsa 776: warnx("symbolic name %s already bound to %s", pb->symbol, rbuf);
1.58 niallo 777: return (-1);
778: } else if (ret == -1) {
1.155 xsa 779: warnx("problem adding symbol: %s", pb->symbol);
1.58 niallo 780: return (-1);
781: }
782: return (0);
783: }
784:
1.59 niallo 785: /*
786: * checkin_revert()
787: *
788: * If there are no differences between the working file and the latest revision
789: * and the -f flag is not specified, simply revert to the latest version and
790: * warn the user.
791: *
792: */
1.58 niallo 793: static void
794: checkin_revert(struct checkin_params *pb)
795: {
796: char rbuf[16];
797:
798: rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf));
1.155 xsa 799: warnx("file is unchanged; reverting to previous revision %s", rbuf);
1.137 niallo 800: pb->flags |= CO_REVERT;
1.58 niallo 801: (void)unlink(pb->filename);
802: if ((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK))
803: checkout_rev(pb->file, pb->frev, pb->filename,
1.89 joris 804: pb->flags, pb->username, pb->author, NULL, NULL);
1.127 xsa 805: if (rcs_lock_remove(pb->file, pb->username, pb->frev) < 0)
806: if (rcs_errno != RCS_ERR_NOENT)
1.155 xsa 807: warnx("failed to remove lock");
1.58 niallo 808: }
809:
1.59 niallo 810: /*
811: * checkin_checklock()
812: *
813: * Check for the existence of a lock on the file. If there are no locks, or it
814: * is not locked by the correct user, return -1. Otherwise, return 0.
815: */
1.58 niallo 816: static int
817: checkin_checklock(struct checkin_params *pb)
818: {
819: struct rcs_lock *lkp;
820:
1.82 joris 821: TAILQ_FOREACH(lkp, &(pb->file->rf_locks), rl_list) {
1.147 deraadt 822: if (!strcmp(lkp->rl_name, pb->username) &&
823: !rcsnum_cmp(lkp->rl_num, pb->frev, 0))
1.82 joris 824: return (0);
1.58 niallo 825: }
1.32 joris 826:
1.155 xsa 827: warnx("%s: no lock set by %s", pb->file->rf_path, pb->username);
1.82 joris 828: return (-1);
1.61 niallo 829: }
830:
831: /*
1.62 niallo 832: * checkin_mtimedate()
1.61 niallo 833: *
834: * Set the date of the revision to be the last modification
1.62 niallo 835: * time of the working file.
1.61 niallo 836: *
837: * On success, return 0. On error return -1.
838: */
839: static int
1.62 niallo 840: checkin_mtimedate(struct checkin_params *pb)
1.61 niallo 841: {
842: struct stat sb;
843: if (stat(pb->filename, &sb) != 0) {
1.155 xsa 844: warn("%s", pb->filename);
1.61 niallo 845: return (-1);
846: }
847: pb->date = (time_t)sb.st_mtimespec.tv_sec;
1.58 niallo 848: return (0);
1.98 niallo 849: }
850:
851: /*
852: * checkin_keywordscan()
853: *
854: * Searches working file for keyword values to determine its revision
855: * number, creation date and author, and uses these values instead of
856: * calculating them locally.
857: *
858: * Params: The data buffer to scan and pointers to pointers of variables in
859: * which to store the outputs.
860: *
861: * On success, return 0. On error return -1.
862: */
863: static int
864: checkin_keywordscan(char *data, RCSNUM **rev, time_t *date, char **author,
865: char **state)
866: {
1.122 ray 867: size_t end;
868: u_int j, found;
1.98 niallo 869: char *c, *kwstr, *start, buf[128];
870:
871: c = start = kwstr = NULL;
872:
1.122 ray 873: found = 0;
1.98 niallo 874:
1.122 ray 875: for (c = data; *c != '\0'; c++) {
1.98 niallo 876: if (*c == '$') {
877: start = c;
1.118 deraadt 878: c++;
1.98 niallo 879: if (!isalpha(*c)) {
880: c = start;
881: continue;
882: }
883: /* look for any matching keywords */
1.107 deraadt 884: found = 0;
885: for (j = 0; j < 10; j++) {
886: if (!strncmp(c, rcs_expkw[j].kw_str,
887: strlen(rcs_expkw[j].kw_str))) {
888: found = 1;
889: kwstr = rcs_expkw[j].kw_str;
890: break;
891: }
892: }
893:
894: /* unknown keyword, continue looking */
895: if (found == 0) {
896: c = start;
897: continue;
898: }
1.98 niallo 899:
900: c += strlen(kwstr);
901: if (*c != ':' && *c != '$') {
902: c = start;
903: continue;
904: }
905:
906: if (*c == ':') {
907: while (*c++) {
908: if (*c == '$') {
909: end = c - start + 2;
910: if (end >= sizeof(buf))
911: fatal("keyword buffer"
912: " too small!");
913: strlcpy(buf, start, end);
914: checkin_parsekeyword(buf, rev,
1.107 deraadt 915: date, author, state);
1.98 niallo 916: break;
917: }
918: }
919:
920: if (*c != '$') {
921: c = start;
922: continue;
923: }
924: }
925: }
926: }
927: if (found == 0)
928: return (-1);
929: else
930: return (0);
931: }
932:
933: /*
934: * checkin_keywordtype()
935: *
936: * Given an RCS keyword string, determine what type of string it is.
937: * This enables us to know what data should be in it.
938: *
939: * Returns type on success, or -1 on failure.
1.107 deraadt 940: */
1.98 niallo 941: static int
942: checkin_keywordtype(char *keystring)
943: {
944: char *p;
1.107 deraadt 945:
1.98 niallo 946: p = keystring;
1.118 deraadt 947: p++;
1.98 niallo 948: if (strncmp(p, KW_ID, strlen(KW_ID)) == 0)
949: return (KW_TYPE_ID);
950: else if (strncmp(p, KW_AUTHOR, strlen(KW_AUTHOR)) == 0)
951: return (KW_TYPE_AUTHOR);
952: else if (strncmp(p, KW_DATE, strlen(KW_DATE)) == 0)
953: return (KW_TYPE_DATE);
954: else if (strncmp(p, KW_STATE, strlen(KW_STATE)) == 0)
955: return (KW_TYPE_STATE);
956: else if (strncmp(p, KW_REVISION, strlen(KW_REVISION)) == 0)
957: return (KW_TYPE_REVISION);
958: else
959: return (-1);
960: }
961:
962: /*
963: * checkin_parsekeyword()
964: *
965: * Do the actual parsing of an RCS keyword string, setting the values passed
966: * to the function to whatever is found.
967: *
968: */
969: static void
970: checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date,
971: char **author, char **state)
972: {
973: char *tokens[10], *p, *datestring;
974: size_t len = 0;
1.99 niallo 975: int i = 0;
1.107 deraadt 976:
977: /* Parse data out of the expanded keyword */
1.98 niallo 978: switch (checkin_keywordtype(keystring)) {
979: case KW_TYPE_ID:
1.101 niallo 980: for ((p = strtok(keystring, " ")); p;
1.107 deraadt 981: (p = strtok(NULL, " "))) {
1.99 niallo 982: if (i < KW_NUMTOKS_ID - 1)
983: tokens[i++] = p;
1.107 deraadt 984: }
1.99 niallo 985: tokens[i] = NULL;
1.98 niallo 986: if (*author != NULL)
987: xfree(*author);
988: if (*state != NULL)
989: xfree(*state);
990: /* only parse revision if one is not already set */
991: if (*rev == NULL) {
992: if ((*rev = rcsnum_parse(tokens[2])) == NULL)
993: fatal("could not parse rcsnum");
994: }
1.135 ray 995: *author = xstrdup(tokens[5]);
1.146 pat 996: *state = xstrdup(tokens[6]);
1.98 niallo 997: len = strlen(tokens[3]) + strlen(tokens[4]) + 2;
998: datestring = xmalloc(len);
999: strlcpy(datestring, tokens[3], len);
1000: strlcat(datestring, " ", len);
1001: strlcat(datestring, tokens[4], len);
1002: if ((*date = cvs_date_parse(datestring)) <= 0)
1003: fatal("could not parse date\n");
1004: xfree(datestring);
1005: break;
1006: case KW_TYPE_AUTHOR:
1.101 niallo 1007: for ((p = strtok(keystring, " ")); p;
1.107 deraadt 1008: (p = strtok(NULL, " "))) {
1.99 niallo 1009: if (i < KW_NUMTOKS_AUTHOR - 1)
1010: tokens[i++] = p;
1.107 deraadt 1011: }
1.98 niallo 1012: if (*author != NULL)
1013: xfree(*author);
1.135 ray 1014: *author = xstrdup(tokens[1]);
1.98 niallo 1015: break;
1016: case KW_TYPE_DATE:
1.101 niallo 1017: for ((p = strtok(keystring, " ")); p;
1.107 deraadt 1018: (p = strtok(NULL, " "))) {
1.99 niallo 1019: if (i < KW_NUMTOKS_DATE - 1)
1020: tokens[i++] = p;
1.98 niallo 1021: }
1022: len = strlen(tokens[1]) + strlen(tokens[2]) + 2;
1023: datestring = xmalloc(len);
1024: strlcpy(datestring, tokens[1], len);
1025: strlcat(datestring, " ", len);
1026: strlcat(datestring, tokens[2], len);
1027: if ((*date = cvs_date_parse(datestring)) <= 0)
1028: fatal("could not parse date\n");
1029: xfree(datestring);
1030: break;
1031: case KW_TYPE_STATE:
1.101 niallo 1032: for ((p = strtok(keystring, " ")); p;
1.107 deraadt 1033: (p = strtok(NULL, " "))) {
1.99 niallo 1034: if (i < KW_NUMTOKS_STATE - 1)
1035: tokens[i++] = p;
1.107 deraadt 1036: }
1.98 niallo 1037: if (*state != NULL)
1038: xfree(*state);
1.135 ray 1039: *state = xstrdup(tokens[1]);
1.98 niallo 1040: break;
1041: case KW_TYPE_REVISION:
1042: /* only parse revision if one is not already set */
1043: if (*rev != NULL)
1044: break;
1.102 niallo 1045: for ((p = strtok(keystring, " ")); p;
1.107 deraadt 1046: (p = strtok(NULL, " "))) {
1.99 niallo 1047: if (i < KW_NUMTOKS_REVISION - 1)
1048: tokens[i++] = p;
1.107 deraadt 1049: }
1.98 niallo 1050: if ((*rev = rcsnum_parse(tokens[1])) == NULL)
1051: fatal("could not parse rcsnum");
1052: break;
1053: }
1.1 niallo 1054: }