Home | History | Annotate | Line # | Download | only in src
      1 /*
      2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
      3  *
      4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
      5  *                                  and others.
      6  *
      7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
      8  * Portions Copyright (C) 1989-1992, Brian Berliner
      9  *
     10  * You may distribute under the terms of the GNU General Public License as
     11  * specified in the README file that comes with the CVS source distribution.
     12  *
     13  */
     14 #include <sys/cdefs.h>
     15 __RCSID("$NetBSD: classify.c,v 1.2 2016/05/17 14:00:09 christos Exp $");
     16 
     17 #include "cvs.h"
     18 
     19 static void sticky_ck (struct file_info *finfo, int aflag,
     20 			      Vers_TS * vers);
     21 
     22 /*
     23  * Classify the state of a file.
     24  *
     25  * INPUTS
     26  *   finfo		Information about the file to be classified.
     27  *   tag
     28  *   date
     29  *   options		Keyword expansion options.  Can be either NULL or "" to
     30  *			indicate none are specified here.
     31  *   force_tag_match
     32  *   aflag
     33  *   versp
     34  *   pipeout		Did the user pass the "pipeout" flag to request that
     35  *			all output go to STDOUT rather than to a file or files?
     36  *
     37  * RETURNS
     38  *   A Ctype (defined as an enum) describing the state of the file relative to
     39  *   the repository.  See the definition of Ctype for more.
     40  */
     41 Ctype
     42 Classify_File (struct file_info *finfo, char *tag, char *date, char *options,
     43                int force_tag_match, int aflag, Vers_TS **versp, int pipeout)
     44 {
     45     Vers_TS *vers;
     46     Ctype ret;
     47 
     48     /* get all kinds of good data about the file */
     49     vers = Version_TS (finfo, options, tag, date,
     50 		       force_tag_match, 0);
     51 
     52     if (vers->vn_user == NULL)
     53     {
     54 	/* No entry available, ts_rcs is invalid */
     55 	if (vers->vn_rcs == NULL)
     56 	{
     57 	    /* there is no RCS file either */
     58 	    if (vers->ts_user == NULL)
     59 	    {
     60 		/* there is no user file */
     61 		/* FIXME: Why do we skip this message if vers->tag or
     62 		   vers->date is set?  It causes "cvs update -r tag98 foo"
     63 		   to silently do nothing, which is seriously confusing
     64 		   behavior.  "cvs update foo" gives this message, which
     65 		   is what I would expect.  */
     66 		if (!force_tag_match || !(vers->tag || vers->date))
     67 		    if (!really_quiet)
     68 			error (0, 0, "nothing known about `%s'",
     69 			       finfo->fullname);
     70 		ret = T_UNKNOWN;
     71 	    }
     72 	    else
     73 	    {
     74 		/* there is a user file */
     75 		/* FIXME: Why do we skip this message if vers->tag or
     76 		   vers->date is set?  It causes "cvs update -r tag98 foo"
     77 		   to silently do nothing, which is seriously confusing
     78 		   behavior.  "cvs update foo" gives this message, which
     79 		   is what I would expect.  */
     80 		if (!force_tag_match || !(vers->tag || vers->date))
     81 		    if (!really_quiet)
     82 			error (0, 0, "use `%s add' to create an entry for `%s'",
     83 			       program_name, finfo->fullname);
     84 		ret = T_UNKNOWN;
     85 	    }
     86 	}
     87 	else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
     88 	{
     89 	    /* there is an RCS file, but it's dead */
     90 	    if (vers->ts_user == NULL)
     91 		ret = T_UPTODATE;
     92 	    else
     93 	    {
     94 		error (0, 0, "use `%s add' to create an entry for `%s'",
     95 		       program_name, finfo->fullname);
     96 		ret = T_UNKNOWN;
     97 	    }
     98 	}
     99 	else if (!pipeout && vers->ts_user && No_Difference (finfo, vers))
    100 	{
    101 	    /* the files were different so it is a conflict */
    102 	    if (!really_quiet)
    103 		error (0, 0, "move away `%s'; it is in the way",
    104 		       finfo->fullname);
    105 	    ret = T_CONFLICT;
    106 	}
    107 	else
    108 	    /* no user file or no difference, just checkout */
    109 	    ret = T_CHECKOUT;
    110     }
    111     else if (strcmp (vers->vn_user, "0") == 0)
    112     {
    113 	/* An entry for a new-born file; ts_rcs is dummy */
    114 
    115 	if (vers->ts_user == NULL)
    116 	{
    117 	    if (pipeout)
    118 	    {
    119 		ret = T_CHECKOUT;
    120 	    }
    121 	    else
    122 	    {
    123 		/*
    124 		 * There is no user file, but there should be one; remove the
    125 		 * entry
    126 		 */
    127 		if (!really_quiet)
    128 		    error (0, 0, "warning: new-born `%s' has disappeared",
    129 			   finfo->fullname);
    130 		ret = T_REMOVE_ENTRY;
    131 	    }
    132 	}
    133 	else if (vers->vn_rcs == NULL ||
    134 		 RCS_isdead (vers->srcfile, vers->vn_rcs))
    135 	    /* No RCS file or RCS file revision is dead  */
    136 	    ret = T_ADDED;
    137 	else
    138 	{
    139 	    if (pipeout)
    140 	    {
    141 		ret = T_CHECKOUT;
    142 	    }
    143 	    else
    144 	    {
    145 		if (vers->srcfile->flags & INATTIC
    146 		    && vers->srcfile->flags & VALID)
    147 		{
    148 		    /* This file has been added on some branch other than
    149 		       the one we are looking at.  In the branch we are
    150 		       looking at, the file was already valid.  */
    151 		    if (!really_quiet)
    152 			error (0, 0,
    153 			   "conflict: `%s' has been added, but already exists",
    154 			       finfo->fullname);
    155 		}
    156 		else
    157 		{
    158 		    /*
    159 		     * There is an RCS file, so someone else must have checked
    160 		     * one in behind our back; conflict
    161 		     */
    162 		    if (!really_quiet)
    163 			error (0, 0,
    164                                "conflict: `%s' created independently by"
    165 			       " second party",
    166 			       finfo->fullname);
    167 		}
    168 		ret = T_CONFLICT;
    169 	    }
    170 	}
    171     }
    172     else if (vers->vn_user[0] == '-')
    173     {
    174 	/* An entry for a removed file, ts_rcs is invalid */
    175 
    176 	if (vers->ts_user == NULL)
    177 	{
    178 	    /* There is no user file (as it should be) */
    179 
    180 	    if (vers->vn_rcs == NULL
    181 		|| RCS_isdead (vers->srcfile, vers->vn_rcs))
    182 	    {
    183 
    184 		/*
    185 		 * There is no RCS file; this is all-right, but it has been
    186 		 * removed independently by a second party; remove the entry
    187 		 */
    188 		ret = T_REMOVE_ENTRY;
    189 	    }
    190 	    else if (strcmp (vers->vn_rcs, vers->vn_user + 1) == 0)
    191 		/*
    192 		 * The RCS file is the same version as the user file was, and
    193 		 * that's OK; remove it
    194 		 */
    195 		ret = T_REMOVED;
    196 	    else if (pipeout)
    197 		/*
    198 		 * The RCS file doesn't match the user's file, but it doesn't
    199 		 * matter in this case
    200 		 */
    201 		ret = T_NEEDS_MERGE;
    202 	    else
    203 	    {
    204 
    205 		/*
    206 		 * The RCS file is a newer version than the removed user file
    207 		 * and this is definitely not OK; make it a conflict.
    208 		 */
    209 		if (!really_quiet)
    210 		    error (0, 0,
    211 			   "conflict: removed `%s' was modified by"
    212 			   " second party",
    213 			   finfo->fullname);
    214 		ret = T_CONFLICT;
    215 	    }
    216 	}
    217 	else
    218 	{
    219 	    /* The user file shouldn't be there */
    220 	    if (!really_quiet)
    221 		error (0, 0, "`%s' should be removed and is still there",
    222 		       finfo->fullname);
    223 	    ret = T_REMOVED;
    224 	}
    225     }
    226     else
    227     {
    228 	/* A normal entry, TS_Rcs is valid */
    229 	if (vers->vn_rcs == NULL || RCS_isdead (vers->srcfile, vers->vn_rcs))
    230 	{
    231 	    /* There is no RCS file */
    232 
    233 	    if (vers->ts_user == NULL)
    234 	    {
    235 		/* There is no user file, so just remove the entry */
    236 		if (!really_quiet)
    237 		    error (0, 0, "warning: `%s' is not (any longer) pertinent",
    238 			   finfo->fullname);
    239 		ret = T_REMOVE_ENTRY;
    240 	    }
    241 	    else if (strcmp (vers->ts_user, vers->ts_rcs)
    242 		     && No_Difference (finfo, vers))
    243 	    {
    244 		/* they are different -> conflict */
    245 		if (!really_quiet)
    246 		    error (0, 0,
    247                            "conflict: `%s' is modified but no longer in the"
    248 			   " repository",
    249 			   finfo->fullname);
    250 		ret = T_CONFLICT;
    251 	    }
    252 	    else
    253 	    {
    254 
    255 		/*
    256 		 * The user file is still unmodified, so just remove it from
    257 		 * the entry list
    258 		 */
    259 		if (!really_quiet)
    260 		    error (0, 0, "`%s' is no longer in the repository",
    261 			   finfo->fullname);
    262 		ret = T_REMOVE_ENTRY;
    263 	    }
    264 	}
    265 	else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
    266 	{
    267 	    /* The RCS file is the same version as the user file */
    268 
    269 	    if (vers->ts_user == NULL)
    270 	    {
    271 
    272 		/*
    273 		 * There is no user file, so note that it was lost and
    274 		 * extract a new version
    275 		 */
    276 		/* Comparing the cvs_cmd_name against "update", in
    277 		   addition to being an ugly way to operate, means
    278 		   that this message does not get printed by the
    279 		   server.  That might be considered just a straight
    280 		   bug, although there is one subtlety: that case also
    281 		   gets hit when a patch fails and the client fetches
    282 		   a file.  I'm not sure there is currently any way
    283 		   for the server to distinguish those two cases.  */
    284 		if (strcmp (cvs_cmd_name, "update") == 0)
    285 		    if (!really_quiet)
    286 			error (0, 0, "warning: `%s' was lost", finfo->fullname);
    287 		ret = T_CHECKOUT;
    288 	    }
    289 	    else if (!strcmp (vers->ts_user,
    290 			      vers->ts_conflict
    291 			      ? vers->ts_conflict : vers->ts_rcs))
    292 	    {
    293 
    294 		/*
    295 		 * The user file is still unmodified, so nothing special at
    296 		 * all to do -- no lists updated, unless the sticky -k option
    297 		 * has changed.  If the sticky tag has changed, we just need
    298 		 * to re-register the entry
    299 		 */
    300 		/* TODO: decide whether we need to check file permissions
    301 		   for a mismatch, and return T_CONFLICT if so. */
    302 		if (vers->entdata->options &&
    303 		    strcmp (vers->entdata->options, vers->options) != 0)
    304 		    ret = T_CHECKOUT;
    305 		else if (vers->ts_conflict)
    306 		    ret = T_CONFLICT;
    307 		else
    308 		{
    309 		    sticky_ck (finfo, aflag, vers);
    310 		    ret = T_UPTODATE;
    311 		}
    312 	    }
    313 	    else if (No_Difference (finfo, vers))
    314 	    {
    315 
    316 		/*
    317 		 * they really are different; modified if we aren't
    318 		 * changing any sticky -k options, else needs merge
    319 		 */
    320 #ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
    321 		if (strcmp (vers->entdata->options ?
    322 		       vers->entdata->options : "", vers->options) == 0)
    323 		    ret = T_MODIFIED;
    324 		else
    325 		    ret = T_NEEDS_MERGE;
    326 #else
    327 		/* Files with conflict markers and new timestamps fall through
    328 		 * here, but they need to.  T_CONFLICT is an error in
    329 		 * commit_fileproc, whereas T_MODIFIED with conflict markers
    330 		 * is caught but only warned about.  Similarly, update_fileproc
    331 		 * currently reregisters a file that was conflicted but lost
    332 		 * its markers.
    333 		 */
    334 		ret = T_MODIFIED;
    335 		sticky_ck (finfo, aflag, vers);
    336 #endif
    337 	    }
    338 	    else if (strcmp (vers->entdata->options ?
    339 		       vers->entdata->options : "", vers->options) != 0)
    340 	    {
    341 		/* file has not changed; check out if -k changed */
    342 		ret = T_CHECKOUT;
    343 	    }
    344 	    else
    345 	    {
    346 
    347 		/*
    348 		 * else -> note that No_Difference will Register the
    349 		 * file already for us, using the new tag/date. This
    350 		 * is the desired behaviour
    351 		 */
    352 		ret = T_UPTODATE;
    353 	    }
    354 	}
    355 	else
    356 	{
    357 	    /* The RCS file is a newer version than the user file */
    358 
    359 	    if (vers->ts_user == NULL)
    360 	    {
    361 		/* There is no user file, so just get it */
    362 
    363 		/* See comment at other "update" compare, for more
    364 		   thoughts on this comparison.  */
    365 		if (strcmp (cvs_cmd_name, "update") == 0)
    366 		    if (!really_quiet)
    367 			error (0, 0, "warning: `%s' was lost", finfo->fullname);
    368 		ret = T_CHECKOUT;
    369 	    }
    370 	    else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
    371 	    {
    372 
    373 		/*
    374 		 * The user file is still unmodified, so just get it as well
    375 		 */
    376 		if (strcmp (vers->entdata->options ?
    377 			    vers->entdata->options : "", vers->options) != 0
    378 		    || (vers->srcfile != NULL
    379 			&& (vers->srcfile->flags & INATTIC) != 0))
    380 		    ret = T_CHECKOUT;
    381 		else
    382 		    ret = T_PATCH;
    383 	    }
    384 	    else if (No_Difference (finfo, vers))
    385 		/* really modified, needs to merge */
    386 		ret = T_NEEDS_MERGE;
    387 	    else if ((strcmp (vers->entdata->options ?
    388 			      vers->entdata->options : "", vers->options)
    389 		      != 0)
    390 		     || (vers->srcfile != NULL
    391 		         && (vers->srcfile->flags & INATTIC) != 0))
    392 		/* not really modified, check it out */
    393 		ret = T_CHECKOUT;
    394 	    else
    395 		ret = T_PATCH;
    396 	}
    397     }
    398 
    399     /* free up the vers struct, or just return it */
    400     if (versp != NULL)
    401 	*versp = vers;
    402     else
    403 	freevers_ts (&vers);
    404 
    405     /* return the status of the file */
    406     return (ret);
    407 }
    408 
    409 static void
    410 sticky_ck (struct file_info *finfo, int aflag, Vers_TS *vers)
    411 {
    412     if (aflag || vers->tag || vers->date)
    413     {
    414 	char *enttag = vers->entdata->tag;
    415 	char *entdate = vers->entdata->date;
    416 
    417 	if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
    418 	    ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
    419 	    (entdate && vers->date && strcmp (entdate, vers->date)) ||
    420 	    ((entdate && !vers->date) || (!entdate && vers->date)))
    421 	{
    422 	    Register (finfo->entries, finfo->file, vers->vn_user, vers->ts_rcs,
    423 		      vers->options, vers->tag, vers->date, vers->ts_conflict);
    424 
    425 #ifdef SERVER_SUPPORT
    426 	    if (server_active)
    427 	    {
    428 		/* We need to update the entries line on the client side.
    429 		   It is possible we will later update it again via
    430 		   server_updated or some such, but that is OK.  */
    431 		server_update_entries
    432 		  (finfo->file, finfo->update_dir, finfo->repository,
    433 		   strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
    434 		   SERVER_UPDATED : SERVER_MERGED);
    435 	    }
    436 #endif
    437 	}
    438     }
    439 }
    440