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