Annotation of src/usr.bin/rcs/ci.c, Revision 1.57
1.57 ! xsa 1: /* $OpenBSD: ci.c,v 1.56 2005/11/02 21:42:16 niallo Exp $ */
1.1 niallo 2: /*
3: * Copyright (c) 2005 Niall O'Higgins <niallo@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following cinditions
8: * are met:
9: *
10: * 1. Redistributions of source cide must retain the above cipyright
11: * notice, this list of cinditions and the following disclaimer.
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:
27: #include <sys/param.h>
28: #include <sys/types.h>
29: #include <sys/stat.h>
30: #include <sys/wait.h>
31:
32: #include <err.h>
33: #include <pwd.h>
34: #include <errno.h>
35: #include <stdio.h>
36: #include <ctype.h>
37: #include <stdlib.h>
38: #include <unistd.h>
39: #include <string.h>
1.20 niallo 40: #include <time.h>
1.1 niallo 41:
42: #include "log.h"
43: #include "rcs.h"
1.4 niallo 44: #include "diff.h"
1.1 niallo 45: #include "rcsprog.h"
1.9 niallo 46:
1.25 niallo 47: #define DATE_NOW -1
48: #define DATE_MTIME -2
49:
1.4 niallo 50: static char * checkin_diff_file(RCSFILE *, RCSNUM *, const char *);
1.34 niallo 51: static char * checkin_getlogmsg(RCSNUM *, RCSNUM *);
1.4 niallo 52:
1.1 niallo 53: void
54: checkin_usage(void)
55: {
56: fprintf(stderr,
1.29 niallo 57: "usage: ci [-jMNqV] [-d[date]] [-f[rev]] [-kmode] [-l[rev]]\n"
1.55 niallo 58: " [-M[rev]] [-mmsg] [-Nsymbol] [-nsymbol] [-r[rev]]\n"
59: " [-sstate] [-u[rev]] [-wusername] file ...\n");
60:
1.1 niallo 61: }
62:
63: /*
64: * checkin_main()
65: *
66: * Handler for the `ci' program.
67: * Returns 0 on success, or >0 on error.
68: */
69: int
70: checkin_main(int argc, char **argv)
71: {
1.54 niallo 72: int found, notlocked, ret, status;
73: int i, ch, flags;
1.1 niallo 74: mode_t fmode;
1.27 niallo 75: time_t date;
1.1 niallo 76: RCSFILE *file;
1.12 niallo 77: RCSNUM *frev, *newrev;
1.1 niallo 78: char fpath[MAXPATHLEN];
1.46 joris 79: char *rcs_msg, *filec, *deltatext, *username, rbuf[16];
1.51 niallo 80: const char *symbol, *state;
1.16 niallo 81: struct rcs_lock *lkp;
1.4 niallo 82: BUF *bp;
1.1 niallo 83:
1.27 niallo 84: date = DATE_NOW;
1.1 niallo 85: file = NULL;
1.31 niallo 86: rcs_msg = username = NULL;
1.51 niallo 87: state = symbol = NULL;
1.12 niallo 88: newrev = NULL;
1.54 niallo 89: fmode = flags = status = 0;
1.1 niallo 90:
1.54 niallo 91: flags |= INTERACTIVE;
1.9 niallo 92:
1.54 niallo 93: while ((ch = rcs_getopt(argc, argv, "d::f::j:k:l::m:M::N:n:qr::s:u::Vw:")) != -1) {
1.1 niallo 94: switch (ch) {
1.20 niallo 95: case 'd':
1.25 niallo 96: if (rcs_optarg == NULL)
97: date = DATE_MTIME;
98: else if ((date = cvs_date_parse(rcs_optarg)) <= 0) {
1.20 niallo 99: cvs_log(LP_ERR, "invalide date");
100: exit(1);
101: }
102: break;
1.29 niallo 103: case 'f':
1.45 joris 104: rcs_set_rev(rcs_optarg, &newrev);
1.54 niallo 105: flags |= FORCE;
1.29 niallo 106: break;
1.1 niallo 107: case 'h':
108: (usage)();
109: exit(0);
1.30 niallo 110: case 'l':
1.45 joris 111: rcs_set_rev(rcs_optarg, &newrev);
1.54 niallo 112: flags |= CO_LOCK;
113: break;
114: case 'M':
115: rcs_set_rev(rcs_optarg, &newrev);
116: flags |= CO_REVDATE;
1.30 niallo 117: break;
1.1 niallo 118: case 'm':
1.24 joris 119: rcs_msg = rcs_optarg;
1.54 niallo 120: flags &= ~INTERACTIVE;
1.3 joris 121: break;
1.42 niallo 122: case 'N':
123: if ((symbol = strdup(rcs_optarg)) == NULL) {
124: cvs_log(LP_ERRNO, "out of memory");
125: exit(1);
126: }
127: if (rcs_sym_check(symbol) != 1) {
128: cvs_log(LP_ERR, "invalid symbol `%s'", symbol);
129: exit(1);
130: }
1.54 niallo 131: flags |= CI_SYMFORCE;
1.42 niallo 132: break;
1.38 niallo 133: case 'n':
134: if ((symbol = strdup(rcs_optarg)) == NULL) {
135: cvs_log(LP_ERRNO, "out of memory");
136: exit(1);
137: }
138: if (rcs_sym_check(symbol) != 1) {
139: cvs_log(LP_ERR, "invalid symbol `%s'", symbol);
140: exit(1);
141: }
142: break;
1.3 joris 143: case 'q':
144: verbose = 0;
1.1 niallo 145: break;
1.30 niallo 146: case 'r':
1.45 joris 147: rcs_set_rev(rcs_optarg, &newrev);
1.54 niallo 148: flags |= CI_DEFAULT;
1.9 niallo 149: break;
1.51 niallo 150: case 's':
151: state = rcs_optarg;
152: if (rcs_state_check(state) < 0) {
1.53 xsa 153: cvs_log(LP_ERR, "invalid state `%s'", state);
1.51 niallo 154: exit(1);
155: }
156: break;
1.9 niallo 157: case 'u':
1.45 joris 158: rcs_set_rev(rcs_optarg, &newrev);
1.54 niallo 159: flags |= CO_UNLOCK;
1.9 niallo 160: break;
1.30 niallo 161: case 'V':
162: printf("%s\n", rcs_version);
163: exit(0);
1.31 niallo 164: case 'w':
165: username = rcs_optarg;
166: break;
1.1 niallo 167: default:
168: (usage)();
169: exit(1);
170: }
171: }
172:
1.24 joris 173: argc -= rcs_optind;
174: argv += rcs_optind;
175:
1.1 niallo 176: if (argc == 0) {
177: cvs_log(LP_ERR, "no input file");
178: (usage)();
179: exit(1);
180: }
181:
1.31 niallo 182: if ((username == NULL) && (username = getlogin()) == NULL) {
183: cvs_log(LP_ERRNO, "failed to get username");
184: exit(1);
185: }
186:
187:
1.1 niallo 188: for (i = 0; i < argc; i++) {
189: if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0)
190: continue;
191:
1.4 niallo 192: file = rcs_open(fpath, RCS_RDWR, fmode);
1.1 niallo 193: if (file == NULL) {
1.4 niallo 194: cvs_log(LP_ERR, "failed to open rcsfile '%s'", fpath);
1.1 niallo 195: exit(1);
196: }
1.29 niallo 197:
1.17 niallo 198: frev = file->rf_head;
1.36 niallo 199:
1.46 joris 200: if (verbose == 1)
201: printf("%s <-- %s\n", fpath, argv[i]);
1.29 niallo 202:
1.17 niallo 203: /*
204: * If revision passed on command line is less than HEAD, bail.
205: */
206: if ((newrev != NULL) && (rcsnum_cmp(newrev, frev, 0) > 0)) {
207: cvs_log(LP_ERR, "revision is too low!");
208: status = 1;
209: rcs_close(file);
210: continue;
211: }
1.4 niallo 212:
213: /*
214: * Load file contents
215: */
216: if ((bp = cvs_buf_load(argv[i], BUF_AUTOEXT)) == NULL) {
217: cvs_log(LP_ERR, "failed to load '%s'", argv[i]);
218: exit(1);
219: }
220:
1.12 niallo 221: if (cvs_buf_putc(bp, '\0') < 0)
222: exit(1);
223:
1.23 niallo 224: filec = (char *)cvs_buf_release(bp);
1.13 joris 225:
1.6 niallo 226: /*
1.29 niallo 227: * Get RCS patch
228: */
229: if ((deltatext = checkin_diff_file(file, frev, argv[i])) == NULL) {
230: cvs_log(LP_ERR, "failed to get diff");
231: exit(1);
232: }
233:
234: /*
235: * If -f is not specified and there are no differences, tell the
236: * user and revert to latest version.
237: */
1.54 niallo 238: if ((flags & FORCE) && (strlen(deltatext) < 1)) {
1.46 joris 239: rcsnum_tostr(frev, rbuf, sizeof(rbuf));
1.48 xsa 240: cvs_log(LP_WARN,
1.29 niallo 241: "file is unchanged; reverting to previous revision %s",
1.46 joris 242: rbuf);
1.29 niallo 243: (void)unlink(argv[i]);
1.54 niallo 244: if ((flags & CO_LOCK) || (flags & CO_UNLOCK))
245: checkout_rev(file, frev, argv[i], flags,
246: username);
1.40 niallo 247: rcs_lock_remove(file, frev);
1.29 niallo 248: rcs_close(file);
1.46 joris 249: if (verbose == 1)
250: printf("done\n");
1.29 niallo 251: continue;
252: }
253:
254: /*
1.16 niallo 255: * Check for a lock belonging to this user. If none,
256: * abort check-in.
257: */
1.44 joris 258: found = 0;
259: notlocked = 1;
260: if (!TAILQ_EMPTY(&(file->rf_locks))) {
261: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
262: if (!strcmp(lkp->rl_name, username))
263: notlocked = 0;
264:
265: if (!strcmp(lkp->rl_name, username) &&
266: !rcsnum_cmp(lkp->rl_num, frev, 0)) {
267: found = 1;
268: break;
269: }
270: }
271: }
272:
1.49 xsa 273: if ((found == 0) && (notlocked == 0)) {
1.44 joris 274: cvs_log(LP_ERR, "no locks set for '%s'", username);
1.16 niallo 275: status = 1;
1.29 niallo 276: rcs_close(file);
1.16 niallo 277: continue;
278: }
279:
280: /*
1.6 niallo 281: * If no log message specified, get it interactively.
282: */
1.14 joris 283: if (rcs_msg == NULL)
1.34 niallo 284: rcs_msg = checkin_getlogmsg(frev, newrev);
1.4 niallo 285:
286: /*
287: * Remove the lock
288: */
289: if (rcs_lock_remove(file, frev) < 0) {
290: if (rcs_errno != RCS_ERR_NOENT)
291: cvs_log(LP_WARN, "failed to remove lock");
292: }
293:
294: /*
295: * Current head revision gets the RCS patch as rd_text
296: */
1.12 niallo 297: if (rcs_deltatext_set(file, frev, deltatext) == -1) {
1.7 niallo 298: cvs_log(LP_ERR,
299: "failed to set new rd_text for head rev");
1.4 niallo 300: exit (1);
1.25 niallo 301: }
1.32 joris 302:
1.25 niallo 303: /*
304: * Set the date of the revision to be the last modification time
305: * of the working file if -d is specified without an argument.
306: */
307: if (date == DATE_MTIME) {
308: struct stat sb;
309: if (stat(argv[i], &sb) != 0) {
1.33 niallo 310: cvs_log(LP_ERRNO, "failed to stat: `%s'",
311: argv[i]);
1.25 niallo 312: rcs_close(file);
313: continue;
314: }
315: date = (time_t)sb.st_mtimespec.tv_sec;
1.4 niallo 316: }
1.32 joris 317:
1.4 niallo 318: /*
319: * Now add our new revision
320: */
1.12 niallo 321: if (rcs_rev_add(file, (newrev == NULL ? RCS_HEAD_REV : newrev),
1.31 niallo 322: rcs_msg, date, username) != 0) {
1.4 niallo 323: cvs_log(LP_ERR, "failed to add new revision");
324: exit(1);
325: }
326:
327: /*
1.12 niallo 328: * If we are checking in to a non-default (ie user-specified)
329: * revision, set head to this revision.
330: */
331: if (newrev != NULL)
332: rcs_head_set(file, newrev);
1.15 niallo 333: else
334: newrev = file->rf_head;
1.32 joris 335:
1.12 niallo 336: /*
1.4 niallo 337: * New head revision has to contain entire file;
338: */
339: if (rcs_deltatext_set(file, frev, filec) == -1) {
340: cvs_log(LP_ERR, "failed to set new head revision");
341: exit(1);
1.38 niallo 342: }
343:
344: /*
345: * Attach a symbolic name to this revision if specified.
346: */
347: if (symbol != NULL) {
1.46 joris 348: if (verbose == 1)
349: printf("symbol: %s\n", symbol);
1.54 niallo 350: if (flags & CI_SYMFORCE)
1.43 niallo 351: rcs_sym_remove(file, symbol);
1.38 niallo 352: if ((ret = rcs_sym_add(file, symbol, newrev) == -1)
353: && (rcs_errno == RCS_ERR_DUPENT)) {
1.42 niallo 354: rcsnum_tostr(rcs_sym_getrev(file, symbol),
1.46 joris 355: rbuf, sizeof(rbuf));
1.42 niallo 356: cvs_log(LP_ERR,
357: "symbolic name %s already bound to %s",
1.46 joris 358: symbol, rbuf);
1.38 niallo 359: status = 1;
360: rcs_close(file);
361: continue;
362: } else if (ret == -1) {
1.46 joris 363: cvs_log(LP_ERR, "problem adding symbol: %s",
1.42 niallo 364: symbol);
1.38 niallo 365: status = 1;
366: rcs_close(file);
367: continue;
368: }
1.4 niallo 369: }
1.51 niallo 370:
371: /*
372: * Set the state of this revision if specified.
373: */
374: if (state != NULL)
375: (void)rcs_state_set(file, newrev, state);
1.4 niallo 376:
377: free(deltatext);
378: free(filec);
1.9 niallo 379: (void)unlink(argv[i]);
1.4 niallo 380:
1.9 niallo 381: /*
382: * Do checkout if -u or -l are specified.
383: */
1.54 niallo 384: if (((flags & CO_LOCK) || (flags & CO_UNLOCK))
385: && !(flags & CI_DEFAULT))
386: checkout_rev(file, newrev, argv[i], flags, username);
1.28 niallo 387:
1.4 niallo 388: /* File will NOW be synced */
1.1 niallo 389: rcs_close(file);
1.4 niallo 390:
1.54 niallo 391: if (flags & INTERACTIVE) {
1.6 niallo 392: free(rcs_msg);
393: rcs_msg = NULL;
394: }
1.4 niallo 395: }
396:
1.16 niallo 397: return (status);
1.4 niallo 398: }
399:
400: static char *
401: checkin_diff_file(RCSFILE *rfp, RCSNUM *rev, const char *filename)
402: {
403: char path1[MAXPATHLEN], path2[MAXPATHLEN];
404: BUF *b1, *b2, *b3;
405: char rbuf[64], *deltatext;
406:
407: rcsnum_tostr(rev, rbuf, sizeof(rbuf));
408:
409: if ((b1 = cvs_buf_load(filename, BUF_AUTOEXT)) == NULL) {
410: cvs_log(LP_ERR, "failed to load file: '%s'", filename);
411: return (NULL);
1.1 niallo 412: }
413:
1.4 niallo 414: if ((b2 = rcs_getrev(rfp, rev)) == NULL) {
415: cvs_log(LP_ERR, "failed to load revision");
416: cvs_buf_free(b1);
417: return (NULL);
418: }
419:
1.57 ! xsa 420: if ((b3 = cvs_buf_alloc((size_t)128, BUF_AUTOEXT)) == NULL) {
1.4 niallo 421: cvs_log(LP_ERR, "failed to allocated buffer for diff");
422: cvs_buf_free(b1);
423: cvs_buf_free(b2);
424: return (NULL);
425: }
426:
1.50 xsa 427: strlcpy(path1, rcs_tmpdir, sizeof(path1));
428: strlcat(path1, "/diff1.XXXXXXXXXX", sizeof(path1));
1.4 niallo 429: if (cvs_buf_write_stmp(b1, path1, 0600) == -1) {
430: cvs_log(LP_ERRNO, "could not write temporary file");
431: cvs_buf_free(b1);
432: cvs_buf_free(b2);
433: return (NULL);
434: }
435: cvs_buf_free(b1);
436:
1.50 xsa 437: strlcpy(path2, rcs_tmpdir, sizeof(path2));
438: strlcat(path2, "/diff2.XXXXXXXXXX", sizeof(path2));
1.4 niallo 439: if (cvs_buf_write_stmp(b2, path2, 0600) == -1) {
440: cvs_buf_free(b2);
441: (void)unlink(path1);
442: return (NULL);
443: }
444: cvs_buf_free(b2);
445:
1.5 niallo 446: diff_format = D_RCSDIFF;
1.4 niallo 447: cvs_diffreg(path1, path2, b3);
448: (void)unlink(path1);
449: (void)unlink(path2);
450:
451: cvs_buf_putc(b3, '\0');
452: deltatext = (char *)cvs_buf_release(b3);
453:
454: return (deltatext);
1.6 niallo 455: }
456:
457: /*
458: * Get log message from user interactively.
459: */
460: static char *
1.34 niallo 461: checkin_getlogmsg(RCSNUM *rev, RCSNUM *rev2)
1.6 niallo 462: {
463: char *rcs_msg, buf[128], nrev[16], prev[16];
464: BUF *logbuf;
465: RCSNUM *tmprev;
466:
1.7 niallo 467: rcs_msg = NULL;
1.6 niallo 468: tmprev = rcsnum_alloc();
469: rcsnum_cpy(rev, tmprev, 16);
1.9 niallo 470: rcsnum_tostr(tmprev, prev, sizeof(prev));
1.12 niallo 471: if (rev2 == NULL)
472: rcsnum_tostr(rcsnum_inc(tmprev), nrev, sizeof(nrev));
473: else
474: rcsnum_tostr(rev2, nrev, sizeof(nrev));
1.6 niallo 475: rcsnum_free(tmprev);
476:
1.57 ! xsa 477: if ((logbuf = cvs_buf_alloc((size_t)64, BUF_AUTOEXT)) == NULL) {
1.7 niallo 478: cvs_log(LP_ERR, "failed to allocate log buffer");
1.6 niallo 479: return (NULL);
480: }
1.32 joris 481:
1.47 niallo 482: if (verbose == 1)
483: printf("new revision: %s; previous revision: %s\n", nrev,
484: prev);
1.46 joris 485: printf("enter log message, terminated with single "
1.6 niallo 486: "'.' or end of file:\n");
1.46 joris 487: printf(">> ");
1.32 joris 488:
1.6 niallo 489: for (;;) {
490: fgets(buf, (int)sizeof(buf), stdin);
1.9 niallo 491: if (feof(stdin) || ferror(stdin) || buf[0] == '.')
1.6 niallo 492: break;
493: cvs_buf_append(logbuf, buf, strlen(buf));
1.46 joris 494: printf(">> ");
1.6 niallo 495: }
1.32 joris 496:
1.8 niallo 497: cvs_buf_putc(logbuf, '\0');
1.6 niallo 498: rcs_msg = (char *)cvs_buf_release(logbuf);
1.32 joris 499:
1.6 niallo 500: return (rcs_msg);
1.1 niallo 501: }