also add -fwrapv to CFLAGS (addresses #698908)
[alioth/cvs.git] / src / classify.c
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 }