Merge from vendor branch NTPD:
[dragonfly.git] / contrib / cvs-1.12.11 / src / ignore.c
1 /* This program is free software; you can redistribute it and/or modify
2    it under the terms of the GNU General Public License as published by
3    the Free Software Foundation; either version 2, or (at your option)
4    any later version.
5
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9    GNU General Public License for more details.  */
10
11 /*
12  * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com>
13  */
14
15 #include "cvs.h"
16 #include "getline.h"
17
18 /*
19  * Ignore file section.
20  * 
21  *      "!" may be included any time to reset the list (i.e. ignore nothing);
22  *      "*" may be specified to ignore everything.  It stays as the first
23  *          element forever, unless a "!" clears it out.
24  */
25
26 static char **ign_list;                 /* List of files to ignore in update
27                                          * and import */
28 static char **s_ign_list = NULL;
29 static int ign_count;                   /* Number of active entries */
30 static int s_ign_count = 0;
31 static int ign_size;                    /* This many slots available (plus
32                                          * one for a NULL) */
33 static int ign_hold = -1;               /* Index where first "temporary" item
34                                          * is held */
35
36 const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
37  .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
38  *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";
39
40 #define IGN_GROW 16                     /* grow the list by 16 elements at a
41                                          * time */
42
43 /* Nonzero if we have encountered an -I ! directive, which means one should
44    no longer ask the server about what is in CVSROOTADM_IGNORE.  */
45 int ign_inhibit_server;
46
47
48
49 /*
50  * To the "ignore list", add the hard-coded default ignored wildcards above,
51  * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
52  * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
53  * variable.
54  */
55 void
56 ign_setup (void)
57 {
58     char *home_dir;
59     char *tmp;
60
61     ign_inhibit_server = 0;
62
63     /* Start with default list and special case */
64     tmp = xstrdup (ign_default);
65     ign_add (tmp, 0);
66     free (tmp);
67
68 #ifdef CLIENT_SUPPORT
69     /* The client handles another way, by (after it does its own ignore file
70        processing, and only if !ign_inhibit_server), letting the server
71        know about the files and letting it decide whether to ignore
72        them based on CVSROOOTADM_IGNORE.  */
73     if (!current_parsed_root->isremote)
74 #endif
75     {
76         char *file = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
77                               + sizeof (CVSROOTADM_IGNORE) + 10);
78         /* Then add entries found in repository, if it exists */
79         (void) sprintf (file, "%s/%s/%s", current_parsed_root->directory,
80                         CVSROOTADM, CVSROOTADM_IGNORE);
81         ign_add_file (file, 0);
82         free (file);
83     }
84
85     /* Then add entries found in home dir, (if user has one) and file exists */
86     home_dir = get_homedir ();
87     /* If we can't find a home directory, ignore ~/.cvsignore.  This may
88        make tracking down problems a bit of a pain, but on the other
89        hand it might be obnoxious to complain when CVS will function
90        just fine without .cvsignore (and many users won't even know what
91        .cvsignore is).  */
92     if (home_dir)
93     {
94         char *file = strcat_filename_onto_homedir (home_dir, CVSDOTIGNORE);
95         ign_add_file (file, 0);
96         free (file);
97     }
98
99     /* Then add entries found in CVSIGNORE environment variable. */
100     ign_add (getenv (IGNORE_ENV), 0);
101
102     /* Later, add ignore entries found in -I arguments */
103 }
104
105
106
107 /*
108  * Open a file and read lines, feeding each line to a line parser. Arrange
109  * for keeping a temporary list of wildcards at the end, if the "hold"
110  * argument is set.
111  */
112 void
113 ign_add_file (char *file, int hold)
114 {
115     FILE *fp;
116     char *line = NULL;
117     size_t line_allocated = 0;
118
119     /* restore the saved list (if any) */
120     if (s_ign_list != NULL)
121     {
122         int i;
123
124         for (i = 0; i < s_ign_count; i++)
125             ign_list[i] = s_ign_list[i];
126         ign_count = s_ign_count;
127         ign_list[ign_count] = NULL;
128
129         s_ign_count = 0;
130         free (s_ign_list);
131         s_ign_list = NULL;
132     }
133
134     /* is this a temporary ignore file? */
135     if (hold)
136     {
137         /* re-set if we had already done a temporary file */
138         if (ign_hold >= 0)
139         {
140             int i;
141
142             for (i = ign_hold; i < ign_count; i++)
143                 free (ign_list[i]);
144             ign_count = ign_hold;
145             ign_list[ign_count] = NULL;
146         }
147         else
148         {
149             ign_hold = ign_count;
150         }
151     }
152
153     /* load the file */
154     fp = CVS_FOPEN (file, "r");
155     if (fp == NULL)
156     {
157         if (! existence_error (errno))
158             error (0, errno, "cannot open %s", file);
159         return;
160     }
161     while (getline (&line, &line_allocated, fp) >= 0)
162         ign_add (line, hold);
163     if (ferror (fp))
164         error (0, errno, "cannot read %s", file);
165     if (fclose (fp) < 0)
166         error (0, errno, "cannot close %s", file);
167     free (line);
168 }
169
170
171
172 /* Parse a line of space-separated wildcards and add them to the list. */
173 void
174 ign_add (char *ign, int hold)
175 {
176     if (!ign || !*ign)
177         return;
178
179     for (; *ign; ign++)
180     {
181         char *mark;
182         char save;
183
184         /* ignore whitespace before the token */
185         if (isspace ((unsigned char) *ign))
186             continue;
187
188         /* If we have used up all the space, add some more.  Do this before
189            processing `!', since an "empty" list still contains the `CVS'
190            entry.  */
191         if (ign_count >= ign_size)
192         {
193             ign_size += IGN_GROW;
194             ign_list = (char **) xrealloc ((char *) ign_list,
195                                            (ign_size + 1) * sizeof (char *));
196         }
197
198         /*
199          * if we find a single character !, we must re-set the ignore list
200          * (saving it if necessary).  We also catch * as a special case in a
201          * global ignore file as an optimization
202          */
203         if ((!*(ign+1) || isspace ((unsigned char) *(ign+1)))
204             && (*ign == '!' || *ign == '*'))
205         {
206             if (!hold)
207             {
208                 /* permanently reset the ignore list */
209                 int i;
210
211                 for (i = 0; i < ign_count; i++)
212                     free (ign_list[i]);
213                 ign_count = 1;
214                 /* Always ignore the "CVS" directory.  */
215                 ign_list[0] = xstrdup("CVS");
216                 ign_list[1] = NULL;
217
218                 /* if we are doing a '!', continue; otherwise add the '*' */
219                 if (*ign == '!')
220                 {
221                     ign_inhibit_server = 1;
222                     continue;
223                 }
224             }
225             else if (*ign == '!')
226             {
227                 /* temporarily reset the ignore list */
228                 int i;
229
230                 if (ign_hold >= 0)
231                 {
232                     for (i = ign_hold; i < ign_count; i++)
233                         free (ign_list[i]);
234                     ign_hold = -1;
235                 }
236                 s_ign_list = (char **) xmalloc (ign_count * sizeof (char *));
237                 for (i = 0; i < ign_count; i++)
238                     s_ign_list[i] = ign_list[i];
239                 s_ign_count = ign_count;
240                 ign_count = 1;
241                 /* Always ignore the "CVS" directory.  */
242                 ign_list[0] = xstrdup ("CVS");
243                 ign_list[1] = NULL;
244                 continue;
245             }
246         }
247
248         /* find the end of this token */
249         for (mark = ign; *mark && !isspace ((unsigned char) *mark); mark++)
250              /* do nothing */ ;
251
252         save = *mark;
253         *mark = '\0';
254
255         ign_list[ign_count++] = xstrdup (ign);
256         ign_list[ign_count] = NULL;
257
258         *mark = save;
259         if (save)
260             ign = mark;
261         else
262             ign = mark - 1;
263     }
264 }
265
266
267
268 /* Return true if the given filename should be ignored by update or import,
269  * else return false.
270  */
271 int
272 ign_name (char *name)
273 {
274     char **cpp = ign_list;
275
276     if (cpp == NULL)
277         return 0;
278
279     while (*cpp)
280         if (CVS_FNMATCH (*cpp++, name, 0) == 0)
281             return 1;
282
283     return 0;
284 }
285
286
287
288 /* FIXME: This list of dirs to ignore stuff seems not to be used.
289    Really?  send_dirent_proc and update_dirent_proc both call
290    ignore_directory and do_module calls ign_dir_add.  No doubt could
291    use some documentation/testsuite work.  */
292
293 static char **dir_ign_list = NULL;
294 static int dir_ign_max = 0;
295 static int dir_ign_current = 0;
296
297 /* Add a directory to list of dirs to ignore.  */
298 void
299 ign_dir_add (char *name)
300 {
301     /* Make sure we've got the space for the entry.  */
302     if (dir_ign_current <= dir_ign_max)
303     {
304         dir_ign_max += IGN_GROW;
305         dir_ign_list =
306             (char **) xrealloc (dir_ign_list,
307                                 (dir_ign_max + 1) * sizeof (char *));
308     }
309
310     dir_ign_list[dir_ign_current++] = xstrdup (name);
311 }
312
313
314 /* Return nonzero if NAME is part of the list of directories to ignore.  */
315
316 int
317 ignore_directory (const char *name)
318 {
319     int i;
320
321     if (!dir_ign_list)
322         return 0;
323
324     i = dir_ign_current;
325     while (i--)
326     {
327         if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])+1) == 0)
328             return 1;
329     }
330
331     return 0;
332 }
333
334
335
336 /*
337  * Process the current directory, looking for files not in ILIST and
338  * not on the global ignore list for this directory.  If we find one,
339  * call PROC passing it the name of the file and the update dir.
340  * ENTRIES is the entries list, which is used to identify known
341  * directories.  ENTRIES may be NULL, in which case we assume that any
342  * directory with a CVS administration directory is known.
343  */
344 void
345 ignore_files (List *ilist, List *entries, const char *update_dir,
346               Ignore_proc proc)
347 {
348     int subdirs;
349     DIR *dirp;
350     struct dirent *dp;
351     struct stat sb;
352     char *file;
353     const char *xdir;
354     List *files;
355     Node *p;
356
357     /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
358     if (entries == NULL)
359         subdirs = 0;
360     else
361     {
362         struct stickydirtag *sdtp = entries->list->data;
363
364         subdirs = sdtp == NULL || sdtp->subdirs;
365     }
366
367     /* we get called with update_dir set to "." sometimes... strip it */
368     if (strcmp (update_dir, ".") == 0)
369         xdir = "";
370     else
371         xdir = update_dir;
372
373     dirp = CVS_OPENDIR (".");
374     if (dirp == NULL)
375     {
376         error (0, errno, "cannot open current directory");
377         return;
378     }
379
380     ign_add_file (CVSDOTIGNORE, 1);
381     wrap_add_file (CVSDOTWRAPPER, 1);
382
383     /* Make a list for the files.  */
384     files = getlist ();
385
386     while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL)
387     {
388         file = dp->d_name;
389         if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
390             continue;
391         if (findnode_fn (ilist, file) != NULL)
392             continue;
393         if (subdirs)
394         {
395             Node *node;
396
397             node = findnode_fn (entries, file);
398             if (node != NULL
399                 && ((Entnode *) node->data)->type == ENT_SUBDIR)
400             {
401                 char *p;
402                 int dir;
403
404                 /* For consistency with past behaviour, we only ignore
405                    this directory if there is a CVS subdirectory.
406                    This will normally be the case, but the user may
407                    have messed up the working directory somehow.  */
408                 p = xmalloc (strlen (file) + sizeof CVSADM + 10);
409                 sprintf (p, "%s/%s", file, CVSADM);
410                 dir = isdir (p);
411                 free (p);
412                 if (dir)
413                     continue;
414             }
415         }
416
417         /* We could be ignoring FIFOs and other files which are neither
418            regular files nor directories here.  */
419         if (ign_name (file))
420             continue;
421
422         if (
423 #ifdef DT_DIR
424                 dp->d_type != DT_UNKNOWN ||
425 #endif
426                 CVS_LSTAT( file, &sb ) != -1 ) 
427         {
428
429             if (
430 #ifdef DT_DIR
431                 dp->d_type == DT_DIR
432                 || (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
433 #else
434                 S_ISDIR (sb.st_mode)
435 #endif
436                 )
437             {
438                 if (! subdirs)
439                 {
440                     char *temp;
441
442                     temp = xmalloc (strlen (file) + sizeof (CVSADM) + 10);
443                     (void) sprintf (temp, "%s/%s", file, CVSADM);
444                     if (isdir (temp))
445                     {
446                         free (temp);
447                         continue;
448                     }
449                     free (temp);
450                 }
451             }
452 #ifdef S_ISLNK
453             else if (
454 #ifdef DT_DIR
455                      dp->d_type == DT_LNK
456                      || (dp->d_type == DT_UNKNOWN && S_ISLNK(sb.st_mode))
457 #else
458                      S_ISLNK (sb.st_mode)
459 #endif
460                      )
461             {
462                 continue;
463             }
464 #endif
465         }
466
467         p = getnode ();
468         p->type = FILES;
469         p->key = xstrdup (file);
470         (void) addnode (files, p);
471     }
472     if (errno != 0)
473         error (0, errno, "error reading current directory");
474     (void) CVS_CLOSEDIR (dirp);
475
476     sortlist (files, fsortcmp);
477     for (p = files->list->next; p != files->list; p = p->next)
478         (*proc) (p->key, xdir);
479     dellist (&files);
480 }