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