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