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