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