xref: /openbsd-src/usr.bin/cvs/checkout.c (revision 66ad965f4873a0970dea06fb53c307b8385e5a94)
1 /*	$OpenBSD: checkout.c,v 1.143 2008/03/09 03:14:52 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 <sys/param.h>
19 #include <sys/dirent.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <libgen.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "cvs.h"
31 #include "diff.h"
32 #include "remote.h"
33 
34 static void checkout_check_repository(int, char **);
35 static int checkout_classify(const char *, const char *);
36 static void checkout_repository(const char *, const char *);
37 
38 extern int print_stdout;
39 extern int prune_dirs;
40 extern int build_dirs;
41 
42 static int flags = CR_REPO | CR_RECURSE_DIRS;
43 static int Aflag = 0;
44 static char *dflag = NULL;
45 static char *koptstr = NULL;
46 static char *dateflag = NULL;
47 
48 static int nflag = 0;
49 
50 char *checkout_target_dir = NULL;
51 time_t cvs_specified_date = -1;
52 
53 struct cvs_cmd cvs_cmd_checkout = {
54 	CVS_OP_CHECKOUT, CVS_USE_WDIR, "checkout",
55 	{ "co", "get" },
56 	"Checkout a working copy of a repository",
57 	"[-AcflNnPpRs] [-D date | -r tag] [-d dir] [-j rev] [-k mode] "
58 	"[-t id] module ...",
59 	"AcD:d:fj:k:lNnPpRr:st:",
60 	NULL,
61 	cvs_checkout
62 };
63 
64 struct cvs_cmd cvs_cmd_export = {
65 	CVS_OP_EXPORT, CVS_USE_WDIR, "export",
66 	{ "exp", "ex" },
67 	"Export sources from CVS, similar to checkout",
68 	"[-flNnR] [-d dir] [-k mode] -D date | -r rev module ...",
69 	"D:d:k:flNnRr:",
70 	NULL,
71 	cvs_export
72 };
73 
74 int
75 cvs_checkout(int argc, char **argv)
76 {
77 	int ch;
78 
79 	while ((ch = getopt(argc, argv, cvs_cmd_checkout.cmd_opts)) != -1) {
80 		switch (ch) {
81 		case 'A':
82 			Aflag = 1;
83 			if (koptstr == NULL)
84 				reset_option = 1;
85 			if (cvs_specified_tag == NULL)
86 				reset_tag = 1;
87 			break;
88 		case 'c':
89 			cvs_modules_list();
90 			exit(0);
91 		case 'D':
92 			dateflag = optarg;
93 			cvs_specified_date = cvs_date_parse(dateflag);
94 			break;
95 		case 'd':
96 			if (dflag != NULL)
97 				fatal("-d specified two or more times");
98 			dflag = optarg;
99 			checkout_target_dir = dflag;
100 			break;
101 		case 'j':
102 			if (cvs_join_rev1 == NULL)
103 				cvs_join_rev1 = optarg;
104 			else if (cvs_join_rev2 == NULL)
105 				cvs_join_rev2 = optarg;
106 			else
107 				fatal("too many -j options");
108 			break;
109 		case 'k':
110 			reset_option = 0;
111 			koptstr = optarg;
112 			kflag = rcs_kflag_get(koptstr);
113 			if (RCS_KWEXP_INVAL(kflag)) {
114 				cvs_log(LP_ERR,
115 				    "invalid RCS keyword expension mode");
116 				fatal("%s", cvs_cmd_add.cmd_synopsis);
117 			}
118 			break;
119 		case 'l':
120 			flags &= ~CR_RECURSE_DIRS;
121 			break;
122 		case 'N':
123 			break;
124 		case 'n':
125 			nflag = 1;
126 			break;
127 		case 'P':
128 			prune_dirs = 1;
129 			break;
130 		case 'p':
131 			cmdp->cmd_flags &= ~CVS_USE_WDIR;
132 			print_stdout = 1;
133 			cvs_noexec = 1;
134 			nflag = 1;
135 			break;
136 		case 'R':
137 			flags |= CR_RECURSE_DIRS;
138 			break;
139 		case 'r':
140 			reset_tag = 0;
141 			cvs_specified_tag = optarg;
142 			break;
143 		default:
144 			fatal("%s", cvs_cmd_checkout.cmd_synopsis);
145 		}
146 	}
147 
148 	argc -= optind;
149 	argv += optind;
150 
151 	if (argc == 0)
152 		fatal("%s", cvs_cmd_checkout.cmd_synopsis);
153 
154 	checkout_check_repository(argc, argv);
155 
156 	return (0);
157 }
158 
159 int
160 cvs_export(int argc, char **argv)
161 {
162 	int ch;
163 
164 	prune_dirs = 1;
165 
166 	while ((ch = getopt(argc, argv, cvs_cmd_export.cmd_opts)) != -1) {
167 		switch (ch) {
168 		case 'k':
169 			koptstr = optarg;
170 			kflag = rcs_kflag_get(koptstr);
171 			if (RCS_KWEXP_INVAL(kflag)) {
172 				cvs_log(LP_ERR,
173 				    "invalid RCS keyword expension mode");
174 				fatal("%s", cvs_cmd_add.cmd_synopsis);
175 			}
176 			break;
177 		case 'l':
178 			flags &= ~CR_RECURSE_DIRS;
179 			break;
180 		case 'R':
181 			flags |= CR_RECURSE_DIRS;
182 			break;
183 		case 'r':
184 			cvs_specified_tag = optarg;
185 			break;
186 		default:
187 			fatal("%s", cvs_cmd_export.cmd_synopsis);
188 		}
189 	}
190 
191 	argc -= optind;
192 	argv += optind;
193 
194 	if (cvs_specified_tag == NULL)
195 		fatal("must specify a tag or date");
196 
197 	if (argc == 0)
198 		fatal("%s", cvs_cmd_export.cmd_synopsis);
199 
200 	checkout_check_repository(argc, argv);
201 
202 	return (0);
203 }
204 
205 static void
206 checkout_check_repository(int argc, char **argv)
207 {
208 	int i;
209 	char *wdir, *d;
210 	struct cvs_recursion cr;
211 	struct module_checkout *mc;
212 	struct cvs_ignpat *ip;
213 	struct cvs_filelist *fl, *nxt;
214 	char repo[MAXPATHLEN], fpath[MAXPATHLEN], *f[1];
215 
216 	build_dirs = print_stdout ? 0 : 1;
217 
218 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
219 		cvs_client_connect_to_server();
220 
221 		if (cvs_specified_tag != NULL)
222 			cvs_client_send_request("Argument -r%s",
223 			    cvs_specified_tag);
224 		if (Aflag)
225 			cvs_client_send_request("Argument -A");
226 
227 		if (dateflag != NULL)
228 			cvs_client_send_request("Argument -D%s", dateflag);
229 
230 		if (kflag)
231 			cvs_client_send_request("Argument -k%s", koptstr);
232 
233 		if (dflag != NULL)
234 			cvs_client_send_request("Argument -d%s", dflag);
235 
236 		if (!(flags & CR_RECURSE_DIRS))
237 			cvs_client_send_request("Argument -l");
238 
239 		if (cvs_cmdop == CVS_OP_CHECKOUT && prune_dirs == 1)
240 			cvs_client_send_request("Argument -P");
241 
242 		if (print_stdout == 1)
243 			cvs_client_send_request("Argument -p");
244 
245 		if (nflag == 1)
246 			cvs_client_send_request("Argument -n");
247 
248 		cr.enterdir = NULL;
249 		cr.leavedir = NULL;
250 		if (print_stdout)
251 			cr.fileproc = NULL;
252 		else
253 			cr.fileproc = cvs_client_sendfile;
254 
255 		flags &= ~CR_REPO;
256 		cr.flags = flags;
257 
258 		if (cvs_cmdop != CVS_OP_EXPORT)
259 			cvs_file_run(argc, argv, &cr);
260 
261 		cvs_client_send_files(argv, argc);
262 		cvs_client_senddir(".");
263 
264 		cvs_client_send_request("%s",
265 		    (cvs_cmdop == CVS_OP_CHECKOUT) ? "co" : "export");
266 
267 		cvs_client_get_responses();
268 
269 		return;
270 	}
271 
272 	cvs_directory_tag = cvs_specified_tag;
273 
274 	for (i = 0; i < argc; i++) {
275 		mc = cvs_module_lookup(argv[i]);
276 		current_module = mc;
277 
278 		TAILQ_FOREACH(fl, &(mc->mc_ignores), flist)
279 			cvs_file_ignore(fl->file_path, &checkout_ign_pats);
280 
281 		TAILQ_FOREACH(fl, &(mc->mc_modules), flist) {
282 			module_repo_root = NULL;
283 
284 			(void)xsnprintf(repo, sizeof(repo), "%s/%s",
285 			    current_cvsroot->cr_dir, fl->file_path);
286 
287 			if (!(mc->mc_flags & MODULE_ALIAS) || dflag != NULL)
288 				module_repo_root = xstrdup(fl->file_path);
289 
290 			if (mc->mc_flags & MODULE_NORECURSE)
291 				flags &= ~CR_RECURSE_DIRS;
292 
293 			if (dflag != NULL)
294 				wdir = dflag;
295 			else if (mc->mc_flags & MODULE_ALIAS)
296 				wdir = fl->file_path;
297 			else
298 				wdir = mc->mc_name;
299 
300 			switch (checkout_classify(repo, fl->file_path)) {
301 			case CVS_FILE:
302 				cr.fileproc = cvs_update_local;
303 				cr.flags = flags;
304 
305 				if (!(mc->mc_flags & MODULE_ALIAS)) {
306 					module_repo_root =
307 					    xstrdup(dirname(fl->file_path));
308 					d = wdir;
309 					(void)xsnprintf(fpath, sizeof(fpath),
310 					    "%s/%s", d,
311 					    basename(fl->file_path));
312 				} else {
313 					d = dirname(wdir);
314 					strlcpy(fpath, fl->file_path,
315 					    sizeof(fpath));
316 				}
317 
318 				if (build_dirs == 1)
319 					cvs_mkpath(d, cvs_specified_tag);
320 
321 				f[0] = fpath;
322 				cvs_file_run(1, f, &cr);
323 				break;
324 			case CVS_DIR:
325 				if (build_dirs == 1)
326 					cvs_mkpath(wdir, cvs_specified_tag);
327 				checkout_repository(repo, wdir);
328 				break;
329 			default:
330 				break;
331 			}
332 
333 			if (nflag != 1 && mc->mc_prog != NULL &&
334 			    mc->mc_flags & MODULE_RUN_ON_CHECKOUT)
335 				cvs_exec(mc->mc_prog);
336 
337 			if (module_repo_root != NULL)
338 				xfree(module_repo_root);
339 		}
340 
341 		if (mc->mc_canfree == 1) {
342 			for (fl = TAILQ_FIRST(&(mc->mc_modules));
343 			    fl != TAILQ_END(&(mc->mc_modules)); fl = nxt) {
344 				nxt = TAILQ_NEXT(fl, flist);
345 				TAILQ_REMOVE(&(mc->mc_modules), fl, flist);
346 				xfree(fl->file_path);
347 				xfree(fl);
348 			}
349 		}
350 
351 		while ((ip = TAILQ_FIRST(&checkout_ign_pats)) != NULL) {
352 			TAILQ_REMOVE(&checkout_ign_pats, ip, ip_list);
353 			xfree(ip);
354 		}
355 
356 		xfree(mc);
357 	}
358 }
359 
360 static int
361 checkout_classify(const char *repo, const char *arg)
362 {
363 	char *d, *f, fpath[MAXPATHLEN];
364 	struct stat sb;
365 
366 	if (stat(repo, &sb) == 0) {
367 		if (S_ISDIR(sb.st_mode))
368 			return CVS_DIR;
369 	}
370 
371 	d = dirname(repo);
372 	f = basename(repo);
373 
374 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s%s", d, f, RCS_FILE_EXT);
375 	if (stat(fpath, &sb) == 0) {
376 		if (!S_ISREG(sb.st_mode)) {
377 			cvs_log(LP_ERR, "ignoring %s: not a regular file", arg);
378 			return 0;
379 		}
380 		return CVS_FILE;
381 	}
382 
383 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s/%s%s",
384 	    d, CVS_PATH_ATTIC, f, RCS_FILE_EXT);
385 	if (stat(fpath, &sb) == 0) {
386 		if (!S_ISREG(sb.st_mode)) {
387 			cvs_log(LP_ERR, "ignoring %s: not a regular file", arg);
388 			return 0;
389 		}
390 		return CVS_FILE;
391 	}
392 
393 	cvs_log(LP_ERR, "cannot find module `%s' - ignored", arg);
394 	return 0;
395 }
396 
397 static void
398 checkout_repository(const char *repobase, const char *wdbase)
399 {
400 	struct cvs_flisthead fl, dl;
401 	struct cvs_recursion cr;
402 
403 	TAILQ_INIT(&fl);
404 	TAILQ_INIT(&dl);
405 
406 	cvs_history_add((cvs_cmdop == CVS_OP_CHECKOUT) ?
407 	    CVS_HISTORY_CHECKOUT : CVS_HISTORY_EXPORT, NULL, wdbase);
408 
409 	if (print_stdout) {
410 		cr.enterdir = NULL;
411 		cr.leavedir = NULL;
412 	} else {
413 		cr.enterdir = cvs_update_enterdir;
414 		cr.leavedir = prune_dirs ? cvs_update_leavedir : NULL;
415 	}
416 	cr.fileproc = cvs_update_local;
417 	cr.flags = flags;
418 
419 	cvs_repository_lock(repobase, 0);
420 	cvs_repository_getdir(repobase, wdbase, &fl, &dl,
421 	    flags & CR_RECURSE_DIRS ? 1 : 0);
422 
423 	cvs_file_walklist(&fl, &cr);
424 	cvs_file_freelist(&fl);
425 
426 	cvs_repository_unlock(repobase);
427 
428 	cvs_file_walklist(&dl, &cr);
429 	cvs_file_freelist(&dl);
430 }
431 
432 void
433 cvs_checkout_file(struct cvs_file *cf, RCSNUM *rnum, char *tag, int co_flags)
434 {
435 	int cf_kflag, exists, fd;
436 	time_t rcstime;
437 	CVSENTRIES *ent;
438 	struct timeval tv[2];
439 	struct tm *datetm;
440 	char *tosend;
441 	char template[MAXPATHLEN], *entry;
442 	char kbuf[8], sticky[CVS_REV_BUFSZ], rev[CVS_REV_BUFSZ];
443 	char timebuf[CVS_TIME_BUFSZ], tbuf[CVS_TIME_BUFSZ];
444 
445 	exists = 0;
446 	tosend = NULL;
447 
448 	if (!(co_flags & CO_REMOVE))
449 		rcsnum_tostr(rnum, rev, sizeof(rev));
450 
451 	cvs_log(LP_TRACE, "cvs_checkout_file(%s, %s, %d) -> %s",
452 	    cf->file_path, rev, co_flags,
453 	    (cvs_server_active) ? "to client" : "to disk");
454 
455 	if (co_flags & CO_DUMP) {
456 		rcs_rev_write_fd(cf->file_rcs, rnum, STDOUT_FILENO, 0);
457 		return;
458 	}
459 
460 	if (cvs_server_active == 0) {
461 		(void)unlink(cf->file_path);
462 
463 		if (!(co_flags & CO_MERGE)) {
464 			if (cf->fd != -1) {
465 				exists = 1;
466 				(void)close(cf->fd);
467 			}
468 
469 			cf->fd = open(cf->file_path,
470 			    O_CREAT | O_RDWR | O_TRUNC);
471 			if (cf->fd == -1)
472 				fatal("cvs_checkout_file: open: %s",
473 				    strerror(errno));
474 
475 			rcs_rev_write_fd(cf->file_rcs, rnum, cf->fd, 0);
476 		} else {
477 			cvs_merge_file(cf, (cvs_join_rev1 == NULL));
478 		}
479 
480 		if (fchmod(cf->fd, 0644) == -1)
481 			fatal("cvs_checkout_file: fchmod: %s", strerror(errno));
482 
483 		if ((exists == 0) && (cf->file_ent == NULL) &&
484 		    !(co_flags & CO_MERGE))
485 			rcstime = rcs_rev_getdate(cf->file_rcs, rnum);
486 		else
487 			time(&rcstime);
488 
489 		tv[0].tv_sec = rcstime;
490 		tv[0].tv_usec = 0;
491 		tv[1] = tv[0];
492 		if (futimes(cf->fd, tv) == -1)
493 			fatal("cvs_checkout_file: futimes: %s",
494 			    strerror(errno));
495 	} else {
496 		time(&rcstime);
497 	}
498 
499 	asctime_r(gmtime(&rcstime), tbuf);
500 	tbuf[strcspn(tbuf, "\n")] = '\0';
501 
502 	if (co_flags & CO_MERGE) {
503 		(void)xsnprintf(timebuf, sizeof(timebuf), "Result of merge+%s",
504 		    tbuf);
505 	} else {
506 		strlcpy(timebuf, tbuf, sizeof(timebuf));
507 	}
508 
509 	if (co_flags & CO_SETSTICKY)
510 		if (tag != NULL)
511 			(void)xsnprintf(sticky, sizeof(sticky), "T%s", tag);
512 		else if (cvs_specified_date != -1) {
513 			datetm = gmtime(&cvs_specified_date);
514 			(void)strftime(sticky, sizeof(sticky),
515 			    "D"CVS_DATE_FMT, datetm);
516 		} else
517 			(void)xsnprintf(sticky, sizeof(sticky), "T%s", rev);
518 	else if (!reset_tag && cf->file_ent != NULL &&
519 	    cf->file_ent->ce_tag != NULL)
520 		(void)xsnprintf(sticky, sizeof(sticky), "T%s",
521 		    cf->file_ent->ce_tag);
522 	else
523 		sticky[0] = '\0';
524 
525 	kbuf[0] = '\0';
526 	if (cf->file_rcs->rf_expand != NULL) {
527 		cf_kflag = rcs_kflag_get(cf->file_rcs->rf_expand);
528 		if (kflag || cf_kflag != RCS_KWEXP_DEFAULT)
529 			(void)xsnprintf(kbuf, sizeof(kbuf),
530 			    "-k%s", cf->file_rcs->rf_expand);
531 	} else if (!reset_option && cf->file_ent != NULL) {
532 		if (cf->file_ent->ce_opts != NULL)
533 			strlcpy(kbuf, cf->file_ent->ce_opts, sizeof(kbuf));
534 	}
535 
536 	entry = xmalloc(CVS_ENT_MAXLINELEN);
537 	cvs_ent_line_str(cf->file_name, rev, timebuf, kbuf, sticky, 0, 0,
538 	    entry, CVS_ENT_MAXLINELEN);
539 
540 	if (cvs_server_active == 0) {
541 		if (!(co_flags & CO_REMOVE) && cvs_cmdop != CVS_OP_EXPORT) {
542 			ent = cvs_ent_open(cf->file_wd);
543 			cvs_ent_add(ent, entry);
544 			cvs_ent_close(ent, ENT_SYNC);
545 			cf->file_ent = cvs_ent_parse(entry);
546 			xfree(entry);
547 		}
548 	} else {
549 		if (co_flags & CO_MERGE) {
550 			(void)unlink(cf->file_path);
551 			cvs_merge_file(cf, (cvs_join_rev1 == NULL));
552 			tosend = cf->file_path;
553 			fd = cf->fd;
554 		}
555 
556 		if (co_flags & CO_COMMIT)
557 			cvs_server_update_entry("Updated", cf);
558 		else if (co_flags & CO_MERGE)
559 			cvs_server_update_entry("Merged", cf);
560 		else if (co_flags & CO_REMOVE)
561 			cvs_server_update_entry("Removed", cf);
562 		else
563 			cvs_server_update_entry("Updated", cf);
564 
565 		if (!(co_flags & CO_REMOVE)) {
566 			cvs_remote_output(entry);
567 			xfree(entry);
568 		}
569 
570 		if (!(co_flags & CO_REMOVE)) {
571 			if (!(co_flags & CO_MERGE)) {
572 				(void)xsnprintf(template, MAXPATHLEN,
573 				    "%s/checkout.XXXXXXXXXX", cvs_tmpdir);
574 
575 				fd = rcs_rev_write_stmp(cf->file_rcs, rnum,
576 				    template, 0);
577 				tosend = template;
578 			}
579 
580 			cvs_remote_send_file(tosend, fd);
581 
582 			if (!(co_flags & CO_MERGE)) {
583 				close(fd);
584 				(void)unlink(template);
585 				cvs_worklist_run(&temp_files,
586 				    cvs_worklist_unlink);
587 			}
588 		}
589 	}
590 }
591