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