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