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