117721Speter/*
2175261Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3175261Sobrien *
4175261Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175261Sobrien *                                  and others.
6175261Sobrien *
7175261Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175261Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
917721Speter *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132785Speter * specified in the README file that comes with the CVS source distribution.
1217721Speter *
1317721Speter */
1417721Speter
15179826Sobrien#include <sys/cdefs.h>
16179826Sobrien__FBSDID("$FreeBSD$");
17179826Sobrien
1817721Speter#include "cvs.h"
1917721Speter
2032785Speterstatic void sticky_ck PROTO ((struct file_info *finfo, int aflag,
2132785Speter			      Vers_TS * vers));
2217721Speter
23177391Sobrien
24177391Sobrien
25177391Sobrienstatic inline int keywords_may_change PROTO ((int aflag, Vers_TS * vers));
26177391Sobrienstatic inline int
27177391Sobrienkeywords_may_change (aflag, vers)
28177391Sobrien    int aflag;
29177391Sobrien    Vers_TS * vers;
30177391Sobrien{
31177391Sobrien    int retval;
32177391Sobrien
33177391Sobrien    if (/* Options are different...  */
34177391Sobrien	strcmp (vers->entdata->options, vers->options)
35177391Sobrien	/* ...or...  */
36177391Sobrien	|| (/* ...clearing stickies...  */
37177391Sobrien	    aflag
38177391Sobrien	    /* ...and...  */
39177391Sobrien	    && (/* ...there used to be a tag which subs in Name keys...  */
40179561Sobrien		(vers->entdata->tag && !isdigit (vers->entdata->tag[0])
41179561Sobrien		    && vers->tag && !isdigit (vers->tag[0])
42179561Sobrien		    && strcmp (vers->entdata->tag, vers->tag))
43177391Sobrien		/* ...or there used to be a keyword mode which may be
44177391Sobrien		 * changed by -A...
45177391Sobrien		 */
46177391Sobrien		|| (strlen (vers->entdata->options)
47179619Sobrien		    && strcmp (vers->entdata->options, vers->options)
48177391Sobrien		    && strcmp (vers->entdata->options, "-kkv")
49177391Sobrien		    && strcmp (vers->entdata->options, "-kb"))))
50177391Sobrien	/* ...or...  */
51177391Sobrien	|| (/* ...this is not commit...  */
52177391Sobrien	    strcmp (cvs_cmd_name, "commit")
53177391Sobrien	    /* ...and...  */
54177391Sobrien	    && (/* ...the tag is changing in a way that affects Name keys...  */
55177391Sobrien		(vers->entdata->tag && vers->tag
56177391Sobrien		 && strcmp (vers->entdata->tag, vers->tag)
57177391Sobrien		 && !(isdigit (vers->entdata->tag[0])
58177391Sobrien		      && isdigit (vers->entdata->tag[0])))
59177391Sobrien		|| (!vers->entdata->tag && vers->tag
60177391Sobrien		    && !isdigit (vers->tag[0])))))
61177391Sobrien	retval = 1;
62177391Sobrien    else
63177391Sobrien	retval = 0;
64177391Sobrien
65177391Sobrien    return retval;
66177391Sobrien}
67177391Sobrien
68177391Sobrien
69177391Sobrien
7017721Speter/*
7117721Speter * Classify the state of a file
7217721Speter */
7317721SpeterCtype
7425839SpeterClassify_File (finfo, tag, date, options, force_tag_match, aflag, versp,
7525839Speter	       pipeout)
7625839Speter    struct file_info *finfo;
7717721Speter    char *tag;
7817721Speter    char *date;
7932785Speter
8032785Speter    /* Keyword expansion options.  Can be either NULL or "" to
8132785Speter       indicate none are specified here.  */
8217721Speter    char *options;
8332785Speter
8417721Speter    int force_tag_match;
8517721Speter    int aflag;
8617721Speter    Vers_TS **versp;
8717721Speter    int pipeout;
8817721Speter{
8917721Speter    Vers_TS *vers;
9017721Speter    Ctype ret;
9117721Speter
9217721Speter    /* get all kinds of good data about the file */
9325839Speter    vers = Version_TS (finfo, options, tag, date,
9425839Speter		       force_tag_match, 0);
9517721Speter
9617721Speter    if (vers->vn_user == NULL)
9717721Speter    {
9817721Speter	/* No entry available, ts_rcs is invalid */
9917721Speter	if (vers->vn_rcs == NULL)
10017721Speter	{
10117721Speter	    /* there is no RCS file either */
10217721Speter	    if (vers->ts_user == NULL)
10317721Speter	    {
10417721Speter		/* there is no user file */
10525839Speter		/* FIXME: Why do we skip this message if vers->tag or
10625839Speter		   vers->date is set?  It causes "cvs update -r tag98 foo"
10725839Speter		   to silently do nothing, which is seriously confusing
10825839Speter		   behavior.  "cvs update foo" gives this message, which
10925839Speter		   is what I would expect.  */
11017721Speter		if (!force_tag_match || !(vers->tag || vers->date))
11117721Speter		    if (!really_quiet)
11225839Speter			error (0, 0, "nothing known about %s", finfo->fullname);
11317721Speter		ret = T_UNKNOWN;
11417721Speter	    }
11517721Speter	    else
11617721Speter	    {
11717721Speter		/* there is a user file */
11825839Speter		/* FIXME: Why do we skip this message if vers->tag or
11925839Speter		   vers->date is set?  It causes "cvs update -r tag98 foo"
12025839Speter		   to silently do nothing, which is seriously confusing
12125839Speter		   behavior.  "cvs update foo" gives this message, which
12225839Speter		   is what I would expect.  */
12317721Speter		if (!force_tag_match || !(vers->tag || vers->date))
12417721Speter		    if (!really_quiet)
12532785Speter			error (0, 0, "use `%s add' to create an entry for %s",
12632785Speter			       program_name, finfo->fullname);
12717721Speter		ret = T_UNKNOWN;
12817721Speter	    }
12917721Speter	}
13017721Speter	else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
13117721Speter	{
13281404Speter	    /* there is an RCS file, but it's dead */
13317721Speter	    if (vers->ts_user == NULL)
13425839Speter		ret = T_UPTODATE;
13517721Speter	    else
13617721Speter	    {
13732785Speter		error (0, 0, "use `%s add' to create an entry for %s",
13832785Speter		       program_name, finfo->fullname);
13917721Speter		ret = T_UNKNOWN;
14017721Speter	    }
14117721Speter	}
14281404Speter	else if (!pipeout && vers->ts_user && No_Difference (finfo, vers))
14317721Speter	{
14481404Speter	    /* the files were different so it is a conflict */
14581404Speter	    if (!really_quiet)
14681404Speter		error (0, 0, "move away %s; it is in the way",
14781404Speter		       finfo->fullname);
14881404Speter	    ret = T_CONFLICT;
14917721Speter	}
15081404Speter	else
15181404Speter	    /* no user file or no difference, just checkout */
15281404Speter	    ret = T_CHECKOUT;
15317721Speter    }
15417721Speter    else if (strcmp (vers->vn_user, "0") == 0)
15517721Speter    {
15617721Speter	/* An entry for a new-born file; ts_rcs is dummy */
15717721Speter
15817721Speter	if (vers->ts_user == NULL)
15917721Speter	{
160107484Speter	    if (pipeout)
161107484Speter	    {
162107484Speter		ret = T_CHECKOUT;
163107484Speter	    }
164107484Speter	    else
165107484Speter	    {
166107484Speter		/*
167107484Speter		 * There is no user file, but there should be one; remove the
168107484Speter		 * entry
169107484Speter		 */
170107484Speter		if (!really_quiet)
171107484Speter		    error (0, 0, "warning: new-born %s has disappeared",
172107484Speter			   finfo->fullname);
173107484Speter		ret = T_REMOVE_ENTRY;
174107484Speter	    }
17517721Speter	}
17681404Speter	else if (vers->vn_rcs == NULL ||
17781404Speter		 RCS_isdead (vers->srcfile, vers->vn_rcs))
17881404Speter	    /* No RCS file or RCS file revision is dead  */
17981404Speter	    ret = T_ADDED;
18017721Speter	else
18117721Speter	{
182107484Speter	    if (pipeout)
18381404Speter	    {
184107484Speter		ret = T_CHECKOUT;
18581404Speter	    }
18617721Speter	    else
18717721Speter	    {
188107484Speter		if (vers->srcfile->flags & INATTIC
189107484Speter		    && vers->srcfile->flags & VALID)
190107484Speter		{
191107484Speter		    /* This file has been added on some branch other than
192107484Speter		       the one we are looking at.  In the branch we are
193107484Speter		       looking at, the file was already valid.  */
194107484Speter		    if (!really_quiet)
195107484Speter			error (0, 0,
196107484Speter			   "conflict: %s has been added, but already exists",
197107484Speter			       finfo->fullname);
198107484Speter		}
199107484Speter		else
200107484Speter		{
201107484Speter		    /*
202107484Speter		     * There is an RCS file, so someone else must have checked
203107484Speter		     * one in behind our back; conflict
204107484Speter		     */
205107484Speter		    if (!really_quiet)
206107484Speter			error (0, 0,
20781404Speter			   "conflict: %s created independently by second party",
208107484Speter			       finfo->fullname);
209107484Speter		}
210107484Speter		ret = T_CONFLICT;
21117721Speter	    }
21217721Speter	}
21317721Speter    }
21417721Speter    else if (vers->vn_user[0] == '-')
21517721Speter    {
21617721Speter	/* An entry for a removed file, ts_rcs is invalid */
21717721Speter
21817721Speter	if (vers->ts_user == NULL)
21917721Speter	{
22017721Speter	    /* There is no user file (as it should be) */
22117721Speter
22225839Speter	    if (vers->vn_rcs == NULL
22325839Speter		|| RCS_isdead (vers->srcfile, vers->vn_rcs))
22417721Speter	    {
22517721Speter
22617721Speter		/*
22717721Speter		 * There is no RCS file; this is all-right, but it has been
22817721Speter		 * removed independently by a second party; remove the entry
22917721Speter		 */
23017721Speter		ret = T_REMOVE_ENTRY;
23117721Speter	    }
23281404Speter	    else if (strcmp (vers->vn_rcs, vers->vn_user + 1) == 0)
23317721Speter		/*
23417721Speter		 * The RCS file is the same version as the user file was, and
23517721Speter		 * that's OK; remove it
23617721Speter		 */
23717721Speter		ret = T_REMOVED;
23881404Speter	    else if (pipeout)
23981404Speter		/*
24081404Speter		 * The RCS file doesn't match the user's file, but it doesn't
24181404Speter		 * matter in this case
24281404Speter		 */
24381404Speter		ret = T_NEEDS_MERGE;
24417721Speter	    else
24517721Speter	    {
24617721Speter
24717721Speter		/*
24817721Speter		 * The RCS file is a newer version than the removed user file
24917721Speter		 * and this is definitely not OK; make it a conflict.
25017721Speter		 */
25117721Speter		if (!really_quiet)
25217721Speter		    error (0, 0,
25317721Speter			   "conflict: removed %s was modified by second party",
25425839Speter			   finfo->fullname);
25517721Speter		ret = T_CONFLICT;
25617721Speter	    }
25717721Speter	}
25817721Speter	else
25917721Speter	{
26017721Speter	    /* The user file shouldn't be there */
26117721Speter	    if (!really_quiet)
26217721Speter		error (0, 0, "%s should be removed and is still there",
26325839Speter		       finfo->fullname);
26417721Speter	    ret = T_REMOVED;
26517721Speter	}
26617721Speter    }
26717721Speter    else
26817721Speter    {
26917721Speter	/* A normal entry, TS_Rcs is valid */
27081404Speter	if (vers->vn_rcs == NULL || RCS_isdead (vers->srcfile, vers->vn_rcs))
27117721Speter	{
27217721Speter	    /* There is no RCS file */
27317721Speter
27417721Speter	    if (vers->ts_user == NULL)
27517721Speter	    {
27617721Speter		/* There is no user file, so just remove the entry */
27717721Speter		if (!really_quiet)
27817721Speter		    error (0, 0, "warning: %s is not (any longer) pertinent",
27925839Speter			   finfo->fullname);
28017721Speter		ret = T_REMOVE_ENTRY;
28117721Speter	    }
28217721Speter	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
28317721Speter	    {
28417721Speter
28517721Speter		/*
28617721Speter		 * The user file is still unmodified, so just remove it from
28717721Speter		 * the entry list
28817721Speter		 */
28917721Speter		if (!really_quiet)
29017721Speter		    error (0, 0, "%s is no longer in the repository",
29125839Speter			   finfo->fullname);
29217721Speter		ret = T_REMOVE_ENTRY;
29317721Speter	    }
29481404Speter	    else if (No_Difference (finfo, vers))
29517721Speter	    {
29681404Speter		/* they are different -> conflict */
29781404Speter		if (!really_quiet)
29881404Speter		    error (0, 0,
29917721Speter	       "conflict: %s is modified but no longer in the repository",
30025839Speter			   finfo->fullname);
30181404Speter		ret = T_CONFLICT;
30217721Speter	    }
30381404Speter	    else
30481404Speter	    {
30581404Speter		/* they weren't really different */
30681404Speter		if (!really_quiet)
30781404Speter		    error (0, 0,
30881404Speter			   "warning: %s is not (any longer) pertinent",
30981404Speter			   finfo->fullname);
31081404Speter		ret = T_REMOVE_ENTRY;
31181404Speter	    }
31217721Speter	}
31317721Speter	else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
31417721Speter	{
31517721Speter	    /* The RCS file is the same version as the user file */
31617721Speter
31717721Speter	    if (vers->ts_user == NULL)
31817721Speter	    {
31917721Speter
32017721Speter		/*
32117721Speter		 * There is no user file, so note that it was lost and
32217721Speter		 * extract a new version
32317721Speter		 */
324128266Speter		/* Comparing the cvs_cmd_name against "update", in
32544852Speter		   addition to being an ugly way to operate, means
32644852Speter		   that this message does not get printed by the
32744852Speter		   server.  That might be considered just a straight
32844852Speter		   bug, although there is one subtlety: that case also
32944852Speter		   gets hit when a patch fails and the client fetches
33044852Speter		   a file.  I'm not sure there is currently any way
33144852Speter		   for the server to distinguish those two cases.  */
332128266Speter		if (strcmp (cvs_cmd_name, "update") == 0)
33317721Speter		    if (!really_quiet)
33425839Speter			error (0, 0, "warning: %s was lost", finfo->fullname);
33517721Speter		ret = T_CHECKOUT;
33617721Speter	    }
337175261Sobrien	    else if (!strcmp (vers->ts_user,
338175261Sobrien			      vers->ts_conflict
339175261Sobrien			      ? vers->ts_conflict : vers->ts_rcs))
34017721Speter	    {
34117721Speter
34217721Speter		/*
34317721Speter		 * The user file is still unmodified, so nothing special at
34417721Speter		 * all to do -- no lists updated, unless the sticky -k option
34517721Speter		 * has changed.  If the sticky tag has changed, we just need
34617721Speter		 * to re-register the entry
34717721Speter		 */
34834461Speter		/* TODO: decide whether we need to check file permissions
34934461Speter		   for a mismatch, and return T_CONFLICT if so. */
350177391Sobrien		if (keywords_may_change (aflag, vers))
351177391Sobrien		    ret = T_PATCH;
352175261Sobrien		else if (vers->ts_conflict)
353175261Sobrien		    ret = T_CONFLICT;
35417721Speter		else
35517721Speter		{
356177391Sobrien		    ret = T_UPTODATE;
35732785Speter		    sticky_ck (finfo, aflag, vers);
35817721Speter		}
35917721Speter	    }
36081404Speter	    else if (No_Difference (finfo, vers))
36117721Speter	    {
36217721Speter
36317721Speter		/*
36481404Speter		 * they really are different; modified if we aren't
36581404Speter		 * changing any sticky -k options, else needs merge
36617721Speter		 */
36717721Speter#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
36881404Speter		if (strcmp (vers->entdata->options ?
36981404Speter		       vers->entdata->options : "", vers->options) == 0)
37081404Speter		    ret = T_MODIFIED;
37181404Speter		else
37281404Speter		    ret = T_NEEDS_MERGE;
37317721Speter#else
374175261Sobrien		/* Files with conflict markers and new timestamps fall through
375175261Sobrien		 * here, but they need to.  T_CONFLICT is an error in
376175261Sobrien		 * commit_fileproc, whereas T_CONFLICT with conflict markers
377175261Sobrien		 * is caught but only warned about.  Similarly, update_fileproc
378175261Sobrien		 * currently reregisters a file that was conflicted but lost
379175261Sobrien		 * its markers.
380175261Sobrien		 */
38181404Speter		ret = T_MODIFIED;
38281404Speter		sticky_ck (finfo, aflag, vers);
38317721Speter#endif
38481404Speter	    }
38581404Speter	    else if (strcmp (vers->entdata->options ?
38681404Speter		       vers->entdata->options : "", vers->options) != 0)
38781404Speter	    {
38881404Speter		/* file has not changed; check out if -k changed */
38981404Speter		ret = T_CHECKOUT;
39081404Speter	    }
39181404Speter	    else
39281404Speter	    {
39317721Speter
39481404Speter		/*
39581404Speter		 * else -> note that No_Difference will Register the
39681404Speter		 * file already for us, using the new tag/date. This
39781404Speter		 * is the desired behaviour
39881404Speter		 */
39981404Speter		ret = T_UPTODATE;
40017721Speter	    }
40117721Speter	}
40217721Speter	else
40317721Speter	{
40417721Speter	    /* The RCS file is a newer version than the user file */
40517721Speter
40617721Speter	    if (vers->ts_user == NULL)
40717721Speter	    {
40817721Speter		/* There is no user file, so just get it */
40917721Speter
41044852Speter		/* See comment at other "update" compare, for more
41144852Speter		   thoughts on this comparison.  */
412128266Speter		if (strcmp (cvs_cmd_name, "update") == 0)
41317721Speter		    if (!really_quiet)
41425839Speter			error (0, 0, "warning: %s was lost", finfo->fullname);
41517721Speter		ret = T_CHECKOUT;
41617721Speter	    }
41717721Speter	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
41817721Speter
41917721Speter		/*
42017721Speter		 * The user file is still unmodified, so just get it as well
42117721Speter		 */
422177391Sobrien		ret = T_PATCH;
42381404Speter	    else if (No_Difference (finfo, vers))
42481404Speter		/* really modified, needs to merge */
42581404Speter		ret = T_NEEDS_MERGE;
42617721Speter	    else
42781404Speter		ret = T_PATCH;
42817721Speter	}
42917721Speter    }
43017721Speter
43117721Speter    /* free up the vers struct, or just return it */
43217721Speter    if (versp != (Vers_TS **) NULL)
43317721Speter	*versp = vers;
43417721Speter    else
43517721Speter	freevers_ts (&vers);
43617721Speter
43717721Speter    /* return the status of the file */
43817721Speter    return (ret);
43917721Speter}
44017721Speter
44117721Speterstatic void
44232785Spetersticky_ck (finfo, aflag, vers)
44332785Speter    struct file_info *finfo;
44417721Speter    int aflag;
44517721Speter    Vers_TS *vers;
44617721Speter{
44717721Speter    if (aflag || vers->tag || vers->date)
44817721Speter    {
44917721Speter	char *enttag = vers->entdata->tag;
45017721Speter	char *entdate = vers->entdata->date;
45117721Speter
45217721Speter	if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
45317721Speter	    ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
45417721Speter	    (entdate && vers->date && strcmp (entdate, vers->date)) ||
45517721Speter	    ((entdate && !vers->date) || (!entdate && vers->date)))
45617721Speter	{
45732785Speter	    Register (finfo->entries, finfo->file, vers->vn_user, vers->ts_rcs,
45817721Speter		      vers->options, vers->tag, vers->date, vers->ts_conflict);
45917721Speter
46017721Speter#ifdef SERVER_SUPPORT
46117721Speter	    if (server_active)
46217721Speter	    {
46317721Speter		/* We need to update the entries line on the client side.
46417721Speter		   It is possible we will later update it again via
46517721Speter		   server_updated or some such, but that is OK.  */
46617721Speter		server_update_entries
46732785Speter		  (finfo->file, finfo->update_dir, finfo->repository,
46817721Speter		   strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
46917721Speter		   SERVER_UPDATED : SERVER_MERGED);
47017721Speter	    }
47117721Speter#endif
47217721Speter	}
47317721Speter    }
47417721Speter}
475