xref: /openbsd-src/gnu/usr.bin/cvs/src/ignore.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
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 _$* *$ *.depend";
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  * To the "ignore list", add the hard-coded default ignored wildcards above,
49  * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
50  * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
51  * variable.
52  */
53 void
54 ign_setup ()
55 {
56     char *home_dir;
57     char *tmp;
58 
59     ign_inhibit_server = 0;
60 
61     /* Start with default list and special case */
62     tmp = xstrdup (ign_default);
63     ign_add (tmp, 0);
64     free (tmp);
65 
66 #ifdef CLIENT_SUPPORT
67     /* The client handles another way, by (after it does its own ignore file
68        processing, and only if !ign_inhibit_server), letting the server
69        know about the files and letting it decide whether to ignore
70        them based on CVSROOOTADM_IGNORE.  */
71     if (!current_parsed_root->isremote)
72 #endif
73     {
74 	char *file = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
75 			      + sizeof (CVSROOTADM_IGNORE) + 10);
76 	/* Then add entries found in repository, if it exists */
77 	(void) sprintf (file, "%s/%s/%s", current_parsed_root->directory,
78 			CVSROOTADM, CVSROOTADM_IGNORE);
79 	ign_add_file (file, 0);
80 	free (file);
81     }
82 
83     /* Then add entries found in home dir, (if user has one) and file exists */
84     home_dir = get_homedir ();
85     /* If we can't find a home directory, ignore ~/.cvsignore.  This may
86        make tracking down problems a bit of a pain, but on the other
87        hand it might be obnoxious to complain when CVS will function
88        just fine without .cvsignore (and many users won't even know what
89        .cvsignore is).  */
90     if (home_dir)
91     {
92 	char *file = xmalloc (strlen (home_dir) + sizeof (CVSDOTIGNORE) + 10);
93 	(void) sprintf (file, "%s/%s", home_dir, CVSDOTIGNORE);
94 	ign_add_file (file, 0);
95 	free (file);
96     }
97 
98     /* Then add entries found in CVSIGNORE environment variable. */
99     ign_add (getenv (IGNORE_ENV), 0);
100 
101     /* Later, add ignore entries found in -I arguments */
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 (file, hold)
111     char *file;
112     int hold;
113 {
114     FILE *fp;
115     char *line = NULL;
116     size_t line_allocated = 0;
117 
118     /* restore the saved list (if any) */
119     if (s_ign_list != NULL)
120     {
121 	int i;
122 
123 	for (i = 0; i < s_ign_count; i++)
124 	    ign_list[i] = s_ign_list[i];
125 	ign_count = s_ign_count;
126 	ign_list[ign_count] = NULL;
127 
128 	s_ign_count = 0;
129 	free (s_ign_list);
130 	s_ign_list = NULL;
131     }
132 
133     /* is this a temporary ignore file? */
134     if (hold)
135     {
136 	/* re-set if we had already done a temporary file */
137 	if (ign_hold >= 0)
138 	{
139 	    int i;
140 
141 	    for (i = ign_hold; i < ign_count; i++)
142 		free (ign_list[i]);
143 	    ign_count = ign_hold;
144 	    ign_list[ign_count] = NULL;
145 	}
146 	else
147 	{
148 	    ign_hold = ign_count;
149 	}
150     }
151 
152     /* load the file */
153     fp = CVS_FOPEN (file, "r");
154     if (fp == NULL)
155     {
156 	if (! existence_error (errno))
157 	    error (0, errno, "cannot open %s", file);
158 	return;
159     }
160     while (getline (&line, &line_allocated, fp) >= 0)
161 	ign_add (line, hold);
162     if (ferror (fp))
163 	error (0, errno, "cannot read %s", file);
164     if (fclose (fp) < 0)
165 	error (0, errno, "cannot close %s", file);
166     free (line);
167 }
168 
169 /* Parse a line of space-separated wildcards and add them to the list. */
170 void
171 ign_add (ign, hold)
172     char *ign;
173     int hold;
174 {
175     if (!ign || !*ign)
176 	return;
177 
178     for (; *ign; ign++)
179     {
180 	char *mark;
181 	char save;
182 
183 	/* ignore whitespace before the token */
184 	if (isspace ((unsigned char) *ign))
185 	    continue;
186 
187 	/*
188 	 * if we find a single character !, we must re-set the ignore list
189 	 * (saving it if necessary).  We also catch * as a special case in a
190 	 * global ignore file as an optimization
191 	 */
192 	if ((!*(ign+1) || isspace ((unsigned char) *(ign+1)))
193 	    && (*ign == '!' || *ign == '*'))
194 	{
195 	    if (!hold)
196 	    {
197 		/* permanently reset the ignore list */
198 		int i;
199 
200 		for (i = 0; i < ign_count; i++)
201 		    free (ign_list[i]);
202 		ign_count = 0;
203 		ign_list[0] = NULL;
204 
205 		/* if we are doing a '!', continue; otherwise add the '*' */
206 		if (*ign == '!')
207 		{
208 		    ign_inhibit_server = 1;
209 		    continue;
210 		}
211 	    }
212 	    else if (*ign == '!')
213 	    {
214 		/* temporarily reset the ignore list */
215 		int i;
216 
217 		if (ign_hold >= 0)
218 		{
219 		    for (i = ign_hold; i < ign_count; i++)
220 			free (ign_list[i]);
221 		    ign_hold = -1;
222 		}
223 		s_ign_list = (char **) xmalloc (ign_count * sizeof (char *));
224 		for (i = 0; i < ign_count; i++)
225 		    s_ign_list[i] = ign_list[i];
226 		s_ign_count = ign_count;
227 		ign_count = 0;
228 		ign_list[0] = NULL;
229 		continue;
230 	    }
231 	}
232 
233 	/* If we have used up all the space, add some more */
234 	if (ign_count >= ign_size)
235 	{
236 	    ign_size += IGN_GROW;
237 	    ign_list = (char **) xrealloc ((char *) ign_list,
238 					   (ign_size + 1) * sizeof (char *));
239 	}
240 
241 	/* find the end of this token */
242 	for (mark = ign; *mark && !isspace ((unsigned char) *mark); mark++)
243 	     /* do nothing */ ;
244 
245 	save = *mark;
246 	*mark = '\0';
247 
248 	ign_list[ign_count++] = xstrdup (ign);
249 	ign_list[ign_count] = NULL;
250 
251 	*mark = save;
252 	if (save)
253 	    ign = mark;
254 	else
255 	    ign = mark - 1;
256     }
257 }
258 
259 /* Set to 1 if filenames should be matched in a case-insensitive
260    fashion.  Note that, contrary to the name and placement in ignore.c,
261    this is no longer just for ignore patterns.  */
262 int ign_case;
263 
264 /* Return 1 if the given filename should be ignored by update or import. */
265 int
266 ign_name (name)
267     char *name;
268 {
269     char **cpp = ign_list;
270 
271     if (cpp == NULL)
272 	return (0);
273 
274     if (ign_case)
275     {
276 	/* We do a case-insensitive match by calling fnmatch on copies of
277 	   the pattern and the name which have been converted to
278 	   lowercase.  FIXME: would be much cleaner to just unify this
279 	   with the other case-insensitive fnmatch stuff (FOLD_FN_CHAR
280 	   in lib/fnmatch.c; os2_fnmatch in emx/system.c).  */
281 	char *name_lower;
282 	char *pat_lower;
283 	char *p;
284 
285 	name_lower = xstrdup (name);
286 	for (p = name_lower; *p != '\0'; ++p)
287 	    *p = tolower (*p);
288 	while (*cpp)
289 	{
290 	    pat_lower = xstrdup (*cpp++);
291 	    for (p = pat_lower; *p != '\0'; ++p)
292 		*p = tolower (*p);
293 	    if (CVS_FNMATCH (pat_lower, name_lower, 0) == 0)
294 		goto matched;
295 	    free (pat_lower);
296 	}
297 	free (name_lower);
298 	return 0;
299       matched:
300 	free (name_lower);
301 	free (pat_lower);
302 	return 1;
303     }
304     else
305     {
306 	while (*cpp)
307 	    if (CVS_FNMATCH (*cpp++, name, 0) == 0)
308 		return 1;
309 	return 0;
310     }
311 }
312 
313 /* FIXME: This list of dirs to ignore stuff seems not to be used.
314    Really?  send_dirent_proc and update_dirent_proc both call
315    ignore_directory and do_module calls ign_dir_add.  No doubt could
316    use some documentation/testsuite work.  */
317 
318 static char **dir_ign_list = NULL;
319 static int dir_ign_max = 0;
320 static int dir_ign_current = 0;
321 
322 /* Add a directory to list of dirs to ignore.  */
323 void
324 ign_dir_add (name)
325     char *name;
326 {
327     /* Make sure we've got the space for the entry.  */
328     if (dir_ign_current <= dir_ign_max)
329     {
330 	dir_ign_max += IGN_GROW;
331 	dir_ign_list =
332 	    (char **) xrealloc (dir_ign_list,
333 				(dir_ign_max + 1) * sizeof (char *));
334     }
335 
336     dir_ign_list[dir_ign_current++] = xstrdup (name);
337 }
338 
339 
340 /* Return nonzero if NAME is part of the list of directories to ignore.  */
341 
342 int
343 ignore_directory (name)
344     char *name;
345 {
346     int i;
347 
348     if (!dir_ign_list)
349 	return 0;
350 
351     i = dir_ign_current;
352     while (i--)
353     {
354 	if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])) == 0)
355 	    return 1;
356     }
357 
358     return 0;
359 }
360 
361 /*
362  * Process the current directory, looking for files not in ILIST and
363  * not on the global ignore list for this directory.  If we find one,
364  * call PROC passing it the name of the file and the update dir.
365  * ENTRIES is the entries list, which is used to identify known
366  * directories.  ENTRIES may be NULL, in which case we assume that any
367  * directory with a CVS administration directory is known.
368  */
369 void
370 ignore_files (ilist, entries, update_dir, proc)
371     List *ilist;
372     List *entries;
373     char *update_dir;
374     Ignore_proc proc;
375 {
376     int subdirs;
377     DIR *dirp;
378     struct dirent *dp;
379     struct stat sb;
380     char *file;
381     char *xdir;
382     List *files;
383     Node *p;
384 
385     /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
386     if (entries == NULL)
387 	subdirs = 0;
388     else
389     {
390 	struct stickydirtag *sdtp;
391 
392 	sdtp = (struct stickydirtag *) entries->list->data;
393 	subdirs = sdtp == NULL || sdtp->subdirs;
394     }
395 
396     /* we get called with update_dir set to "." sometimes... strip it */
397     if (strcmp (update_dir, ".") == 0)
398 	xdir = "";
399     else
400 	xdir = update_dir;
401 
402     dirp = CVS_OPENDIR (".");
403     if (dirp == NULL)
404     {
405 	error (0, errno, "cannot open current directory");
406 	return;
407     }
408 
409     ign_add_file (CVSDOTIGNORE, 1);
410     wrap_add_file (CVSDOTWRAPPER, 1);
411 
412     /* Make a list for the files.  */
413     files = getlist ();
414 
415     while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL)
416     {
417 	file = dp->d_name;
418 	if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
419 	    continue;
420 	if (findnode_fn (ilist, file) != NULL)
421 	    continue;
422 	if (subdirs)
423 	{
424 	    Node *node;
425 
426 	    node = findnode_fn (entries, file);
427 	    if (node != NULL
428 		&& ((Entnode *) node->data)->type == ENT_SUBDIR)
429 	    {
430 		char *p;
431 		int dir;
432 
433 		/* For consistency with past behaviour, we only ignore
434 		   this directory if there is a CVS subdirectory.
435 		   This will normally be the case, but the user may
436 		   have messed up the working directory somehow.  */
437 		p = xmalloc (strlen (file) + sizeof CVSADM + 10);
438 		sprintf (p, "%s/%s", file, CVSADM);
439 		dir = isdir (p);
440 		free (p);
441 		if (dir)
442 		    continue;
443 	    }
444 	}
445 
446 	/* We could be ignoring FIFOs and other files which are neither
447 	   regular files nor directories here.  */
448 	if (ign_name (file))
449 	    continue;
450 
451 	if (
452 #ifdef DT_DIR
453 		dp->d_type != DT_UNKNOWN ||
454 #endif
455 		lstat(file, &sb) != -1)
456 	{
457 
458 	    if (
459 #ifdef DT_DIR
460 		dp->d_type == DT_DIR
461 		|| (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
462 #else
463 		S_ISDIR (sb.st_mode)
464 #endif
465 		)
466 	    {
467 		if (! subdirs)
468 		{
469 		    char *temp;
470 
471 		    temp = xmalloc (strlen (file) + sizeof (CVSADM) + 10);
472 		    (void) sprintf (temp, "%s/%s", file, CVSADM);
473 		    if (isdir (temp))
474 		    {
475 			free (temp);
476 			continue;
477 		    }
478 		    free (temp);
479 		}
480 	    }
481 #ifdef S_ISLNK
482 	    else if (
483 #ifdef DT_DIR
484 		     dp->d_type == DT_LNK
485 		     || (dp->d_type == DT_UNKNOWN && S_ISLNK(sb.st_mode))
486 #else
487 		     S_ISLNK (sb.st_mode)
488 #endif
489 		     )
490 	    {
491 		continue;
492 	    }
493 #endif
494 	}
495 
496 	p = getnode ();
497 	p->type = FILES;
498 	p->key = xstrdup (file);
499 	(void) addnode (files, p);
500     }
501     if (errno != 0)
502 	error (0, errno, "error reading current directory");
503     (void) CVS_CLOSEDIR (dirp);
504 
505     sortlist (files, fsortcmp);
506     for (p = files->list->next; p != files->list; p = p->next)
507 	(*proc) (p->key, xdir);
508     dellist (&files);
509 }
510