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
17static 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 */
39Ctype
40Classify_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
407static void
408sticky_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