xref: /openbsd-src/usr.bin/cvs/commit.c (revision b2fef78a7279392608c9a02a5c5f385dea6c1b39)
1 /*	$OpenBSD: commit.c,v 1.70 2006/06/06 05:18:23 joris Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include "includes.h"
19 
20 #include "cvs.h"
21 #include "diff.h"
22 #include "log.h"
23 #include "proto.h"
24 
25 int	cvs_commit(int, char **);
26 void	cvs_commit_local(struct cvs_file *);
27 void	cvs_commit_check_conflicts(struct cvs_file *);
28 
29 static char *commit_diff_file(struct cvs_file *);
30 
31 struct	cvs_flisthead files_affected;
32 int	conflicts_found;
33 char	*logmsg;
34 
35 struct cvs_cmd cvs_cmd_commit = {
36 	CVS_OP_COMMIT, CVS_REQ_CI, "commit",
37 	{ "ci", "com" },
38 	"Check files into the repository",
39 	"[-flR] [-F logfile | -m msg] [-r rev] ...",
40 	"F:flm:Rr:",
41 	NULL,
42 	cvs_commit
43 };
44 
45 int
46 cvs_commit(int argc, char **argv)
47 {
48 	int ch;
49 	BUF *bp;
50 	char *arg = ".";
51 	int flags;
52 	struct cvs_recursion cr;
53 
54 	flags = CR_RECURSE_DIRS;
55 
56 	while ((ch = getopt(argc, argv, cvs_cmd_commit.cmd_opts)) != -1) {
57 		switch (ch) {
58 		case 'f':
59 			break;
60 		case 'F':
61 			bp = cvs_buf_load(optarg, BUF_AUTOEXT);
62 			if (bp == NULL)
63 				fatal("failed to load commit message %s",
64 				    optarg);
65 			cvs_buf_putc(bp, '\0');
66 			logmsg = cvs_buf_release(bp);
67 			break;
68 		case 'l':
69 			flags &= ~CR_RECURSE_DIRS;
70 			break;
71 		case 'm':
72 			logmsg = xstrdup(optarg);
73 			break;
74 		case 'r':
75 			break;
76 		case 'R':
77 			break;
78 		default:
79 			fatal("%s", cvs_cmd_commit.cmd_synopsis);
80 		}
81 	}
82 
83 	argc -= optind;
84 	argv += optind;
85 
86 	if (logmsg == NULL)
87 		fatal("please use -m to specify a log message for now");
88 
89 	TAILQ_INIT(&files_affected);
90 	conflicts_found = 0;
91 
92 	cr.enterdir = NULL;
93 	cr.leavedir = NULL;
94 	cr.local = cvs_commit_check_conflicts;
95 	cr.remote = NULL;
96 	cr.flags = flags;
97 
98 	if (argc > 0)
99 		cvs_file_run(argc, argv, &cr);
100 	else
101 		cvs_file_run(1, &arg, &cr);
102 
103 	if (conflicts_found != 0)
104 		fatal("%d conflicts found, please correct these first",
105 		    conflicts_found);
106 
107 	cr.local = cvs_commit_local;
108 	cvs_file_walklist(&files_affected, &cr);
109 	cvs_file_freelist(&files_affected);
110 
111 	return (0);
112 }
113 
114 void
115 cvs_commit_check_conflicts(struct cvs_file *cf)
116 {
117 	cvs_log(LP_TRACE, "cvs_commit_check_conflicts(%s)", cf->file_path);
118 
119 	/*
120 	 * cvs_file_classify makes the noise for us
121 	 * XXX - we want that?
122 	 */
123 	cvs_file_classify(cf, NULL, 1);
124 
125 	if (cf->file_type == CVS_DIR) {
126 		if (verbosity > 1)
127 			cvs_log(LP_NOTICE, "Examining %s", cf->file_path);
128 		return;
129 	}
130 
131 	if (cf->file_status == FILE_CONFLICT ||
132 	    cf->file_status == FILE_LOST ||
133 	    cf->file_status == FILE_UNLINK)
134 		conflicts_found++;
135 
136 	if (cf->file_status != FILE_REMOVED &&
137 	    update_has_conflict_markers(cf)) {
138 		cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from "
139 		    "merging, please fix these first", cf->file_path);
140 		conflicts_found++;
141 	}
142 
143 	if (cf->file_status == FILE_MERGE ||
144 	    cf->file_status == FILE_PATCH) {
145 		cvs_log(LP_ERR, "conflict: %s is not up-to-date",
146 		    cf->file_path);
147 		conflicts_found++;
148 	}
149 
150 	if (cf->file_status == FILE_ADDED ||
151 	    cf->file_status == FILE_REMOVED ||
152 	    cf->file_status == FILE_MODIFIED)
153 		cvs_file_get(cf->file_path, &files_affected);
154 }
155 
156 void
157 cvs_commit_local(struct cvs_file *cf)
158 {
159 	BUF *b;
160 	int isnew;
161 	int l, openflags, rcsflags;
162 	char *d, *f, rbuf[24];
163 	CVSENTRIES *entlist;
164 	char *attic, *repo, *rcsfile;
165 
166 	cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path);
167 	cvs_file_classify(cf, NULL, 0);
168 
169 	if (cvs_noexec == 1)
170 		return;
171 
172 	if (cf->file_type != CVS_FILE)
173 		fatal("cvs_commit_local: '%s' is not a file", cf->file_path);
174 
175 	if (cf->file_status == FILE_MODIFIED ||
176 	    cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED
177 	    && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1))
178 		rcsnum_tostr(rcs_head_get(cf->file_rcs), rbuf, sizeof(rbuf));
179 	else
180 		strlcpy(rbuf, "Non-existent", sizeof(rbuf));
181 
182 	isnew = 0;
183 	if (cf->file_status == FILE_ADDED) {
184 		isnew = 1;
185 		rcsflags = RCS_CREATE;
186 		openflags = O_CREAT | O_TRUNC | O_WRONLY;
187 		if (cf->file_rcs != NULL) {
188 			if (cf->file_rcs->rf_inattic == 0)
189 				cvs_log(LP_ERR, "warning: expected %s "
190 				    "to be in the Attic", cf->file_path);
191 
192 			if (cf->file_rcs->rf_dead == 0)
193 				cvs_log(LP_ERR, "warning: expected %s "
194 				    "to be dead", cf->file_path);
195 
196 			rcsfile = xmalloc(MAXPATHLEN);
197 			repo = xmalloc(MAXPATHLEN);
198 			cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
199 			l = snprintf(rcsfile, MAXPATHLEN, "%s/%s%s",
200 			    repo, cf->file_name, RCS_FILE_EXT);
201 			if (l == -1 || l >= MAXPATHLEN)
202 				fatal("cvs_commit_local: overflow");
203 
204 			if (rename(cf->file_rpath, rcsfile) == -1)
205 				fatal("cvs_commit_local: failed to move %s "
206 				    "outside the Attic: %s", cf->file_path,
207 				    strerror(errno));
208 
209 			xfree(cf->file_rpath);
210 			cf->file_rpath = xstrdup(rcsfile);
211 			xfree(rcsfile);
212 			xfree(repo);
213 
214 			rcsflags = RCS_READ | RCS_PARSE_FULLY;
215 			openflags = O_RDONLY;
216 			rcs_close(cf->file_rcs);
217 			isnew = 0;
218 		}
219 
220 		cf->repo_fd = open(cf->file_rpath, openflags);
221 		if (cf->repo_fd < 0)
222 			fatal("cvs_commit_local: %s", strerror(errno));
223 
224 		cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
225 		    rcsflags, 0600);
226 		if (cf->file_rcs == NULL)
227 			fatal("cvs_commit_local: failed to create RCS file "
228 			    "for %s", cf->file_path);
229 	}
230 
231 	cvs_printf("Checking in %s:\n", cf->file_path);
232 	cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path);
233 	cvs_printf("old revision: %s; ", rbuf);
234 
235 	if (isnew == 0)
236 		d = commit_diff_file(cf);
237 
238 	if (cf->file_status == FILE_REMOVED) {
239 		b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head);
240 		if (b == NULL)
241 			fatal("cvs_commit_local: failed to get HEAD");
242 	} else {
243 		if ((b = cvs_buf_load(cf->file_path, BUF_AUTOEXT)) == NULL)
244 			fatal("cvs_commit_local: failed to load file");
245 	}
246 
247 	cvs_buf_putc(b, '\0');
248 	f = cvs_buf_release(b);
249 
250 	if (isnew == 0) {
251 		if (rcs_deltatext_set(cf->file_rcs,
252 		    cf->file_rcs->rf_head, d) == -1)
253 			fatal("cvs_commit_local: failed to set delta");
254 	}
255 
256 	if (rcs_rev_add(cf->file_rcs, RCS_HEAD_REV, logmsg, -1, NULL) == -1)
257 		fatal("cvs_commit_local: failed to add new revision");
258 
259 	if (rcs_deltatext_set(cf->file_rcs, cf->file_rcs->rf_head, f) == -1)
260 		fatal("cvs_commit_local: failed to set new HEAD delta");
261 
262 	xfree(f);
263 
264 	if (isnew == 0)
265 		xfree(d);
266 
267 	if (cf->file_status == FILE_REMOVED) {
268 		if (rcs_state_set(cf->file_rcs,
269 		    cf->file_rcs->rf_head, RCS_STATE_DEAD) == -1)
270 			fatal("cvs_commit_local: failed to set state");
271 	}
272 
273 	if (cf->file_rcs->rf_branch != NULL) {
274 		rcsnum_free(cf->file_rcs->rf_branch);
275 		cf->file_rcs->rf_branch = NULL;
276 	}
277 
278 	rcs_write(cf->file_rcs);
279 
280 	if (cf->file_status == FILE_REMOVED) {
281 		strlcpy(rbuf, "Removed", sizeof(rbuf));
282 	} else if (cf->file_status == FILE_ADDED) {
283 		if (cf->file_rcs->rf_dead == 1)
284 			strlcpy(rbuf, "Initial Revision", sizeof(rbuf));
285 		else
286 			rcsnum_tostr(cf->file_rcs->rf_head,
287 			    rbuf, sizeof(rbuf));
288 	} else if (cf->file_status == FILE_MODIFIED) {
289 		rcsnum_tostr(cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
290 	}
291 
292 	cvs_printf("new revision: %s\n", rbuf);
293 
294 	(void)unlink(cf->file_path);
295 	(void)close(cf->fd);
296 	cf->fd = -1;
297 
298 	if (cf->file_status != FILE_REMOVED) {
299 		b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head);
300 		if (b == NULL)
301 			fatal("cvs_commit_local: failed to get HEAD");
302 
303 		cvs_checkout_file(cf, cf->file_rcs->rf_head, b, 0);
304 	} else {
305 		entlist = cvs_ent_open(cf->file_wd);
306 		cvs_ent_remove(entlist, cf->file_name);
307 		cvs_ent_close(entlist, ENT_SYNC);
308 
309 		repo = xmalloc(MAXPATHLEN);
310 		attic = xmalloc(MAXPATHLEN);
311 		cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
312 
313 		l = snprintf(attic, MAXPATHLEN, "%s/%s", repo, CVS_PATH_ATTIC);
314 		if (l == -1 || l >= MAXPATHLEN)
315 			fatal("cvs_commit_local: overflow");
316 
317 		if (mkdir(attic, 0755) == -1 && errno != EEXIST)
318 			fatal("cvs_commit_local: failed to create Attic");
319 
320 		l = snprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
321 		    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
322 		if (l == -1 || l >= MAXPATHLEN)
323 			fatal("cvs_commit_local: overflow");
324 
325 		if (rename(cf->file_rpath, attic) == -1)
326 			fatal("cvs_commit_local: failed to move %s to Attic",
327 			    cf->file_path);
328 
329 		xfree(repo);
330 		xfree(attic);
331 	}
332 
333 	cvs_printf("done\n");
334 
335 }
336 
337 static char *
338 commit_diff_file(struct cvs_file *cf)
339 {
340 	char*delta,  *p1, *p2;
341 	BUF *b1, *b2, *b3;
342 
343 	if (cf->file_status == FILE_MODIFIED ||
344 	    cf->file_status == FILE_ADDED) {
345 		if ((b1 = cvs_buf_load(cf->file_path, BUF_AUTOEXT)) == NULL)
346 			fatal("commit_diff_file: failed to load '%s'",
347 			    cf->file_path);
348 	} else {
349 		b1 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head);
350 		if (b1 == NULL)
351 			fatal("commit_diff_file: failed to load HEAD");
352 		b1 = rcs_kwexp_buf(b1, cf->file_rcs, cf->file_rcs->rf_head);
353 	}
354 
355 	if ((b2 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head)) == NULL)
356 		fatal("commit_diff_file: failed to load HEAD for '%s'",
357 		    cf->file_path);
358 
359 	if ((b3 = cvs_buf_alloc(128, BUF_AUTOEXT)) == NULL)
360 		fatal("commit_diff_file: failed to create diff buf");
361 
362 	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
363 	cvs_buf_write_stmp(b1, p1, 0600, NULL);
364 	cvs_buf_free(b1);
365 
366 	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
367 	cvs_buf_write_stmp(b2, p2, 0600, NULL);
368 	cvs_buf_free(b2);
369 
370 	diff_format = D_RCSDIFF;
371 	if (cvs_diffreg(p1, p2, b3) == D_ERROR)
372 		fatal("commit_diff_file: failed to get RCS patch");
373 
374 	cvs_buf_putc(b3, '\0');
375 	delta = cvs_buf_release(b3);
376 	return (delta);
377 }
378