xref: /netbsd-src/external/gpl2/xcvs/dist/src/classify.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
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
Classify_File(struct file_info * finfo,char * tag,char * date,char * options,int force_tag_match,int aflag,Vers_TS ** versp,int pipeout)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
sticky_ck(struct file_info * finfo,int aflag,Vers_TS * vers)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