117721Speter/*
2175280Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
354431Speter *
4175280Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175280Sobrien *                                  and others.
6175280Sobrien *
7175280Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175280Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
9175280Sobrien *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132788Speter * specified in the README file that comes with the CVS source distribution.
1254431Speter *
1317721Speter * "update" updates the version in the present directory with respect to the RCS
1417721Speter * repository.  The present version must have been created by "checkout". The
1517721Speter * user can keep up-to-date by calling "update" whenever he feels like it.
1654431Speter *
1717721Speter * The present version can be committed by "commit", but this keeps the version
1817721Speter * in tact.
1954431Speter *
2017721Speter * Arguments following the options are taken to be file names to be updated,
2117721Speter * rather than updating the entire directory.
2254431Speter *
2317721Speter * Modified or non-existent RCS files are checked out and reported as U
2417721Speter * <user_file>
2554431Speter *
2617721Speter * Modified user files are reported as M <user_file>.  If both the RCS file and
2717721Speter * the user file have been modified, the user file is replaced by the result
2817721Speter * of rcsmerge, and a backup file is written for the user in .#file.version.
2917721Speter * If this throws up irreconcilable differences, the file is reported as C
3017721Speter * <user_file>, and as M <user_file> otherwise.
3154431Speter *
3217721Speter * Files added but not yet committed are reported as A <user_file>. Files
3317721Speter * removed but not yet committed are reported as R <user_file>.
3454431Speter *
3517721Speter * If the current directory contains subdirectories that hold concurrent
3617721Speter * versions, these are updated too.  If the -d option was specified, new
3717721Speter * directories added to the repository are automatically created and updated
3817721Speter * as well.
3954431Speter *
4054431Speter * $FreeBSD$
4117721Speter */
4217721Speter
4317721Speter#include "cvs.h"
44175280Sobrien#include <assert.h>
4525839Speter#include "savecwd.h"
4617721Speter#ifdef SERVER_SUPPORT
47102843Speter# include "md5.h"
4817721Speter#endif
4917721Speter#include "watch.h"
5017721Speter#include "fileattr.h"
5117721Speter#include "edit.h"
5225839Speter#include "getline.h"
5334467Speter#include "buffer.h"
5434467Speter#include "hardlink.h"
5517721Speter
5625839Speterstatic int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts,
5734467Speter				 int adding, int merging, int update_server));
5817721Speter#ifdef SERVER_SUPPORT
5934467Speterstatic void checkout_to_buffer PROTO ((void *, const char *, size_t));
6025839Speterstatic int patch_file PROTO ((struct file_info *finfo,
6125839Speter			      Vers_TS *vers_ts,
6225839Speter			      int *docheckout, struct stat *file_info,
6325839Speter			      unsigned char *checksum));
6425839Speterstatic void patch_file_write PROTO ((void *, const char *, size_t));
65128269Speter#endif /* SERVER_SUPPORT */
6625839Speterstatic int merge_file PROTO ((struct file_info *finfo, Vers_TS *vers));
6781407Speterstatic int scratch_file PROTO((struct file_info *finfo, Vers_TS *vers));
68128269Speterstatic Dtype update_dirent_proc PROTO ((void *callerdat, const char *dir,
69128269Speter                                        const char *repository,
70128269Speter                                        const char *update_dir,
71128269Speter                                        List *entries));
72128269Speterstatic int update_dirleave_proc PROTO ((void *callerdat, const char *dir,
73128269Speter					int err, const char *update_dir,
7425839Speter					List *entries));
7525839Speterstatic int update_fileproc PROTO ((void *callerdat, struct file_info *));
7625839Speterstatic int update_filesdone_proc PROTO ((void *callerdat, int err,
77128269Speter                                         const char *repository,
78128269Speter                                         const char *update_dir,
79128269Speter                                         List *entries));
8034467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT
8134467Speterstatic int get_linkinfo_proc PROTO ((void *callerdat, struct file_info *));
8234467Speter#endif
8325839Speterstatic void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
8417721Speter
8517721Speterstatic char *options = NULL;
8617721Speterstatic char *tag = NULL;
8717721Speterstatic char *date = NULL;
8825839Speter/* This is a bit of a kludge.  We call WriteTag at the beginning
8925839Speter   before we know whether nonbranch is set or not.  And then at the
9025839Speter   end, once we have the right value for nonbranch, we call WriteTag
9125839Speter   again.  I don't know whether the first call is necessary or not.
9225839Speter   rewrite_tag is nonzero if we are going to have to make that second
9325839Speter   call.  */
9425839Speterstatic int rewrite_tag;
9525839Speterstatic int nonbranch;
9625839Speter
9734467Speter/* If we set the tag or date for a subdirectory, we use this to undo
9834467Speter   the setting.  See update_dirent_proc.  */
9934467Speterstatic char *tag_update_dir;
10034467Speter
10117721Speterstatic char *join_rev1, *date_rev1;
10217721Speterstatic char *join_rev2, *date_rev2;
10317721Speterstatic int aflag = 0;
10466528Speterstatic int toss_local_changes = 0;
10517721Speterstatic int force_tag_match = 1;
10683496Sdillonstatic int pull_template = 0;
10717721Speterstatic int update_build_dirs = 0;
10817721Speterstatic int update_prune_dirs = 0;
10917721Speterstatic int pipeout = 0;
11017721Speter#ifdef SERVER_SUPPORT
11117721Speterstatic int patches = 0;
11225839Speterstatic int rcs_diff_patches = 0;
11317721Speter#endif
11417721Speterstatic List *ignlist = (List *) NULL;
11517721Speterstatic time_t last_register_time;
11617721Speterstatic const char *const update_usage[] =
11717721Speter{
11881407Speter    "Usage: %s %s [-APCdflRp] [-k kopt] [-r rev] [-D date] [-j rev]\n",
11917721Speter    "    [-I ign] [-W spec] [files...]\n",
12017721Speter    "\t-A\tReset any sticky tags/date/kopts.\n",
12117721Speter    "\t-P\tPrune empty directories.\n",
12266528Speter    "\t-C\tOverwrite locally modified files with clean repository copies.\n",
12317721Speter    "\t-d\tBuild directories, like checkout does.\n",
12417721Speter    "\t-f\tForce a head revision match if tag/date not found.\n",
12517721Speter    "\t-l\tLocal directory only, no recursion.\n",
12617721Speter    "\t-R\tProcess directories recursively.\n",
12725839Speter    "\t-p\tSend updates to standard output (avoids stickiness).\n",
12881407Speter    "\t-k kopt\tUse RCS kopt -k option on checkout. (is sticky)\n",
12925839Speter    "\t-r rev\tUpdate using specified revision/tag (is sticky).\n",
13025839Speter    "\t-D date\tSet date to update from (is sticky).\n",
13117721Speter    "\t-j rev\tMerge in changes made between current revision and rev.\n",
13217721Speter    "\t-I ign\tMore files to ignore (! to reset).\n",
13317721Speter    "\t-W spec\tWrappers specification line.\n",
13483496Sdillon    "\t-T\tCreate CVS/Template.\n",
13532788Speter    "(Specify the --help global option for a list of other help options)\n",
13617721Speter    NULL
13717721Speter};
13817721Speter
13917721Speter/*
14017721Speter * update is the argv,argc based front end for arg parsing
14117721Speter */
14217721Speterint
14317721Speterupdate (argc, argv)
14417721Speter    int argc;
14517721Speter    char **argv;
14617721Speter{
14717721Speter    int c, err;
14817721Speter    int local = 0;			/* recursive by default */
14917721Speter    int which;				/* where to look for files and dirs */
15083496Sdillon    int xpull_template = 0;
15117721Speter
15217721Speter    if (argc == -1)
15317721Speter	usage (update_usage);
15417721Speter
15517721Speter    ign_setup ();
15617721Speter    wrap_setup ();
15717721Speter
15817721Speter    /* parse the args */
15926065Speter    optind = 0;
16083496Sdillon    while ((c = getopt (argc, argv, "+ApCPflRQTqduk:r:D:j:I:W:")) != -1)
16117721Speter    {
16217721Speter	switch (c)
16317721Speter	{
16417721Speter	    case 'A':
16517721Speter		aflag = 1;
16617721Speter		break;
16766528Speter	    case 'C':
16866528Speter		toss_local_changes = 1;
16966528Speter		break;
17017721Speter	    case 'I':
17117721Speter		ign_add (optarg, 0);
17217721Speter		break;
17317721Speter	    case 'W':
17417721Speter		wrap_add (optarg, 0);
17517721Speter		break;
17617721Speter	    case 'k':
17717721Speter		if (options)
17817721Speter		    free (options);
17917721Speter		options = RCS_check_kflag (optarg);
18017721Speter		break;
18117721Speter	    case 'l':
18217721Speter		local = 1;
18317721Speter		break;
18417721Speter	    case 'R':
18517721Speter		local = 0;
18617721Speter		break;
18717721Speter	    case 'Q':
18817721Speter	    case 'q':
18917721Speter		/* The CVS 1.5 client sends these options (in addition to
19017721Speter		   Global_option requests), so we must ignore them.  */
19117721Speter		if (!server_active)
19217721Speter		    error (1, 0,
19317721Speter			   "-q or -Q must be specified before \"%s\"",
194128269Speter			   cvs_cmd_name);
19517721Speter		break;
19683496Sdillon	    case 'T':
19783496Sdillon		xpull_template = 1;
19883496Sdillon		break;
19917721Speter	    case 'd':
20017721Speter		update_build_dirs = 1;
20117721Speter		break;
20217721Speter	    case 'f':
20317721Speter		force_tag_match = 0;
20417721Speter		break;
20517721Speter	    case 'r':
20617721Speter		tag = optarg;
20717721Speter		break;
20817721Speter	    case 'D':
209175280Sobrien		if (date) free (date);
21017721Speter		date = Make_Date (optarg);
21117721Speter		break;
21217721Speter	    case 'P':
21317721Speter		update_prune_dirs = 1;
21417721Speter		break;
21517721Speter	    case 'p':
21617721Speter		pipeout = 1;
21717721Speter		noexec = 1;		/* so no locks will be created */
21817721Speter		break;
21917721Speter	    case 'j':
22017721Speter		if (join_rev2)
22117721Speter		    error (1, 0, "only two -j options can be specified");
22217721Speter		if (join_rev1)
22317721Speter		    join_rev2 = optarg;
22417721Speter		else
22517721Speter		    join_rev1 = optarg;
22617721Speter		break;
22717721Speter	    case 'u':
22817721Speter#ifdef SERVER_SUPPORT
22917721Speter		if (server_active)
23025839Speter		{
23117721Speter		    patches = 1;
23225839Speter		    rcs_diff_patches = server_use_rcs_diff ();
23325839Speter		}
23417721Speter		else
23517721Speter#endif
23617721Speter		    usage (update_usage);
23717721Speter		break;
23817721Speter	    case '?':
23917721Speter	    default:
24017721Speter		usage (update_usage);
24117721Speter		break;
24217721Speter	}
24317721Speter    }
24417721Speter    argc -= optind;
24517721Speter    argv += optind;
24617721Speter
24717721Speter#ifdef CLIENT_SUPPORT
24881407Speter    if (current_parsed_root->isremote)
24917721Speter    {
25025839Speter	int pass;
25125839Speter
25217721Speter	/* The first pass does the regular update.  If we receive at least
25317721Speter	   one patch which failed, we do a second pass and just fetch
25417721Speter	   those files whose patches failed.  */
25525839Speter	pass = 1;
25617721Speter	do
25717721Speter	{
25817721Speter	    int status;
25917721Speter
26017721Speter	    start_server ();
26117721Speter
26217721Speter	    if (local)
26317721Speter		send_arg("-l");
26417721Speter	    if (update_build_dirs)
26517721Speter		send_arg("-d");
26617721Speter	    if (pipeout)
26717721Speter		send_arg("-p");
26817721Speter	    if (!force_tag_match)
26917721Speter		send_arg("-f");
27017721Speter	    if (aflag)
27117721Speter		send_arg("-A");
27266528Speter	    if (toss_local_changes)
27366528Speter		send_arg("-C");
27417721Speter	    if (update_prune_dirs)
27517721Speter		send_arg("-P");
27617721Speter	    client_prune_dirs = update_prune_dirs;
27717721Speter	    option_with_arg ("-r", tag);
27825839Speter	    if (options && options[0] != '\0')
27925839Speter		send_arg (options);
28017721Speter	    if (date)
28117721Speter		client_senddate (date);
28217721Speter	    if (join_rev1)
28317721Speter		option_with_arg ("-j", join_rev1);
28417721Speter	    if (join_rev2)
28517721Speter		option_with_arg ("-j", join_rev2);
28625839Speter	    wrap_send ();
28717721Speter
28866528Speter	    if (failed_patches_count == 0)
28966528Speter	    {
29066528Speter                unsigned int flags = 0;
29132788Speter
29266528Speter		/* If the server supports the command "update-patches", that
29366528Speter		   means that it knows how to handle the -u argument to update,
29466528Speter		   which means to send patches instead of complete files.
29566528Speter
29666528Speter		   We don't send -u if failed_patches != NULL, so that the
29766528Speter		   server doesn't try to send patches which will just fail
29866528Speter		   again.  At least currently, the client also clobbers the
29966528Speter		   file and tells the server it is lost, which also will get
30066528Speter		   a full file instead of a patch, but it seems clean to omit
30166528Speter		   -u.  */
30225839Speter		if (supported_request ("update-patches"))
30325839Speter		    send_arg ("-u");
30417721Speter
305107487Speter		send_arg ("--");
306107487Speter
30766528Speter                if (update_build_dirs)
30866528Speter                    flags |= SEND_BUILD_DIRS;
30966528Speter
31066528Speter                if (toss_local_changes) {
31166528Speter                    flags |= SEND_NO_CONTENTS;
31266528Speter                    flags |= BACKUP_MODIFIED_FILES;
31366528Speter                }
31466528Speter
31525839Speter		/* If noexec, probably could be setting SEND_NO_CONTENTS.
31625839Speter		   Same caveats as for "cvs status" apply.  */
31766528Speter
31866528Speter		send_files (argc, argv, local, aflag, flags);
31954431Speter		send_file_names (argc, argv, SEND_EXPAND_WILD);
32017721Speter	    }
32117721Speter	    else
32217721Speter	    {
32317721Speter		int i;
32417721Speter
32517721Speter		(void) printf ("%s client: refetching unpatchable files\n",
32617721Speter			       program_name);
32717721Speter
32825839Speter		if (toplevel_wd != NULL
32925839Speter		    && CVS_CHDIR (toplevel_wd) < 0)
33017721Speter		{
33117721Speter		    error (1, errno, "could not chdir to %s", toplevel_wd);
33217721Speter		}
33317721Speter
334107487Speter		send_arg ("--");
335107487Speter
33617721Speter		for (i = 0; i < failed_patches_count; i++)
33754431Speter		    if (unlink_file (failed_patches[i]) < 0
33854431Speter			&& !existence_error (errno))
33954431Speter			error (0, errno, "cannot remove %s",
34054431Speter			       failed_patches[i]);
34117721Speter		send_files (failed_patches_count, failed_patches, local,
34225839Speter			    aflag, update_build_dirs ? SEND_BUILD_DIRS : 0);
34354431Speter		send_file_names (failed_patches_count, failed_patches, 0);
34466528Speter		free_names (&failed_patches_count, failed_patches);
34517721Speter	    }
34617721Speter
34717721Speter	    send_to_server ("update\012", 0);
34817721Speter
34917721Speter	    status = get_responses_and_close ();
35025839Speter
35125839Speter	    /* If there are any conflicts, the server will return a
35225839Speter               non-zero exit status.  If any patches failed, we still
35325839Speter               want to run the update again.  We use a pass count to
35425839Speter               avoid an endless loop.  */
35525839Speter
35625839Speter	    /* Notes: (1) assuming that status != 0 implies a
35725839Speter	       potential conflict is the best we can cleanly do given
35825839Speter	       the current protocol.  I suppose that trying to
35925839Speter	       re-fetch in cases where there was a more serious error
36025839Speter	       is probably more or less harmless, but it isn't really
36125839Speter	       ideal.  (2) it would be nice to have a testsuite case for the
36225839Speter	       conflict-and-patch-failed case.  */
36325839Speter
36425839Speter	    if (status != 0
36566528Speter		&& (failed_patches_count == 0 || pass > 1))
36625839Speter	    {
36766528Speter		if (failed_patches_count > 0)
36866528Speter		    free_names (&failed_patches_count, failed_patches);
36917721Speter		return status;
37025839Speter	    }
37117721Speter
37225839Speter	    ++pass;
37366528Speter	} while (failed_patches_count > 0);
37417721Speter
37517721Speter	return 0;
37617721Speter    }
37717721Speter#endif
37817721Speter
37917721Speter    if (tag != NULL)
38017721Speter	tag_check_valid (tag, argc, argv, local, aflag, "");
38125839Speter    if (join_rev1 != NULL)
38225839Speter        tag_check_valid_join (join_rev1, argc, argv, local, aflag, "");
38325839Speter    if (join_rev2 != NULL)
38425839Speter        tag_check_valid_join (join_rev2, argc, argv, local, aflag, "");
38517721Speter
38617721Speter    /*
38717721Speter     * If we are updating the entire directory (for real) and building dirs
38817721Speter     * as we go, we make sure there is no static entries file and write the
38917721Speter     * tag file as appropriate
39017721Speter     */
39117721Speter    if (argc <= 0 && !pipeout)
39217721Speter    {
39317721Speter	if (update_build_dirs)
39417721Speter	{
39517721Speter	    if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
39617721Speter		error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
39717721Speter#ifdef SERVER_SUPPORT
39817721Speter	    if (server_active)
39966528Speter	    {
40066528Speter		char *repos = Name_Repository (NULL, NULL);
40166528Speter		server_clear_entstat (".", repos);
40266528Speter		free (repos);
40366528Speter	    }
40417721Speter#endif
40517721Speter	}
40617721Speter
40717721Speter	/* keep the CVS/Tag file current with the specified arguments */
40817721Speter	if (aflag || tag || date)
40917721Speter	{
41066528Speter	    char *repos = Name_Repository (NULL, NULL);
41166528Speter	    WriteTag ((char *) NULL, tag, date, 0, ".", repos);
41266528Speter	    free (repos);
41325839Speter	    rewrite_tag = 1;
41425839Speter	    nonbranch = 0;
41517721Speter	}
41617721Speter    }
41717721Speter
41817721Speter    /* look for files/dirs locally and in the repository */
41917721Speter    which = W_LOCAL | W_REPOS;
42017721Speter
42117721Speter    /* look in the attic too if a tag or date is specified */
42217721Speter    if (tag != NULL || date != NULL || joining())
42317721Speter	which |= W_ATTIC;
42417721Speter
42517721Speter    /* call the command line interface */
42617721Speter    err = do_update (argc, argv, options, tag, date, force_tag_match,
42717721Speter		     local, update_build_dirs, aflag, update_prune_dirs,
42883496Sdillon		     pipeout, which, join_rev1, join_rev2, (char *) NULL,
429128269Speter		     xpull_template, (char *) NULL);
43017721Speter
43117721Speter    /* free the space Make_Date allocated if necessary */
43217721Speter    if (date != NULL)
43317721Speter	free (date);
43417721Speter
435130307Speter    return err;
43617721Speter}
43717721Speter
438130307Speter
439130307Speter
44017721Speter/*
44117721Speter * Command line interface to update (used by checkout)
44217721Speter */
44317721Speterint
44417721Speterdo_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
44581407Speter	   xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir,
446128269Speter	   xpull_template, repository)
44717721Speter    int argc;
44817721Speter    char **argv;
44917721Speter    char *xoptions;
45017721Speter    char *xtag;
45117721Speter    char *xdate;
45217721Speter    int xforce;
45317721Speter    int local;
45417721Speter    int xbuild;
45517721Speter    int xaflag;
45617721Speter    int xprune;
45717721Speter    int xpipeout;
45817721Speter    int which;
45917721Speter    char *xjoin_rev1;
46017721Speter    char *xjoin_rev2;
46117721Speter    char *preload_update_dir;
46283496Sdillon    int xpull_template;
463128269Speter    char *repository;
46417721Speter{
46517721Speter    int err = 0;
46617721Speter    char *cp;
46717721Speter
46817721Speter    /* fill in the statics */
46917721Speter    options = xoptions;
47017721Speter    tag = xtag;
47117721Speter    date = xdate;
47217721Speter    force_tag_match = xforce;
47317721Speter    update_build_dirs = xbuild;
47417721Speter    aflag = xaflag;
47517721Speter    update_prune_dirs = xprune;
47617721Speter    pipeout = xpipeout;
47783496Sdillon    pull_template = xpull_template;
47817721Speter
47917721Speter    /* setup the join support */
48017721Speter    join_rev1 = xjoin_rev1;
48117721Speter    join_rev2 = xjoin_rev2;
48217721Speter    if (join_rev1 && (cp = strchr (join_rev1, ':')) != NULL)
48317721Speter    {
48417721Speter	*cp++ = '\0';
48517721Speter	date_rev1 = Make_Date (cp);
48617721Speter    }
48717721Speter    else
48817721Speter	date_rev1 = (char *) NULL;
48917721Speter    if (join_rev2 && (cp = strchr (join_rev2, ':')) != NULL)
49017721Speter    {
49117721Speter	*cp++ = '\0';
49217721Speter	date_rev2 = Make_Date (cp);
49317721Speter    }
49417721Speter    else
49517721Speter	date_rev2 = (char *) NULL;
49617721Speter
49734467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT
49834467Speter    if (preserve_perms)
49934467Speter    {
50034467Speter	/* We need to do an extra recursion, bleah.  It's to make sure
50134467Speter	   that we know as much as possible about file linkage. */
50234467Speter	hardlist = getlist();
50334467Speter	working_dir = xgetwd();		/* save top-level working dir */
50434467Speter
50534467Speter	/* FIXME-twp: the arguments to start_recursion make me dizzy.  This
50634467Speter	   function call was copied from the update_fileproc call that
50734467Speter	   follows it; someone should make sure that I did it right. */
50834467Speter	err = start_recursion (get_linkinfo_proc, (FILESDONEPROC) NULL,
50934467Speter			       (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
510109660Speter			       argc, argv, local, which, aflag, CVS_LOCK_READ,
511128269Speter			       preload_update_dir, 1, (char *) NULL);
51234467Speter	if (err)
513130307Speter	    return err;
51434467Speter
51534467Speter	/* FIXME-twp: at this point we should walk the hardlist
51634467Speter	   and update the `links' field of each hardlink_info struct
51734467Speter	   to list the files that are linked on dist.  That would make
51834467Speter	   it easier & more efficient to compare the disk linkage with
51934467Speter	   the repository linkage (a simple strcmp). */
52034467Speter    }
52134467Speter#endif
52234467Speter
52317721Speter    /* call the recursion processor */
52417721Speter    err = start_recursion (update_fileproc, update_filesdone_proc,
52525839Speter			   update_dirent_proc, update_dirleave_proc, NULL,
526109660Speter			   argc, argv, local, which, aflag, CVS_LOCK_READ,
527128269Speter			   preload_update_dir, 1, repository);
52817721Speter
52966528Speter    /* see if we need to sleep before returning to avoid time-stamp races */
530175280Sobrien    if (!server_active && last_register_time)
53117721Speter    {
53281407Speter	sleep_past (last_register_time);
53317721Speter    }
53417721Speter
535130307Speter    return err;
53617721Speter}
53717721Speter
53834467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT
53917721Speter/*
54034467Speter * The get_linkinfo_proc callback adds each file to the hardlist
54134467Speter * (see hardlink.c).
54234467Speter */
54334467Speter
54434467Speterstatic int
54534467Speterget_linkinfo_proc (callerdat, finfo)
54634467Speter    void *callerdat;
54734467Speter    struct file_info *finfo;
54834467Speter{
54934467Speter    char *fullpath;
55034467Speter    Node *linkp;
55134467Speter    struct hardlink_info *hlinfo;
55234467Speter
55334467Speter    /* Get the full pathname of the current file. */
55434467Speter    fullpath = xmalloc (strlen(working_dir) +
55534467Speter			strlen(finfo->fullname) + 2);
55634467Speter    sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
55734467Speter
55834467Speter    /* To permit recursing into subdirectories, files
55934467Speter       are keyed on the full pathname and not on the basename. */
56034467Speter    linkp = lookup_file_by_inode (fullpath);
56134467Speter    if (linkp == NULL)
56234467Speter    {
56334467Speter	/* The file isn't on disk; we are probably restoring
56434467Speter	   a file that was removed. */
56534467Speter	return 0;
56634467Speter    }
56734467Speter
56834467Speter    /* Create a new, empty hardlink_info node. */
56934467Speter    hlinfo = (struct hardlink_info *)
57034467Speter	xmalloc (sizeof (struct hardlink_info));
57134467Speter
57234467Speter    hlinfo->status = (Ctype) 0;	/* is this dumb? */
57334467Speter    hlinfo->checked_out = 0;
57434467Speter
575128269Speter    linkp->data = hlinfo;
57634467Speter
57734467Speter    return 0;
57834467Speter}
57934467Speter#endif
58034467Speter
581128269Speter
582128269Speter
58334467Speter/*
58417721Speter * This is the callback proc for update.  It is called for each file in each
58517721Speter * directory by the recursion code.  The current directory is the local
58617721Speter * instantiation.  file is the file name we are to operate on. update_dir is
58717721Speter * set to the path relative to where we started (for pretty printing).
58817721Speter * repository is the repository. entries and srcfiles are the pre-parsed
58917721Speter * entries and source control files.
59017721Speter *
59117721Speter * This routine decides what needs to be done for each file and does the
59217721Speter * appropriate magic for checkout
59317721Speter */
59417721Speterstatic int
59525839Speterupdate_fileproc (callerdat, finfo)
59625839Speter    void *callerdat;
59717721Speter    struct file_info *finfo;
59817721Speter{
59917721Speter    int retval;
60017721Speter    Ctype status;
60117721Speter    Vers_TS *vers;
60217721Speter
60325839Speter    status = Classify_File (finfo, tag, date, options, force_tag_match,
60425839Speter			    aflag, &vers, pipeout);
60525839Speter
60625839Speter    /* Keep track of whether TAG is a branch tag.
60725839Speter       Note that if it is a branch tag in some files and a nonbranch tag
60825839Speter       in others, treat it as a nonbranch tag.  It is possible that case
60925839Speter       should elicit a warning or an error.  */
61025839Speter    if (rewrite_tag
61125839Speter	&& tag != NULL
61225839Speter	&& finfo->rcs != NULL)
61325839Speter    {
61432467Sjulian	char *rev = RCS_getversion (finfo->rcs, tag, date, 1, NULL);
61525839Speter	if (rev != NULL
61625839Speter	    && !RCS_nodeisbranch (finfo->rcs, tag))
61725839Speter	    nonbranch = 1;
61825839Speter	if (rev != NULL)
61925839Speter	    free (rev);
62025839Speter    }
62125839Speter
62217721Speter    if (pipeout)
62317721Speter    {
62417721Speter	/*
62517721Speter	 * We just return success without doing anything if any of the really
62617721Speter	 * funky cases occur
62717721Speter	 *
62817721Speter	 * If there is still a valid RCS file, do a regular checkout type
62917721Speter	 * operation
63017721Speter	 */
63117721Speter	switch (status)
63217721Speter	{
63317721Speter	    case T_UNKNOWN:		/* unknown file was explicitly asked
63417721Speter					 * about */
63517721Speter	    case T_REMOVE_ENTRY:	/* needs to be un-registered */
63617721Speter	    case T_ADDED:		/* added but not committed */
63717721Speter		retval = 0;
63817721Speter		break;
63917721Speter	    case T_CONFLICT:		/* old punt-type errors */
64017721Speter		retval = 1;
64117721Speter		break;
64217721Speter	    case T_UPTODATE:		/* file was already up-to-date */
64317721Speter	    case T_NEEDS_MERGE:		/* needs merging */
64417721Speter	    case T_MODIFIED:		/* locally modified */
64517721Speter	    case T_REMOVED:		/* removed but not committed */
64617721Speter	    case T_CHECKOUT:		/* needs checkout */
64717721Speter	    case T_PATCH:		/* needs patch */
64834467Speter		retval = checkout_file (finfo, vers, 0, 0, 0);
64917721Speter		break;
65017721Speter
65117721Speter	    default:			/* can't ever happen :-) */
65217721Speter		error (0, 0,
65317721Speter		       "unknown file status %d for file %s", status, finfo->file);
65417721Speter		retval = 0;
65517721Speter		break;
65617721Speter	}
65717721Speter    }
65817721Speter    else
65917721Speter    {
66017721Speter	switch (status)
66117721Speter	{
66217721Speter	    case T_UNKNOWN:		/* unknown file was explicitly asked
66317721Speter					 * about */
66417721Speter	    case T_UPTODATE:		/* file was already up-to-date */
66517721Speter		retval = 0;
66617721Speter		break;
66717721Speter	    case T_CONFLICT:		/* old punt-type errors */
66817721Speter		retval = 1;
66932788Speter		write_letter (finfo, 'C');
67017721Speter		break;
67117721Speter	    case T_NEEDS_MERGE:		/* needs merging */
67281407Speter		if (! toss_local_changes)
67381407Speter		{
67481407Speter		    retval = merge_file (finfo, vers);
67581407Speter		    break;
67681407Speter		}
67781407Speter		/* else FALL THROUGH */
67817721Speter	    case T_MODIFIED:		/* locally modified */
67917721Speter		retval = 0;
68066528Speter                if (toss_local_changes)
68166528Speter                {
68266528Speter                    char *bakname;
68366528Speter                    bakname = backup_file (finfo->file, vers->vn_user);
68466528Speter                    /* This behavior is sufficiently unexpected to
68566528Speter                       justify overinformativeness, I think. */
686175280Sobrien                    if (!really_quiet && !server_active)
68766528Speter                        (void) printf ("(Locally modified %s moved to %s)\n",
68866528Speter                                       finfo->file, bakname);
68966528Speter                    free (bakname);
69017721Speter
69166528Speter                    /* The locally modified file is still present, but
69266528Speter                       it will be overwritten by the repository copy
69366528Speter                       after this. */
69466528Speter                    status = T_CHECKOUT;
69566528Speter                    retval = checkout_file (finfo, vers, 0, 0, 1);
69666528Speter                }
69766528Speter                else
69866528Speter                {
69966528Speter                    if (vers->ts_conflict)
70066528Speter                    {
701175280Sobrien			if (file_has_markers (finfo))
70266528Speter                        {
70366528Speter                            write_letter (finfo, 'C');
70466528Speter                            retval = 1;
70566528Speter                        }
70666528Speter                        else
70766528Speter                        {
70866528Speter                            /* Reregister to clear conflict flag. */
70966528Speter                            Register (finfo->entries, finfo->file,
71066528Speter                                      vers->vn_rcs, vers->ts_rcs,
71166528Speter                                      vers->options, vers->tag,
71266528Speter                                      vers->date, (char *)0);
71366528Speter                        }
71466528Speter                    }
71566528Speter                    if (!retval)
71666528Speter                        write_letter (finfo, 'M');
71766528Speter                }
71817721Speter		break;
71981407Speter	    case T_PATCH:		/* needs patch */
72017721Speter#ifdef SERVER_SUPPORT
72117721Speter		if (patches)
72217721Speter		{
72317721Speter		    int docheckout;
72417721Speter		    struct stat file_info;
72517721Speter		    unsigned char checksum[16];
72617721Speter
72725839Speter		    retval = patch_file (finfo,
72825839Speter					 vers, &docheckout,
72917721Speter					 &file_info, checksum);
73017721Speter		    if (! docheckout)
73117721Speter		    {
73217721Speter		        if (server_active && retval == 0)
73325839Speter			    server_updated (finfo, vers,
73425839Speter					    (rcs_diff_patches
73525839Speter					     ? SERVER_RCS_DIFF
73625839Speter					     : SERVER_PATCHED),
73734467Speter					    file_info.st_mode, checksum,
73834467Speter					    (struct buffer *) NULL);
73917721Speter			break;
74017721Speter		    }
74117721Speter		}
74281407Speter#endif
74317721Speter		/* If we're not running as a server, just check the
74432788Speter		   file out.  It's simpler and faster than producing
74532788Speter		   and applying patches.  */
74617721Speter		/* Fall through.  */
74717721Speter	    case T_CHECKOUT:		/* needs checkout */
74834467Speter		retval = checkout_file (finfo, vers, 0, 0, 1);
74917721Speter		break;
75017721Speter	    case T_ADDED:		/* added but not committed */
75132788Speter		write_letter (finfo, 'A');
75232788Speter		retval = 0;
75317721Speter		break;
75417721Speter	    case T_REMOVED:		/* removed but not committed */
75532788Speter		write_letter (finfo, 'R');
75632788Speter		retval = 0;
75717721Speter		break;
75817721Speter	    case T_REMOVE_ENTRY:	/* needs to be un-registered */
75981407Speter		retval = scratch_file (finfo, vers);
76017721Speter		break;
76117721Speter	    default:			/* can't ever happen :-) */
76217721Speter		error (0, 0,
76317721Speter		       "unknown file status %d for file %s", status, finfo->file);
76417721Speter		retval = 0;
76517721Speter		break;
76617721Speter	}
76717721Speter    }
76817721Speter
76917721Speter    /* only try to join if things have gone well thus far */
77017721Speter    if (retval == 0 && join_rev1)
77125839Speter	join_file (finfo, vers);
77217721Speter
77317721Speter    /* if this directory has an ignore list, add this file to it */
77481407Speter    if (ignlist && (status != T_UNKNOWN || vers->ts_user == NULL))
77517721Speter    {
77617721Speter	Node *p;
77717721Speter
77817721Speter	p = getnode ();
77917721Speter	p->type = FILES;
78017721Speter	p->key = xstrdup (finfo->file);
78117721Speter	if (addnode (ignlist, p) != 0)
78217721Speter	    freenode (p);
78317721Speter    }
78417721Speter
78517721Speter    freevers_ts (&vers);
786128269Speter    return retval;
78717721Speter}
78817721Speter
78917721Speter
790128269Speter
791128269Speterstatic void update_ignproc PROTO ((const char *, const char *));
792128269Speter
79317721Speterstatic void
79417721Speterupdate_ignproc (file, dir)
795128269Speter    const char *file;
796128269Speter    const char *dir;
79717721Speter{
79832788Speter    struct file_info finfo;
799128269Speter    char *tmp;
80032788Speter
80132788Speter    memset (&finfo, 0, sizeof (finfo));
80232788Speter    finfo.file = file;
80332788Speter    finfo.update_dir = dir;
80432788Speter    if (dir[0] == '\0')
805128269Speter	tmp = xstrdup (file);
80632788Speter    else
80732788Speter    {
808128269Speter	tmp = xmalloc (strlen (file) + strlen (dir) + 10);
809128269Speter	strcpy (tmp, dir);
810128269Speter	strcat (tmp, "/");
811128269Speter	strcat (tmp, file);
81232788Speter    }
81332788Speter
814128269Speter    finfo.fullname = tmp;
81532788Speter    write_letter (&finfo, '?');
816128269Speter    free (tmp);
81717721Speter}
81817721Speter
819128269Speter
820128269Speter
82117721Speter/* ARGSUSED */
82217721Speterstatic int
82325839Speterupdate_filesdone_proc (callerdat, err, repository, update_dir, entries)
82425839Speter    void *callerdat;
82517721Speter    int err;
826128269Speter    const char *repository;
827128269Speter    const char *update_dir;
82825839Speter    List *entries;
82917721Speter{
83025839Speter    if (rewrite_tag)
83125839Speter    {
83225839Speter	WriteTag (NULL, tag, date, nonbranch, update_dir, repository);
83325839Speter	rewrite_tag = 0;
83425839Speter    }
83525839Speter
83617721Speter    /* if this directory has an ignore list, process it then free it */
83717721Speter    if (ignlist)
83817721Speter    {
83925839Speter	ignore_files (ignlist, entries, update_dir, update_ignproc);
84017721Speter	dellist (&ignlist);
84117721Speter    }
84217721Speter
84317721Speter    /* Clean up CVS admin dirs if we are export */
844128269Speter    if (strcmp (cvs_cmd_name, "export") == 0)
84517721Speter    {
84617721Speter	/* I'm not sure the existence_error is actually possible (except
84717721Speter	   in cases where we really should print a message), but since
84817721Speter	   this code used to ignore all errors, I'll play it safe.  */
84917721Speter	if (unlink_file_dir (CVSADM) < 0 && !existence_error (errno))
85017721Speter	    error (0, errno, "cannot remove %s directory", CVSADM);
85117721Speter    }
85217721Speter    else if (!server_active && !pipeout)
85317721Speter    {
85417721Speter        /* If there is no CVS/Root file, add one */
85517721Speter        if (!isfile (CVSADM_ROOT))
85681407Speter	    Create_Root ((char *) NULL, current_parsed_root->original);
85717721Speter    }
85817721Speter
859130307Speter    return err;
86017721Speter}
86117721Speter
862128269Speter
863128269Speter
86417721Speter/*
86517721Speter * update_dirent_proc () is called back by the recursion processor before a
86617721Speter * sub-directory is processed for update.  In this case, update_dirent proc
86717721Speter * will probably create the directory unless -d isn't specified and this is a
86817721Speter * new directory.  A return code of 0 indicates the directory should be
86917721Speter * processed by the recursion code.  A return of non-zero indicates the
87017721Speter * recursion code should skip this directory.
87117721Speter */
87217721Speterstatic Dtype
87325839Speterupdate_dirent_proc (callerdat, dir, repository, update_dir, entries)
87425839Speter    void *callerdat;
875128269Speter    const char *dir;
876128269Speter    const char *repository;
877128269Speter    const char *update_dir;
87825839Speter    List *entries;
87917721Speter{
88017721Speter    if (ignore_directory (update_dir))
88125839Speter    {
88217721Speter	/* print the warm fuzzy message */
88317721Speter	if (!quiet)
88417721Speter	  error (0, 0, "Ignoring %s", update_dir);
88517721Speter        return R_SKIP_ALL;
88625839Speter    }
88717721Speter
88817721Speter    if (!isdir (dir))
88917721Speter    {
89017721Speter	/* if we aren't building dirs, blow it off */
89117721Speter	if (!update_build_dirs)
892130307Speter	    return R_SKIP_ALL;
89317721Speter
89454431Speter	/* Various CVS administrators are in the habit of removing
89554431Speter	   the repository directory for things they don't want any
89654431Speter	   more.  I've even been known to do it myself (on rare
89754431Speter	   occasions).  Not the usual recommended practice, but we
89854431Speter	   want to try to come up with some kind of
89954431Speter	   reasonable/documented/sensible behavior.  Generally
90054431Speter	   the behavior is to just skip over that directory (see
90154431Speter	   dirs test in sanity.sh; the case which reaches here
90254431Speter	   is when update -d is specified, and the working directory
90354431Speter	   is gone but the subdirectory is still mentioned in
90454431Speter	   CVS/Entries).  */
905175280Sobrien	/* In the remote case, the client should refrain from
906175280Sobrien	   sending us the directory in the first place.  So we
907175280Sobrien	   want to continue to give an error, so clients make
908175280Sobrien	   sure to do this.  */
909175280Sobrien	if (!server_active && !isdir (repository))
91054431Speter	    return R_SKIP_ALL;
91154431Speter
91217721Speter	if (noexec)
91317721Speter	{
91417721Speter	    /* ignore the missing dir if -n is specified */
91532788Speter	    error (0, 0, "New directory `%s' -- ignored", update_dir);
916130307Speter	    return R_SKIP_ALL;
91717721Speter	}
91817721Speter	else
91917721Speter	{
92017721Speter	    /* otherwise, create the dir and appropriate adm files */
92134467Speter
92234467Speter	    /* If no tag or date were specified on the command line,
92334467Speter               and we're not using -A, we want the subdirectory to use
92434467Speter               the tag and date, if any, of the current directory.
92534467Speter               That way, update -d will work correctly when working on
92634467Speter               a branch.
92734467Speter
92834467Speter	       We use TAG_UPDATE_DIR to undo the tag setting in
92934467Speter	       update_dirleave_proc.  If we did not do this, we would
93034467Speter	       not correctly handle a working directory with multiple
93134467Speter	       tags (and maybe we should prohibit such working
93234467Speter	       directories, but they work now and we shouldn't make
93334467Speter	       them stop working without more thought).  */
93434467Speter	    if ((tag == NULL && date == NULL) && ! aflag)
93534467Speter	    {
93634467Speter		ParseTag (&tag, &date, &nonbranch);
93734467Speter		if (tag != NULL || date != NULL)
93834467Speter		    tag_update_dir = xstrdup (update_dir);
93934467Speter	    }
94034467Speter
94117721Speter	    make_directory (dir);
94225839Speter	    Create_Admin (dir, update_dir, repository, tag, date,
94325839Speter			  /* This is a guess.  We will rewrite it later
94425839Speter			     via WriteTag.  */
94532788Speter			  0,
94666528Speter			  0,
94783496Sdillon			  pull_template);
94825839Speter	    rewrite_tag = 1;
94925839Speter	    nonbranch = 0;
95025839Speter	    Subdir_Register (entries, (char *) NULL, dir);
95117721Speter	}
95217721Speter    }
95317721Speter    /* Do we need to check noexec here? */
95417721Speter    else if (!pipeout)
95517721Speter    {
95617721Speter	char *cvsadmdir;
95717721Speter
95817721Speter	/* The directory exists.  Check to see if it has a CVS
95917721Speter	   subdirectory.  */
96017721Speter
96117721Speter	cvsadmdir = xmalloc (strlen (dir) + 80);
96217721Speter	strcpy (cvsadmdir, dir);
96317721Speter	strcat (cvsadmdir, "/");
96417721Speter	strcat (cvsadmdir, CVSADM);
96517721Speter
96617721Speter	if (!isdir (cvsadmdir))
96717721Speter	{
96817721Speter	    /* We cannot successfully recurse into a directory without a CVS
96917721Speter	       subdirectory.  Generally we will have already printed
97017721Speter	       "? foo".  */
97117721Speter	    free (cvsadmdir);
97217721Speter	    return R_SKIP_ALL;
97317721Speter	}
97417721Speter	free (cvsadmdir);
97517721Speter    }
97617721Speter
97717721Speter    /*
97817721Speter     * If we are building dirs and not going to stdout, we make sure there is
97917721Speter     * no static entries file and write the tag file as appropriate
98017721Speter     */
98117721Speter    if (!pipeout)
98217721Speter    {
98317721Speter	if (update_build_dirs)
98417721Speter	{
98525839Speter	    char *tmp;
98617721Speter
98725839Speter	    tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ENTSTAT) + 10);
98817721Speter	    (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT);
98917721Speter	    if (unlink_file (tmp) < 0 && ! existence_error (errno))
99017721Speter		error (1, errno, "cannot remove file %s", tmp);
99117721Speter#ifdef SERVER_SUPPORT
99217721Speter	    if (server_active)
99317721Speter		server_clear_entstat (update_dir, repository);
99417721Speter#endif
99525839Speter	    free (tmp);
99617721Speter	}
99717721Speter
99817721Speter	/* keep the CVS/Tag file current with the specified arguments */
99917721Speter	if (aflag || tag || date)
100017721Speter	{
100125839Speter	    WriteTag (dir, tag, date, 0, update_dir, repository);
100225839Speter	    rewrite_tag = 1;
100325839Speter	    nonbranch = 0;
100417721Speter	}
100517721Speter
100683496Sdillon	/* keep the CVS/Template file current */
100783496Sdillon	if (pull_template)
100883496Sdillon	{
100983496Sdillon	    WriteTemplate (dir, update_dir);
101083496Sdillon	}
101183496Sdillon
101217721Speter	/* initialize the ignore list for this directory */
101317721Speter	ignlist = getlist ();
101417721Speter    }
101517721Speter
101617721Speter    /* print the warm fuzzy message */
101717721Speter    if (!quiet)
101817721Speter	error (0, 0, "Updating %s", update_dir);
101917721Speter
1020130307Speter    return R_PROCESS;
102117721Speter}
102217721Speter
1023128269Speter
1024128269Speter
102517721Speter/*
102617721Speter * update_dirleave_proc () is called back by the recursion code upon leaving
102717721Speter * a directory.  It will prune empty directories if needed and will execute
102817721Speter * any appropriate update programs.
102917721Speter */
103017721Speter/* ARGSUSED */
103117721Speterstatic int
103225839Speterupdate_dirleave_proc (callerdat, dir, err, update_dir, entries)
103325839Speter    void *callerdat;
1034128269Speter    const char *dir;
103517721Speter    int err;
1036128269Speter    const char *update_dir;
103725839Speter    List *entries;
103817721Speter{
103966528Speter    /* Delete the ignore list if it hasn't already been done.  */
104066528Speter    if (ignlist)
104166528Speter	dellist (&ignlist);
104266528Speter
104334467Speter    /* If we set the tag or date for a new subdirectory in
104434467Speter       update_dirent_proc, and we're now done with that subdirectory,
104534467Speter       undo the tag/date setting.  Note that we know that the tag and
104634467Speter       date were both originally NULL in this case.  */
104734467Speter    if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0)
104834467Speter    {
104934467Speter	if (tag != NULL)
105034467Speter	{
105134467Speter	    free (tag);
105234467Speter	    tag = NULL;
105334467Speter	}
105434467Speter	if (date != NULL)
105534467Speter	{
105634467Speter	    free (date);
105734467Speter	    date = NULL;
105834467Speter	}
105934467Speter	nonbranch = 0;
106034467Speter	free (tag_update_dir);
106134467Speter	tag_update_dir = NULL;
106234467Speter    }
106334467Speter
106425839Speter    if (strchr (dir, '/') == NULL)
106517721Speter    {
106625839Speter	/* FIXME: chdir ("..") loses with symlinks.  */
106725839Speter	/* Prune empty dirs on the way out - if necessary */
106825839Speter	(void) CVS_CHDIR ("..");
106925839Speter	if (update_prune_dirs && isemptydir (dir, 0))
107025839Speter	{
107125839Speter	    /* I'm not sure the existence_error is actually possible (except
107225839Speter	       in cases where we really should print a message), but since
107325839Speter	       this code used to ignore all errors, I'll play it safe.	*/
107425839Speter	    if (unlink_file_dir (dir) < 0 && !existence_error (errno))
107525839Speter		error (0, errno, "cannot remove %s directory", dir);
107625839Speter	    Subdir_Deregister (entries, (char *) NULL, dir);
107725839Speter	}
107817721Speter    }
107917721Speter
1080130307Speter    return err;
108117721Speter}
108217721Speter
1083130307Speter
1084130307Speter
108525839Speterstatic int isremoved PROTO ((Node *, void *));
108625839Speter
108725839Speter/* Returns 1 if the file indicated by node has been removed.  */
108817721Speterstatic int
108925839Speterisremoved (node, closure)
109025839Speter    Node *node;
109125839Speter    void *closure;
109225839Speter{
1093128269Speter    Entnode *entdata = node->data;
109425839Speter
109525839Speter    /* If the first character of the version is a '-', the file has been
109625839Speter       removed. */
109725839Speter    return (entdata->version && entdata->version[0] == '-') ? 1 : 0;
109825839Speter}
109925839Speter
1100130307Speter
1101130307Speter
110225839Speter/* Returns 1 if the argument directory is completely empty, other than the
110325839Speter   existence of the CVS directory entry.  Zero otherwise.  If MIGHT_NOT_EXIST
110425839Speter   and the directory doesn't exist, then just return 0.  */
110525839Speterint
110625839Speterisemptydir (dir, might_not_exist)
1107128269Speter    const char *dir;
110825839Speter    int might_not_exist;
110917721Speter{
111017721Speter    DIR *dirp;
111117721Speter    struct dirent *dp;
111217721Speter
111325839Speter    if ((dirp = CVS_OPENDIR (dir)) == NULL)
111417721Speter    {
111525839Speter	if (might_not_exist && existence_error (errno))
111625839Speter	    return 0;
111725839Speter	error (0, errno, "cannot open directory %s for empty check", dir);
1118130307Speter	return 0;
111917721Speter    }
112025839Speter    errno = 0;
112181407Speter    while ((dp = CVS_READDIR (dirp)) != NULL)
112217721Speter    {
112325839Speter	if (strcmp (dp->d_name, ".") != 0
112425839Speter	    && strcmp (dp->d_name, "..") != 0)
112517721Speter	{
112625839Speter	    if (strcmp (dp->d_name, CVSADM) != 0)
112725839Speter	    {
112825839Speter		/* An entry other than the CVS directory.  The directory
112925839Speter		   is certainly not empty. */
113081407Speter		(void) CVS_CLOSEDIR (dirp);
1131130307Speter		return 0;
113225839Speter	    }
113325839Speter	    else
113425839Speter	    {
113525839Speter		/* The CVS directory entry.  We don't have to worry about
113625839Speter		   this unless the Entries file indicates that files have
113725839Speter		   been removed, but not committed, in this directory.
113825839Speter		   (Removing the directory would prevent people from
113925839Speter		   comitting the fact that they removed the files!) */
114025839Speter		List *l;
114125839Speter		int files_removed;
114225839Speter		struct saved_cwd cwd;
114325839Speter
114425839Speter		if (save_cwd (&cwd))
114525839Speter		    error_exit ();
114625839Speter
114725839Speter		if (CVS_CHDIR (dir) < 0)
114825839Speter		    error (1, errno, "cannot change directory to %s", dir);
114934467Speter		l = Entries_Open (0, NULL);
115025839Speter		files_removed = walklist (l, isremoved, 0);
115125839Speter		Entries_Close (l);
115225839Speter
115325839Speter		if (restore_cwd (&cwd, NULL))
115425839Speter		    error_exit ();
115525839Speter		free_cwd (&cwd);
115625839Speter
115725839Speter		if (files_removed != 0)
115825839Speter		{
115925839Speter		    /* There are files that have been removed, but not
116025839Speter		       committed!  Do not consider the directory empty. */
116181407Speter		    (void) CVS_CLOSEDIR (dirp);
1162130307Speter		    return 0;
116325839Speter		}
116425839Speter	    }
116517721Speter	}
116625839Speter	errno = 0;
116717721Speter    }
116825839Speter    if (errno != 0)
116925839Speter    {
117025839Speter	error (0, errno, "cannot read directory %s", dir);
117181407Speter	(void) CVS_CLOSEDIR (dirp);
1172130307Speter	return 0;
117325839Speter    }
117481407Speter    (void) CVS_CLOSEDIR (dirp);
1175130307Speter    return 1;
117617721Speter}
117717721Speter
1178130307Speter
1179130307Speter
118017721Speter/*
118117721Speter * scratch the Entries file entry associated with a file
118217721Speter */
118317721Speterstatic int
118481407Speterscratch_file (finfo, vers)
118525839Speter    struct file_info *finfo;
118681407Speter    Vers_TS *vers;
118717721Speter{
118825839Speter    history_write ('W', finfo->update_dir, "", finfo->file, finfo->repository);
118925839Speter    Scratch_Entry (finfo->entries, finfo->file);
119081407Speter#ifdef SERVER_SUPPORT
119181407Speter    if (server_active)
119281407Speter    {
119381407Speter	if (vers->ts_user == NULL)
119481407Speter	    server_scratch_entry_only ();
119581407Speter	server_updated (finfo, vers,
119681407Speter		SERVER_UPDATED, (mode_t) -1,
119781407Speter		(unsigned char *) NULL,
119881407Speter		(struct buffer *) NULL);
119981407Speter    }
120081407Speter#endif
120125839Speter    if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
120225839Speter	error (0, errno, "unable to remove %s", finfo->fullname);
1203175280Sobrien    else if (!server_active)
1204175280Sobrien    {
120581407Speter	/* skip this step when the server is running since
120681407Speter	 * server_updated should have handled it */
120781407Speter	/* keep the vers structure up to date in case we do a join
120881407Speter	 * - if there isn't a file, it can't very well have a version number, can it?
120981407Speter	 */
121081407Speter	if (vers->vn_user != NULL)
121181407Speter	{
121281407Speter	    free (vers->vn_user);
121381407Speter	    vers->vn_user = NULL;
121481407Speter	}
121581407Speter	if (vers->ts_user != NULL)
121681407Speter	{
121781407Speter	    free (vers->ts_user);
121881407Speter	    vers->ts_user = NULL;
121981407Speter	}
122081407Speter    }
1221130307Speter    return 0;
122217721Speter}
122317721Speter
1224130307Speter
1225130307Speter
122617721Speter/*
122725839Speter * Check out a file.
122817721Speter */
122917721Speterstatic int
123034467Spetercheckout_file (finfo, vers_ts, adding, merging, update_server)
123125839Speter    struct file_info *finfo;
123217721Speter    Vers_TS *vers_ts;
123325839Speter    int adding;
123434467Speter    int merging;
123534467Speter    int update_server;
123617721Speter{
123725839Speter    char *backup;
123817721Speter    int set_time, retval = 0;
123917721Speter    int status;
124017721Speter    int file_is_dead;
124134467Speter    struct buffer *revbuf;
124217721Speter
124332788Speter    backup = NULL;
124434467Speter    revbuf = NULL;
124532788Speter
124634467Speter    /* Don't screw with backup files if we're going to stdout, or if
124734467Speter       we are the server.  */
1248175280Sobrien    if (!pipeout && !server_active)
124917721Speter    {
125025839Speter	backup = xmalloc (strlen (finfo->file)
125125839Speter			  + sizeof (CVSADM)
125225839Speter			  + sizeof (CVSPREFIX)
125325839Speter			  + 10);
125425839Speter	(void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
125525839Speter	if (isfile (finfo->file))
125625839Speter	    rename_file (finfo->file, backup);
125717721Speter	else
125834467Speter	{
125925839Speter	    /* If -f/-t wrappers are being used to wrap up a directory,
126025839Speter	       then backup might be a directory instead of just a file.  */
126132788Speter	    if (unlink_file_dir (backup) < 0)
126232788Speter	    {
126332788Speter		/* Not sure if the existence_error check is needed here.  */
126432788Speter		if (!existence_error (errno))
126532788Speter		    /* FIXME: should include update_dir in message.  */
126632788Speter		    error (0, errno, "error removing %s", backup);
126732788Speter	    }
126834467Speter	    free (backup);
126934467Speter	    backup = NULL;
127034467Speter	}
127117721Speter    }
127217721Speter
127317721Speter    file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
127417721Speter
127517721Speter    if (!file_is_dead)
127617721Speter    {
127717721Speter	/*
127817721Speter	 * if we are checking out to stdout, print a nice message to
127917721Speter	 * stderr, and add the -p flag to the command */
128017721Speter	if (pipeout)
128117721Speter	{
128217721Speter	    if (!quiet)
128317721Speter	    {
128425839Speter		cvs_outerr ("\
128525839Speter===================================================================\n\
128625839SpeterChecking out ", 0);
128725839Speter		cvs_outerr (finfo->fullname, 0);
128825839Speter		cvs_outerr ("\n\
128925839SpeterRCS:  ", 0);
129025839Speter		cvs_outerr (vers_ts->srcfile->path, 0);
129125839Speter		cvs_outerr ("\n\
129225839SpeterVERS: ", 0);
129325839Speter		cvs_outerr (vers_ts->vn_rcs, 0);
129425839Speter		cvs_outerr ("\n***************\n", 0);
129517721Speter	    }
129617721Speter	}
129717721Speter
129834467Speter#ifdef SERVER_SUPPORT
129934467Speter	if (update_server
130034467Speter	    && server_active
130134467Speter	    && ! pipeout
130234467Speter	    && ! file_gzip_level
130334467Speter	    && ! joining ()
130434467Speter	    && ! wrap_name_has (finfo->file, WRAP_FROMCVS))
130534467Speter	{
130634467Speter	    revbuf = buf_nonio_initialize ((BUFMEMERRPROC) NULL);
130734467Speter	    status = RCS_checkout (vers_ts->srcfile, (char *) NULL,
1308128269Speter				   vers_ts->vn_rcs, vers_ts->tag,
130934467Speter				   vers_ts->options, RUN_TTY,
131034467Speter				   checkout_to_buffer, revbuf);
131134467Speter	}
131234467Speter	else
131334467Speter#endif
131434467Speter	    status = RCS_checkout (vers_ts->srcfile,
131534467Speter				   pipeout ? NULL : finfo->file,
1316128269Speter				   vers_ts->vn_rcs, vers_ts->tag,
131734467Speter				   vers_ts->options, RUN_TTY,
131834467Speter				   (RCSCHECKOUTPROC) NULL, (void *) NULL);
131917721Speter    }
132017721Speter    if (file_is_dead || status == 0)
132117721Speter    {
132234467Speter	mode_t mode;
132334467Speter
132434467Speter	mode = (mode_t) -1;
132534467Speter
132617721Speter	if (!pipeout)
132717721Speter	{
132817721Speter	    Vers_TS *xvers_ts;
132917721Speter
133054431Speter	    if (revbuf != NULL && !noexec)
133134467Speter	    {
133234467Speter		struct stat sb;
133334467Speter
133454431Speter		/* FIXME: We should have RCS_checkout return the mode.
133554431Speter		   That would also fix the kludge with noexec, above, which
133654431Speter		   is here only because noexec doesn't write srcfile->path
133754431Speter		   for us to stat.  */
133834467Speter		if (stat (vers_ts->srcfile->path, &sb) < 0)
1339128269Speter		{
1340175280Sobrien#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
1341128269Speter		    buf_free (revbuf);
1342175280Sobrien#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */
134334467Speter		    error (1, errno, "cannot stat %s",
134434467Speter			   vers_ts->srcfile->path);
1345128269Speter		}
134634467Speter		mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH);
134734467Speter	    }
134834467Speter
134932788Speter	    if (cvswrite
135017721Speter		&& !file_is_dead
135125839Speter		&& !fileattr_get (finfo->file, "_watched"))
135234467Speter	    {
135334467Speter		if (revbuf == NULL)
135434467Speter		    xchmod (finfo->file, 1);
135534467Speter		else
135634467Speter		{
135734467Speter		    /* We know that we are the server here, so
135834467Speter                       although xchmod checks umask, we don't bother.  */
135934467Speter		    mode |= (((mode & S_IRUSR) ? S_IWUSR : 0)
136034467Speter			     | ((mode & S_IRGRP) ? S_IWGRP : 0)
136134467Speter			     | ((mode & S_IROTH) ? S_IWOTH : 0));
136234467Speter		}
136334467Speter	    }
136417721Speter
136517721Speter	    {
136617721Speter		/* A newly checked out file is never under the spell
136717721Speter		   of "cvs edit".  If we think we were editing it
136817721Speter		   from a previous life, clean up.  Would be better to
136917721Speter		   check for same the working directory instead of
137017721Speter		   same user, but that is hairy.  */
137117721Speter
137217721Speter		struct addremove_args args;
137317721Speter
137425839Speter		editor_set (finfo->file, getcaller (), NULL);
137517721Speter
137617721Speter		memset (&args, 0, sizeof args);
137717721Speter		args.remove_temp = 1;
137825839Speter		watch_modify_watchers (finfo->file, &args);
137917721Speter	    }
138017721Speter
138117721Speter	    /* set the time from the RCS file iff it was unknown before */
138232788Speter	    set_time =
138332788Speter		(!noexec
138432788Speter		 && (vers_ts->vn_user == NULL ||
138532788Speter		     strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
138632788Speter		 && !file_is_dead);
138717721Speter
138825839Speter	    wrap_fromcvs_process_file (finfo->file);
138917721Speter
139025839Speter	    xvers_ts = Version_TS (finfo, options, tag, date,
139125839Speter				   force_tag_match, set_time);
139217721Speter	    if (strcmp (xvers_ts->options, "-V4") == 0)
139317721Speter		xvers_ts->options[0] = '\0';
139417721Speter
139534467Speter	    if (revbuf != NULL)
139634467Speter	    {
139734467Speter		/* If we stored the file data into a buffer, then we
139834467Speter                   didn't create a file at all, so xvers_ts->ts_user
139934467Speter                   is wrong.  The correct value is to have it be the
140034467Speter                   same as xvers_ts->ts_rcs, meaning that the working
140134467Speter                   file is unchanged from the RCS file.
140234467Speter
140334467Speter		   FIXME: We should tell Version_TS not to waste time
140434467Speter		   statting the nonexistent file.
140534467Speter
140634467Speter		   FIXME: Actually, I don't think the ts_user value
140734467Speter		   matters at all here.  The only use I know of is
140834467Speter		   that it is printed in a trace message by
140934467Speter		   Server_Register.  */
141034467Speter
141134467Speter		if (xvers_ts->ts_user != NULL)
141234467Speter		    free (xvers_ts->ts_user);
141334467Speter		xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs);
141434467Speter	    }
141534467Speter
141617721Speter	    (void) time (&last_register_time);
141717721Speter
141817721Speter	    if (file_is_dead)
141917721Speter	    {
142017721Speter		if (xvers_ts->vn_user != NULL)
142117721Speter		{
142225839Speter		    error (0, 0,
142325839Speter			   "warning: %s is not (any longer) pertinent",
142434467Speter 			   finfo->fullname);
142517721Speter		}
142625839Speter		Scratch_Entry (finfo->entries, finfo->file);
142725839Speter#ifdef SERVER_SUPPORT
142825839Speter		if (server_active && xvers_ts->ts_user == NULL)
142925839Speter		    server_scratch_entry_only ();
143025839Speter#endif
143125839Speter		/* FIXME: Rather than always unlink'ing, and ignoring the
143225839Speter		   existence_error, we should do the unlink only if
143325839Speter		   vers_ts->ts_user is non-NULL.  Then there would be no
143425839Speter		   need to ignore an existence_error (for example, if the
143525839Speter		   user removes the file while we are running).  */
143625839Speter		if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
143717721Speter		{
143825839Speter		    error (0, errno, "cannot remove %s", finfo->fullname);
143917721Speter		}
144017721Speter	    }
144117721Speter	    else
144225839Speter		Register (finfo->entries, finfo->file,
144325839Speter			  adding ? "0" : xvers_ts->vn_rcs,
144425839Speter			  xvers_ts->ts_user, xvers_ts->options,
144525839Speter			  xvers_ts->tag, xvers_ts->date,
144625839Speter			  (char *)0); /* Clear conflict flag on fresh checkout */
144717721Speter
144817721Speter	    /* fix up the vers structure, in case it is used by join */
144917721Speter	    if (join_rev1)
145017721Speter	    {
1451177401Sobrien		/* FIXME: It seems like we should be preserving ts_user
1452177401Sobrien		 * & ts_rcs here, but setting them causes problems in
1453177401Sobrien		 * join_file().
1454177401Sobrien		 */
145517721Speter		if (vers_ts->vn_user != NULL)
145617721Speter		    free (vers_ts->vn_user);
145717721Speter		if (vers_ts->vn_rcs != NULL)
145817721Speter		    free (vers_ts->vn_rcs);
145917721Speter		vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs);
146017721Speter		vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs);
146117721Speter	    }
146217721Speter
146317721Speter	    /* If this is really Update and not Checkout, recode history */
1464128269Speter	    if (strcmp (cvs_cmd_name, "update") == 0)
146525839Speter		history_write ('U', finfo->update_dir, xvers_ts->vn_rcs, finfo->file,
146625839Speter			       finfo->repository);
146717721Speter
146817721Speter	    freevers_ts (&xvers_ts);
146917721Speter
147017721Speter	    if (!really_quiet && !file_is_dead)
147117721Speter	    {
147232788Speter		write_letter (finfo, 'U');
147317721Speter	    }
147417721Speter	}
147534467Speter
147634467Speter#ifdef SERVER_SUPPORT
147734467Speter	if (update_server && server_active)
147834467Speter	    server_updated (finfo, vers_ts,
147934467Speter			    merging ? SERVER_MERGED : SERVER_UPDATED,
148034467Speter			    mode, (unsigned char *) NULL, revbuf);
148134467Speter#endif
148217721Speter    }
148317721Speter    else
148417721Speter    {
148534467Speter	if (backup != NULL)
148634467Speter	{
148725839Speter	    rename_file (backup, finfo->file);
148834467Speter	    free (backup);
148934467Speter	    backup = NULL;
149034467Speter	}
149117721Speter
149234467Speter	error (0, 0, "could not check out %s", finfo->fullname);
149317721Speter
149434467Speter	retval = status;
149517721Speter    }
149617721Speter
149734467Speter    if (backup != NULL)
149825839Speter    {
149925839Speter	/* If -f/-t wrappers are being used to wrap up a directory,
150025839Speter	   then backup might be a directory instead of just a file.  */
150132788Speter	if (unlink_file_dir (backup) < 0)
150232788Speter	{
150332788Speter	    /* Not sure if the existence_error check is needed here.  */
150432788Speter	    if (!existence_error (errno))
150532788Speter		/* FIXME: should include update_dir in message.  */
150632788Speter		error (0, errno, "error removing %s", backup);
150732788Speter	}
150825839Speter	free (backup);
150925839Speter    }
151017721Speter
1511175280Sobrien#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
1512128269Speter    if (revbuf != NULL)
1513128269Speter	buf_free (revbuf);
1514175280Sobrien#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */
1515130307Speter    return retval;
151617721Speter}
151717721Speter
1518130307Speter
1519130307Speter
152017721Speter#ifdef SERVER_SUPPORT
152125839Speter
152234467Speter/* This function is used to write data from a file being checked out
152334467Speter   into a buffer.  */
152434467Speter
152534467Speterstatic void
152634467Spetercheckout_to_buffer (callerdat, data, len)
152734467Speter     void *callerdat;
152834467Speter     const char *data;
152934467Speter     size_t len;
153034467Speter{
153134467Speter    struct buffer *buf = (struct buffer *) callerdat;
153234467Speter
153334467Speter    buf_output (buf, data, len);
153434467Speter}
153534467Speter
153634467Speter#endif /* SERVER_SUPPORT */
153734467Speter
153834467Speter#ifdef SERVER_SUPPORT
153934467Speter
154025839Speter/* This structure is used to pass information between patch_file and
154125839Speter   patch_file_write.  */
154225839Speter
154325839Speterstruct patch_file_data
154425839Speter{
154525839Speter    /* File name, for error messages.  */
154625839Speter    const char *filename;
154725839Speter    /* File to which to write.  */
154825839Speter    FILE *fp;
154925839Speter    /* Whether to compute the MD5 checksum.  */
155025839Speter    int compute_checksum;
155125839Speter    /* Data structure for computing the MD5 checksum.  */
155254431Speter    struct cvs_MD5Context context;
155325839Speter    /* Set if the file has a final newline.  */
155425839Speter    int final_nl;
155525839Speter};
155625839Speter
155725839Speter/* Patch a file.  Runs diff.  This is only done when running as the
155817721Speter * server.  The hope is that the diff will be smaller than the file
155917721Speter * itself.
156017721Speter */
156117721Speterstatic int
156225839Speterpatch_file (finfo, vers_ts, docheckout, file_info, checksum)
156325839Speter    struct file_info *finfo;
156417721Speter    Vers_TS *vers_ts;
156517721Speter    int *docheckout;
156617721Speter    struct stat *file_info;
156717721Speter    unsigned char *checksum;
156817721Speter{
156925839Speter    char *backup;
157025839Speter    char *file1;
157125839Speter    char *file2;
157217721Speter    int retval = 0;
157317721Speter    int retcode = 0;
157417721Speter    int fail;
157517721Speter    FILE *e;
157625839Speter    struct patch_file_data data;
157717721Speter
157817721Speter    *docheckout = 0;
157917721Speter
158025839Speter    if (noexec || pipeout || joining ())
158117721Speter    {
158217721Speter	*docheckout = 1;
158317721Speter	return 0;
158417721Speter    }
158517721Speter
158625839Speter    /* If this file has been marked as being binary, then never send a
158725839Speter       patch.  */
158825839Speter    if (strcmp (vers_ts->options, "-kb") == 0)
158925839Speter    {
159025839Speter	*docheckout = 1;
159125839Speter	return 0;
159225839Speter    }
159325839Speter
159432788Speter    /* First check that the first revision exists.  If it has been nuked
159532788Speter       by cvs admin -o, then just fall back to checking out entire
159632788Speter       revisions.  In some sense maybe we don't have to do this; after
159732788Speter       all cvs.texinfo says "Make sure that no-one has checked out a
159832788Speter       copy of the revision you outdate" but then again, that advice
159932788Speter       doesn't really make complete sense, because "cvs admin" operates
160032788Speter       on a working directory and so _someone_ will almost always have
160132788Speter       _some_ revision checked out.  */
160232788Speter    {
160332788Speter	char *rev;
160432788Speter
160532788Speter	rev = RCS_gettag (finfo->rcs, vers_ts->vn_user, 1, NULL);
160632788Speter	if (rev == NULL)
160732788Speter	{
160832788Speter	    *docheckout = 1;
160932788Speter	    return 0;
161032788Speter	}
161132788Speter	else
161232788Speter	    free (rev);
161332788Speter    }
161432788Speter
161534467Speter    /* If the revision is dead, let checkout_file handle it rather
161634467Speter       than duplicating the processing here.  */
161734467Speter    if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs))
161834467Speter    {
161934467Speter	*docheckout = 1;
162034467Speter	return 0;
162134467Speter    }
162234467Speter
162325839Speter    backup = xmalloc (strlen (finfo->file)
162425839Speter		      + sizeof (CVSADM)
162525839Speter		      + sizeof (CVSPREFIX)
162625839Speter		      + 10);
162725839Speter    (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
162825839Speter    if (isfile (finfo->file))
162925839Speter        rename_file (finfo->file, backup);
163017721Speter    else
163154431Speter    {
163254431Speter	if (unlink_file (backup) < 0
163354431Speter	    && !existence_error (errno))
163454431Speter	    error (0, errno, "cannot remove %s", backup);
163554431Speter    }
163617721Speter
163725839Speter    file1 = xmalloc (strlen (finfo->file)
163825839Speter		     + sizeof (CVSADM)
163925839Speter		     + sizeof (CVSPREFIX)
164025839Speter		     + 10);
164125839Speter    (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, finfo->file);
164225839Speter    file2 = xmalloc (strlen (finfo->file)
164325839Speter		     + sizeof (CVSADM)
164425839Speter		     + sizeof (CVSPREFIX)
164525839Speter		     + 10);
164625839Speter    (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, finfo->file);
164725839Speter
164817721Speter    fail = 0;
164917721Speter
165017721Speter    /* We need to check out both revisions first, to see if either one
165117721Speter       has a trailing newline.  Because of this, we don't use rcsdiff,
165217721Speter       but just use diff.  */
165317721Speter
165425839Speter    e = CVS_FOPEN (file1, "w");
165525839Speter    if (e == NULL)
165625839Speter	error (1, errno, "cannot open %s", file1);
165725839Speter
165825839Speter    data.filename = file1;
165925839Speter    data.fp = e;
166025839Speter    data.final_nl = 0;
166125839Speter    data.compute_checksum = 0;
166225839Speter
1663177401Sobrien    /* Duplicating the client working file, so use the original sticky options.
1664128269Speter     */
166525839Speter    retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
1666177401Sobrien			    vers_ts->vn_user, vers_ts->entdata->tag,
1667177401Sobrien			    vers_ts->entdata->options, RUN_TTY,
166825839Speter			    patch_file_write, (void *) &data);
166925839Speter
167025839Speter    if (fclose (e) < 0)
167125839Speter	error (1, errno, "cannot close %s", file1);
167225839Speter
167325839Speter    if (retcode != 0 || ! data.final_nl)
167425839Speter	fail = 1;
167525839Speter
167617721Speter    if (! fail)
167717721Speter    {
167825839Speter	e = CVS_FOPEN (file2, "w");
167925839Speter	if (e == NULL)
168025839Speter	    error (1, errno, "cannot open %s", file2);
168117721Speter
168225839Speter	data.filename = file2;
168325839Speter	data.fp = e;
168425839Speter	data.final_nl = 0;
168525839Speter	data.compute_checksum = 1;
168654431Speter	cvs_MD5Init (&data.context);
168717721Speter
168825839Speter	retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
1689128269Speter				vers_ts->vn_rcs, vers_ts->tag,
169025839Speter				vers_ts->options, RUN_TTY,
169125839Speter				patch_file_write, (void *) &data);
169217721Speter
169325839Speter	if (fclose (e) < 0)
169425839Speter	    error (1, errno, "cannot close %s", file2);
169517721Speter
169625839Speter	if (retcode != 0 || ! data.final_nl)
169725839Speter	    fail = 1;
169825839Speter	else
169954431Speter	    cvs_MD5Final (checksum, &data.context);
170017721Speter    }
170117721Speter
170217721Speter    retcode = 0;
170317721Speter    if (! fail)
170417721Speter    {
1705175280Sobrien	int dargc = 0;
1706175280Sobrien	size_t darg_allocated = 0;
1707175280Sobrien	char **dargv = NULL;
170817721Speter
170925839Speter	/* If the client does not support the Rcs-diff command, we
171025839Speter           send a context diff, and the client must invoke patch.
171125839Speter           That approach was problematical for various reasons.  The
171225839Speter           new approach only requires running diff in the server; the
171325839Speter           client can handle everything without invoking an external
171425839Speter           program.  */
1715175280Sobrien	if (!rcs_diff_patches)
171644856Speter	    /* We use -c, not -u, because that is what CVS has
171744856Speter	       traditionally used.  Kind of a moot point, now that
171844856Speter	       Rcs-diff is preferred, so there is no point in making
171944856Speter	       the compatibility issues worse.  */
1720175280Sobrien	    run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c");
172125839Speter	else
172232788Speter	    /* Now that diff is librarified, we could be passing -a if
172332788Speter	       we wanted to.  However, it is unclear to me whether we
172432788Speter	       would want to.  Does diff -a, in any significant
172532788Speter	       percentage of cases, produce patches which are smaller
172632788Speter	       than the files it is patching?  I guess maybe text
172732788Speter	       files with character sets which diff regards as
172832788Speter	       'binary'.  Conversely, do they tend to be much larger
172932788Speter	       in the bad cases?  This needs some more
173032788Speter	       thought/investigation, I suspect.  */
1731175280Sobrien	    run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
1732175280Sobrien	retcode = diff_exec (file1, file2, NULL, NULL, dargc, dargv,
1733175280Sobrien			     finfo->file);
1734175280Sobrien	run_arg_free_p (dargc, dargv);
1735175280Sobrien	free (dargv);
173632788Speter
173717721Speter	/* A retcode of 0 means no differences.  1 means some differences.  */
173832788Speter	if (retcode != 0
173917721Speter	    && retcode != 1)
174017721Speter	{
174117721Speter	    fail = 1;
174217721Speter	}
1743107487Speter    }
1744107487Speter
1745107487Speter    if (! fail)
1746107487Speter    {
1747107487Speter	struct stat file2_info;
1748107487Speter
1749107487Speter	/* Check to make sure the patch is really shorter */
1750107487Speter	if (CVS_STAT (file2, &file2_info) < 0)
1751107487Speter	    error (1, errno, "could not stat %s", file2);
1752107487Speter	if (CVS_STAT (finfo->file, file_info) < 0)
1753107487Speter	    error (1, errno, "could not stat %s", finfo->file);
1754107487Speter	if (file2_info.st_size <= file_info->st_size)
1755107487Speter	    fail = 1;
1756107487Speter    }
1757107487Speter
1758107487Speter    if (! fail)
1759107487Speter    {
1760102843Speter# define BINARY "Binary"
1761107487Speter	char buf[sizeof BINARY];
1762107487Speter	unsigned int c;
176317721Speter
1764107487Speter	/* Check the diff output to make sure patch will be handle it.  */
1765107487Speter	e = CVS_FOPEN (finfo->file, "r");
1766107487Speter	if (e == NULL)
1767107487Speter	    error (1, errno, "could not open diff output file %s",
1768107487Speter		   finfo->fullname);
1769107487Speter	c = fread (buf, 1, sizeof BINARY - 1, e);
1770107487Speter	buf[c] = '\0';
1771107487Speter	if (strcmp (buf, BINARY) == 0)
1772107487Speter	{
1773107487Speter	    /* These are binary files.  We could use diff -a, but
1774107487Speter	       patch can't handle that.  */
1775107487Speter	    fail = 1;
177617721Speter	}
1777107487Speter	fclose (e);
177817721Speter    }
177917721Speter
178017721Speter    if (! fail)
178117721Speter    {
178217721Speter        Vers_TS *xvers_ts;
178317721Speter
1784107487Speter	/* Stat the original RCS file, and then adjust it the way
1785107487Speter	   that RCS_checkout would.  FIXME: This is an abstraction
1786107487Speter	   violation.  */
1787107487Speter	if (CVS_STAT (vers_ts->srcfile->path, file_info) < 0)
1788107487Speter	    error (1, errno, "could not stat %s", vers_ts->srcfile->path);
1789107487Speter	if (chmod (finfo->file,
1790107487Speter		   file_info->st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH))
1791107487Speter	    < 0)
1792107487Speter	    error (0, errno, "cannot change mode of file %s", finfo->file);
1793107487Speter	if (cvswrite
1794107487Speter	    && !fileattr_get (finfo->file, "_watched"))
1795107487Speter	    xchmod (finfo->file, 1);
1796107487Speter
179717721Speter        /* This stuff is just copied blindly from checkout_file.  I
179817721Speter	   don't really know what it does.  */
179925839Speter        xvers_ts = Version_TS (finfo, options, tag, date,
180025839Speter			       force_tag_match, 0);
180117721Speter	if (strcmp (xvers_ts->options, "-V4") == 0)
180217721Speter	    xvers_ts->options[0] = '\0';
180317721Speter
180425839Speter	Register (finfo->entries, finfo->file, xvers_ts->vn_rcs,
180517721Speter		  xvers_ts->ts_user, xvers_ts->options,
180617721Speter		  xvers_ts->tag, xvers_ts->date, NULL);
180717721Speter
180825839Speter	if (CVS_STAT (finfo->file, file_info) < 0)
180925839Speter	    error (1, errno, "could not stat %s", finfo->file);
181017721Speter
1811128269Speter	/* If this is really Update and not Checkout, record history.  */
1812128269Speter	if (strcmp (cvs_cmd_name, "update") == 0)
1813128269Speter	    history_write ('P', finfo->update_dir, xvers_ts->vn_rcs,
1814128269Speter	                   finfo->file, finfo->repository);
181517721Speter
181617721Speter	freevers_ts (&xvers_ts);
181717721Speter
181817721Speter	if (!really_quiet)
181917721Speter	{
182032788Speter	    write_letter (finfo, 'P');
182117721Speter	}
182217721Speter    }
182317721Speter    else
182417721Speter    {
182517721Speter	int old_errno = errno;		/* save errno value over the rename */
182617721Speter
182717721Speter	if (isfile (backup))
182825839Speter	    rename_file (backup, finfo->file);
182917721Speter
183017721Speter	if (retcode != 0 && retcode != 1)
183117721Speter	    error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
183225839Speter		   "could not diff %s", finfo->fullname);
183317721Speter
183417721Speter	*docheckout = 1;
183517721Speter	retval = retcode;
183617721Speter    }
183717721Speter
183854431Speter    if (unlink_file (backup) < 0
183954431Speter	&& !existence_error (errno))
184054431Speter	error (0, errno, "cannot remove %s", backup);
184154431Speter    if (unlink_file (file1) < 0
184254431Speter	&& !existence_error (errno))
184354431Speter	error (0, errno, "cannot remove %s", file1);
184454431Speter    if (unlink_file (file2) < 0
184554431Speter	&& !existence_error (errno))
184654431Speter	error (0, errno, "cannot remove %s", file2);
184717721Speter
184825839Speter    free (backup);
184925839Speter    free (file1);
185025839Speter    free (file2);
1851130307Speter    return retval;
185217721Speter}
185317721Speter
1854130307Speter
1855130307Speter
185625839Speter/* Write data to a file.  Record whether the last byte written was a
185725839Speter   newline.  Optionally compute a checksum.  This is called by
185825839Speter   patch_file via RCS_checkout.  */
185925839Speter
186025839Speterstatic void
186125839Speterpatch_file_write (callerdat, buffer, len)
186225839Speter     void *callerdat;
186325839Speter     const char *buffer;
186425839Speter     size_t len;
186525839Speter{
186625839Speter    struct patch_file_data *data = (struct patch_file_data *) callerdat;
186725839Speter
186825839Speter    if (fwrite (buffer, 1, len, data->fp) != len)
186925839Speter	error (1, errno, "cannot write %s", data->filename);
187025839Speter
187125839Speter    data->final_nl = (buffer[len - 1] == '\n');
187225839Speter
187325839Speter    if (data->compute_checksum)
187454431Speter	cvs_MD5Update (&data->context, (unsigned char *) buffer, len);
187525839Speter}
187625839Speter
187725839Speter#endif /* SERVER_SUPPORT */
187825839Speter
187917721Speter/*
188017721Speter * Several of the types we process only print a bit of information consisting
188117721Speter * of a single letter and the name.
188217721Speter */
1883128269Spetervoid
188432788Speterwrite_letter (finfo, letter)
188532788Speter    struct file_info *finfo;
188617721Speter    int letter;
188717721Speter{
188817721Speter    if (!really_quiet)
188917721Speter    {
189032788Speter	char *tag = NULL;
189132788Speter	/* Big enough for "+updated" or any of its ilk.  */
189232788Speter	char buf[80];
189332788Speter
189432788Speter	switch (letter)
189532788Speter	{
189632788Speter	    case 'U':
189732788Speter		tag = "updated";
189832788Speter		break;
189932788Speter	    default:
190032788Speter		/* We don't yet support tagged output except for "U".  */
190132788Speter		break;
190232788Speter	}
190332788Speter
190432788Speter	if (tag != NULL)
190532788Speter	{
190632788Speter	    sprintf (buf, "+%s", tag);
190732788Speter	    cvs_output_tagged (buf, NULL);
190832788Speter	}
190917721Speter	buf[0] = letter;
191017721Speter	buf[1] = ' ';
191132788Speter	buf[2] = '\0';
191232788Speter	cvs_output_tagged ("text", buf);
191332788Speter	cvs_output_tagged ("fname", finfo->fullname);
191432788Speter	cvs_output_tagged ("newline", NULL);
191532788Speter	if (tag != NULL)
191617721Speter	{
191732788Speter	    sprintf (buf, "-%s", tag);
191832788Speter	    cvs_output_tagged (buf, NULL);
191917721Speter	}
192017721Speter    }
192132788Speter    return;
192217721Speter}
192317721Speter
1924128269Speter
1925128269Speter
1926175280Sobrien/* Reregister a file after a merge.  */
1927175280Sobrienstatic void
1928175280SobrienRegisterMerge PROTO((struct file_info *finfo, Vers_TS *vers,
1929175280Sobrien		     const char *backup, int has_conflicts));
1930175280Sobrienstatic void
1931175280SobrienRegisterMerge (finfo, vers, backup, has_conflicts)
1932175280Sobrien    struct file_info *finfo;
1933175280Sobrien    Vers_TS *vers;
1934175280Sobrien    const char *backup;
1935175280Sobrien    int has_conflicts;
1936175280Sobrien{
1937175280Sobrien    /* This file is the result of a merge, which means that it has
1938175280Sobrien       been modified.  We use a special timestamp string which will
1939175280Sobrien       not compare equal to any actual timestamp.  */
1940175280Sobrien    char *cp = NULL;
1941175280Sobrien
1942175280Sobrien    if (has_conflicts)
1943175280Sobrien    {
1944175280Sobrien	time (&last_register_time);
1945175280Sobrien	cp = time_stamp (finfo->file);
1946175280Sobrien    }
1947175280Sobrien    Register (finfo->entries, finfo->file, vers->vn_rcs ? vers->vn_rcs : "0",
1948175280Sobrien	      "Result of merge", vers->options, vers->tag, vers->date, cp);
1949175280Sobrien    if (cp)
1950175280Sobrien	free (cp);
1951175280Sobrien
1952175280Sobrien#ifdef SERVER_SUPPORT
1953175280Sobrien    /* Send the new contents of the file before the message.  If we
1954175280Sobrien       wanted to be totally correct, we would have the client write
1955175280Sobrien       the message only after the file has safely been written.  */
1956175280Sobrien    if (server_active)
1957175280Sobrien    {
1958175280Sobrien        server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
1959175280Sobrien			  backup);
1960175280Sobrien	server_updated (finfo, vers, SERVER_MERGED, (mode_t) -1, NULL, NULL);
1961175280Sobrien    }
1962175280Sobrien#endif
1963175280Sobrien}
1964175280Sobrien
1965175280Sobrien
1966175280Sobrien
196717721Speter/*
196817721Speter * Do all the magic associated with a file which needs to be merged
196917721Speter */
197017721Speterstatic int
197125839Spetermerge_file (finfo, vers)
197225839Speter    struct file_info *finfo;
197317721Speter    Vers_TS *vers;
197417721Speter{
197525839Speter    char *backup;
197617721Speter    int status;
197717721Speter    int retcode = 0;
197825839Speter    int retval;
197917721Speter
1980175280Sobrien    assert (vers->vn_user);
1981175280Sobrien
198217721Speter    /*
198317721Speter     * The users currently modified file is moved to a backup file name
198417721Speter     * ".#filename.version", so that it will stay around for a few days
198517721Speter     * before being automatically removed by some cron daemon.  The "version"
198617721Speter     * is the version of the file that the user was most up-to-date with
198717721Speter     * before the merge.
198817721Speter     */
198925839Speter    backup = xmalloc (strlen (finfo->file)
199025839Speter		      + strlen (vers->vn_user)
199125839Speter		      + sizeof (BAKPREFIX)
199225839Speter		      + 10);
199325839Speter    (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
199417721Speter
199554431Speter    if (unlink_file (backup) && !existence_error (errno))
199654431Speter	error (0, errno, "unable to remove %s", backup);
199725839Speter    copy_file (finfo->file, backup);
199825839Speter    xchmod (finfo->file, 1);
199917721Speter
200032788Speter    if (strcmp (vers->options, "-kb") == 0
200134467Speter	|| wrap_merge_is_copy (finfo->file)
200234467Speter	|| special_file_mismatch (finfo, NULL, vers->vn_rcs))
200325839Speter    {
200434467Speter	/* For binary files, a merge is always a conflict.  Same for
200534467Speter	   files whose permissions or linkage do not match.  We give the
200625839Speter	   user the two files, and let them resolve it.  It is possible
200725839Speter	   that we should require a "touch foo" or similar step before
200825839Speter	   we allow a checkin.  */
200934467Speter
201034467Speter	/* TODO: it may not always be necessary to regard a permission
201134467Speter	   mismatch as a conflict.  The working file and the RCS file
201234467Speter	   have a common ancestor `A'; if the working file's permissions
201334467Speter	   match A's, then it's probably safe to overwrite them with the
201434467Speter	   RCS permissions.  Only if the working file, the RCS file, and
201534467Speter	   A all disagree should this be considered a conflict.  But more
201634467Speter	   thought needs to go into this, and in the meantime it is safe
201734467Speter	   to treat any such mismatch as an automatic conflict. -twp */
201834467Speter
2019175280Sobrien	retcode = RCS_checkout (finfo->rcs, finfo->file,
2020175280Sobrien				vers->vn_rcs, vers->tag,
2021175280Sobrien				vers->options, NULL, NULL, NULL);
2022175280Sobrien	if (retcode)
2023175280Sobrien	{
2024175280Sobrien	    error (0, 0, "failed to check out `%s' file", finfo->fullname);
2025175280Sobrien	    error (0, 0, "restoring `%s' from backup file `%s'",
2026175280Sobrien		   finfo->fullname, backup);
2027175280Sobrien	    rename_file (backup, finfo->file);
2028175280Sobrien	    retval = 1;
2029175280Sobrien	    goto out;
2030175280Sobrien	}
2031175280Sobrien	xchmod (finfo->file, 1);
203234467Speter
2033175280Sobrien	RegisterMerge (finfo, vers, backup, 1);
203434467Speter
203532788Speter	/* Is there a better term than "nonmergeable file"?  What we
203632788Speter	   really mean is, not something that CVS cannot or does not
203732788Speter	   want to merge (there might be an external manual or
203832788Speter	   automatic merge process).  */
203932788Speter	error (0, 0, "nonmergeable file needs merge");
204025839Speter	error (0, 0, "revision %s from repository is now in %s",
204125839Speter	       vers->vn_rcs, finfo->fullname);
204225839Speter	error (0, 0, "file from working directory is now in %s", backup);
204332788Speter	write_letter (finfo, 'C');
204425839Speter
204525839Speter	history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
204625839Speter		       finfo->repository);
204725839Speter	retval = 0;
204825839Speter	goto out;
204925839Speter    }
205025839Speter
2051128269Speter    status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
2052128269Speter		        vers->options, vers->vn_user, vers->vn_rcs);
205317721Speter    if (status != 0 && status != 1)
205417721Speter    {
205517721Speter	error (0, status == -1 ? errno : 0,
205625839Speter	       "could not merge revision %s of %s", vers->vn_user, finfo->fullname);
205717721Speter	error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
205825839Speter	       finfo->fullname, backup);
205925839Speter	rename_file (backup, finfo->file);
206025839Speter	retval = 1;
206125839Speter	goto out;
206217721Speter    }
206317721Speter
206417721Speter    if (strcmp (vers->options, "-V4") == 0)
206517721Speter	vers->options[0] = '\0';
206644856Speter
206717721Speter    /* fix up the vers structure, in case it is used by join */
206817721Speter    if (join_rev1)
206917721Speter    {
2070128269Speter	/* FIXME: Throwing away the original revision info is almost
2071128269Speter	   certainly wrong -- what if join_rev1 is "BASE"?  */
207217721Speter	if (vers->vn_user != NULL)
207317721Speter	    free (vers->vn_user);
207417721Speter	vers->vn_user = xstrdup (vers->vn_rcs);
207517721Speter    }
207617721Speter
2077175280Sobrien    RegisterMerge (finfo, vers, backup, status);
207817721Speter
207954431Speter    /* FIXME: the noexec case is broken.  RCS_merge could be doing the
208054431Speter       xcmp on the temporary files without much hassle, I think.  */
208125839Speter    if (!noexec && !xcmp (backup, finfo->file))
208217721Speter    {
208354431Speter	cvs_output (finfo->fullname, 0);
208454431Speter	cvs_output (" already contains the differences between ", 0);
208554431Speter	cvs_output (vers->vn_user, 0);
208654431Speter	cvs_output (" and ", 0);
208754431Speter	cvs_output (vers->vn_rcs, 0);
208854431Speter	cvs_output ("\n", 1);
208954431Speter
209025839Speter	history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
209125839Speter		       finfo->repository);
209225839Speter	retval = 0;
209325839Speter	goto out;
209417721Speter    }
209517721Speter
209617721Speter    if (status == 1)
209717721Speter    {
209854431Speter	error (0, 0, "conflicts found in %s", finfo->fullname);
209917721Speter
210032788Speter	write_letter (finfo, 'C');
210117721Speter
2102128269Speter	history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
2103128269Speter	               finfo->repository);
210417721Speter
210517721Speter    }
210617721Speter    else if (retcode == -1)
210717721Speter    {
210825839Speter	error (1, errno, "fork failed while examining update of %s",
210925839Speter	       finfo->fullname);
211017721Speter    }
211117721Speter    else
211217721Speter    {
211332788Speter	write_letter (finfo, 'M');
211425839Speter	history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
211525839Speter		       finfo->repository);
211617721Speter    }
211725839Speter    retval = 0;
211825839Speter out:
211925839Speter    free (backup);
212025839Speter    return retval;
212117721Speter}
212217721Speter
2123128269Speter
2124128269Speter
212517721Speter/*
212617721Speter * Do all the magic associated with a file which needs to be joined
2127128269Speter * (reached via the -j option to checkout or update).
2128128269Speter *
2129128269Speter * INPUTS
2130128269Speter *   finfo		File information about the destination file.
2131128269Speter *   vers		The Vers_TS structure for finfo.
2132128269Speter *
2133128269Speter * GLOBALS
2134128269Speter *   join_rev1		From the command line.
2135128269Speter *   join_rev2		From the command line.
2136128269Speter *   server_active	Natch.
2137128269Speter *
2138128269Speter * ASSUMPTIONS
2139128269Speter *   1.  Is not called in client mode.
214017721Speter */
214117721Speterstatic void
214225839Speterjoin_file (finfo, vers)
214325839Speter    struct file_info *finfo;
214417721Speter    Vers_TS *vers;
214517721Speter{
214625839Speter    char *backup;
214766528Speter    char *t_options;
214817721Speter    int status;
214917721Speter
215017721Speter    char *rev1;
215117721Speter    char *rev2;
215217721Speter    char *jrev1;
215317721Speter    char *jrev2;
215417721Speter    char *jdate1;
215517721Speter    char *jdate2;
215617721Speter
215781407Speter    if (trace)
215881407Speter	fprintf (stderr, "%s-> join_file(%s, %s%s%s%s, %s, %s)\n",
215981407Speter		CLIENT_SERVER_STR,
216081407Speter		finfo->file,
216181407Speter		vers->tag ? vers->tag : "",
216281407Speter		vers->tag ? " (" : "",
216381407Speter		vers->vn_rcs ? vers->vn_rcs : "",
216481407Speter		vers->tag ? ")" : "",
216581407Speter		join_rev1 ? join_rev1 : "",
216681407Speter		join_rev2 ? join_rev2 : "");
216781407Speter
216817721Speter    jrev1 = join_rev1;
216917721Speter    jrev2 = join_rev2;
217017721Speter    jdate1 = date_rev1;
217117721Speter    jdate2 = date_rev2;
217217721Speter
217325839Speter    /* Determine if we need to do anything at all.  */
217417721Speter    if (vers->srcfile == NULL ||
217517721Speter	vers->srcfile->path == NULL)
217617721Speter    {
217717721Speter	return;
217817721Speter    }
217917721Speter
218025839Speter    /* If only one join revision is specified, it becomes the second
218125839Speter       revision.  */
218217721Speter    if (jrev2 == NULL)
218317721Speter    {
218417721Speter	jrev2 = jrev1;
218517721Speter	jrev1 = NULL;
218617721Speter	jdate2 = jdate1;
218717721Speter	jdate1 = NULL;
218817721Speter    }
218917721Speter
2190128269Speter    /* FIXME: Need to handle "BASE" for jrev1 and/or jrev2.  Note caveat
2191128269Speter       below about vn_user.  */
2192128269Speter
219325839Speter    /* Convert the second revision, walking branches and dates.  */
219425839Speter    rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, (int *) NULL);
219525839Speter
219625839Speter    /* If this is a merge of two revisions, get the first revision.
219725839Speter       If only one join tag was specified, then the first revision is
219825839Speter       the greatest common ancestor of the second revision and the
219925839Speter       working file.  */
220025839Speter    if (jrev1 != NULL)
220125839Speter	rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, (int *) NULL);
220225839Speter    else
220317721Speter    {
220425839Speter	/* Note that we use vn_rcs here, since vn_user may contain a
220525839Speter           special string such as "-nn".  */
220625839Speter	if (vers->vn_rcs == NULL)
220725839Speter	    rev1 = NULL;
220825839Speter	else if (rev2 == NULL)
220925839Speter	{
221025839Speter	    /* This means that the file never existed on the branch.
221125839Speter               It does not mean that the file was removed on the
221225839Speter               branch: that case is represented by a dead rev2.  If
221325839Speter               the file never existed on the branch, then we have
221425839Speter               nothing to merge, so we just return.  */
221525839Speter	    return;
221625839Speter	}
221717721Speter	else
221825839Speter	    rev1 = gca (vers->vn_rcs, rev2);
221917721Speter    }
222017721Speter
222125839Speter    /* Handle a nonexistent or dead merge target.  */
222225839Speter    if (rev2 == NULL || RCS_isdead (vers->srcfile, rev2))
222317721Speter    {
222425839Speter	char *mrev;
2225177401Sobrien	short conflict = 0;
222617721Speter
222725839Speter	if (rev2 != NULL)
222825839Speter	    free (rev2);
222917721Speter
223025839Speter	/* If the first revision doesn't exist either, then there is
223125839Speter           no change between the two revisions, so we don't do
223225839Speter           anything.  */
223325839Speter	if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
223417721Speter	{
223525839Speter	    if (rev1 != NULL)
223625839Speter		free (rev1);
223725839Speter	    return;
223825839Speter	}
223925839Speter
224025839Speter	/* If we are merging two revisions, then the file was removed
224125839Speter	   between the first revision and the second one.  In this
224225839Speter	   case we want to mark the file for removal.
224325839Speter
224425839Speter	   If we are merging one revision, then the file has been
224525839Speter	   removed between the greatest common ancestor and the merge
224625839Speter	   revision.  From the perspective of the branch on to which
224725839Speter	   we ar emerging, which may be the trunk, either 1) the file
224825839Speter	   does not currently exist on the target, or 2) the file has
224925839Speter	   not been modified on the target branch since the greatest
225025839Speter	   common ancestor, or 3) the file has been modified on the
225125839Speter	   target branch since the greatest common ancestor.  In case
225225839Speter	   1 there is nothing to do.  In case 2 we mark the file for
225325839Speter	   removal.  In case 3 we have a conflict.
225425839Speter
225525839Speter	   Note that the handling is slightly different depending upon
225625839Speter	   whether one or two join targets were specified.  If two
225725839Speter	   join targets were specified, we don't check whether the
225825839Speter	   file was modified since a given point.  My reasoning is
225925839Speter	   that if you ask for an explicit merge between two tags,
226025839Speter	   then you want to merge in whatever was changed between
226125839Speter	   those two tags.  If a file was removed between the two
226225839Speter	   tags, then you want it to be removed.  However, if you ask
226325839Speter	   for a merge of a branch, then you want to merge in all
226425839Speter	   changes which were made on the branch.  If a file was
226525839Speter	   removed on the branch, that is a change to the file.  If
226625839Speter	   the file was also changed on the main line, then that is
226725839Speter	   also a change.  These two changes--the file removal and the
226825839Speter	   modification--must be merged.  This is a conflict.  */
226925839Speter
227025839Speter	/* If the user file is dead, or does not exist, or has been
227125839Speter           marked for removal, then there is nothing to do.  */
227225839Speter	if (vers->vn_user == NULL
227325839Speter	    || vers->vn_user[0] == '-'
227425839Speter	    || RCS_isdead (vers->srcfile, vers->vn_user))
227525839Speter	{
2276177401Sobrien	    free (rev1);
227725839Speter	    return;
227825839Speter	}
227925839Speter
228025839Speter	/* If the user file has been marked for addition, or has been
228125839Speter	   locally modified, then we have a conflict which we can not
228225839Speter	   resolve.  No_Difference will already have been called in
228325839Speter	   this case, so comparing the timestamps is sufficient to
228425839Speter	   determine whether the file is locally modified.  */
2285177401Sobrien	if (/* may have changed on destination branch */
2286177401Sobrien	    /* file added locally */
2287177401Sobrien	    !strcmp (vers->vn_user, "0")
2288177401Sobrien	    || /* destination branch modified in repository */
2289177401Sobrien	       strcmp (rev1, vers->vn_user)
2290177401Sobrien	    || /* locally modified */
2291177401Sobrien	       vers->ts_user && strcmp (vers->ts_user, vers->ts_rcs))
229225839Speter	{
2293177401Sobrien	    /* The removal should happen if either the file has never changed
2294177401Sobrien	     * on the destination or the file has changed to be identical to
2295177401Sobrien	     * the first join revision.
2296177401Sobrien	     *
2297177401Sobrien	     * ------R-----------D
2298177401Sobrien	     *       |
2299177401Sobrien	     *       \----J1---J2-----S
2300177401Sobrien	     *
2301177401Sobrien	     * So:
2302177401Sobrien	     *
2303177401Sobrien	     * J2 is dead.
2304177401Sobrien	     * D is destination.
2305177401Sobrien	     * R is source branch root/GCA.
2306177401Sobrien	     * if J1 == D       removal should happen
2307177401Sobrien	     * if D == R        removal should happen
2308177401Sobrien	     * otherwise, fail.
2309177401Sobrien	     *
2310177401Sobrien	     * (In the source, J2 = REV2, D = user file (potentially VN_USER),
2311177401Sobrien	     * R = GCA computed below)
2312177401Sobrien	     */
2313177401Sobrien	    char *gca_rev1 = gca (rev1, vers->vn_user);
2314177401Sobrien#ifdef SERVER_SUPPORT
2315177401Sobrien	    if (server_active && !isreadable (finfo->file))
2316177401Sobrien	    {
2317177401Sobrien		int retcode;
2318177401Sobrien		/* The file is up to date.  Need to check out the current
2319177401Sobrien		 * contents.
2320177401Sobrien		 */
2321177401Sobrien		/* FIXME - see the FIXME comment above the call to RCS_checkout
2322177401Sobrien		 * in the patch_file function.
2323177401Sobrien		 */
2324177401Sobrien		retcode = RCS_checkout (vers->srcfile, finfo->file,
2325177401Sobrien					vers->vn_user, vers->tag,
2326177401Sobrien					NULL, RUN_TTY, NULL, NULL);
2327177401Sobrien		if (retcode)
2328177401Sobrien		    error (1, 0,
2329177401Sobrien			   "failed to check out %s file", finfo->fullname);
2330177401Sobrien	    }
2331177401Sobrien#endif
2332177401Sobrien	    if (/* genuinely changed on destination branch */
2333177401Sobrien	        RCS_cmp_file (vers->srcfile, gca_rev1, NULL,
2334177401Sobrien			      NULL, vers->options, finfo->file)
2335177401Sobrien	        && /* genuinely different from REV1 */
2336177401Sobrien		   RCS_cmp_file (vers->srcfile, rev1, NULL,
2337177401Sobrien				 NULL, vers->options, finfo->file))
2338177401Sobrien		conflict = 1;
2339177401Sobrien	}
234025839Speter
2341177401Sobrien	free (rev1);
234225839Speter
2343177401Sobrien	if (conflict)
2344177401Sobrien	{
2345177401Sobrien	    char *cp;
234625839Speter
2347177401Sobrien	    if (jdate2)
234825839Speter		error (0, 0,
2349177401Sobrien		       "file %s has been removed in revision %s as of %s, but the destination is incompatibly modified",
235025839Speter		       finfo->fullname, jrev2, jdate2);
235125839Speter	    else
235225839Speter		error (0, 0,
2353177401Sobrien		       "file %s has been removed in revision %s, but the destination is incompatibly modified",
235425839Speter		       finfo->fullname, jrev2);
235525839Speter
2356177401Sobrien	    /* Register the conflict with the client.  */
235725839Speter
2358177401Sobrien	    /* FIXME: vers->ts_user should always be set here but sometimes
2359177401Sobrien	     * isn't, namely when checkout_file() has just created the file,
2360177401Sobrien	     * but simply setting it in checkout_file() appears to cause other
2361177401Sobrien	     * problems.
2362177401Sobrien	     */
2363177401Sobrien	    if (isfile (finfo->file))
2364177401Sobrien		cp = time_stamp (finfo->file);
2365177401Sobrien	    else
2366177401Sobrien		cp = xstrdup (vers->ts_user);
236725839Speter
2368177401Sobrien	    Register (finfo->entries, finfo->file, vers->vn_user,
2369177401Sobrien		      "Result of merge", vers->options, vers->tag, vers->date,
2370177401Sobrien		      cp);
2371177401Sobrien	    write_letter (finfo, 'C');
2372177401Sobrien	    free (cp);
2373177401Sobrien
2374177401Sobrien#ifdef SERVER_SUPPORT
2375177401Sobrien	    /* Abuse server_checked_in() to send the updated entry without
2376177401Sobrien	     * needing to update the file.
2377177401Sobrien	     */
2378177401Sobrien	    if (server_active)
2379177401Sobrien		server_checked_in (finfo->file, finfo->update_dir,
2380177401Sobrien				   finfo->repository);
2381177401Sobrien#endif
2382177401Sobrien
238325839Speter	    return;
238425839Speter	}
238525839Speter
238625839Speter	/* The user file exists and has not been modified.  Mark it
238725839Speter           for removal.  FIXME: If we are doing a checkout, this has
238825839Speter           the effect of first checking out the file, and then
238925839Speter           removing it.  It would be better to just register the
239081407Speter           removal.
239181407Speter
239281407Speter	   The same goes for a removal then an add.  e.g.
239381407Speter	   cvs up -rbr -jbr2 could remove and readd the same file
239481407Speter	 */
239581407Speter	/* save the rev since server_updated might invalidate it */
239681407Speter	mrev = xmalloc (strlen (vers->vn_user) + 2);
239781407Speter	sprintf (mrev, "-%s", vers->vn_user);
239825839Speter#ifdef SERVER_SUPPORT
239925839Speter	if (server_active)
240025839Speter	{
240125839Speter	    server_scratch (finfo->file);
240234467Speter	    server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1,
240334467Speter			    (unsigned char *) NULL, (struct buffer *) NULL);
240425839Speter	}
240525839Speter#endif
240625839Speter	Register (finfo->entries, finfo->file, mrev, vers->ts_rcs,
240725839Speter		  vers->options, vers->tag, vers->date, vers->ts_conflict);
240825839Speter	free (mrev);
240925839Speter	/* We need to check existence_error here because if we are
241025839Speter           running as the server, and the file is up to date in the
241125839Speter           working directory, the client will not have sent us a copy.  */
241225839Speter	if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
241325839Speter	    error (0, errno, "cannot remove file %s", finfo->fullname);
241425839Speter#ifdef SERVER_SUPPORT
241525839Speter	if (server_active)
241625839Speter	    server_checked_in (finfo->file, finfo->update_dir,
241725839Speter			       finfo->repository);
241825839Speter#endif
241925839Speter	if (! really_quiet)
242025839Speter	    error (0, 0, "scheduling %s for removal", finfo->fullname);
242125839Speter
242217721Speter	return;
242317721Speter    }
242417721Speter
2425128269Speter    /* If the two merge revisions are the same, then there is nothing
2426128269Speter     * to do.  This needs to be checked before the rev2 == up-to-date base
2427128269Speter     * revision check tha comes next.  Otherwise, rev1 can == rev2 and get an
2428128269Speter     * "already contains the changes between <rev1> and <rev1>" message.
2429128269Speter     */
2430128269Speter    if (rev1 && strcmp (rev1, rev2) == 0)
243117721Speter    {
2432128269Speter	free (rev1);
2433128269Speter	free (rev2);
2434128269Speter	return;
2435128269Speter    }
2436128269Speter
2437128269Speter    /* If we know that the user file is up-to-date, then it becomes an
2438128269Speter     * optimization to skip the merge when rev2 is the same as the base
2439128269Speter     * revision.  i.e. we know that diff3(file2,file1,file2) will produce
2440128269Speter     * file2.
2441128269Speter     */
2442128269Speter    if (vers->vn_user != NULL && vers->ts_user != NULL
2443128269Speter        && strcmp (vers->ts_user, vers->ts_rcs) == 0
2444128269Speter        && strcmp (rev2, vers->vn_user) == 0)
2445128269Speter    {
2446128269Speter	if (!really_quiet)
2447128269Speter	{
2448128269Speter	    cvs_output (finfo->fullname, 0);
2449128269Speter	    cvs_output (" already contains the differences between ", 0);
2450128269Speter	    cvs_output (rev1 ? rev1 : "creation", 0);
2451128269Speter	    cvs_output (" and ", 0);
2452128269Speter	    cvs_output (rev2, 0);
2453128269Speter	    cvs_output ("\n", 1);
2454128269Speter	}
2455128269Speter
245625839Speter	if (rev1 != NULL)
245725839Speter	    free (rev1);
245817721Speter	free (rev2);
2459128269Speter
246017721Speter	return;
246117721Speter    }
246217721Speter
246325839Speter    /* If rev1 is dead or does not exist, then the file was added
246425839Speter       between rev1 and rev2.  */
246525839Speter    if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
246617721Speter    {
246725839Speter	if (rev1 != NULL)
246825839Speter	    free (rev1);
246925839Speter	free (rev2);
247017721Speter
247125839Speter	/* If the file does not exist in the working directory, then
247225839Speter           we can just check out the new revision and mark it for
247325839Speter           addition.  */
247425839Speter	if (vers->vn_user == NULL)
247517721Speter	{
247681407Speter	    char *saved_options = options;
247725839Speter	    Vers_TS *xvers;
247817721Speter
247925839Speter	    xvers = Version_TS (finfo, vers->options, jrev2, jdate2, 1, 0);
248017721Speter
248166528Speter	    /* Reset any keyword expansion option.  Otherwise, when a
248266528Speter	       command like `cvs update -kk -jT1 -jT2' creates a new file
248366528Speter	       (because a file had the T2 tag, but not T1), the subsequent
248466528Speter	       commit of that just-added file effectively would set the
248566528Speter	       admin `-kk' option for that file in the repository.  */
248666528Speter	    options = NULL;
248766528Speter
248825839Speter	    /* FIXME: If checkout_file fails, we should arrange to
248925839Speter               return a non-zero exit status.  */
249034467Speter	    status = checkout_file (finfo, xvers, 1, 0, 1);
249181407Speter	    options = saved_options;
249225839Speter
249325839Speter	    freevers_ts (&xvers);
249425839Speter
249517721Speter	    return;
249617721Speter	}
249725839Speter
249825839Speter	/* The file currently exists in the working directory, so we
249925839Speter           have a conflict which we can not resolve.  Note that this
250025839Speter           is true even if the file is marked for addition or removal.  */
250125839Speter
250225839Speter	if (jdate2 != NULL)
250325839Speter	    error (0, 0,
250425839Speter		   "file %s exists, but has been added in revision %s as of %s",
250525839Speter		   finfo->fullname, jrev2, jdate2);
250625839Speter	else
250725839Speter	    error (0, 0,
250825839Speter		   "file %s exists, but has been added in revision %s",
250925839Speter		   finfo->fullname, jrev2);
251025839Speter
251125839Speter	return;
251217721Speter    }
251325839Speter
251425839Speter    /* If there is no working file, then we can't do the merge.  */
2515128269Speter    if (vers->vn_user == NULL || vers->vn_user[0] == '-')
251625839Speter    {
251725839Speter	free (rev1);
251825839Speter	free (rev2);
251917721Speter
252025839Speter	if (jdate2 != NULL)
252125839Speter	    error (0, 0,
252266528Speter		   "file %s does not exist, but is present in revision %s as of %s",
252325839Speter		   finfo->fullname, jrev2, jdate2);
252417721Speter	else
252525839Speter	    error (0, 0,
252666528Speter		   "file %s does not exist, but is present in revision %s",
252725839Speter		   finfo->fullname, jrev2);
252825839Speter
252925839Speter	/* FIXME: Should we arrange to return a non-zero exit status?  */
253025839Speter
253125839Speter	return;
253217721Speter    }
253317721Speter
253417721Speter#ifdef SERVER_SUPPORT
253525839Speter    if (server_active && !isreadable (finfo->file))
253617721Speter    {
253717721Speter	int retcode;
253817721Speter	/* The file is up to date.  Need to check out the current contents.  */
2539128269Speter	/* FIXME - see the FIXME comment above the call to RCS_checkout in the
2540128269Speter	 * patch_file function.
2541128269Speter	 */
254225839Speter	retcode = RCS_checkout (vers->srcfile, finfo->file,
2543128269Speter				vers->vn_user, vers->tag,
254425839Speter				(char *) NULL, RUN_TTY,
254525839Speter				(RCSCHECKOUTPROC) NULL, (void *) NULL);
254617721Speter	if (retcode != 0)
254734467Speter	    error (1, 0,
254825839Speter		   "failed to check out %s file", finfo->fullname);
254917721Speter    }
255017721Speter#endif
255132788Speter
255217721Speter    /*
255317721Speter     * The users currently modified file is moved to a backup file name
255417721Speter     * ".#filename.version", so that it will stay around for a few days
255517721Speter     * before being automatically removed by some cron daemon.  The "version"
255617721Speter     * is the version of the file that the user was most up-to-date with
255717721Speter     * before the merge.
255817721Speter     */
255925839Speter    backup = xmalloc (strlen (finfo->file)
256025839Speter		      + strlen (vers->vn_user)
256125839Speter		      + sizeof (BAKPREFIX)
256225839Speter		      + 10);
256325839Speter    (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
256417721Speter
256554431Speter    if (unlink_file (backup) < 0
256654431Speter	&& !existence_error (errno))
256754431Speter	error (0, errno, "cannot remove %s", backup);
256825839Speter    copy_file (finfo->file, backup);
256925839Speter    xchmod (finfo->file, 1);
257017721Speter
257166528Speter    t_options = vers->options;
257217721Speter#if 0
257366528Speter    if (*t_options == '\0')
257466528Speter	t_options = "-kk";		/* to ignore keyword expansions */
257517721Speter#endif
257617721Speter
257732788Speter    /* If the source of the merge is the same as the working file
257832788Speter       revision, then we can just RCS_checkout the target (no merging
257932788Speter       as such).  In the text file case, this is probably quite
258032788Speter       similar to the RCS_merge, but in the binary file case,
258132788Speter       RCS_merge gives all kinds of trouble.  */
258232788Speter    if (vers->vn_user != NULL
258332788Speter	&& strcmp (rev1, vers->vn_user) == 0
258432788Speter	/* See comments above about how No_Difference has already been
258532788Speter	   called.  */
258632788Speter	&& vers->ts_user != NULL
258732788Speter	&& strcmp (vers->ts_user, vers->ts_rcs) == 0
258832788Speter
2589128269Speter	/* Avoid this in the text file case.  See below for why.
2590128269Speter	 */
259166528Speter	&& (strcmp (t_options, "-kb") == 0
259232788Speter	    || wrap_merge_is_copy (finfo->file)))
259332788Speter    {
2594128269Speter	/* FIXME: Verify my comment below:
2595128269Speter	 *
2596128269Speter	 * RCS_merge does nothing with keywords.  It merges the changes between
2597128269Speter	 * two revisions without expanding the keywords (it might expand in
2598128269Speter	 * -kk mode before computing the diff between rev1 and rev2 - I'm not
2599128269Speter	 * sure).  In other words, the keyword lines in the current work file
2600128269Speter	 * get left alone.
2601128269Speter	 *
2602128269Speter	 * Therfore, checking out the destination revision (rev2) is probably
2603128269Speter	 * incorrect in the text case since we should see the keywords that were
2604128269Speter	 * substituted into the original file at the time it was checked out
2605128269Speter	 * and not the keywords from rev2.
2606128269Speter	 *
2607128269Speter	 * Also, it is safe to pass in NULL for nametag since we know no
2608128269Speter	 * substitution is happening during the binary mode checkout.
2609128269Speter	 */
2610128269Speter	if (RCS_checkout ( finfo->rcs, finfo->file, rev2, (char *)NULL, t_options,
2611128269Speter			   RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0 )
261232788Speter	    status = 2;
261332788Speter	else
261432788Speter	    status = 0;
261532788Speter
261632788Speter	/* OK, this is really stupid.  RCS_checkout carefully removes
261732788Speter	   write permissions, and we carefully put them back.  But
261832788Speter	   until someone gets around to fixing it, that seems like the
261932788Speter	   easiest way to get what would seem to be the right mode.
262032788Speter	   I don't check CVSWRITE or _watched; I haven't thought about
262132788Speter	   that in great detail, but it seems like a watched file should
262232788Speter	   be checked out (writable) after a merge.  */
262332788Speter	xchmod (finfo->file, 1);
262432788Speter
262532788Speter	/* Traditionally, the text file case prints a whole bunch of
262632788Speter	   scary looking and verbose output which fails to tell the user
262732788Speter	   what is really going on (it gives them rev1 and rev2 but doesn't
262832788Speter	   indicate in any way that rev1 == vn_user).  I think just a
262932788Speter	   simple "U foo" is good here; it seems analogous to the case in
263032788Speter	   which the file was added on the branch in terms of what to
263132788Speter	   print.  */
263232788Speter	write_letter (finfo, 'U');
263332788Speter    }
263466528Speter    else if (strcmp (t_options, "-kb") == 0
263534467Speter	     || wrap_merge_is_copy (finfo->file)
263634467Speter	     || special_file_mismatch (finfo, rev1, rev2))
263732788Speter    {
263834467Speter	/* We are dealing with binary files, or files with a
2639128269Speter	   permission/linkage mismatch (this second case only occurs when
2640128269Speter	   PRESERVE_PERMISSIONS_SUPPORT is enabled), and real merging would
264132788Speter	   need to take place.  This is a conflict.  We give the user
264232788Speter	   the two files, and let them resolve it.  It is possible
264332788Speter	   that we should require a "touch foo" or similar step before
264432788Speter	   we allow a checkin.  */
2645128269Speter	if (RCS_checkout ( finfo->rcs, finfo->file, rev2, (char *)NULL,
2646128269Speter			   t_options, RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0)
264732788Speter	    status = 2;
264832788Speter	else
264932788Speter	    status = 0;
265032788Speter
265132788Speter	/* OK, this is really stupid.  RCS_checkout carefully removes
265232788Speter	   write permissions, and we carefully put them back.  But
265332788Speter	   until someone gets around to fixing it, that seems like the
265432788Speter	   easiest way to get what would seem to be the right mode.
265532788Speter	   I don't check CVSWRITE or _watched; I haven't thought about
265632788Speter	   that in great detail, but it seems like a watched file should
265732788Speter	   be checked out (writable) after a merge.  */
265832788Speter	xchmod (finfo->file, 1);
265932788Speter
266032788Speter	/* Hmm.  We don't give them REV1 anywhere.  I guess most people
266132788Speter	   probably don't have a 3-way merge tool for the file type in
266232788Speter	   question, and might just get confused if we tried to either
266332788Speter	   provide them with a copy of the file from REV1, or even just
266432788Speter	   told them what REV1 is so they can get it themself, but it
266532788Speter	   might be worth thinking about.  */
266632788Speter	/* See comment in merge_file about the "nonmergeable file"
266732788Speter	   terminology.  */
266832788Speter	error (0, 0, "nonmergeable file needs merge");
266932788Speter	error (0, 0, "revision %s from repository is now in %s",
267032788Speter	       rev2, finfo->fullname);
267132788Speter	error (0, 0, "file from working directory is now in %s", backup);
267232788Speter	write_letter (finfo, 'C');
267332788Speter    }
267432788Speter    else
267532788Speter	status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
267666528Speter			    t_options, rev1, rev2);
267732788Speter
2678128269Speter    if (status != 0)
267917721Speter    {
2680128269Speter	if (status != 1)
2681128269Speter	{
2682128269Speter	    error (0, status == -1 ? errno : 0,
2683128269Speter		   "could not merge revision %s of %s", rev2, finfo->fullname);
2684128269Speter	    error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
2685128269Speter		   finfo->fullname, backup);
2686128269Speter	    rename_file (backup, finfo->file);
2687128269Speter	}
268817721Speter    }
2689128269Speter    else /* status == 0 */
2690128269Speter    {
2691128269Speter	/* FIXME: the noexec case is broken.  RCS_merge could be doing the
2692128269Speter	   xcmp on the temporary files without much hassle, I think.  */
2693128269Speter	if (!noexec && !xcmp (backup, finfo->file))
2694128269Speter	{
2695128269Speter	    if (!really_quiet)
2696128269Speter	    {
2697128269Speter		cvs_output (finfo->fullname, 0);
2698128269Speter		cvs_output (" already contains the differences between ", 0);
2699128269Speter		cvs_output (rev1, 0);
2700128269Speter		cvs_output (" and ", 0);
2701128269Speter		cvs_output (rev2, 0);
2702128269Speter		cvs_output ("\n", 1);
2703128269Speter	    }
270417721Speter
2705128269Speter	    /* and skip the registering and sending the new file since it
2706128269Speter	     * hasn't been updated.
2707128269Speter	     */
2708128269Speter	    goto out;
2709128269Speter	}
2710128269Speter    }
2711128269Speter
271244856Speter    /* The file has changed, but if we just checked it out it may
271344856Speter       still have the same timestamp it did when it was first
271444856Speter       registered above in checkout_file.  We register it again with a
271544856Speter       dummy timestamp to make sure that later runs of CVS will
271644856Speter       recognize that it has changed.
271744856Speter
271844856Speter       We don't actually need to register again if we called
271944856Speter       RCS_checkout above, and we aren't running as the server.
272044856Speter       However, that is not the normal case, and calling Register
272144856Speter       again won't cost much in that case.  */
2722175280Sobrien    RegisterMerge (finfo, vers, backup, status);
272317721Speter
2724128269Speterout:
2725128269Speter    free (rev1);
2726128269Speter    free (rev2);
272725839Speter    free (backup);
272817721Speter}
272917721Speter
2730128269Speter
2731128269Speter
273234467Speter/*
273334467Speter * Report whether revisions REV1 and REV2 of FINFO agree on:
273434467Speter *   . file ownership
273534467Speter *   . permissions
273634467Speter *   . major and minor device numbers
273734467Speter *   . symbolic links
273834467Speter *   . hard links
273934467Speter *
274034467Speter * If either REV1 or REV2 is NULL, the working copy is used instead.
274134467Speter *
274234467Speter * Return 1 if the files differ on these data.
274334467Speter */
274434467Speter
274517721Speterint
274634467Speterspecial_file_mismatch (finfo, rev1, rev2)
274734467Speter    struct file_info *finfo;
274834467Speter    char *rev1;
274934467Speter    char *rev2;
275034467Speter{
275134467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT
275234467Speter    struct stat sb;
275334467Speter    RCSVers *vp;
275434467Speter    Node *n;
275534467Speter    uid_t rev1_uid, rev2_uid;
275634467Speter    gid_t rev1_gid, rev2_gid;
275734467Speter    mode_t rev1_mode, rev2_mode;
275834467Speter    unsigned long dev_long;
275934467Speter    dev_t rev1_dev, rev2_dev;
276034467Speter    char *rev1_symlink = NULL;
276134467Speter    char *rev2_symlink = NULL;
276266528Speter    List *rev1_hardlinks = NULL;
276366528Speter    List *rev2_hardlinks = NULL;
276434467Speter    int check_uids, check_gids, check_modes;
276534467Speter    int result;
276634467Speter
276734467Speter    /* If we don't care about special file info, then
276834467Speter       don't report a mismatch in any case. */
276934467Speter    if (!preserve_perms)
277034467Speter	return 0;
277134467Speter
277234467Speter    /* When special_file_mismatch is called from No_Difference, the
277334467Speter       RCS file has been only partially parsed.  We must read the
277434467Speter       delta tree in order to compare special file info recorded in
277534467Speter       the delta nodes.  (I think this is safe. -twp) */
277634467Speter    if (finfo->rcs->flags & PARTIAL)
277734467Speter	RCS_reparsercsfile (finfo->rcs, NULL, NULL);
277834467Speter
277934467Speter    check_uids = check_gids = check_modes = 1;
278034467Speter
278134467Speter    /* Obtain file information for REV1.  If this is null, then stat
278234467Speter       finfo->file and use that info. */
278334467Speter    /* If a revision does not know anything about its status,
278434467Speter       then presumably it doesn't matter, and indicates no conflict. */
278534467Speter
278634467Speter    if (rev1 == NULL)
278734467Speter    {
278834467Speter	if (islink (finfo->file))
278934467Speter	    rev1_symlink = xreadlink (finfo->file);
279034467Speter	else
279134467Speter	{
2792107487Speter# ifdef HAVE_STRUCT_STAT_ST_RDEV
279334467Speter	    if (CVS_LSTAT (finfo->file, &sb) < 0)
279434467Speter		error (1, errno, "could not get file information for %s",
279534467Speter		       finfo->file);
279634467Speter	    rev1_uid = sb.st_uid;
279734467Speter	    rev1_gid = sb.st_gid;
279834467Speter	    rev1_mode = sb.st_mode;
279934467Speter	    if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode))
280034467Speter		rev1_dev = sb.st_rdev;
2801102843Speter# else
280266528Speter	    error (1, 0, "cannot handle device files on this system (%s)",
280366528Speter		   finfo->file);
2804102843Speter# endif
280534467Speter	}
280644856Speter	rev1_hardlinks = list_linked_files_on_disk (finfo->file);
280734467Speter    }
280834467Speter    else
280934467Speter    {
281034467Speter	n = findnode (finfo->rcs->versions, rev1);
2811128269Speter	vp = n->data;
281234467Speter
281334467Speter	n = findnode (vp->other_delta, "symlink");
281434467Speter	if (n != NULL)
281534467Speter	    rev1_symlink = xstrdup (n->data);
281634467Speter	else
281734467Speter	{
281834467Speter	    n = findnode (vp->other_delta, "owner");
281934467Speter	    if (n == NULL)
282034467Speter		check_uids = 0;	/* don't care */
282134467Speter	    else
282234467Speter		rev1_uid = strtoul (n->data, NULL, 10);
282334467Speter
282434467Speter	    n = findnode (vp->other_delta, "group");
282534467Speter	    if (n == NULL)
282634467Speter		check_gids = 0;	/* don't care */
282734467Speter	    else
282834467Speter		rev1_gid = strtoul (n->data, NULL, 10);
282934467Speter
283034467Speter	    n = findnode (vp->other_delta, "permissions");
283134467Speter	    if (n == NULL)
283234467Speter		check_modes = 0;	/* don't care */
283334467Speter	    else
283434467Speter		rev1_mode = strtoul (n->data, NULL, 8);
283534467Speter
283634467Speter	    n = findnode (vp->other_delta, "special");
283734467Speter	    if (n == NULL)
283834467Speter		rev1_mode |= S_IFREG;
283934467Speter	    else
284034467Speter	    {
284134467Speter		/* If the size of `ftype' changes, fix the sscanf call also */
284234467Speter		char ftype[16];
2843102843Speter		if (sscanf (n->data, "%15s %lu", ftype,
284434467Speter			    &dev_long) < 2)
284534467Speter		    error (1, 0, "%s:%s has bad `special' newphrase %s",
2846128269Speter			   finfo->file, rev1, (char *)n->data);
284734467Speter		rev1_dev = dev_long;
284834467Speter		if (strcmp (ftype, "character") == 0)
284934467Speter		    rev1_mode |= S_IFCHR;
285034467Speter		else if (strcmp (ftype, "block") == 0)
285134467Speter		    rev1_mode |= S_IFBLK;
285234467Speter		else
285334467Speter		    error (0, 0, "%s:%s unknown file type `%s'",
285434467Speter			   finfo->file, rev1, ftype);
285534467Speter	    }
285634467Speter
285744856Speter	    rev1_hardlinks = vp->hardlinks;
285844856Speter	    if (rev1_hardlinks == NULL)
285944856Speter		rev1_hardlinks = getlist();
286034467Speter	}
286134467Speter    }
286234467Speter
286334467Speter    /* Obtain file information for REV2. */
286434467Speter    if (rev2 == NULL)
286534467Speter    {
286634467Speter	if (islink (finfo->file))
286734467Speter	    rev2_symlink = xreadlink (finfo->file);
286834467Speter	else
286934467Speter	{
2870107487Speter# ifdef HAVE_STRUCT_STAT_ST_RDEV
287134467Speter	    if (CVS_LSTAT (finfo->file, &sb) < 0)
287234467Speter		error (1, errno, "could not get file information for %s",
287334467Speter		       finfo->file);
287434467Speter	    rev2_uid = sb.st_uid;
287534467Speter	    rev2_gid = sb.st_gid;
287634467Speter	    rev2_mode = sb.st_mode;
287734467Speter	    if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode))
287834467Speter		rev2_dev = sb.st_rdev;
2879102843Speter# else
288066528Speter	    error (1, 0, "cannot handle device files on this system (%s)",
288166528Speter		   finfo->file);
2882102843Speter# endif
288334467Speter	}
288444856Speter	rev2_hardlinks = list_linked_files_on_disk (finfo->file);
288534467Speter    }
288634467Speter    else
288734467Speter    {
288834467Speter	n = findnode (finfo->rcs->versions, rev2);
2889128269Speter	vp = n->data;
289034467Speter
289134467Speter	n = findnode (vp->other_delta, "symlink");
289234467Speter	if (n != NULL)
289334467Speter	    rev2_symlink = xstrdup (n->data);
289434467Speter	else
289534467Speter	{
289634467Speter	    n = findnode (vp->other_delta, "owner");
289734467Speter	    if (n == NULL)
289834467Speter		check_uids = 0;	/* don't care */
289934467Speter	    else
290034467Speter		rev2_uid = strtoul (n->data, NULL, 10);
290134467Speter
290234467Speter	    n = findnode (vp->other_delta, "group");
290334467Speter	    if (n == NULL)
290434467Speter		check_gids = 0;	/* don't care */
290534467Speter	    else
290634467Speter		rev2_gid = strtoul (n->data, NULL, 10);
290734467Speter
290834467Speter	    n = findnode (vp->other_delta, "permissions");
290934467Speter	    if (n == NULL)
291034467Speter		check_modes = 0;	/* don't care */
291134467Speter	    else
291234467Speter		rev2_mode = strtoul (n->data, NULL, 8);
291334467Speter
291434467Speter	    n = findnode (vp->other_delta, "special");
291534467Speter	    if (n == NULL)
291634467Speter		rev2_mode |= S_IFREG;
291734467Speter	    else
291834467Speter	    {
291934467Speter		/* If the size of `ftype' changes, fix the sscanf call also */
292034467Speter		char ftype[16];
2921102843Speter		if (sscanf (n->data, "%15s %lu", ftype,
292234467Speter			    &dev_long) < 2)
292334467Speter		    error (1, 0, "%s:%s has bad `special' newphrase %s",
2924128269Speter			   finfo->file, rev2, (char *)n->data);
292534467Speter		rev2_dev = dev_long;
292634467Speter		if (strcmp (ftype, "character") == 0)
292734467Speter		    rev2_mode |= S_IFCHR;
292834467Speter		else if (strcmp (ftype, "block") == 0)
292934467Speter		    rev2_mode |= S_IFBLK;
293034467Speter		else
293134467Speter		    error (0, 0, "%s:%s unknown file type `%s'",
293234467Speter			   finfo->file, rev2, ftype);
293334467Speter	    }
293434467Speter
293544856Speter	    rev2_hardlinks = vp->hardlinks;
293644856Speter	    if (rev2_hardlinks == NULL)
293744856Speter		rev2_hardlinks = getlist();
293834467Speter	}
293934467Speter    }
294034467Speter
294134467Speter    /* Check the user/group ownerships and file permissions, printing
294234467Speter       an error for each mismatch found.  Return 0 if all characteristics
294334467Speter       matched, and 1 otherwise. */
294434467Speter
294534467Speter    result = 0;
294634467Speter
294734467Speter    /* Compare symlinks first, since symlinks are simpler (don't have
294834467Speter       any other characteristics). */
294934467Speter    if (rev1_symlink != NULL && rev2_symlink == NULL)
295034467Speter    {
295134467Speter	error (0, 0, "%s is a symbolic link",
295234467Speter	       (rev1 == NULL ? "working file" : rev1));
295334467Speter	result = 1;
295434467Speter    }
295534467Speter    else if (rev1_symlink == NULL && rev2_symlink != NULL)
295634467Speter    {
295734467Speter	error (0, 0, "%s is a symbolic link",
295834467Speter	       (rev2 == NULL ? "working file" : rev2));
295934467Speter	result = 1;
296034467Speter    }
296134467Speter    else if (rev1_symlink != NULL)
296234467Speter	result = (strcmp (rev1_symlink, rev2_symlink) == 0);
296334467Speter    else
296434467Speter    {
296534467Speter	/* Compare user ownership. */
296634467Speter	if (check_uids && rev1_uid != rev2_uid)
296734467Speter	{
296834467Speter	    error (0, 0, "%s: owner mismatch between %s and %s",
296934467Speter		   finfo->file,
297034467Speter		   (rev1 == NULL ? "working file" : rev1),
297134467Speter		   (rev2 == NULL ? "working file" : rev2));
297234467Speter	    result = 1;
297334467Speter	}
297434467Speter
297534467Speter	/* Compare group ownership. */
297634467Speter	if (check_gids && rev1_gid != rev2_gid)
297734467Speter	{
297834467Speter	    error (0, 0, "%s: group mismatch between %s and %s",
297934467Speter		   finfo->file,
298034467Speter		   (rev1 == NULL ? "working file" : rev1),
298134467Speter		   (rev2 == NULL ? "working file" : rev2));
298234467Speter	    result = 1;
298334467Speter	}
298434467Speter
298534467Speter	/* Compare permissions. */
298634467Speter	if (check_modes &&
298734467Speter	    (rev1_mode & 07777) != (rev2_mode & 07777))
298834467Speter	{
298934467Speter	    error (0, 0, "%s: permission mismatch between %s and %s",
299034467Speter		   finfo->file,
299134467Speter		   (rev1 == NULL ? "working file" : rev1),
299234467Speter		   (rev2 == NULL ? "working file" : rev2));
299334467Speter	    result = 1;
299434467Speter	}
299534467Speter
299634467Speter	/* Compare device file characteristics. */
299734467Speter	if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT))
299834467Speter	{
299934467Speter	    error (0, 0, "%s: %s and %s are different file types",
300034467Speter		   finfo->file,
300134467Speter		   (rev1 == NULL ? "working file" : rev1),
300234467Speter		   (rev2 == NULL ? "working file" : rev2));
300334467Speter	    result = 1;
300434467Speter	}
300534467Speter	else if (S_ISBLK (rev1_mode))
300634467Speter	{
300734467Speter	    if (rev1_dev != rev2_dev)
300834467Speter	    {
300934467Speter		error (0, 0, "%s: device numbers of %s and %s do not match",
301034467Speter		       finfo->file,
301134467Speter		       (rev1 == NULL ? "working file" : rev1),
301234467Speter		       (rev2 == NULL ? "working file" : rev2));
301334467Speter		result = 1;
301434467Speter	    }
301534467Speter	}
301634467Speter
301734467Speter	/* Compare hard links. */
301844856Speter	if (compare_linkage_lists (rev1_hardlinks, rev2_hardlinks) == 0)
301934467Speter	{
302034467Speter	    error (0, 0, "%s: hard linkage of %s and %s do not match",
302134467Speter		   finfo->file,
302234467Speter		   (rev1 == NULL ? "working file" : rev1),
302334467Speter		   (rev2 == NULL ? "working file" : rev2));
302434467Speter	    result = 1;
302534467Speter	}
302634467Speter    }
302734467Speter
302834467Speter    if (rev1_symlink != NULL)
302934467Speter	free (rev1_symlink);
303034467Speter    if (rev2_symlink != NULL)
303134467Speter	free (rev2_symlink);
303234467Speter    if (rev1_hardlinks != NULL)
303344856Speter	dellist (&rev1_hardlinks);
303434467Speter    if (rev2_hardlinks != NULL)
303544856Speter	dellist (&rev2_hardlinks);
303634467Speter
303734467Speter    return result;
303834467Speter#else
303934467Speter    return 0;
304034467Speter#endif
304134467Speter}
304234467Speter
3043130307Speter
3044130307Speter
304534467Speterint
304617721Speterjoining ()
304717721Speter{
3048130307Speter    return join_rev1 != NULL;
304917721Speter}
3050