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