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