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