Hi guys, I recently stumbled across what I think is an rsync bug. The problem was I was trying to rsync from one CVS checkout, to another, and was using rsync -Cavz to do the job. In this CVS module, there happens to be two sub-directories with the names of "core" and "tags" respectively, which were ignored, since these names matched the default CVS ignore list. When rsync builds the file lists with the -C option, it should include any CVS controlled files (by checking CVS/Entries) before applying the default CVS excludes, and the per-directory .cvsignore files. Its actually surprising how many projects out there have sub-directories with the name "core". Linux is one of them. Anyway, I have included a patch against rsync 2.4.6 which I believe fixes this bug. If this patch is ok, could it be included in the next release? Thanks for writing and maintaining such a useful tool! -- Cheers, David diff -r -u --exclude=.o --exclude=rsync --exclude=configure --exclude=config.log /home/sits/orig/rsync-2.4.6/exclude.c ./exclude.c --- /home/sits/orig/rsync-2.4.6/exclude.c Wed Sep 6 13:46:43 2000 +++ ./exclude.c Sat Sep 29 21:45:53 2001 @@ -27,6 +27,10 @@ static struct exclude_struct **exclude_list; +/* Global CVS exclude list, set from cvs_ignore_list, ~/.cvsignore + and getenv("CVSIGNORE"). */ +static struct exclude_struct **global_cvs_exclude_list; + /* build an exclude structure given a exclude pattern */ static struct exclude_struct *make_exclude(char *pattern, int include) { @@ -125,6 +129,7 @@ STRUCT_STAT *st) { int n; + extern int cvs_exclude; if (name && (name[0] == '.') && !name[1]) /* never exclude '.', even if somebody does --exclude '*' */ @@ -142,6 +147,18 @@ return !local_exclude_list[n]->include; } + /* The global CVS exclude list must be checked last, since the user may + have files under CVS control (eg directories called "core" or + "tags") that should not be excluded if cvs_exclude is set. CVS + controlled files and directories will be set in + local_exclude_list. */ + if (cvs_exclude) { + for (n=0; global_cvs_exclude_list[n]; n++) + if (check_one_exclude(name, + global_cvs_exclude_list[n],st)) + return !global_cvs_exclude_list[n]->include; + } + return 0; } @@ -319,16 +336,21 @@ return(t); } - -void add_exclude_line(char *p) +static void add_exclude_line_to_list(char *p, + struct exclude_struct ***list) { char *tok; if (!p || !*p) return; p = strdup(p); if (!p) out_of_memory("add_exclude_line"); for (tok=get_exclude_tok(p); tok; tok=get_exclude_tok(NULL)) - add_exclude(tok, 0); + add_exclude_list(tok, list, 0); free(p); +} + +void add_exclude_line(char *p) +{ + add_exclude_line_to_list(p, &exclude_list); } void add_include_line(char *p) @@ -342,6 +364,35 @@ free(p); } +/* Include all entries from fname, which is a CVS/Entries file, into list */ +void add_cvs_entries(char *fname, struct exclude_struct ***list) +{ + char line[MAXPATHLEN]; + FILE *f = fopen(fname,"r"); + if (f) { + while (fgets(line,MAXPATHLEN,f)) { + int len = strlen(line); + int offset = -1; + + /* Determine offset for filename in line, depending + if the entry is a directory or a file */ + if (len >= 4 && line[0] == 'D' && line[1] == '/') + offset = 2; + else if (len >= 3 && line[0] == '/') + offset = 1; + + /* Extract the entry name */ + if (offset != -1) { + char *p = strchr(&line[offset],'/'); + if (p) { + *p = 0; + add_exclude_list(&line[offset], + list, 1); + } + } + } + } +} static char *cvs_ignore_list[] = { "RCS","SCCS","CVS","CVS.adm","RCSLOG","cvslog.*", @@ -359,12 +410,13 @@ int i; for (i=0; cvs_ignore_list[i]; i++) - add_exclude(cvs_ignore_list[i], 0); + add_exclude_list(cvs_ignore_list[i], &global_cvs_exclude_list, 0); if ((p=getenv("HOME")) && strlen(p) < (MAXPATHLEN-12)) { slprintf(fname,sizeof(fname), "%s/.cvsignore",p); - add_exclude_file(fname,0,0); + global_cvs_exclude_list + make_exclude_list(fname, global_cvs_exclude_list, 0, 0); } - add_exclude_line(getenv("CVSIGNORE")); + add_exclude_line_to_list(getenv("CVSIGNORE"), &global_cvs_exclude_list); } Binary files /home/sits/orig/rsync-2.4.6/exclude.o and ./exclude.o differ diff -r -u --exclude=.o --exclude=rsync --exclude=configure --exclude=config.log /home/sits/orig/rsync-2.4.6/flist.c ./flist.c --- /home/sits/orig/rsync-2.4.6/flist.c Wed Sep 6 13:46:43 2000 +++ ./flist.c Sat Sep 29 21:46:59 2001 @@ -663,9 +663,26 @@ local_exclude_list = NULL; if (cvs_exclude) { + /* Add the files specified in CVS/Entries to + local_include_list, since they may contain directory + names like "core" and "tags" which shouldn't be + ignored. Note this should be added before the exclude + patterns in ./.cvsignore, since it may contain bogus + entries that actually refer to CVS controlled files. */ + if (strlen(fname) + strlen("CVS/Entries") <= MAXPATHLEN-1) { + strcpy(p,"CVS/Entries"); + add_cvs_entries(fname,&local_exclude_list); + } else { + io_error = 1; + rprintf(FINFO,"cannot scan CVS/Entries in long-named directory %s\n",fname); + } + + /* Add the exclude patterns specified in the ./.cvsignore file + if it exists. */ if (strlen(fname) + strlen(".cvsignore") <= MAXPATHLEN-1) { strcpy(p,".cvsignore"); - local_exclude_list = make_exclude_list(fname,NULL,0,0); + local_exclude_list = make_exclude_list(fname,local_exclude_list, + 0,0); } else { io_error = 1; rprintf(FINFO,"cannot cvs-exclude in long-named directory %s\n",fname); @@ -681,6 +698,7 @@ send_file_name(f,flist,fname,recurse,0); } + /* Free the exclude entries in local_exclude_list */ if (local_exclude_list) { add_exclude_list("!", &local_exclude_list, 0); } Binary files /home/sits/orig/rsync-2.4.6/flist.o and ./flist.o differ diff -r -u --exclude=.o --exclude=rsync --exclude=configure --exclude=config.log /home/sits/orig/rsync-2.4.6/proto.h ./proto.h --- /home/sits/orig/rsync-2.4.6/proto.h Wed Sep 6 13:46:43 2000 +++ ./proto.h Sat Sep 29 20:33:47 2001 @@ -33,6 +33,7 @@ char *get_exclude_tok(char *p); void add_exclude_line(char *p); void add_include_line(char *p); +void add_cvs_entries(char *fname, struct exclude_struct ***list); void add_cvs_excludes(void); int sparse_end(int f); int write_file(int f,char *buf,int len);