/* 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 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<ign_count; i++)
{
if(ign_list[i])
{
send_to_server (ign_list[i], 0);
send_to_server (" ",1);
}
}
}
send_to_server ("\n", 0);
}
int ign_close()
{
if(ign_list)
{
int i;
for(i=0; i<ign_count; i++)
xfree(ign_list[i]);
xfree(ign_list);
ign_count=0;
ign_size=0;
}
return 0;
}
void ign_display()
{
int i;
if(ign_inhibit_server)
printf("! ");
if(ign_list)
{
for(i=0; i<ign_count; i++)
{
if(ign_list[i])
{
printf("%s ",ign_list[i]);
}
}
}
printf("\n");
}
syntax highlighted by Code2HTML, v. 0.9.1