classify.c revision 177391
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...  */
37177391Sobrien		(vers->entdata->tag && !isdigit (vers->entdata->tag[0]))
38177391Sobrien		/* ...or there used to be a keyword mode which may be
39177391Sobrien		 * changed by -A...
40177391Sobrien		 */
41177391Sobrien		|| (strlen (vers->entdata->options)
42177391Sobrien		    && strcmp (vers->entdata->options, "-kkv")
43177391Sobrien		    && strcmp (vers->entdata->options, "-kb"))))
44177391Sobrien	/* ...or...  */
45177391Sobrien	|| (/* ...this is not commit...  */
46177391Sobrien	    strcmp (cvs_cmd_name, "commit")
47177391Sobrien	    /* ...and...  */
48177391Sobrien	    && (/* ...the tag is changing in a way that affects Name keys...  */
49177391Sobrien		(vers->entdata->tag && vers->tag
50177391Sobrien		 && strcmp (vers->entdata->tag, vers->tag)
51177391Sobrien		 && !(isdigit (vers->entdata->tag[0])
52177391Sobrien		      && isdigit (vers->entdata->tag[0])))
53177391Sobrien		|| (!vers->entdata->tag && vers->tag
54177391Sobrien		    && !isdigit (vers->tag[0])))))
55177391Sobrien	retval = 1;
56177391Sobrien    else
57177391Sobrien	retval = 0;
58177391Sobrien
59177391Sobrien    return retval;
60177391Sobrien}
61177391Sobrien
62177391Sobrien
63177391Sobrien
6417721Speter/*
6517721Speter * Classify the state of a file
6617721Speter */
6717721SpeterCtype
6825839SpeterClassify_File (finfo, tag, date, options, force_tag_match, aflag, versp,
6925839Speter	       pipeout)
7025839Speter    struct file_info *finfo;
7117721Speter    char *tag;
7217721Speter    char *date;
7332785Speter
7432785Speter    /* Keyword expansion options.  Can be either NULL or "" to
7532785Speter       indicate none are specified here.  */
7617721Speter    char *options;
7732785Speter
7817721Speter    int force_tag_match;
7917721Speter    int aflag;
8017721Speter    Vers_TS **versp;
8117721Speter    int pipeout;
8217721Speter{
8317721Speter    Vers_TS *vers;
8417721Speter    Ctype ret;
8517721Speter
8617721Speter    /* get all kinds of good data about the file */
8725839Speter    vers = Version_TS (finfo, options, tag, date,
8825839Speter		       force_tag_match, 0);
8917721Speter
9017721Speter    if (vers->vn_user == NULL)
9117721Speter    {
9217721Speter	/* No entry available, ts_rcs is invalid */
9317721Speter	if (vers->vn_rcs == NULL)
9417721Speter	{
9517721Speter	    /* there is no RCS file either */
9617721Speter	    if (vers->ts_user == NULL)
9717721Speter	    {
9817721Speter		/* there is no user file */
9925839Speter		/* FIXME: Why do we skip this message if vers->tag or
10025839Speter		   vers->date is set?  It causes "cvs update -r tag98 foo"
10125839Speter		   to silently do nothing, which is seriously confusing
10225839Speter		   behavior.  "cvs update foo" gives this message, which
10325839Speter		   is what I would expect.  */
10417721Speter		if (!force_tag_match || !(vers->tag || vers->date))
10517721Speter		    if (!really_quiet)
10625839Speter			error (0, 0, "nothing known about %s", finfo->fullname);
10717721Speter		ret = T_UNKNOWN;
10817721Speter	    }
10917721Speter	    else
11017721Speter	    {
11117721Speter		/* there is a user file */
11225839Speter		/* FIXME: Why do we skip this message if vers->tag or
11325839Speter		   vers->date is set?  It causes "cvs update -r tag98 foo"
11425839Speter		   to silently do nothing, which is seriously confusing
11525839Speter		   behavior.  "cvs update foo" gives this message, which
11625839Speter		   is what I would expect.  */
11717721Speter		if (!force_tag_match || !(vers->tag || vers->date))
11817721Speter		    if (!really_quiet)
11932785Speter			error (0, 0, "use `%s add' to create an entry for %s",
12032785Speter			       program_name, finfo->fullname);
12117721Speter		ret = T_UNKNOWN;
12217721Speter	    }
12317721Speter	}
12417721Speter	else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
12517721Speter	{
12681404Speter	    /* there is an RCS file, but it's dead */
12717721Speter	    if (vers->ts_user == NULL)
12825839Speter		ret = T_UPTODATE;
12917721Speter	    else
13017721Speter	    {
13132785Speter		error (0, 0, "use `%s add' to create an entry for %s",
13232785Speter		       program_name, finfo->fullname);
13317721Speter		ret = T_UNKNOWN;
13417721Speter	    }
13517721Speter	}
13681404Speter	else if (!pipeout && vers->ts_user && No_Difference (finfo, vers))
13717721Speter	{
13881404Speter	    /* the files were different so it is a conflict */
13981404Speter	    if (!really_quiet)
14081404Speter		error (0, 0, "move away %s; it is in the way",
14181404Speter		       finfo->fullname);
14281404Speter	    ret = T_CONFLICT;
14317721Speter	}
14481404Speter	else
14581404Speter	    /* no user file or no difference, just checkout */
14681404Speter	    ret = T_CHECKOUT;
14717721Speter    }
14817721Speter    else if (strcmp (vers->vn_user, "0") == 0)
14917721Speter    {
15017721Speter	/* An entry for a new-born file; ts_rcs is dummy */
15117721Speter
15217721Speter	if (vers->ts_user == NULL)
15317721Speter	{
154107484Speter	    if (pipeout)
155107484Speter	    {
156107484Speter		ret = T_CHECKOUT;
157107484Speter	    }
158107484Speter	    else
159107484Speter	    {
160107484Speter		/*
161107484Speter		 * There is no user file, but there should be one; remove the
162107484Speter		 * entry
163107484Speter		 */
164107484Speter		if (!really_quiet)
165107484Speter		    error (0, 0, "warning: new-born %s has disappeared",
166107484Speter			   finfo->fullname);
167107484Speter		ret = T_REMOVE_ENTRY;
168107484Speter	    }
16917721Speter	}
17081404Speter	else if (vers->vn_rcs == NULL ||
17181404Speter		 RCS_isdead (vers->srcfile, vers->vn_rcs))
17281404Speter	    /* No RCS file or RCS file revision is dead  */
17381404Speter	    ret = T_ADDED;
17417721Speter	else
17517721Speter	{
176107484Speter	    if (pipeout)
17781404Speter	    {
178107484Speter		ret = T_CHECKOUT;
17981404Speter	    }
18017721Speter	    else
18117721Speter	    {
182107484Speter		if (vers->srcfile->flags & INATTIC
183107484Speter		    && vers->srcfile->flags & VALID)
184107484Speter		{
185107484Speter		    /* This file has been added on some branch other than
186107484Speter		       the one we are looking at.  In the branch we are
187107484Speter		       looking at, the file was already valid.  */
188107484Speter		    if (!really_quiet)
189107484Speter			error (0, 0,
190107484Speter			   "conflict: %s has been added, but already exists",
191107484Speter			       finfo->fullname);
192107484Speter		}
193107484Speter		else
194107484Speter		{
195107484Speter		    /*
196107484Speter		     * There is an RCS file, so someone else must have checked
197107484Speter		     * one in behind our back; conflict
198107484Speter		     */
199107484Speter		    if (!really_quiet)
200107484Speter			error (0, 0,
20181404Speter			   "conflict: %s created independently by second party",
202107484Speter			       finfo->fullname);
203107484Speter		}
204107484Speter		ret = T_CONFLICT;
20517721Speter	    }
20617721Speter	}
20717721Speter    }
20817721Speter    else if (vers->vn_user[0] == '-')
20917721Speter    {
21017721Speter	/* An entry for a removed file, ts_rcs is invalid */
21117721Speter
21217721Speter	if (vers->ts_user == NULL)
21317721Speter	{
21417721Speter	    /* There is no user file (as it should be) */
21517721Speter
21625839Speter	    if (vers->vn_rcs == NULL
21725839Speter		|| RCS_isdead (vers->srcfile, vers->vn_rcs))
21817721Speter	    {
21917721Speter
22017721Speter		/*
22117721Speter		 * There is no RCS file; this is all-right, but it has been
22217721Speter		 * removed independently by a second party; remove the entry
22317721Speter		 */
22417721Speter		ret = T_REMOVE_ENTRY;
22517721Speter	    }
22681404Speter	    else if (strcmp (vers->vn_rcs, vers->vn_user + 1) == 0)
22717721Speter		/*
22817721Speter		 * The RCS file is the same version as the user file was, and
22917721Speter		 * that's OK; remove it
23017721Speter		 */
23117721Speter		ret = T_REMOVED;
23281404Speter	    else if (pipeout)
23381404Speter		/*
23481404Speter		 * The RCS file doesn't match the user's file, but it doesn't
23581404Speter		 * matter in this case
23681404Speter		 */
23781404Speter		ret = T_NEEDS_MERGE;
23817721Speter	    else
23917721Speter	    {
24017721Speter
24117721Speter		/*
24217721Speter		 * The RCS file is a newer version than the removed user file
24317721Speter		 * and this is definitely not OK; make it a conflict.
24417721Speter		 */
24517721Speter		if (!really_quiet)
24617721Speter		    error (0, 0,
24717721Speter			   "conflict: removed %s was modified by second party",
24825839Speter			   finfo->fullname);
24917721Speter		ret = T_CONFLICT;
25017721Speter	    }
25117721Speter	}
25217721Speter	else
25317721Speter	{
25417721Speter	    /* The user file shouldn't be there */
25517721Speter	    if (!really_quiet)
25617721Speter		error (0, 0, "%s should be removed and is still there",
25725839Speter		       finfo->fullname);
25817721Speter	    ret = T_REMOVED;
25917721Speter	}
26017721Speter    }
26117721Speter    else
26217721Speter    {
26317721Speter	/* A normal entry, TS_Rcs is valid */
26481404Speter	if (vers->vn_rcs == NULL || RCS_isdead (vers->srcfile, vers->vn_rcs))
26517721Speter	{
26617721Speter	    /* There is no RCS file */
26717721Speter
26817721Speter	    if (vers->ts_user == NULL)
26917721Speter	    {
27017721Speter		/* There is no user file, so just remove the entry */
27117721Speter		if (!really_quiet)
27217721Speter		    error (0, 0, "warning: %s is not (any longer) pertinent",
27325839Speter			   finfo->fullname);
27417721Speter		ret = T_REMOVE_ENTRY;
27517721Speter	    }
27617721Speter	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
27717721Speter	    {
27817721Speter
27917721Speter		/*
28017721Speter		 * The user file is still unmodified, so just remove it from
28117721Speter		 * the entry list
28217721Speter		 */
28317721Speter		if (!really_quiet)
28417721Speter		    error (0, 0, "%s is no longer in the repository",
28525839Speter			   finfo->fullname);
28617721Speter		ret = T_REMOVE_ENTRY;
28717721Speter	    }
28881404Speter	    else if (No_Difference (finfo, vers))
28917721Speter	    {
29081404Speter		/* they are different -> conflict */
29181404Speter		if (!really_quiet)
29281404Speter		    error (0, 0,
29317721Speter	       "conflict: %s is modified but no longer in the repository",
29425839Speter			   finfo->fullname);
29581404Speter		ret = T_CONFLICT;
29617721Speter	    }
29781404Speter	    else
29881404Speter	    {
29981404Speter		/* they weren't really different */
30081404Speter		if (!really_quiet)
30181404Speter		    error (0, 0,
30281404Speter			   "warning: %s is not (any longer) pertinent",
30381404Speter			   finfo->fullname);
30481404Speter		ret = T_REMOVE_ENTRY;
30581404Speter	    }
30617721Speter	}
30717721Speter	else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
30817721Speter	{
30917721Speter	    /* The RCS file is the same version as the user file */
31017721Speter
31117721Speter	    if (vers->ts_user == NULL)
31217721Speter	    {
31317721Speter
31417721Speter		/*
31517721Speter		 * There is no user file, so note that it was lost and
31617721Speter		 * extract a new version
31717721Speter		 */
318128266Speter		/* Comparing the cvs_cmd_name against "update", in
31944852Speter		   addition to being an ugly way to operate, means
32044852Speter		   that this message does not get printed by the
32144852Speter		   server.  That might be considered just a straight
32244852Speter		   bug, although there is one subtlety: that case also
32344852Speter		   gets hit when a patch fails and the client fetches
32444852Speter		   a file.  I'm not sure there is currently any way
32544852Speter		   for the server to distinguish those two cases.  */
326128266Speter		if (strcmp (cvs_cmd_name, "update") == 0)
32717721Speter		    if (!really_quiet)
32825839Speter			error (0, 0, "warning: %s was lost", finfo->fullname);
32917721Speter		ret = T_CHECKOUT;
33017721Speter	    }
331175261Sobrien	    else if (!strcmp (vers->ts_user,
332175261Sobrien			      vers->ts_conflict
333175261Sobrien			      ? vers->ts_conflict : vers->ts_rcs))
33417721Speter	    {
33517721Speter
33617721Speter		/*
33717721Speter		 * The user file is still unmodified, so nothing special at
33817721Speter		 * all to do -- no lists updated, unless the sticky -k option
33917721Speter		 * has changed.  If the sticky tag has changed, we just need
34017721Speter		 * to re-register the entry
34117721Speter		 */
34234461Speter		/* TODO: decide whether we need to check file permissions
34334461Speter		   for a mismatch, and return T_CONFLICT if so. */
344177391Sobrien		if (keywords_may_change (aflag, vers))
345177391Sobrien		    ret = T_PATCH;
346175261Sobrien		else if (vers->ts_conflict)
347175261Sobrien		    ret = T_CONFLICT;
34817721Speter		else
34917721Speter		{
350177391Sobrien		    ret = T_UPTODATE;
35132785Speter		    sticky_ck (finfo, aflag, vers);
35217721Speter		}
35317721Speter	    }
35481404Speter	    else if (No_Difference (finfo, vers))
35517721Speter	    {
35617721Speter
35717721Speter		/*
35881404Speter		 * they really are different; modified if we aren't
35981404Speter		 * changing any sticky -k options, else needs merge
36017721Speter		 */
36117721Speter#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
36281404Speter		if (strcmp (vers->entdata->options ?
36381404Speter		       vers->entdata->options : "", vers->options) == 0)
36481404Speter		    ret = T_MODIFIED;
36581404Speter		else
36681404Speter		    ret = T_NEEDS_MERGE;
36717721Speter#else
368175261Sobrien		/* Files with conflict markers and new timestamps fall through
369175261Sobrien		 * here, but they need to.  T_CONFLICT is an error in
370175261Sobrien		 * commit_fileproc, whereas T_CONFLICT with conflict markers
371175261Sobrien		 * is caught but only warned about.  Similarly, update_fileproc
372175261Sobrien		 * currently reregisters a file that was conflicted but lost
373175261Sobrien		 * its markers.
374175261Sobrien		 */
37581404Speter		ret = T_MODIFIED;
37681404Speter		sticky_ck (finfo, aflag, vers);
37717721Speter#endif
37881404Speter	    }
37981404Speter	    else if (strcmp (vers->entdata->options ?
38081404Speter		       vers->entdata->options : "", vers->options) != 0)
38181404Speter	    {
38281404Speter		/* file has not changed; check out if -k changed */
38381404Speter		ret = T_CHECKOUT;
38481404Speter	    }
38581404Speter	    else
38681404Speter	    {
38717721Speter
38881404Speter		/*
38981404Speter		 * else -> note that No_Difference will Register the
39081404Speter		 * file already for us, using the new tag/date. This
39181404Speter		 * is the desired behaviour
39281404Speter		 */
39381404Speter		ret = T_UPTODATE;
39417721Speter	    }
39517721Speter	}
39617721Speter	else
39717721Speter	{
39817721Speter	    /* The RCS file is a newer version than the user file */
39917721Speter
40017721Speter	    if (vers->ts_user == NULL)
40117721Speter	    {
40217721Speter		/* There is no user file, so just get it */
40317721Speter
40444852Speter		/* See comment at other "update" compare, for more
40544852Speter		   thoughts on this comparison.  */
406128266Speter		if (strcmp (cvs_cmd_name, "update") == 0)
40717721Speter		    if (!really_quiet)
40825839Speter			error (0, 0, "warning: %s was lost", finfo->fullname);
40917721Speter		ret = T_CHECKOUT;
41017721Speter	    }
41117721Speter	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
41217721Speter
41317721Speter		/*
41417721Speter		 * The user file is still unmodified, so just get it as well
41517721Speter		 */
416177391Sobrien		ret = T_PATCH;
41781404Speter	    else if (No_Difference (finfo, vers))
41881404Speter		/* really modified, needs to merge */
41981404Speter		ret = T_NEEDS_MERGE;
42017721Speter	    else
42181404Speter		ret = T_PATCH;
42217721Speter	}
42317721Speter    }
42417721Speter
42517721Speter    /* free up the vers struct, or just return it */
42617721Speter    if (versp != (Vers_TS **) NULL)
42717721Speter	*versp = vers;
42817721Speter    else
42917721Speter	freevers_ts (&vers);
43017721Speter
43117721Speter    /* return the status of the file */
43217721Speter    return (ret);
43317721Speter}
43417721Speter
43517721Speterstatic void
43632785Spetersticky_ck (finfo, aflag, vers)
43732785Speter    struct file_info *finfo;
43817721Speter    int aflag;
43917721Speter    Vers_TS *vers;
44017721Speter{
44117721Speter    if (aflag || vers->tag || vers->date)
44217721Speter    {
44317721Speter	char *enttag = vers->entdata->tag;
44417721Speter	char *entdate = vers->entdata->date;
44517721Speter
44617721Speter	if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
44717721Speter	    ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
44817721Speter	    (entdate && vers->date && strcmp (entdate, vers->date)) ||
44917721Speter	    ((entdate && !vers->date) || (!entdate && vers->date)))
45017721Speter	{
45132785Speter	    Register (finfo->entries, finfo->file, vers->vn_user, vers->ts_rcs,
45217721Speter		      vers->options, vers->tag, vers->date, vers->ts_conflict);
45317721Speter
45417721Speter#ifdef SERVER_SUPPORT
45517721Speter	    if (server_active)
45617721Speter	    {
45717721Speter		/* We need to update the entries line on the client side.
45817721Speter		   It is possible we will later update it again via
45917721Speter		   server_updated or some such, but that is OK.  */
46017721Speter		server_update_entries
46132785Speter		  (finfo->file, finfo->update_dir, finfo->repository,
46217721Speter		   strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
46317721Speter		   SERVER_UPDATED : SERVER_MERGED);
46417721Speter	    }
46517721Speter#endif
46617721Speter	}
46717721Speter    }
46817721Speter}
469