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