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