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