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