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