commit.c revision 1.88
1/*	$OpenBSD: commit.c,v 1.88 2007/01/07 03:08: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
26void	cvs_commit_local(struct cvs_file *);
27void	cvs_commit_check_files(struct cvs_file *);
28
29static char *commit_diff_file(struct cvs_file *);
30static void commit_desc_set(struct cvs_file *);
31
32struct	cvs_flisthead files_affected;
33struct	cvs_flisthead files_added;
34struct	cvs_flisthead files_removed;
35struct	cvs_flisthead files_modified;
36
37int	conflicts_found;
38char	*logmsg = NULL;
39
40struct 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
50int
51cvs_commit(int argc, char **argv)
52{
53	int ch;
54	BUF *bp;
55	char *arg = ".";
56	int flags;
57	struct cvs_recursion cr;
58
59	flags = CR_RECURSE_DIRS;
60
61	while ((ch = getopt(argc, argv, cvs_cmd_commit.cmd_opts)) != -1) {
62		switch (ch) {
63		case 'F':
64			logmsg = cvs_logmsg_read(optarg);
65			break;
66		case 'f':
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	TAILQ_INIT(&files_affected);
87	TAILQ_INIT(&files_added);
88	TAILQ_INIT(&files_removed);
89	TAILQ_INIT(&files_modified);
90	conflicts_found = 0;
91
92	cr.enterdir = NULL;
93	cr.leavedir = NULL;
94	cr.fileproc = cvs_commit_check_files;
95	cr.flags = flags;
96
97	if (argc > 0)
98		cvs_file_run(argc, argv, &cr);
99	else
100		cvs_file_run(1, &arg, &cr);
101
102	if (conflicts_found != 0)
103		fatal("%d conflicts found, please correct these first",
104		    conflicts_found);
105
106	if (logmsg == NULL && cvs_server_active == 0) {
107		logmsg = cvs_logmsg_create(&files_added, &files_removed,
108		    &files_modified);
109	}
110
111	if (logmsg == NULL)
112		fatal("This shouldnt happen, honestly!");
113
114	cvs_file_freelist(&files_modified);
115	cvs_file_freelist(&files_removed);
116	cvs_file_freelist(&files_added);
117
118	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
119		cr.fileproc = cvs_client_sendfile;
120
121		if (argc > 0)
122			cvs_file_run(argc, argv, &cr);
123		else
124			cvs_file_run(1, &arg, &cr);
125
126		cvs_client_send_request("Argument -m%s", logmsg);
127
128		cvs_client_send_files(argv, argc);
129		cvs_client_senddir(".");
130		cvs_client_send_request("ci");
131		cvs_client_get_responses();
132	} else {
133		cr.fileproc = cvs_commit_local;
134		cvs_file_walklist(&files_affected, &cr);
135		cvs_file_freelist(&files_affected);
136	}
137
138	return (0);
139}
140
141void
142cvs_commit_check_files(struct cvs_file *cf)
143{
144	cvs_log(LP_TRACE, "cvs_commit_check_files(%s)", cf->file_path);
145
146	/*
147	 * cvs_file_classify makes the noise for us
148	 * XXX - we want that?
149	 */
150	cvs_file_classify(cf, NULL, 1);
151
152	if (cf->file_type == CVS_DIR) {
153		if (verbosity > 1)
154			cvs_log(LP_NOTICE, "Examining %s", cf->file_path);
155		return;
156	}
157
158	if (cf->file_status == FILE_CONFLICT ||
159	    cf->file_status == FILE_UNLINK) {
160		conflicts_found++;
161		return;
162	}
163
164	if (cf->file_status != FILE_REMOVED &&
165	    update_has_conflict_markers(cf)) {
166		cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from "
167		    "merging, please fix these first", cf->file_path);
168		conflicts_found++;
169		return;
170	}
171
172	if (cf->file_status == FILE_MERGE ||
173	    cf->file_status == FILE_PATCH ||
174	    cf->file_status == FILE_CHECKOUT ||
175	    cf->file_status == FILE_LOST) {
176		cvs_log(LP_ERR, "conflict: %s is not up-to-date",
177		    cf->file_path);
178		conflicts_found++;
179		return;
180	}
181
182	if (cf->file_status == FILE_ADDED ||
183	    cf->file_status == FILE_REMOVED ||
184	    cf->file_status == FILE_MODIFIED)
185		cvs_file_get(cf->file_path, &files_affected);
186
187	switch (cf->file_status) {
188	case FILE_ADDED:
189		cvs_file_get(cf->file_path, &files_added);
190		break;
191	case FILE_REMOVED:
192		cvs_file_get(cf->file_path, &files_removed);
193		break;
194	case FILE_MODIFIED:
195		cvs_file_get(cf->file_path, &files_modified);
196		break;
197	}
198}
199
200void
201cvs_commit_local(struct cvs_file *cf)
202{
203	BUF *b;
204	int isnew;
205	int l, openflags, rcsflags;
206	char *d, *f, rbuf[24], nbuf[24];
207	CVSENTRIES *entlist;
208	char *attic, *repo, *rcsfile, *p;
209
210	cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path);
211	cvs_file_classify(cf, NULL, 0);
212
213	if (cvs_noexec == 1)
214		return;
215
216	if (cf->file_type != CVS_FILE)
217		fatal("cvs_commit_local: '%s' is not a file", cf->file_path);
218
219	if (cf->file_status == FILE_MODIFIED ||
220	    cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED
221	    && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1))
222		rcsnum_tostr(rcs_head_get(cf->file_rcs), rbuf, sizeof(rbuf));
223	else
224		strlcpy(rbuf, "Non-existent", sizeof(rbuf));
225
226	isnew = 0;
227	if (cf->file_status == FILE_ADDED) {
228		isnew = 1;
229		rcsflags = RCS_CREATE;
230		openflags = O_CREAT | O_TRUNC | O_WRONLY;
231		if (cf->file_rcs != NULL) {
232			if (cf->file_rcs->rf_inattic == 0)
233				cvs_log(LP_ERR, "warning: expected %s "
234				    "to be in the Attic", cf->file_path);
235
236			if (cf->file_rcs->rf_dead == 0)
237				cvs_log(LP_ERR, "warning: expected %s "
238				    "to be dead", cf->file_path);
239
240			rcsfile = xmalloc(MAXPATHLEN);
241			repo = xmalloc(MAXPATHLEN);
242			cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
243			l = snprintf(rcsfile, MAXPATHLEN, "%s/%s%s",
244			    repo, cf->file_name, RCS_FILE_EXT);
245			if (l == -1 || l >= MAXPATHLEN)
246				fatal("cvs_commit_local: overflow");
247
248			if (rename(cf->file_rpath, rcsfile) == -1)
249				fatal("cvs_commit_local: failed to move %s "
250				    "outside the Attic: %s", cf->file_path,
251				    strerror(errno));
252
253			xfree(cf->file_rpath);
254			cf->file_rpath = xstrdup(rcsfile);
255			xfree(rcsfile);
256			xfree(repo);
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, 0600);
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_getrev(cf->file_rcs, cf->file_rcs->rf_head);
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	cvs_buf_putc(b, '\0');
296	f = cvs_buf_release(b);
297
298	if (isnew == 0) {
299		if (rcs_deltatext_set(cf->file_rcs,
300		    cf->file_rcs->rf_head, d) == -1)
301			fatal("cvs_commit_local: failed to set delta");
302	}
303
304	if (rcs_rev_add(cf->file_rcs, RCS_HEAD_REV, logmsg, -1, NULL) == -1)
305		fatal("cvs_commit_local: failed to add new revision");
306
307	if (rcs_deltatext_set(cf->file_rcs, cf->file_rcs->rf_head, f) == -1)
308		fatal("cvs_commit_local: failed to set new HEAD delta");
309
310	xfree(f);
311
312	if (isnew == 0)
313		xfree(d);
314
315	if (cf->file_status == FILE_REMOVED) {
316		if (rcs_state_set(cf->file_rcs,
317		    cf->file_rcs->rf_head, RCS_STATE_DEAD) == -1)
318			fatal("cvs_commit_local: failed to set state");
319	}
320
321	if (cf->file_rcs->rf_branch != NULL) {
322		rcsnum_free(cf->file_rcs->rf_branch);
323		cf->file_rcs->rf_branch = NULL;
324	}
325
326	rcs_write(cf->file_rcs);
327
328	if (cf->file_status == FILE_REMOVED) {
329		strlcpy(nbuf, "Removed", sizeof(nbuf));
330	} else if (cf->file_status == FILE_ADDED) {
331		if (cf->file_rcs->rf_dead == 1)
332			strlcpy(nbuf, "Initial Revision", sizeof(nbuf));
333		else
334			rcsnum_tostr(cf->file_rcs->rf_head,
335			    nbuf, sizeof(nbuf));
336	} else if (cf->file_status == FILE_MODIFIED) {
337		rcsnum_tostr(cf->file_rcs->rf_head, nbuf, sizeof(nbuf));
338	}
339
340	if (verbosity > 1)
341		cvs_printf("new revision: %s\n", nbuf);
342
343	(void)unlink(cf->file_path);
344	(void)close(cf->fd);
345	cf->fd = -1;
346
347	if (cf->file_status != FILE_REMOVED) {
348		b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head);
349		if (b == NULL)
350			fatal("cvs_commit_local: failed to get HEAD");
351
352		cvs_checkout_file(cf, cf->file_rcs->rf_head, b, CO_COMMIT);
353	} else {
354		entlist = cvs_ent_open(cf->file_wd);
355		cvs_ent_remove(entlist, cf->file_name);
356		cvs_ent_close(entlist, ENT_SYNC);
357
358		repo = xmalloc(MAXPATHLEN);
359		attic = xmalloc(MAXPATHLEN);
360		cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
361
362		l = snprintf(attic, MAXPATHLEN, "%s/%s", repo, CVS_PATH_ATTIC);
363		if (l == -1 || l >= MAXPATHLEN)
364			fatal("cvs_commit_local: overflow");
365
366		if (mkdir(attic, 0755) == -1 && errno != EEXIST)
367			fatal("cvs_commit_local: failed to create Attic");
368
369		l = snprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
370		    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
371		if (l == -1 || l >= MAXPATHLEN)
372			fatal("cvs_commit_local: overflow");
373
374		if (rename(cf->file_rpath, attic) == -1)
375			fatal("cvs_commit_local: failed to move %s to Attic",
376			    cf->file_path);
377
378		xfree(repo);
379		xfree(attic);
380
381		if (cvs_server_active == 1)
382			cvs_server_update_entry("Remove-entry", cf);
383	}
384
385	if (verbosity > 1)
386		cvs_printf("done\n");
387	else {
388		cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s",
389		    cf->file_path, rbuf, nbuf);
390	}
391}
392
393static char *
394commit_diff_file(struct cvs_file *cf)
395{
396	char*delta,  *p1, *p2;
397	BUF *b1, *b2, *b3;
398
399	if (cf->file_status == FILE_MODIFIED ||
400	    cf->file_status == FILE_ADDED) {
401		if ((b1 = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL)
402			fatal("commit_diff_file: failed to load '%s'",
403			    cf->file_path);
404	} else {
405		b1 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head);
406		if (b1 == NULL)
407			fatal("commit_diff_file: failed to load HEAD");
408		b1 = rcs_kwexp_buf(b1, cf->file_rcs, cf->file_rcs->rf_head);
409	}
410
411	if ((b2 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head)) == NULL)
412		fatal("commit_diff_file: failed to load HEAD for '%s'",
413		    cf->file_path);
414
415	if ((b3 = cvs_buf_alloc(128, BUF_AUTOEXT)) == NULL)
416		fatal("commit_diff_file: failed to create diff buf");
417
418	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
419	cvs_buf_write_stmp(b1, p1, NULL);
420	cvs_buf_free(b1);
421
422	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
423	cvs_buf_write_stmp(b2, p2, NULL);
424	cvs_buf_free(b2);
425
426	diff_format = D_RCSDIFF;
427	if (cvs_diffreg(p1, p2, b3) == D_ERROR)
428		fatal("commit_diff_file: failed to get RCS patch");
429
430	cvs_buf_putc(b3, '\0');
431	delta = cvs_buf_release(b3);
432	return (delta);
433}
434
435static void
436commit_desc_set(struct cvs_file *cf)
437{
438	BUF *bp;
439	int l, fd;
440	char *desc_path, *desc;
441
442	desc_path = xmalloc(MAXPATHLEN);
443	l = snprintf(desc_path, MAXPATHLEN, "%s/%s%s",
444	    CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT);
445	if (l == -1 || l >= MAXPATHLEN)
446		fatal("commit_desc_set: overflow");
447
448	if ((fd = open(desc_path, O_RDONLY)) == -1) {
449		xfree(desc_path);
450		return;
451	}
452
453	bp = cvs_buf_load_fd(fd, BUF_AUTOEXT);
454	cvs_buf_putc(bp, '\0');
455	desc = cvs_buf_release(bp);
456
457	rcs_desc_set(cf->file_rcs, desc);
458
459	(void)close(fd);
460	(void)cvs_unlink(desc_path);
461
462	xfree(desc);
463	xfree(desc_path);
464}
465