xref: /openbsd-src/usr.bin/cvs/update.c (revision 94fd4554194a14f126fba33b837cc68a1df42468)
1 /*	$OpenBSD: update.c,v 1.97 2007/02/22 06:42:09 otto 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 <sys/stat.h>
19 
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #include "cvs.h"
26 #include "diff.h"
27 #include "remote.h"
28 
29 int	prune_dirs = 0;
30 int	print = 0;
31 int	build_dirs = 0;
32 int	reset_stickies = 0;
33 static char *tag = NULL;
34 
35 static void update_clear_conflict(struct cvs_file *);
36 
37 struct cvs_cmd cvs_cmd_update = {
38 	CVS_OP_UPDATE, 0, "update",
39 	{ "up", "upd" },
40 	"Bring work tree in sync with repository",
41 	"[-ACdflPpR] [-D date | -r rev] [-I ign] [-j rev] [-k mode] "
42 	"[-t id] ...",
43 	"ACD:dfI:j:k:lPpQqRr:t:",
44 	NULL,
45 	cvs_update
46 };
47 
48 int
49 cvs_update(int argc, char **argv)
50 {
51 	int ch;
52 	char *arg = ".";
53 	int flags;
54 	struct cvs_recursion cr;
55 
56 	flags = CR_RECURSE_DIRS;
57 
58 	while ((ch = getopt(argc, argv, cvs_cmd_update.cmd_opts)) != -1) {
59 		switch (ch) {
60 		case 'A':
61 			reset_stickies = 1;
62 			break;
63 		case 'C':
64 		case 'D':
65 			tag = optarg;
66 			break;
67 		case 'd':
68 			build_dirs = 1;
69 			break;
70 		case 'f':
71 			break;
72 		case 'I':
73 			break;
74 		case 'j':
75 			break;
76 		case 'k':
77 			break;
78 		case 'l':
79 			flags &= ~CR_RECURSE_DIRS;
80 			break;
81 		case 'P':
82 			prune_dirs = 1;
83 			break;
84 		case 'p':
85 			print = 1;
86 			cvs_noexec = 1;
87 			break;
88 		case 'Q':
89 		case 'q':
90 			break;
91 		case 'R':
92 			break;
93 		case 'r':
94 			tag = optarg;
95 			break;
96 		default:
97 			fatal("%s", cvs_cmd_update.cmd_synopsis);
98 		}
99 	}
100 
101 	argc -= optind;
102 	argv += optind;
103 
104 	if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
105 		cr.enterdir = cvs_update_enterdir;
106 		cr.leavedir = cvs_update_leavedir;
107 		cr.fileproc = cvs_update_local;
108 		flags |= CR_REPO;
109 	} else {
110 		cvs_client_connect_to_server();
111 		if (reset_stickies)
112 			cvs_client_send_request("Argument -A");
113 		if (build_dirs)
114 			cvs_client_send_request("Argument -d");
115 		if (!(flags & CR_RECURSE_DIRS))
116 			cvs_client_send_request("Argument -l");
117 		if (prune_dirs)
118 			cvs_client_send_request("Argument -P");
119 		if (print)
120 			cvs_client_send_request("Argument -p");
121 
122 		cr.enterdir = NULL;
123 		cr.leavedir = NULL;
124 		cr.fileproc = cvs_client_sendfile;
125 	}
126 
127 	cr.flags = flags;
128 
129 	if (argc > 0)
130 		cvs_file_run(argc, argv, &cr);
131 	else
132 		cvs_file_run(1, &arg, &cr);
133 
134 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
135 		cvs_client_send_files(argv, argc);
136 		cvs_client_senddir(".");
137 		cvs_client_send_request("update");
138 		cvs_client_get_responses();
139 	}
140 
141 	return (0);
142 }
143 
144 void
145 cvs_update_enterdir(struct cvs_file *cf)
146 {
147 	char *entry;
148 	CVSENTRIES *entlist;
149 
150 	cvs_log(LP_TRACE, "cvs_update_enterdir(%s)", cf->file_path);
151 
152 	cvs_file_classify(cf, NULL);
153 
154 	if (cf->file_status == DIR_CREATE && build_dirs == 1) {
155 		cvs_mkpath(cf->file_path);
156 		if ((cf->fd = open(cf->file_path, O_RDONLY)) == -1)
157 			fatal("cvs_update_enterdir: `%s': %s",
158 			    cf->file_path, strerror(errno));
159 
160 		entry = xmalloc(CVS_ENT_MAXLINELEN);
161 		(void)xsnprintf(entry, CVS_ENT_MAXLINELEN, "D/%s////",
162 		    cf->file_name);
163 
164 		entlist = cvs_ent_open(cf->file_wd);
165 		cvs_ent_add(entlist, entry);
166 		cvs_ent_close(entlist, ENT_SYNC);
167 		xfree(entry);
168 	} else if ((cf->file_status == DIR_CREATE && build_dirs == 0) ||
169 		    cf->file_status == FILE_UNKNOWN) {
170 		cf->file_status = FILE_SKIP;
171 	}
172 }
173 
174 void
175 cvs_update_leavedir(struct cvs_file *cf)
176 {
177 	long base;
178 	int nbytes;
179 	int isempty;
180 	size_t bufsize;
181 	struct stat st;
182 	struct dirent *dp;
183 	char *buf, *ebuf, *cp;
184 	struct cvs_ent *ent;
185 	struct cvs_ent_line *line;
186 	CVSENTRIES *entlist;
187 	char export[MAXPATHLEN];
188 
189 	cvs_log(LP_TRACE, "cvs_update_leavedir(%s)", cf->file_path);
190 
191 	if (cvs_cmdop == CVS_OP_EXPORT) {
192 		(void)xsnprintf(export, MAXPATHLEN, "%s/%s",
193 		    cf->file_path, CVS_PATH_CVSDIR);
194 
195 		/* XXX */
196 		if (cvs_rmdir(export) == -1)
197 			fatal("cvs_update_leavedir: %s: %s:", export,
198 			    strerror(errno));
199 
200 		return;
201 	}
202 
203 	if (fstat(cf->fd, &st) == -1)
204 		fatal("cvs_update_leavedir: %s", strerror(errno));
205 
206 	bufsize = st.st_size;
207 	if (bufsize < st.st_blksize)
208 		bufsize = st.st_blksize;
209 
210 	isempty = 1;
211 	buf = xmalloc(bufsize);
212 
213 	if (lseek(cf->fd, 0, SEEK_SET) == -1)
214 		fatal("cvs_update_leavedir: %s", strerror(errno));
215 
216 	while ((nbytes = getdirentries(cf->fd, buf, bufsize, &base)) > 0) {
217 		ebuf = buf + nbytes;
218 		cp = buf;
219 
220 		while (cp < ebuf) {
221 			dp = (struct dirent *)cp;
222 			if (!strcmp(dp->d_name, ".") ||
223 			    !strcmp(dp->d_name, "..") ||
224 			    dp->d_fileno == 0) {
225 				cp += dp->d_reclen;
226 				continue;
227 			}
228 
229 			if (!strcmp(dp->d_name, CVS_PATH_CVSDIR)) {
230 				entlist = cvs_ent_open(cf->file_path);
231 				TAILQ_FOREACH(line, &(entlist->cef_ent),
232 				    entries_list) {
233 					ent = cvs_ent_parse(line->buf);
234 
235 					if (ent->ce_status == CVS_ENT_REMOVED) {
236 						isempty = 0;
237 						cvs_ent_free(ent);
238 						break;
239 					}
240 
241 					cvs_ent_free(ent);
242 				}
243 				cvs_ent_close(entlist, ENT_NOSYNC);
244 			} else {
245 				isempty = 0;
246 			}
247 
248 			if (isempty == 0)
249 				break;
250 
251 			cp += dp->d_reclen;
252 		}
253 	}
254 
255 	if (nbytes == -1)
256 		fatal("cvs_update_leavedir: %s", strerror(errno));
257 
258 	xfree(buf);
259 
260 	if (isempty == 1 && prune_dirs == 1) {
261 		/* XXX */
262 		cvs_rmdir(cf->file_path);
263 
264 		if (cvs_server_active == 0) {
265 			entlist = cvs_ent_open(cf->file_wd);
266 			cvs_ent_remove(entlist, cf->file_name);
267 			cvs_ent_close(entlist, ENT_SYNC);
268 		}
269 	}
270 }
271 
272 void
273 cvs_update_local(struct cvs_file *cf)
274 {
275 	int ret, flags;
276 	CVSENTRIES *entlist;
277 	char rbuf[16];
278 
279 	cvs_log(LP_TRACE, "cvs_update_local(%s)", cf->file_path);
280 
281 	if (cf->file_type == CVS_DIR) {
282 		if (cf->file_status == FILE_SKIP)
283 			return;
284 
285 		if (cf->file_status != FILE_UNKNOWN &&
286 		    verbosity > 1)
287 			cvs_log(LP_NOTICE, "Updating %s", cf->file_path);
288 		return;
289 	}
290 
291 	flags = 0;
292 	cvs_file_classify(cf, tag);
293 
294 	if ((cf->file_status == FILE_UPTODATE ||
295 	    cf->file_status == FILE_MODIFIED) && cf->file_ent != NULL &&
296 	    cf->file_ent->ce_tag != NULL && reset_stickies == 1) {
297 		if (cf->file_status == FILE_MODIFIED)
298 			cf->file_status = FILE_MERGE;
299 		else
300 			cf->file_status = FILE_CHECKOUT;
301 		cf->file_rcsrev = rcs_head_get(cf->file_rcs);
302 	}
303 
304 	if (print && cf->file_status != FILE_UNKNOWN) {
305 		rcsnum_tostr(cf->file_rcsrev, rbuf, sizeof(rbuf));
306 		if (verbosity > 1)
307 			cvs_printf("%s\nChecking out %s\n"
308 			    "RCS:\t%s\nVERS:\t%s\n***************\n",
309 			    RCS_DIFF_DIV, cf->file_path, cf->file_rpath, rbuf);
310 		cvs_checkout_file(cf, cf->file_rcsrev, CO_DUMP);
311 		return;
312 	}
313 
314 	switch (cf->file_status) {
315 	case FILE_UNKNOWN:
316 		cvs_printf("? %s\n", cf->file_path);
317 		break;
318 	case FILE_MODIFIED:
319 		ret = update_has_conflict_markers(cf);
320 		if (cf->file_ent->ce_conflict != NULL && ret == 1) {
321 			cvs_printf("C %s\n", cf->file_path);
322 		} else {
323 			if (cf->file_ent->ce_conflict != NULL && ret == 0)
324 				update_clear_conflict(cf);
325 			cvs_printf("M %s\n", cf->file_path);
326 		}
327 		break;
328 	case FILE_ADDED:
329 		cvs_printf("A %s\n", cf->file_path);
330 		break;
331 	case FILE_REMOVED:
332 		cvs_printf("R %s\n", cf->file_path);
333 		break;
334 	case FILE_CONFLICT:
335 		cvs_printf("C %s\n", cf->file_path);
336 		break;
337 	case FILE_LOST:
338 	case FILE_CHECKOUT:
339 	case FILE_PATCH:
340 		if (tag != NULL)
341 			flags = CO_SETSTICKY;
342 
343 		cvs_checkout_file(cf, cf->file_rcsrev, flags);
344 		cvs_printf("U %s\n", cf->file_path);
345 		break;
346 	case FILE_MERGE:
347 		cvs_checkout_file(cf, cf->file_rcsrev, CO_MERGE);
348 
349 		if (diff3_conflicts != 0) {
350 			cvs_printf("C %s\n", cf->file_path);
351 		} else {
352 			update_clear_conflict(cf);
353 			cvs_printf("M %s\n", cf->file_path);
354 		}
355 		break;
356 	case FILE_UNLINK:
357 		(void)unlink(cf->file_path);
358 	case FILE_REMOVE_ENTRY:
359 		entlist = cvs_ent_open(cf->file_wd);
360 		cvs_ent_remove(entlist, cf->file_name);
361 		cvs_ent_close(entlist, ENT_SYNC);
362 		break;
363 	default:
364 		break;
365 	}
366 }
367 
368 static void
369 update_clear_conflict(struct cvs_file *cf)
370 {
371 	time_t now;
372 	CVSENTRIES *entlist;
373 	char *entry, revbuf[16], timebuf[32];
374 
375 	cvs_log(LP_TRACE, "update_clear_conflict(%s)", cf->file_path);
376 
377 	time(&now);
378 	ctime_r(&now, timebuf);
379 	if (timebuf[strlen(timebuf) - 1] == '\n')
380 		timebuf[strlen(timebuf) - 1] = '\0';
381 
382 	rcsnum_tostr(cf->file_ent->ce_rev, revbuf, sizeof(revbuf));
383 
384 	entry = xmalloc(CVS_ENT_MAXLINELEN);
385 	(void)xsnprintf(entry, CVS_ENT_MAXLINELEN, "/%s/%s/%s//",
386 	    cf->file_name, revbuf, timebuf);
387 
388 	entlist = cvs_ent_open(cf->file_wd);
389 	cvs_ent_add(entlist, entry);
390 	cvs_ent_close(entlist, ENT_SYNC);
391 	xfree(entry);
392 }
393 
394 /*
395  * XXX - this is the way GNU cvs checks for outstanding conflicts
396  * in a file after a merge. It is a very very bad approach and
397  * should be looked at once opencvs is working decently.
398  */
399 int
400 update_has_conflict_markers(struct cvs_file *cf)
401 {
402 	BUF *bp;
403 	int conflict;
404 	char *content;
405 	struct cvs_line *lp;
406 	struct cvs_lines *lines;
407 	size_t len;
408 
409 	cvs_log(LP_TRACE, "update_has_conflict_markers(%s)", cf->file_path);
410 
411 	if (cf->fd == -1)
412 		return (0);
413 
414 	if ((bp = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL)
415 		fatal("update_has_conflict_markers: failed to load %s",
416 		    cf->file_path);
417 
418 	cvs_buf_putc(bp, '\0');
419 	len = cvs_buf_len(bp);
420 	content = cvs_buf_release(bp);
421 	if ((lines = cvs_splitlines(content, len)) == NULL)
422 		fatal("update_has_conflict_markers: failed to split lines");
423 
424 	conflict = 0;
425 	TAILQ_FOREACH(lp, &(lines->l_lines), l_list) {
426 		if (lp->l_line == NULL)
427 			continue;
428 
429 		if (!strncmp(lp->l_line, RCS_CONFLICT_MARKER1,
430 		    strlen(RCS_CONFLICT_MARKER1)) ||
431 		    !strncmp(lp->l_line, RCS_CONFLICT_MARKER2,
432 		    strlen(RCS_CONFLICT_MARKER2)) ||
433 		    !strncmp(lp->l_line, RCS_CONFLICT_MARKER3,
434 		    strlen(RCS_CONFLICT_MARKER3))) {
435 			conflict = 1;
436 			break;
437 		}
438 	}
439 
440 	cvs_freelines(lines);
441 	xfree(content);
442 	return (conflict);
443 }
444