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