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