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