summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/ignore.c
blob: df08017e2a0b0362b3283f4555ef487f9017f727 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
/* This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.  */

/*
 * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com>
 */

#include "cvs.h"
#include "getline.h"

/*
 * Ignore file section.
 * 
 *	"!" may be included any time to reset the list (i.e. ignore nothing);
 *	"*" may be specified to ignore everything.  It stays as the first
 *	    element forever, unless a "!" clears it out.
 */

static char **ign_list;			/* List of files to ignore in update
					 * and import */
static char **s_ign_list = NULL;
static int ign_count;			/* Number of active entries */
static int s_ign_count = 0;
static int ign_size;			/* This many slots available (plus
					 * one for a NULL) */
static int ign_hold = -1;		/* Index where first "temporary" item
					 * is held */

const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
 .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
 *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";

#define IGN_GROW 16			/* grow the list by 16 elements at a
					 * time */

/* Nonzero if we have encountered an -I ! directive, which means one should
   no longer ask the server about what is in CVSROOTADM_IGNORE.  */
int ign_inhibit_server;

/*
 * To the "ignore list", add the hard-coded default ignored wildcards above,
 * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
 * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
 * variable.
 */
void
ign_setup ()
{
    char *home_dir;
    char *tmp;

    ign_inhibit_server = 0;

    /* Start with default list and special case */
    tmp = xstrdup (ign_default);
    ign_add (tmp, 0);
    free (tmp);

#ifdef CLIENT_SUPPORT
    /* The client handles another way, by (after it does its own ignore file
       processing, and only if !ign_inhibit_server), letting the server
       know about the files and letting it decide whether to ignore
       them based on CVSROOOTADM_IGNORE.  */
    if (!client_active)
#endif
    {
	char *file = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM)
			      + sizeof (CVSROOTADM_IGNORE) + 10);
	/* Then add entries found in repository, if it exists */
	(void) sprintf (file, "%s/%s/%s", CVSroot_directory,
			CVSROOTADM, CVSROOTADM_IGNORE);
	ign_add_file (file, 0);
	free (file);
    }

    /* Then add entries found in home dir, (if user has one) and file exists */
    home_dir = get_homedir ();
    /* If we can't find a home directory, ignore ~/.cvsignore.  This may
       make tracking down problems a bit of a pain, but on the other
       hand it might be obnoxious to complain when CVS will function
       just fine without .cvsignore (and many users won't even know what
       .cvsignore is).  */
    if (home_dir)
    {
	char *file = xmalloc (strlen (home_dir) + sizeof (CVSDOTIGNORE) + 10);
	(void) sprintf (file, "%s/%s", home_dir, CVSDOTIGNORE);
	ign_add_file (file, 0);
	free (file);
    }

    /* Then add entries found in CVSIGNORE environment variable. */
    ign_add (getenv (IGNORE_ENV), 0);

    /* Later, add ignore entries found in -I arguments */
}

/*
 * Open a file and read lines, feeding each line to a line parser. Arrange
 * for keeping a temporary list of wildcards at the end, if the "hold"
 * argument is set.
 */
void
ign_add_file (file, hold)
    char *file;
    int hold;
{
    FILE *fp;
    char *line = NULL;
    size_t line_allocated = 0;

    /* restore the saved list (if any) */
    if (s_ign_list != NULL)
    {
	int i;

	for (i = 0; i < s_ign_count; i++)
	    ign_list[i] = s_ign_list[i];
	ign_count = s_ign_count;
	ign_list[ign_count] = NULL;

	s_ign_count = 0;
	free (s_ign_list);
	s_ign_list = NULL;
    }

    /* is this a temporary ignore file? */
    if (hold)
    {
	/* re-set if we had already done a temporary file */
	if (ign_hold >= 0)
	{
	    int i;

	    for (i = ign_hold; i < ign_count; i++)
		free (ign_list[i]);
	    ign_count = ign_hold;
	    ign_list[ign_count] = NULL;
	}
	else
	{
	    ign_hold = ign_count;
	}
    }

    /* load the file */
    fp = CVS_FOPEN (file, "r");
    if (fp == NULL)
    {
	if (! existence_error (errno))
	    error (0, errno, "cannot open %s", file);
	return;
    }
    while (getline (&line, &line_allocated, fp) >= 0)
	ign_add (line, hold);
    if (ferror (fp))
	error (0, errno, "cannot read %s", file);
    if (fclose (fp) < 0)
	error (0, errno, "cannot close %s", file);
    free (line);
}

/* Parse a line of space-separated wildcards and add them to the list. */
void
ign_add (ign, hold)
    char *ign;
    int hold;
{
    if (!ign || !*ign)
	return;

    for (; *ign; ign++)
    {
	char *mark;
	char save;

	/* ignore whitespace before the token */
	if (isspace ((unsigned char) *ign))
	    continue;

	/*
	 * if we find a single character !, we must re-set the ignore list
	 * (saving it if necessary).  We also catch * as a special case in a
	 * global ignore file as an optimization
	 */
	if ((!*(ign+1) || isspace ((unsigned char) *(ign+1)))
	    && (*ign == '!' || *ign == '*'))
	{
	    if (!hold)
	    {
		/* permanently reset the ignore list */
		int i;

		for (i = 0; i < ign_count; i++)
		    free (ign_list[i]);
		ign_count = 0;
		ign_list[0] = NULL;

		/* if we are doing a '!', continue; otherwise add the '*' */
		if (*ign == '!')
		{
		    ign_inhibit_server = 1;
		    continue;
		}
	    }
	    else if (*ign == '!')
	    {
		/* temporarily reset the ignore list */
		int i;

		if (ign_hold >= 0)
		{
		    for (i = ign_hold; i < ign_count; i++)
			free (ign_list[i]);
		    ign_hold = -1;
		}
		s_ign_list = (char **) xmalloc (ign_count * sizeof (char *));
		for (i = 0; i < ign_count; i++)
		    s_ign_list[i] = ign_list[i];
		s_ign_count = ign_count;
		ign_count = 0;
		ign_list[0] = NULL;
		continue;
	    }
	}

	/* If we have used up all the space, add some more */
	if (ign_count >= ign_size)
	{
	    ign_size += IGN_GROW;
	    ign_list = (char **) xrealloc ((char *) ign_list,
					   (ign_size + 1) * sizeof (char *));
	}

	/* find the end of this token */
	for (mark = ign; *mark && !isspace ((unsigned char) *mark); mark++)
	     /* do nothing */ ;

	save = *mark;
	*mark = '\0';

	ign_list[ign_count++] = xstrdup (ign);
	ign_list[ign_count] = NULL;

	*mark = save;
	if (save)
	    ign = mark;
	else
	    ign = mark - 1;
    }
}

/* Set to 1 if filenames should be matched in a case-insensitive
   fashion.  Note that, contrary to the name and placement in ignore.c,
   this is no longer just for ignore patterns.  */
int ign_case;

/* Return 1 if the given filename should be ignored by update or import. */
int
ign_name (name)
    char *name;
{
    char **cpp = ign_list;

    if (cpp == NULL)
	return (0);

    if (ign_case)
    {
	/* We do a case-insensitive match by calling fnmatch on copies of
	   the pattern and the name which have been converted to
	   lowercase.  FIXME: would be much cleaner to just unify this
	   with the other case-insensitive fnmatch stuff (FOLD_FN_CHAR
	   in lib/fnmatch.c; os2_fnmatch in emx/system.c).  */
	char *name_lower;
	char *pat_lower;
	char *p;

	name_lower = xstrdup (name);
	for (p = name_lower; *p != '\0'; ++p)
	    *p = tolower (*p);
	while (*cpp)
	{
	    pat_lower = xstrdup (*cpp++);
	    for (p = pat_lower; *p != '\0'; ++p)
		*p = tolower (*p);
	    if (CVS_FNMATCH (pat_lower, name_lower, 0) == 0)
		goto matched;
	    free (pat_lower);
	}
	free (name_lower);
	return 0;
      matched:
	free (name_lower);
	free (pat_lower);
	return 1;
    }
    else
    {
	while (*cpp)
	    if (CVS_FNMATCH (*cpp++, name, 0) == 0)
		return 1;
	return 0;
    }
}

/* FIXME: This list of dirs to ignore stuff seems not to be used.
   Really?  send_dirent_proc and update_dirent_proc both call
   ignore_directory and do_module calls ign_dir_add.  No doubt could
   use some documentation/testsuite work.  */

static char **dir_ign_list = NULL;
static int dir_ign_max = 0;
static int dir_ign_current = 0;

/* Add a directory to list of dirs to ignore.  */
void
ign_dir_add (name)
    char *name;
{
    /* Make sure we've got the space for the entry.  */
    if (dir_ign_current <= dir_ign_max)
    {
	dir_ign_max += IGN_GROW;
	dir_ign_list =
	    (char **) xrealloc (dir_ign_list,
				(dir_ign_max + 1) * sizeof (char *));
    }

    dir_ign_list[dir_ign_current] = name;

    dir_ign_current += 1 ;
}


/* Return nonzero if NAME is part of the list of directories to ignore.  */

int
ignore_directory (name)
    char *name;
{
    int i;

    if (!dir_ign_list)
	return 0;

    i = dir_ign_current;
    while (i--)
    {
	if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])) == 0)
	    return 1;
    }

    return 0;
}

/*
 * Process the current directory, looking for files not in ILIST and
 * not on the global ignore list for this directory.  If we find one,
 * call PROC passing it the name of the file and the update dir.
 * ENTRIES is the entries list, which is used to identify known
 * directories.  ENTRIES may be NULL, in which case we assume that any
 * directory with a CVS administration directory is known.
 */
void
ignore_files (ilist, entries, update_dir, proc)
    List *ilist;
    List *entries;
    char *update_dir;
    Ignore_proc proc;
{
    int subdirs;
    DIR *dirp;
    struct dirent *dp;
    struct stat sb;
    char *file;
    char *xdir;

    /* Set SUBDIRS if we have subdirectory information in ENTRIES.  */
    if (entries == NULL)
	subdirs = 0;
    else
    {
	struct stickydirtag *sdtp;

	sdtp = (struct stickydirtag *) entries->list->data;
	subdirs = sdtp == NULL || sdtp->subdirs;
    }

    /* we get called with update_dir set to "." sometimes... strip it */
    if (strcmp (update_dir, ".") == 0)
	xdir = "";
    else
	xdir = update_dir;

    dirp = CVS_OPENDIR (".");
    if (dirp == NULL)
    {
	error (0, errno, "cannot open current directory");
	return;
    }

    ign_add_file (CVSDOTIGNORE, 1);
    wrap_add_file (CVSDOTWRAPPER, 1);

    errno = 0;
    while ((dp = readdir (dirp)) != NULL)
    {
	file = dp->d_name;
	if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
	    continue;
	if (findnode_fn (ilist, file) != NULL)
	    continue;
	if (subdirs)
	{
	    Node *node;

	    node = findnode_fn (entries, file);
	    if (node != NULL
		&& ((Entnode *) node->data)->type == ENT_SUBDIR)
	    {
		char *p;
		int dir;

		/* For consistency with past behaviour, we only ignore
		   this directory if there is a CVS subdirectory.
		   This will normally be the case, but the user may
		   have messed up the working directory somehow.  */
		p = xmalloc (strlen (file) + sizeof CVSADM + 10);
		sprintf (p, "%s/%s", file, CVSADM);
		dir = isdir (p);
		free (p);
		if (dir)
		    continue;
	    }
	}

	/* We could be ignoring FIFOs and other files which are neither
	   regular files nor directories here.  */
	if (ign_name (file))
	    continue;

	if (
#ifdef DT_DIR
		dp->d_type != DT_UNKNOWN ||
#endif
		lstat(file, &sb) != -1) 
	{

	    if (
#ifdef DT_DIR
		dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN &&
#endif
		S_ISDIR(sb.st_mode))
	    {
		if (! subdirs)
		{
		    char *temp;

		    temp = xmalloc (strlen (file) + sizeof (CVSADM) + 10);
		    (void) sprintf (temp, "%s/%s", file, CVSADM);
		    if (isdir (temp))
		    {
			free (temp);
			continue;
		    }
		    free (temp);
		}
	    }
#ifdef S_ISLNK
	    else if (
#ifdef DT_DIR
		dp->d_type == DT_LNK || dp->d_type == DT_UNKNOWN && 
#endif
		S_ISLNK(sb.st_mode))
	    {
		continue;
	    }
#endif
    	}

	(*proc) (file, xdir);
	errno = 0;
    }
    if (errno != 0)
	error (0, errno, "error reading current directory");
    (void) closedir (dirp);
}
OpenPOWER on IntegriCloud