classify.c revision 179561
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
1517721Speter#include "cvs.h"
1617721Speter
1732785Speterstatic void sticky_ck PROTO ((struct file_info *finfo, int aflag,
1832785Speter			      Vers_TS * vers));
1917721Speter
20177391Sobrien
21177391Sobrien
22177391Sobrienstatic inline int keywords_may_change PROTO ((int aflag, Vers_TS * vers));
23177391Sobrienstatic inline int
24177391Sobrienkeywords_may_change (aflag, vers)
25177391Sobrien    int aflag;
26177391Sobrien    Vers_TS * vers;
27177391Sobrien{
28177391Sobrien    int retval;
29177391Sobrien
30177391Sobrien    if (/* Options are different...  */
31177391Sobrien	strcmp (vers->entdata->options, vers->options)
32177391Sobrien	/* ...or...  */
33177391Sobrien	|| (/* ...clearing stickies...  */
34177391Sobrien	    aflag
35177391Sobrien	    /* ...and...  */
36177391Sobrien	    && (/* ...there used to be a tag which subs in Name keys...  */
37179561Sobrien		(vers->entdata->tag && !isdigit (vers->entdata->tag[0])
38179561Sobrien		    && vers->tag && !isdigit (vers->tag[0])
39179561Sobrien		    && strcmp (vers->entdata->tag, vers->tag))
40177391Sobrien		/* ...or there used to be a keyword mode which may be
41177391Sobrien		 * changed by -A...
42177391Sobrien		 */
43177391Sobrien		|| (strlen (vers->entdata->options)
44177391Sobrien		    && strcmp (vers->entdata->options, "-kkv")
45177391Sobrien		    && strcmp (vers->entdata->options, "-kb"))))
46177391Sobrien	/* ...or...  */
47177391Sobrien	|| (/* ...this is not commit...  */
48177391Sobrien	    strcmp (cvs_cmd_name, "commit")
49177391Sobrien	    /* ...and...  */
50177391Sobrien	    && (/* ...the tag is changing in a way that affects Name keys...  */
51177391Sobrien		(vers->entdata->tag && vers->tag
52177391Sobrien		 && strcmp (vers->entdata->tag, vers->tag)
53177391Sobrien		 && !(isdigit (vers->entdata->tag[0])
54177391Sobrien		      && isdigit (vers->entdata->tag[0])))
55177391Sobrien		|| (!vers->entdata->tag && vers->tag
56177391Sobrien		    && !isdigit (vers->tag[0])))))
57177391Sobrien	retval = 1;
58177391Sobrien    else
59177391Sobrien	retval = 0;
60177391Sobrien
61177391Sobrien    return retval;
62177391Sobrien}
63177391Sobrien
64177391Sobrien
65177391Sobrien
6617721Speter/*
6717721Speter * Classify the state of a file
6817721Speter */
6917721SpeterCtype
7025839SpeterClassify_File (finfo, tag, date, options, force_tag_match, aflag, versp,
7125839Speter	       pipeout)
7225839Speter    struct file_info *finfo;
7317721Speter    char *tag;
7417721Speter    char *date;
7532785Speter
7632785Speter    /* Keyword expansion options.  Can be either NULL or "" to
7732785Speter       indicate none are specified here.  */
7817721Speter    char *options;
7932785Speter
8017721Speter    int force_tag_match;
8117721Speter    int aflag;
8217721Speter    Vers_TS **versp;
8317721Speter    int pipeout;
8417721Speter{
8517721Speter    Vers_TS *vers;
8617721Speter    Ctype ret;
8717721Speter
8817721Speter    /* get all kinds of good data about the file */
8925839Speter    vers = Version_TS (finfo, options, tag, date,
9025839Speter		       force_tag_match, 0);
9117721Speter
9217721Speter    if (vers->vn_user == NULL)
9317721Speter    {
9417721Speter	/* No entry available, ts_rcs is invalid */
9517721Speter	if (vers->vn_rcs == NULL)
9617721Speter	{
9717721Speter	    /* there is no RCS file either */
9817721Speter	    if (vers->ts_user == NULL)
9917721Speter	    {
10017721Speter		/* there is no user file */
10125839Speter		/* FIXME: Why do we skip this message if vers->tag or
10225839Speter		   vers->date is set?  It causes "cvs update -r tag98 foo"
10325839Speter		   to silently do nothing, which is seriously confusing
10425839Speter		   behavior.  "cvs update foo" gives this message, which
10525839Speter		   is what I would expect.  */
10617721Speter		if (!force_tag_match || !(vers->tag || vers->date))
10717721Speter		    if (!really_quiet)
10825839Speter			error (0, 0, "nothing known about %s", finfo->fullname);
10917721Speter		ret = T_UNKNOWN;
11017721Speter	    }
11117721Speter	    else
11217721Speter	    {
11317721Speter		/* there is a user file */
11425839Speter		/* FIXME: Why do we skip this message if vers->tag or
11525839Speter		   vers->date is set?  It causes "cvs update -r tag98 foo"
11625839Speter		   to silently do nothing, which is seriously confusing
11725839Speter		   behavior.  "cvs update foo" gives this message, which
11825839Speter		   is what I would expect.  */
11917721Speter		if (!force_tag_match || !(vers->tag || vers->date))
12017721Speter		    if (!really_quiet)
12132785Speter			error (0, 0, "use `%s add' to create an entry for %s",
12232785Speter			       program_name, finfo->fullname);
12317721Speter		ret = T_UNKNOWN;
12417721Speter	    }
12517721Speter	}
12617721Speter	else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
12717721Speter	{
12881404Speter	    /* there is an RCS file, but it's dead */
12917721Speter	    if (vers->ts_user == NULL)
13025839Speter		ret = T_UPTODATE;
13117721Speter	    else
13217721Speter	    {
13332785Speter		error (0, 0, "use `%s add' to create an entry for %s",
13432785Speter		       program_name, finfo->fullname);
13517721Speter		ret = T_UNKNOWN;
13617721Speter	    }
13717721Speter	}
13881404Speter	else if (!pipeout && vers->ts_user && No_Difference (finfo, vers))
13917721Speter	{
14081404Speter	    /* the files were different so it is a conflict */
14181404Speter	    if (!really_quiet)
14281404Speter		error (0, 0, "move away %s; it is in the way",
14381404Speter		       finfo->fullname);
14481404Speter	    ret = T_CONFLICT;
14517721Speter	}
14681404Speter	else
14781404Speter	    /* no user file or no difference, just checkout */
14881404Speter	    ret = T_CHECKOUT;
14917721Speter    }
15017721Speter    else if (strcmp (vers->vn_user, "0") == 0)
15117721Speter    {
15217721Speter	/* An entry for a new-born file; ts_rcs is dummy */
15317721Speter
15417721Speter	if (vers->ts_user == NULL)
15517721Speter	{
156107484Speter	    if (pipeout)
157107484Speter	    {
158107484Speter		ret = T_CHECKOUT;
159107484Speter	    }
160107484Speter	    else
161107484Speter	    {
162107484Speter		/*
163107484Speter		 * There is no user file, but there should be one; remove the
164107484Speter		 * entry
165107484Speter		 */
166107484Speter		if (!really_quiet)
167107484Speter		    error (0, 0, "warning: new-born %s has disappeared",
168107484Speter			   finfo->fullname);
169107484Speter		ret = T_REMOVE_ENTRY;
170107484Speter	    }
17117721Speter	}
17281404Speter	else if (vers->vn_rcs == NULL ||
17381404Speter		 RCS_isdead (vers->srcfile, vers->vn_rcs))
17481404Speter	    /* No RCS file or RCS file revision is dead  */
17581404Speter	    ret = T_ADDED;
17617721Speter	else
17717721Speter	{
178107484Speter	    if (pipeout)
17981404Speter	    {
180107484Speter		ret = T_CHECKOUT;
18181404Speter	    }
18217721Speter	    else
18317721Speter	    {
184107484Speter		if (vers->srcfile->flags & INATTIC
185107484Speter		    && vers->srcfile->flags & VALID)
186107484Speter		{
187107484Speter		    /* This file has been added on some branch other than
188107484Speter		       the one we are looking at.  In the branch we are
189107484Speter		       looking at, the file was already valid.  */
190107484Speter		    if (!really_quiet)
191107484Speter			error (0, 0,
192107484Speter			   "conflict: %s has been added, but already exists",
193107484Speter			       finfo->fullname);
194107484Speter		}
195107484Speter		else
196107484Speter		{
197107484Speter		    /*
198107484Speter		     * There is an RCS file, so someone else must have checked
199107484Speter		     * one in behind our back; conflict
200107484Speter		     */
201107484Speter		    if (!really_quiet)
202107484Speter			error (0, 0,
20381404Speter			   "conflict: %s created independently by second party",
204107484Speter			       finfo->fullname);
205107484Speter		}
206107484Speter		ret = T_CONFLICT;
20717721Speter	    }
20817721Speter	}
20917721Speter    }
21017721Speter    else if (vers->vn_user[0] == '-')
21117721Speter    {
21217721Speter	/* An entry for a removed file, ts_rcs is invalid */
21317721Speter
21417721Speter	if (vers->ts_user == NULL)
21517721Speter	{
21617721Speter	    /* There is no user file (as it should be) */
21717721Speter
21825839Speter	    if (vers->vn_rcs == NULL
21925839Speter		|| RCS_isdead (vers->srcfile, vers->vn_rcs))
22017721Speter	    {
22117721Speter
22217721Speter		/*
22317721Speter		 * There is no RCS file; this is all-right, but it has been
22417721Speter		 * removed independently by a second party; remove the entry
22517721Speter		 */
22617721Speter		ret = T_REMOVE_ENTRY;
22717721Speter	    }
22881404Speter	    else if (strcmp (vers->vn_rcs, vers->vn_user + 1) == 0)
22917721Speter		/*
23017721Speter		 * The RCS file is the same version as the user file was, and
23117721Speter		 * that's OK; remove it
23217721Speter		 */
23317721Speter		ret = T_REMOVED;
23481404Speter	    else if (pipeout)
23581404Speter		/*
23681404Speter		 * The RCS file doesn't match the user's file, but it doesn't
23781404Speter		 * matter in this case
23881404Speter		 */
23981404Speter		ret = T_NEEDS_MERGE;
24017721Speter	    else
24117721Speter	    {
24217721Speter
24317721Speter		/*
24417721Speter		 * The RCS file is a newer version than the removed user file
24517721Speter		 * and this is definitely not OK; make it a conflict.
24617721Speter		 */
24717721Speter		if (!really_quiet)
24817721Speter		    error (0, 0,
24917721Speter			   "conflict: removed %s was modified by second party",
25025839Speter			   finfo->fullname);
25117721Speter		ret = T_CONFLICT;
25217721Speter	    }
25317721Speter	}
25417721Speter	else
25517721Speter	{
25617721Speter	    /* The user file shouldn't be there */
25717721Speter	    if (!really_quiet)
25817721Speter		error (0, 0, "%s should be removed and is still there",
25925839Speter		       finfo->fullname);
26017721Speter	    ret = T_REMOVED;
26117721Speter	}
26217721Speter    }
26317721Speter    else
26417721Speter    {
26517721Speter	/* A normal entry, TS_Rcs is valid */
26681404Speter	if (vers->vn_rcs == NULL || RCS_isdead (vers->srcfile, vers->vn_rcs))
26717721Speter	{
26817721Speter	    /* There is no RCS file */
26917721Speter
27017721Speter	    if (vers->ts_user == NULL)
27117721Speter	    {
27217721Speter		/* There is no user file, so just remove the entry */
27317721Speter		if (!really_quiet)
27417721Speter		    error (0, 0, "warning: %s is not (any longer) pertinent",
27525839Speter			   finfo->fullname);
27617721Speter		ret = T_REMOVE_ENTRY;
27717721Speter	    }
27817721Speter	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
27917721Speter	    {
28017721Speter
28117721Speter		/*
28217721Speter		 * The user file is still unmodified, so just remove it from
28317721Speter		 * the entry list
28417721Speter		 */
28517721Speter		if (!really_quiet)
28617721Speter		    error (0, 0, "%s is no longer in the repository",
28725839Speter			   finfo->fullname);
28817721Speter		ret = T_REMOVE_ENTRY;
28917721Speter	    }
29081404Speter	    else if (No_Difference (finfo, vers))
29117721Speter	    {
29281404Speter		/* they are different -> conflict */
29381404Speter		if (!really_quiet)
29481404Speter		    error (0, 0,
29517721Speter	       "conflict: %s is modified but no longer in the repository",
29625839Speter			   finfo->fullname);
29781404Speter		ret = T_CONFLICT;
29817721Speter	    }
29981404Speter	    else
30081404Speter	    {
30181404Speter		/* they weren't really different */
30281404Speter		if (!really_quiet)
30381404Speter		    error (0, 0,
30481404Speter			   "warning: %s is not (any longer) pertinent",
30581404Speter			   finfo->fullname);
30681404Speter		ret = T_REMOVE_ENTRY;
30781404Speter	    }
30817721Speter	}
30917721Speter	else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
31017721Speter	{
31117721Speter	    /* The RCS file is the same version as the user file */
31217721Speter
31317721Speter	    if (vers->ts_user == NULL)
31417721Speter	    {
31517721Speter
31617721Speter		/*
31717721Speter		 * There is no user file, so note that it was lost and
31817721Speter		 * extract a new version
31917721Speter		 */
320128266Speter		/* Comparing the cvs_cmd_name against "update", in
32144852Speter		   addition to being an ugly way to operate, means
32244852Speter		   that this message does not get printed by the
32344852Speter		   server.  That might be considered just a straight
32444852Speter		   bug, although there is one subtlety: that case also
32544852Speter		   gets hit when a patch fails and the client fetches
32644852Speter		   a file.  I'm not sure there is currently any way
32744852Speter		   for the server to distinguish those two cases.  */
328128266Speter		if (strcmp (cvs_cmd_name, "update") == 0)
32917721Speter		    if (!really_quiet)
33025839Speter			error (0, 0, "warning: %s was lost", finfo->fullname);
33117721Speter		ret = T_CHECKOUT;
33217721Speter	    }
333175261Sobrien	    else if (!strcmp (vers->ts_user,
334175261Sobrien			      vers->ts_conflict
335175261Sobrien			      ? vers->ts_conflict : vers->ts_rcs))
33617721Speter	    {
33717721Speter
33817721Speter		/*
33917721Speter		 * The user file is still unmodified, so nothing special at
34017721Speter		 * all to do -- no lists updated, unless the sticky -k option
34117721Speter		 * has changed.  If the sticky tag has changed, we just need
34217721Speter		 * to re-register the entry
34317721Speter		 */
34434461Speter		/* TODO: decide whether we need to check file permissions
34534461Speter		   for a mismatch, and return T_CONFLICT if so. */
346177391Sobrien		if (keywords_may_change (aflag, vers))
347177391Sobrien		    ret = T_PATCH;
348175261Sobrien		else if (vers->ts_conflict)
349175261Sobrien		    ret = T_CONFLICT;
35017721Speter		else
35117721Speter		{
352177391Sobrien		    ret = T_UPTODATE;
35332785Speter		    sticky_ck (finfo, aflag, vers);
35417721Speter		}
35517721Speter	    }
35681404Speter	    else if (No_Difference (finfo, vers))
35717721Speter	    {
35817721Speter
35917721Speter		/*
36081404Speter		 * they really are different; modified if we aren't
36181404Speter		 * changing any sticky -k options, else needs merge
36217721Speter		 */
36317721Speter#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
36481404Speter		if (strcmp (vers->entdata->options ?
36581404Speter		       vers->entdata->options : "", vers->options) == 0)
36681404Speter		    ret = T_MODIFIED;
36781404Speter		else
36881404Speter		    ret = T_NEEDS_MERGE;
36917721Speter#else
370175261Sobrien		/* Files with conflict markers and new timestamps fall through
371175261Sobrien		 * here, but they need to.  T_CONFLICT is an error in
372175261Sobrien		 * commit_fileproc, whereas T_CONFLICT with conflict markers
373175261Sobrien		 * is caught but only warned about.  Similarly, update_fileproc
374175261Sobrien		 * currently reregisters a file that was conflicted but lost
375175261Sobrien		 * its markers.
376175261Sobrien		 */
37781404Speter		ret = T_MODIFIED;
37881404Speter		sticky_ck (finfo, aflag, vers);
37917721Speter#endif
38081404Speter	    }
38181404Speter	    else if (strcmp (vers->entdata->options ?
38281404Speter		       vers->entdata->options : "", vers->options) != 0)
38381404Speter	    {
38481404Speter		/* file has not changed; check out if -k changed */
38581404Speter		ret = T_CHECKOUT;
38681404Speter	    }
38781404Speter	    else
38881404Speter	    {
38917721Speter
39081404Speter		/*
39181404Speter		 * else -> note that No_Difference will Register the
39281404Speter		 * file already for us, using the new tag/date. This
39381404Speter		 * is the desired behaviour
39481404Speter		 */
39581404Speter		ret = T_UPTODATE;
39617721Speter	    }
39717721Speter	}
39817721Speter	else
39917721Speter	{
40017721Speter	    /* The RCS file is a newer version than the user file */
40117721Speter
40217721Speter	    if (vers->ts_user == NULL)
40317721Speter	    {
40417721Speter		/* There is no user file, so just get it */
40517721Speter
40644852Speter		/* See comment at other "update" compare, for more
40744852Speter		   thoughts on this comparison.  */
408128266Speter		if (strcmp (cvs_cmd_name, "update") == 0)
40917721Speter		    if (!really_quiet)
41025839Speter			error (0, 0, "warning: %s was lost", finfo->fullname);
41117721Speter		ret = T_CHECKOUT;
41217721Speter	    }
41317721Speter	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
41417721Speter
41517721Speter		/*
41617721Speter		 * The user file is still unmodified, so just get it as well
41717721Speter		 */
418177391Sobrien		ret = T_PATCH;
41981404Speter	    else if (No_Difference (finfo, vers))
42081404Speter		/* really modified, needs to merge */
42181404Speter		ret = T_NEEDS_MERGE;
42217721Speter	    else
42381404Speter		ret = T_PATCH;
42417721Speter	}
42517721Speter    }
42617721Speter
42717721Speter    /* free up the vers struct, or just return it */
42817721Speter    if (versp != (Vers_TS **) NULL)
42917721Speter	*versp = vers;
43017721Speter    else
43117721Speter	freevers_ts (&vers);
43217721Speter
43317721Speter    /* return the status of the file */
43417721Speter    return (ret);
43517721Speter}
43617721Speter
43717721Speterstatic void
43832785Spetersticky_ck (finfo, aflag, vers)
43932785Speter    struct file_info *finfo;
44017721Speter    int aflag;
44117721Speter    Vers_TS *vers;
44217721Speter{
44317721Speter    if (aflag || vers->tag || vers->date)
44417721Speter    {
44517721Speter	char *enttag = vers->entdata->tag;
44617721Speter	char *entdate = vers->entdata->date;
44717721Speter
44817721Speter	if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
44917721Speter	    ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
45017721Speter	    (entdate && vers->date && strcmp (entdate, vers->date)) ||
45117721Speter	    ((entdate && !vers->date) || (!entdate && vers->date)))
45217721Speter	{
45332785Speter	    Register (finfo->entries, finfo->file, vers->vn_user, vers->ts_rcs,
45417721Speter		      vers->options, vers->tag, vers->date, vers->ts_conflict);
45517721Speter
45617721Speter#ifdef SERVER_SUPPORT
45717721Speter	    if (server_active)
45817721Speter	    {
45917721Speter		/* We need to update the entries line on the client side.
46017721Speter		   It is possible we will later update it again via
46117721Speter		   server_updated or some such, but that is OK.  */
46217721Speter		server_update_entries
46332785Speter		  (finfo->file, finfo->update_dir, finfo->repository,
46417721Speter		   strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
46517721Speter		   SERVER_UPDATED : SERVER_MERGED);
46617721Speter	    }
46717721Speter#endif
46817721Speter	}
46917721Speter    }
47017721Speter}
471