/* 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 */ #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 const char **ign_list; /* List of files to ignore in update * and import */ static const 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 *.dll *.pdb *.lib " "*.ncb *.ilk *.exp *.suo .DS_Store _$* *$ *.lo " "*.pch *.idb *.class ~*"; #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. */ static 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, *ptr, *server_line; int len; ign_inhibit_server = 0; /* Start with default list and special case */ tmp = xstrdup (ign_default); ign_add (tmp, 0); xfree (tmp); /* 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 (current_parsed_root && !current_parsed_root->isremote) { char *file = (char*)xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM) + sizeof (CVSROOTADM_IGNORE) + 10); /* Then add entries found in repository, if it exists */ sprintf (file, "%s/%s/%s", current_parsed_root->directory, CVSROOTADM, CVSROOTADM_IGNORE); ign_add_file (file, 0); xfree (file); } else if(supported_request("read-cvsignore")) { TRACE(1,"Requesting server cvsignore"); send_to_server ("read-cvsignore\n", 0); read_line(&server_line); if(server_line[0]=='E' && server_line[1]==' ') { fprintf (stderr, "%s\n", server_line + 2); error_exit(); } len = atoi(server_line); tmp = (char*)xmalloc(len+1); ptr = tmp; while (len > 0) { size_t n; n = try_read_from_server (ptr, len); len -= n; ptr += n; } *ptr = '\0'; ptr=strtok(tmp,"\n"); while(ptr && *ptr) { ign_add(ptr,0); ptr=strtok(NULL,"\n"); } xfree(tmp); xfree(server_line); ign_inhibit_server = 1; /* We have all the server-side ignore stuff... ignore any more */ } /* 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 = (char*)xmalloc (strlen (home_dir) + sizeof (CVSDOTIGNORE) + 10); sprintf (file, "%s/%s", home_dir, CVSDOTIGNORE); ign_add_file (file, 0); xfree (file); } /* Then add entries found in CVSIGNORE environment variable. */ ign_add (CProtocolLibrary::GetEnvironment (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 (const 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; xfree (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++) xfree (ign_list[i]); ign_count = ign_hold; if(ign_list) ign_list[ign_count] = NULL; } else { ign_hold = ign_count; } } /* load the file */ fp = fopen (file, "r"); if (fp == NULL) { return; // Fail silently } 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); xfree (line); } static char *next_token(const char **line) { const char *cp; char *ptr,*np; int in_string,esc; if(!**line) return NULL; cp=*line; ptr=np=(char*)xmalloc(strlen(cp)+1); in_string=0; esc=0; for(;*cp && (in_string || esc || !isspace(*(unsigned char *)cp));cp++) { if(!esc) { if(*cp=='\\') { esc=1; continue; } else if(*cp=='"') { in_string=!in_string; continue; } } *(np++)=*(cp); esc=0; } *(np++)='\0'; while(*cp && isspace(*cp)) cp++; *line=cp; return (char*)xrealloc(ptr,strlen(ptr)+1); } /* Parse a line of space-separated wildcards and add them to the list. */ void ign_add (const char *ign, int hold) { const char *ptr; if (!ign || !*ign) return; for (; *ign; ) { ptr = next_token(&ign); if(!ptr) break; /* Shouldn't happen */ /* * 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((ptr[0]=='!' || ptr[0]=='*') && !ptr[1]) { if (!hold) { /* permanently reset the ignore list */ int i; for (i = 0; i < ign_count; i++) xfree (ign_list[i]); ign_count = 0; ign_list[0] = NULL; /* if we are doing a '!', continue; otherwise add the '*' */ if (ptr[0] == '!') { ign_inhibit_server = 1; continue; } } } else if (ptr[0] == '!') { /* temporarily reset the ignore list */ int i; if (ign_hold >= 0) { for (i = ign_hold; i < ign_count; i++) xfree (ign_list[i]); ign_hold = -1; } s_ign_list = (const 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 = (const char **) xrealloc ((void*)ign_list, (ign_size + 1) * sizeof (char *)); } ign_list[ign_count++] = ptr; ign_list[ign_count] = NULL; } } /* Return 1 if the given filename should be ignored by update or import. */ int ign_name (const char *name) { const char **cpp = ign_list; if (cpp == NULL) return (0); while (*cpp) { if (CVS_FNMATCH (*cpp++, name, CVS_CASEFOLD) == 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 (const 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++] = xstrdup (name); } /* Return nonzero if NAME is part of the list of directories to ignore. */ int ignore_directory (const char *name) { int i; if (!dir_ign_list) return 0; i = dir_ign_current; while (i--) { if (fnncmp (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 (List *ilist, List *entries, char *update_dir, Ignore_proc proc) { int subdirs; DIR *dirp; struct dirent *dp; struct stat sb; char *file; char *xdir; List *files; Node *p; /* 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 = opendir ("."); if (dirp == NULL) { error (0, errno, "cannot open current directory"); return; } ign_add_file (CVSDOTIGNORE, 1); wrap_add_file (CVSDOTWRAPPER, true); /* Make a list for the files. */ files = getlist (); while (errno = 0, (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 = (char*)xmalloc (strlen (file) + sizeof CVSADM + 10); sprintf (p, "%s/%s", file, CVSADM); dir = isdir (p); xfree (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 (CVS_LSTAT(file, &sb) != -1) { if (S_ISDIR (sb.st_mode)) { if (! subdirs) { char *temp; temp = (char*)xmalloc (strlen (file) + sizeof (CVSADM) + 10); sprintf (temp, "%s/%s", file, CVSADM); if (isdir (temp)) { xfree (temp); continue; } xfree (temp); } } #ifdef S_ISLNK else if (S_ISLNK (sb.st_mode)) { continue; } #endif } p = getnode (); p->type = FILES; p->key = xstrdup (file); addnode (files, p); } if (errno != 0) error (0, errno, "error reading current directory"); closedir (dirp); sortlist (files, fsortcmp); for (p = files->list->next; p != files->list; p = p->next) (*proc) (p->key, xdir); dellist (&files); } /* Send -I arguments for the cvsignore to the server. The command must be one that accepts them (e.g. update, import). */ void ign_send () { int i; send_to_server ("Argument -I\nArgument ", 0); if(ign_inhibit_server) send_to_server("! ",2); if(ign_list) { for(i=0; i