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