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