1*6541b77cSguenther /* $OpenBSD: co.c,v 1.127 2023/08/11 05:02:21 guenther Exp $ */
202cf0257Sjoris /*
302cf0257Sjoris * Copyright (c) 2005 Joris Vink <joris@openbsd.org>
402cf0257Sjoris * All rights reserved.
502cf0257Sjoris *
602cf0257Sjoris * Redistribution and use in source and binary forms, with or without
702cf0257Sjoris * modification, are permitted provided that the following conditions
802cf0257Sjoris * are met:
902cf0257Sjoris *
1002cf0257Sjoris * 1. Redistributions of source code must retain the above copyright
1102cf0257Sjoris * notice, this list of conditions and the following disclaimer.
1202cf0257Sjoris * 2. The name of the author may not be used to endorse or promote products
1302cf0257Sjoris * derived from this software without specific prior written permission.
1402cf0257Sjoris *
1502cf0257Sjoris * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
1602cf0257Sjoris * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
1702cf0257Sjoris * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
1802cf0257Sjoris * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
1902cf0257Sjoris * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
2002cf0257Sjoris * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
2102cf0257Sjoris * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2202cf0257Sjoris * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
2302cf0257Sjoris * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2402cf0257Sjoris * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2502cf0257Sjoris */
2602cf0257Sjoris
274781e2faSxsa #include <sys/stat.h>
28955855ffSchl #include <sys/time.h>
294781e2faSxsa
304781e2faSxsa #include <err.h>
314781e2faSxsa #include <fcntl.h>
324781e2faSxsa #include <stdio.h>
334781e2faSxsa #include <stdlib.h>
344781e2faSxsa #include <string.h>
354781e2faSxsa #include <unistd.h>
3602cf0257Sjoris
3702cf0257Sjoris #include "rcsprog.h"
3899ee9073Smillert #include "diff.h"
3902cf0257Sjoris
402357b7edSxsa #define CO_OPTSTRING "d:f::I::k:l::M::p::q::r::s:Tu::Vw::x::z::"
414c1d437fSxsa
420cb35f28Sxsa static void checkout_err_nobranch(RCSFILE *, const char *, const char *,
430cb35f28Sxsa const char *, int);
4499ee9073Smillert static int checkout_file_has_diffs(RCSFILE *, RCSNUM *, const char *);
453d0c23ffSjoris
4602cf0257Sjoris int
checkout_main(int argc,char ** argv)4702cf0257Sjoris checkout_main(int argc, char **argv)
4802cf0257Sjoris {
49f83708c8Sxsa int fd, i, ch, flags, kflag, ret;
50573be5e9Spat RCSNUM *rev;
5102cf0257Sjoris RCSFILE *file;
526e2f6b91Sray const char *author, *date, *state;
53b9fc9a72Sderaadt char fpath[PATH_MAX];
546e2f6b91Sray char *rev_str, *username;
55*6541b77cSguenther struct timespec rcs_mtime = { .tv_sec = 0, .tv_nsec = UTIME_OMIT };
5602cf0257Sjoris
57f83708c8Sxsa flags = ret = 0;
58a9a3f677Sxsa kflag = RCS_KWEXP_ERR;
5936076781Sray rev_str = NULL;
606e2f6b91Sray author = date = state = NULL;
613d0c23ffSjoris
624c1d437fSxsa while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) {
6302cf0257Sjoris switch (ch) {
6443f1a30dSjoris case 'd':
656e2f6b91Sray date = rcs_optarg;
6643f1a30dSjoris break;
6752d3afaaSjoris case 'f':
6836076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
69c4521683Sniallo flags |= FORCE;
7052d3afaaSjoris break;
71d50fe670Sniallo case 'I':
7236076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
73d50fe670Sniallo flags |= INTERACTIVE;
74d50fe670Sniallo break;
75d50fe670Sniallo
76a9a3f677Sxsa case 'k':
77a9a3f677Sxsa kflag = rcs_kflag_get(rcs_optarg);
78a9a3f677Sxsa if (RCS_KWEXP_INVAL(kflag)) {
7925a1df5bSxsa warnx("invalid RCS keyword substitution mode");
80a9a3f677Sxsa (usage)();
81a9a3f677Sxsa }
82a9a3f677Sxsa break;
833d0c23ffSjoris case 'l':
846a194f4eSxsa if (flags & CO_UNLOCK) {
8582ca7eaaSxsa warnx("warning: -u overridden by -l");
866a194f4eSxsa flags &= ~CO_UNLOCK;
876a194f4eSxsa }
8836076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
89c4521683Sniallo flags |= CO_LOCK;
903d0c23ffSjoris break;
9142c920d5Sniallo case 'M':
9236076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
9342c920d5Sniallo flags |= CO_REVDATE;
9442c920d5Sniallo break;
95259cdaa0Sjoris case 'p':
9636076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
978e965226Sxsa flags |= PIPEOUT;
98259cdaa0Sjoris break;
993d0c23ffSjoris case 'q':
10036076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
101913586deSxsa flags |= QUIET;
1023d0c23ffSjoris break;
1033d0c23ffSjoris case 'r':
10436076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
1053d0c23ffSjoris break;
106c4521683Sniallo case 's':
1076e2f6b91Sray state = rcs_optarg;
108c4521683Sniallo flags |= CO_STATE;
109c4521683Sniallo break;
1104ecf1ebbSxsa case 'T':
1114ecf1ebbSxsa flags |= PRESERVETIME;
1124ecf1ebbSxsa break;
1133d0c23ffSjoris case 'u':
11436076781Sray rcs_setrevstr(&rev_str, rcs_optarg);
1156a194f4eSxsa if (flags & CO_LOCK) {
11682ca7eaaSxsa warnx("warning: -l overridden by -u");
1176a194f4eSxsa flags &= ~CO_LOCK;
1186a194f4eSxsa }
119c4521683Sniallo flags |= CO_UNLOCK;
12002cf0257Sjoris break;
12130c55194Sjoris case 'V':
12230c55194Sjoris printf("%s\n", rcs_version);
12330c55194Sjoris exit(0);
1244c1d437fSxsa case 'w':
125bbfcb536Sjoris /* if no argument, assume current user */
126bbfcb536Sjoris if (rcs_optarg == NULL) {
127f5b81ab4Sxsa if ((author = getlogin()) == NULL)
128a3660ae3Sxsa err(1, "getlogin");
1296e2f6b91Sray } else
1306e2f6b91Sray author = rcs_optarg;
1310cb35f28Sxsa flags |= CO_AUTHOR;
1324c1d437fSxsa break;
1337518c1e9Sxsa case 'x':
1344365263eSray /* Use blank extension if none given. */
1354365263eSray rcs_suffixes = rcs_optarg ? rcs_optarg : "";
1367518c1e9Sxsa break;
1376baf4b21Sjoris case 'z':
1386baf4b21Sjoris timezone_flag = rcs_optarg;
1396baf4b21Sjoris break;
14002cf0257Sjoris default:
14102cf0257Sjoris (usage)();
14202cf0257Sjoris }
14302cf0257Sjoris }
14402cf0257Sjoris
145a2b34663Sjoris argc -= rcs_optind;
146a2b34663Sjoris argv += rcs_optind;
14702cf0257Sjoris
14802cf0257Sjoris if (argc == 0) {
14982ca7eaaSxsa warnx("no input file");
15002cf0257Sjoris (usage)();
15102cf0257Sjoris }
15202cf0257Sjoris
15382ca7eaaSxsa if ((username = getlogin()) == NULL)
154a3660ae3Sxsa err(1, "getlogin");
1554c1d437fSxsa
15602cf0257Sjoris for (i = 0; i < argc; i++) {
157f3876e23Sray fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
158f3876e23Sray if (fd < 0) {
15999e66255Sniallo warn("%s", fpath);
160f83708c8Sxsa ret = 1;
16102cf0257Sjoris continue;
162f3876e23Sray }
163168f4dcaSotto rcs_strip_suffix(argv[i]);
16402cf0257Sjoris
165913586deSxsa if (!(flags & QUIET))
1664a1cfd68Sxsa (void)fprintf(stderr, "%s --> %s\n", fpath,
1678e965226Sxsa (flags & PIPEOUT) ? "standard output" : argv[i]);
16863427cc3Sniallo
169faf44ba2Sxsa if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) {
17082ca7eaaSxsa warnx("%s: cannot combine -kv and -l", fpath);
171bc8d37c3Sjoris (void)close(fd);
172faf44ba2Sxsa continue;
173faf44ba2Sxsa }
174faf44ba2Sxsa
175bc8d37c3Sjoris if ((file = rcs_open(fpath, fd,
176bc8d37c3Sjoris RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
17702cf0257Sjoris continue;
17802cf0257Sjoris
179c11f4f2bSxsa if (flags & PRESERVETIME)
180bc8d37c3Sjoris rcs_mtime = rcs_get_mtime(file);
181c11f4f2bSxsa
182c11f4f2bSxsa rcs_kwexp_set(file, kflag);
183c11f4f2bSxsa
184e7816acdSray if (rev_str != NULL) {
185e7816acdSray if ((rev = rcs_getrevnum(rev_str, file)) == NULL)
186a3660ae3Sxsa errx(1, "invalid revision: %s", rev_str);
187e7816acdSray } else {
188848faecaSjoris /* no revisions in RCS file, generate empty 0.0 */
189848faecaSjoris if (file->rf_ndelta == 0) {
190848faecaSjoris rev = rcsnum_parse("0.0");
191848faecaSjoris if (rev == NULL)
192a3660ae3Sxsa errx(1, "failed to generate rev 0.0");
193848faecaSjoris } else {
19436076781Sray rev = rcsnum_alloc();
19536076781Sray rcsnum_cpy(file->rf_head, rev, 0);
19636076781Sray }
197848faecaSjoris }
1983fb753dbSjoris
199f83708c8Sxsa if (checkout_rev(file, rev, argv[i], flags,
200f83708c8Sxsa username, author, state, date) < 0) {
201c4521683Sniallo rcs_close(file);
20236076781Sray rcsnum_free(rev);
203f83708c8Sxsa ret = 1;
204c4521683Sniallo continue;
205c4521683Sniallo }
2063fb753dbSjoris
207913586deSxsa if (!(flags & QUIET))
2084a1cfd68Sxsa (void)fprintf(stderr, "done\n");
209d7a2981eSxsa
21036076781Sray rcsnum_free(rev);
211c11f4f2bSxsa
212bc8d37c3Sjoris rcs_write(file);
213c11f4f2bSxsa if (flags & PRESERVETIME)
214bc8d37c3Sjoris rcs_set_mtime(file, rcs_mtime);
215bc8d37c3Sjoris rcs_close(file);
21602cf0257Sjoris }
21702cf0257Sjoris
218f83708c8Sxsa return (ret);
21902cf0257Sjoris }
22002cf0257Sjoris
221960e00beSotto __dead void
checkout_usage(void)22202cf0257Sjoris checkout_usage(void)
22302cf0257Sjoris {
224248620a5Sderaadt fprintf(stderr,
2251bbb12c7Sxsa "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n"
2261bbb12c7Sxsa " [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n"
2271bbb12c7Sxsa " [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n");
228960e00beSotto
229960e00beSotto exit(1);
23002cf0257Sjoris }
23172803d6eSniallo
23272803d6eSniallo /*
23372803d6eSniallo * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst>
234a27b87a5Sray * Currently recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE.
23572803d6eSniallo *
23643f1a30dSjoris * Looks up revision based upon <lockname>, <author>, <state> and <date>
237bbfcb536Sjoris *
23872803d6eSniallo * Returns 0 on success, -1 on failure.
23972803d6eSniallo */
24072803d6eSniallo int
checkout_rev(RCSFILE * file,RCSNUM * frev,const char * dst,int flags,const char * lockname,const char * author,const char * state,const char * date)241c4521683Sniallo checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
24243f1a30dSjoris const char *lockname, const char *author, const char *state,
24343f1a30dSjoris const char *date)
24472803d6eSniallo {
245bbfcb536Sjoris BUF *bp;
246b1ce7b24Sjoris u_int i;
247bc8d37c3Sjoris int fd, lcount;
248950e09dcSxsa char buf[RCS_REV_BUFSZ];
2499642f3ecSmillert mode_t mode = DEFFILEMODE;
25052d3afaaSjoris struct stat st;
251bbfcb536Sjoris struct rcs_delta *rdp;
252bbfcb536Sjoris struct rcs_lock *lkp;
25329095643Sray char *fdate;
25499ee9073Smillert const char *fstatus;
25543f1a30dSjoris time_t rcsdate, givendate;
256b1ce7b24Sjoris RCSNUM *rev;
25743f1a30dSjoris
25893443395Sotto givendate = -1;
259092db204Sray if (date != NULL && (givendate = date_parse(date)) == -1) {
260092db204Sray warnx("invalid date: %s", date);
261092db204Sray return -1;
262092db204Sray }
26372803d6eSniallo
2644a1cfd68Sxsa if (file->rf_ndelta == 0 && !(flags & QUIET))
2654a1cfd68Sxsa (void)fprintf(stderr,
2664a1cfd68Sxsa "no revisions present; generating empty revision 0.0\n");
267848faecaSjoris
268b1ce7b24Sjoris /* XXX rcsnum_cmp()
269b1ce7b24Sjoris * Check out the latest revision if <frev> is greater than HEAD
270b1ce7b24Sjoris */
271848faecaSjoris if (file->rf_ndelta != 0) {
272b1ce7b24Sjoris for (i = 0; i < file->rf_head->rn_len; i++) {
273b1ce7b24Sjoris if (file->rf_head->rn_id[i] < frev->rn_id[i]) {
274905eed26Sniallo frev = file->rf_head;
275b1ce7b24Sjoris break;
276b1ce7b24Sjoris }
277b1ce7b24Sjoris }
278848faecaSjoris }
279905eed26Sniallo
280bbfcb536Sjoris lcount = 0;
281bbfcb536Sjoris TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
282bbfcb536Sjoris if (!strcmp(lkp->rl_name, lockname))
283bbfcb536Sjoris lcount++;
284bbfcb536Sjoris }
285bbfcb536Sjoris
286bbfcb536Sjoris /*
287bbfcb536Sjoris * If the user didn't specify any revision, we cycle through
288bbfcb536Sjoris * revisions to lookup the first one that matches what he specified.
289bbfcb536Sjoris *
290bbfcb536Sjoris * If we cannot find one, we return an error.
291bbfcb536Sjoris */
292bbfcb536Sjoris rdp = NULL;
2937249ec9bSderaadt if (file->rf_ndelta != 0 && frev == file->rf_head) {
294bbfcb536Sjoris if (lcount > 1) {
29582ca7eaaSxsa warnx("multiple revisions locked by %s; "
296bbfcb536Sjoris "please specify one", lockname);
297bbfcb536Sjoris return (-1);
298bbfcb536Sjoris }
299bbfcb536Sjoris
300bbfcb536Sjoris TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
30143f1a30dSjoris if (date != NULL) {
30243f1a30dSjoris fdate = asctime(&rdp->rd_date);
303092db204Sray if ((rcsdate = date_parse(fdate)) == -1) {
304092db204Sray warnx("invalid date: %s", fdate);
305092db204Sray return -1;
306092db204Sray }
30743f1a30dSjoris if (givendate <= rcsdate)
30843f1a30dSjoris continue;
30943f1a30dSjoris }
31043f1a30dSjoris
3117249ec9bSderaadt if (author != NULL &&
3127249ec9bSderaadt strcmp(rdp->rd_author, author))
313bbfcb536Sjoris continue;
31443f1a30dSjoris
3157249ec9bSderaadt if (state != NULL &&
3167249ec9bSderaadt strcmp(rdp->rd_state, state))
317bbfcb536Sjoris continue;
318bbfcb536Sjoris
319bbfcb536Sjoris frev = rdp->rd_num;
320bbfcb536Sjoris break;
321bbfcb536Sjoris }
322848faecaSjoris } else if (file->rf_ndelta != 0) {
323bbfcb536Sjoris rdp = rcs_findrev(file, frev);
324bbfcb536Sjoris }
325bbfcb536Sjoris
3267249ec9bSderaadt if (file->rf_ndelta != 0 && rdp == NULL) {
32743f1a30dSjoris checkout_err_nobranch(file, author, date, state, flags);
328bbfcb536Sjoris return (-1);
329bbfcb536Sjoris }
330bbfcb536Sjoris
331848faecaSjoris if (file->rf_ndelta == 0)
332848faecaSjoris rev = frev;
333848faecaSjoris else
334b1ce7b24Sjoris rev = rdp->rd_num;
335848faecaSjoris
336b1ce7b24Sjoris rcsnum_tostr(rev, buf, sizeof(buf));
337905eed26Sniallo
3387249ec9bSderaadt if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) {
339bbfcb536Sjoris if (strcmp(lockname, rdp->rd_locker)) {
34029095643Sray warnx("Revision %s is already locked by %s; %s",
34129095643Sray buf, rdp->rd_locker,
34229095643Sray (flags & CO_UNLOCK) ? "use co -r or rcs -u" : "");
343bbfcb536Sjoris return (-1);
344bbfcb536Sjoris }
345bbfcb536Sjoris }
346bbfcb536Sjoris
347913586deSxsa if (!(flags & QUIET) && !(flags & NEWFILE) &&
3487249ec9bSderaadt !(flags & CO_REVERT) && file->rf_ndelta != 0)
349db3d0dd3Sxsa (void)fprintf(stderr, "revision %s", buf);
35052d3afaaSjoris
351848faecaSjoris if (file->rf_ndelta != 0) {
352b1ce7b24Sjoris if ((bp = rcs_getrev(file, rev)) == NULL) {
35382ca7eaaSxsa warnx("cannot find revision `%s'", buf);
35472803d6eSniallo return (-1);
35572803d6eSniallo }
356848faecaSjoris } else {
3570ee14128Sray bp = buf_alloc(1);
358848faecaSjoris }
359b1ce7b24Sjoris
3606647b23bSniallo /*
361c51cb395Sniallo * File inherits permissions from its ,v file
362c51cb395Sniallo */
363394437e6Stobias if (file->rf_file != NULL) {
364394437e6Stobias if (fstat(fileno(file->rf_file), &st) == -1)
365a3660ae3Sxsa err(1, "%s", file->rf_path);
366b40d8ec8Sxsa file->rf_mode = mode = st.st_mode;
3670825db65Sniallo } else {
3680825db65Sniallo mode = file->rf_mode;
369bc8d37c3Sjoris }
370c51cb395Sniallo
371c4521683Sniallo if (flags & CO_LOCK) {
3729642f3ecSmillert /* File should only be writable by owner. */
3739642f3ecSmillert mode &= ~(S_IWGRP|S_IWOTH);
374c51cb395Sniallo mode |= S_IWUSR;
375848faecaSjoris
376848faecaSjoris if (file->rf_ndelta != 0) {
377913586deSxsa if (!(flags & QUIET) && !(flags & NEWFILE) &&
3787249ec9bSderaadt !(flags & CO_REVERT))
379d1c7061eSjoris (void)fprintf(stderr, " (locked)\n");
380848faecaSjoris }
381c4521683Sniallo } else if (flags & CO_UNLOCK) {
382848faecaSjoris if (file->rf_ndelta != 0) {
383b1ce7b24Sjoris if (rcs_lock_remove(file, lockname, rev) < 0) {
384c31a7f03Sjoris if (rcs_errno != RCS_ERR_NOENT)
385bbfcb536Sjoris return (-1);
386c31a7f03Sjoris }
387848faecaSjoris }
38852d3afaaSjoris
389c51cb395Sniallo /* Strip all write bits from mode */
3909642f3ecSmillert mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
391848faecaSjoris
392848faecaSjoris if (file->rf_ndelta != 0) {
393913586deSxsa if (!(flags & QUIET) && !(flags & NEWFILE) &&
3947249ec9bSderaadt !(flags & CO_REVERT))
395d1c7061eSjoris (void)fprintf(stderr, " (unlocked)\n");
39652d3afaaSjoris }
39774745a41Smillert } else {
39874745a41Smillert if (file->rf_ndelta != 0) {
39974745a41Smillert if (!(flags & QUIET) && !(flags & NEWFILE) &&
40074745a41Smillert !(flags & CO_REVERT))
40174745a41Smillert (void)fprintf(stderr, "\n");
40274745a41Smillert }
403848faecaSjoris }
40452d3afaaSjoris
40599ee9073Smillert if ((flags & (PIPEOUT|FORCE)) == 0 && stat(dst, &st) != -1) {
4061e724e86Sray /*
40799ee9073Smillert * Prompt the user if the file is writable or the file is
40899ee9073Smillert * not writable but is different from the RCS head version.
40999ee9073Smillert * This is different from GNU which will silently overwrite
41099ee9073Smillert * the file regardless of its contents so long as it is
41199ee9073Smillert * read-only.
4121e724e86Sray */
4131e724e86Sray if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
41499ee9073Smillert fstatus = "writable";
41599ee9073Smillert else if (checkout_file_has_diffs(file, frev, dst) != D_SAME)
41699ee9073Smillert fstatus = "modified";
41799ee9073Smillert else
41899ee9073Smillert fstatus = NULL;
41999ee9073Smillert if (fstatus) {
42099ee9073Smillert (void)fprintf(stderr, "%s %s exists%s; ", fstatus, dst,
42124b25f4eSniallo (getuid() == st.st_uid) ? "" :
422214f3d18Sxsa ", and you do not own it");
423db3d0dd3Sxsa (void)fprintf(stderr, "remove it? [ny](n): ");
4249642f3ecSmillert if (rcs_yesno('n') == 'n') {
425913586deSxsa if (!(flags & QUIET) && isatty(STDIN_FILENO))
4269642f3ecSmillert warnx("%s %s exists; checkout aborted",
42799ee9073Smillert fstatus, dst);
4281e724e86Sray else
42982ca7eaaSxsa warnx("checkout aborted");
43052d3afaaSjoris return (-1);
43152d3afaaSjoris }
43252d3afaaSjoris }
43399ee9073Smillert }
4343fb753dbSjoris
435d1c7061eSjoris if (flags & CO_LOCK) {
436d1c7061eSjoris if (file->rf_ndelta != 0) {
437d1c7061eSjoris if (lockname != NULL &&
438d1c7061eSjoris rcs_lock_add(file, lockname, rev) < 0) {
439d1c7061eSjoris if (rcs_errno != RCS_ERR_DUPENT)
440d1c7061eSjoris return (-1);
441d1c7061eSjoris }
442d1c7061eSjoris }
443d1c7061eSjoris }
444d1c7061eSjoris
445d1c7061eSjoris /* If strict locking is disabled, make file writable by owner. */
446d1c7061eSjoris if (rcs_lock_getmode(file) == RCS_LOCK_LOOSE)
447d1c7061eSjoris mode |= S_IWUSR;
448d1c7061eSjoris
449d1c7061eSjoris if (file->rf_ndelta == 0 && !(flags & QUIET) &&
450d1c7061eSjoris ((flags & CO_LOCK) || (flags & CO_UNLOCK))) {
451d1c7061eSjoris (void)fprintf(stderr, "no revisions, so nothing can be %s\n",
452d1c7061eSjoris (flags & CO_LOCK) ? "locked" : "unlocked");
453d1c7061eSjoris }
454d1c7061eSjoris
455d1c7061eSjoris if (flags & CO_LOCK) {
456d1c7061eSjoris if (rcs_errno != RCS_ERR_DUPENT)
457d1c7061eSjoris lcount++;
458d1c7061eSjoris if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT))
459d1c7061eSjoris warnx("%s: warning: You now have %d locks.",
460d1c7061eSjoris file->rf_path, lcount);
461d1c7061eSjoris }
462d1c7061eSjoris
463d1c7061eSjoris /* Finally do keyword expansion if required. */
464d1c7061eSjoris if (file->rf_ndelta != 0)
465d1c7061eSjoris bp = rcs_kwexp_buf(bp, file, rev);
466d1c7061eSjoris
46751c4a85dSray if (flags & PIPEOUT)
4687bb3ddb0Sray buf_write_fd(bp, STDOUT_FILENO);
46951c4a85dSray else {
470bc8d37c3Sjoris (void)unlink(dst);
471bc8d37c3Sjoris
4723aaa63ebSderaadt if ((fd = open(dst, O_WRONLY|O_CREAT|O_TRUNC, mode)) == -1)
473bc8d37c3Sjoris err(1, "%s", dst);
474bc8d37c3Sjoris
4757bb3ddb0Sray if (buf_write_fd(bp, fd) < 0) {
47682ca7eaaSxsa warnx("failed to write revision to file");
4777bb3ddb0Sray buf_free(bp);
478bc8d37c3Sjoris (void)close(fd);
47972803d6eSniallo return (-1);
48072803d6eSniallo }
481bc8d37c3Sjoris
482bc8d37c3Sjoris if (fchmod(fd, mode) == -1)
483bc8d37c3Sjoris warn("%s", dst);
484bc8d37c3Sjoris
485c4521683Sniallo if (flags & CO_REVDATE) {
486c4521683Sniallo struct timeval tv[2];
4879e5401e6Sxsa memset(&tv, 0, sizeof(tv));
488db59da07Sderaadt tv[0].tv_sec = rcs_rev_getdate(file, rev);
489c4521683Sniallo tv[1].tv_sec = tv[0].tv_sec;
4903aaa63ebSderaadt if (futimes(fd, (const struct timeval *)&tv) == -1)
49182ca7eaaSxsa warn("utimes");
492c4521683Sniallo }
493bc8d37c3Sjoris
494bc8d37c3Sjoris (void)close(fd);
495259cdaa0Sjoris }
4963fb753dbSjoris
4977bb3ddb0Sray buf_free(bp);
49851c4a85dSray
49972803d6eSniallo return (0);
50072803d6eSniallo }
50172803d6eSniallo
502c4521683Sniallo /*
5030cb35f28Sxsa * checkout_err_nobranch()
5040cb35f28Sxsa *
5050cb35f28Sxsa * XXX - should handle the dates too.
5060cb35f28Sxsa */
5070cb35f28Sxsa static void
checkout_err_nobranch(RCSFILE * file,const char * author,const char * date,const char * state,int flags)5080cb35f28Sxsa checkout_err_nobranch(RCSFILE *file, const char *author, const char *date,
5090cb35f28Sxsa const char *state, int flags)
5100cb35f28Sxsa {
5110cb35f28Sxsa if (!(flags & CO_AUTHOR))
5120cb35f28Sxsa author = NULL;
5130cb35f28Sxsa if (!(flags & CO_STATE))
5140cb35f28Sxsa state = NULL;
5150cb35f28Sxsa
5161294ab80Sotto warnx("%s: No revision on branch has %s%s%s%s%s%s%s%s.",
5170cb35f28Sxsa file->rf_path,
5180cb35f28Sxsa date ? "a date before " : "",
5190cb35f28Sxsa date ? date : "",
5201294ab80Sotto (date && author) ? " and " : "",
5211294ab80Sotto author ? "author " : "",
5220cb35f28Sxsa author ? author : "",
5231294ab80Sotto ((date || author) && state) ? " and " : "",
5241294ab80Sotto state ? "state " : "",
5250cb35f28Sxsa state ? state : "");
5261294ab80Sotto
5270cb35f28Sxsa }
52899ee9073Smillert
52999ee9073Smillert /*
53099ee9073Smillert * checkout_file_has_diffs()
53199ee9073Smillert *
53299ee9073Smillert * Check for diffs between the working file and its current revision.
533f049f428Sray * Same return values as diffreg()
53499ee9073Smillert */
53599ee9073Smillert static int
checkout_file_has_diffs(RCSFILE * rfp,RCSNUM * frev,const char * dst)53699ee9073Smillert checkout_file_has_diffs(RCSFILE *rfp, RCSNUM *frev, const char *dst)
53799ee9073Smillert {
53899ee9073Smillert char *tempfile;
53999ee9073Smillert BUF *bp;
54099ee9073Smillert int ret;
54199ee9073Smillert
54299ee9073Smillert tempfile = NULL;
54399ee9073Smillert
54499ee9073Smillert if ((bp = rcs_getrev(rfp, frev)) == NULL) {
54599ee9073Smillert warnx("failed to load revision");
54699ee9073Smillert return (D_ERROR);
54799ee9073Smillert }
548d571282cSmillert if ((bp = rcs_kwexp_buf(bp, rfp, frev)) == NULL) {
549d571282cSmillert warnx("failed to expand tags");
550d571282cSmillert return (D_ERROR);
551d571282cSmillert }
55299ee9073Smillert
55399ee9073Smillert (void)xasprintf(&tempfile, "%s/diff.XXXXXXXXXX", rcs_tmpdir);
5547bb3ddb0Sray buf_write_stmp(bp, tempfile);
5557bb3ddb0Sray buf_empty(bp);
55699ee9073Smillert
55799ee9073Smillert diff_format = D_RCSDIFF;
55868b523edSray ret = diffreg(dst, tempfile, bp, D_FORCEASCII);
55999ee9073Smillert
5607bb3ddb0Sray buf_free(bp);
56199ee9073Smillert unlink(tempfile);
5628ac837e5Snicm free(tempfile);
56399ee9073Smillert
56499ee9073Smillert return (ret);
56599ee9073Smillert }
566