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