--- exclude.c	22 Apr 2004 09:58:15 -0000	1.70
+++ exclude.c	22 Apr 2004 10:32:52 -0000
@@ -27,15 +27,17 @@
 #include "rsync.h"
 
 extern int verbose;
+extern int protocol_version;
 extern int eol_nulls;
 extern int list_only;
 extern int recurse;
 
 extern char curr_dir[];
 
-struct exclude_list_struct exclude_list = { 0, 0, "" };
-struct exclude_list_struct local_exclude_list = { 0, 0, "local-cvsignore " };
-struct exclude_list_struct server_exclude_list = { 0, 0, "server " };
+struct exclude_list_struct exclude_list = { 0, 0, 0, "" };
+struct exclude_list_struct perdir_exclude_list = { 0, 0, 0, "perdir " };
+struct exclude_list_struct local_exclude_list = { 0, 0, 0, "local-cvsignore " };
+struct exclude_list_struct server_exclude_list = { 0, 0, 0, "server " };
 char *exclude_path_prefix = NULL;
 
 /** Build an exclude structure given a exclude pattern */
@@ -85,6 +87,8 @@ static void make_exclude(struct exclude_
 	for (cp = ret->pattern; (cp = strchr(cp, '/')) != NULL; cp++)
 		ret->slash_cnt++;
 
+	ret->next = listp->extra;
+
 	if (!listp->tail)
 		listp->head = listp->tail = ret;
 	else {
@@ -108,12 +112,26 @@ void free_exclude_list(struct exclude_li
 			who_am_i(), listp->debug_type);
 	}
 
+	if (listp->extra) {
+		if (listp->tail)
+			listp->tail->next = NULL;
+		else
+			listp->head = NULL;
+	}
+
 	for (ent = listp->head; ent; ent = next) {
 		next = ent->next;
 		free_exclude(ent);
 	}
 
-	listp->head = listp->tail = NULL;
+	clear_exclude_list(listp, NULL);
+}
+
+void clear_exclude_list(struct exclude_list_struct *listp,
+			struct exclude_struct *extra)
+{
+	listp->head = listp->extra = extra;
+	listp->tail = NULL;
 }
 
 static int check_one_exclude(char *name, struct exclude_struct *ex,
@@ -215,21 +233,25 @@ static void report_exclude_result(char c
 
 /*
  * Return true if file NAME is defined to be excluded by the specified
- * exclude list.
+ * exclude list.  Returns -1 for exclude, 0 for normal include, or 1
+ * for super include.
  */
-int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir)
+int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir,
+		  int current_exclusion)
 {
 	struct exclude_struct *ent;
 
 	for (ent = listp->head; ent; ent = ent->next) {
+		if (current_exclusion < 0 && ent->include < 2)
+			continue;
 		if (check_one_exclude(name, ent, name_is_dir)) {
 			report_exclude_result(name, ent, name_is_dir,
 					      listp->debug_type);
-			return !ent->include;
+			return ent->include - 1;
 		}
 	}
 
-	return 0;
+	return current_exclusion;
 }
 
 
@@ -255,10 +277,10 @@ static const char *get_exclude_tok(const
 		p = (const char *)s;
 	}
 
-	/* Is this a '+' or '-' followed by a space (not whitespace)? */
+	/* Is this a +/-/& followed by a space (not whitespace)? */
 	if (!(xflags & XFLG_NO_PREFIXES)
-	    && (*s == '-' || *s == '+') && s[1] == ' ') {
-		*incl_ptr = *s == '+';
+	    && (*s == '-' || *s == '+' || *s == '&') && s[1] == ' ') {
+		*incl_ptr = *s == '+' ? 1 : *s == '&' ? 2 : 0;
 		s += 2;
 	} else
 		*incl_ptr = xflags & XFLG_DEF_INCLUDE;
@@ -390,8 +412,12 @@ void send_exclude_list(int f)
 
 		if (ent->include) {
 			write_int(f, l + 2);
-			write_buf(f, "+ ", 2);
-		} else if ((*p == '-' || *p == '+') && p[1] == ' ') {
+			if (ent->include > 1 && protocol_version >= 28)
+				write_buf(f, "& ", 2);
+			else
+				write_buf(f, "+ ", 2);
+		} else if ((*p == '-' || *p == '+' || *p == '&')
+		    && p[1] == ' ') {
 			write_int(f, l + 2);
 			write_buf(f, "- ", 2);
 		} else
--- flist.c	22 Apr 2004 09:58:18 -0000	1.215
+++ flist.c	22 Apr 2004 10:32:52 -0000
@@ -40,6 +40,7 @@ extern int ignore_errors;
 extern int numeric_ids;
 
 extern int cvs_exclude;
+extern const char *perdir_exclude_from;
 
 extern int recurse;
 extern char curr_dir[MAXPATHLEN];
@@ -66,6 +67,7 @@ extern int write_batch;
 
 extern struct exclude_list_struct exclude_list;
 extern struct exclude_list_struct server_exclude_list;
+extern struct exclude_list_struct perdir_exclude_list;
 extern struct exclude_list_struct local_exclude_list;
 
 int io_error;
@@ -211,6 +213,10 @@ int link_stat(const char *path, STRUCT_S
  */
 static int check_exclude_file(char *fname, int is_dir, int exclude_level)
 {
+	static struct exclude_list_struct *elist[] = {
+	    &exclude_list, &perdir_exclude_list, &local_exclude_list, NULL };
+	int i, rc;
+
 #if 0 /* This currently never happens, so avoid a useless compare. */
 	if (exclude_level == NO_EXCLUDES)
 		return 0;
@@ -227,16 +233,18 @@ static int check_exclude_file(char *fnam
 		}
 	}
 	if (server_exclude_list.head
-	 && check_exclude(&server_exclude_list, fname, is_dir))
+	 && check_exclude(&server_exclude_list, fname, is_dir, 0) < 0)
 		return 1;
 	if (exclude_level != ALL_EXCLUDES)
 		return 0;
-	if (exclude_list.head && check_exclude(&exclude_list, fname, is_dir))
-		return 1;
-	if (local_exclude_list.head
-	    && check_exclude(&local_exclude_list, fname, is_dir))
-		return 1;
-	return 0;
+	for (i = 0, rc = 0; elist[i]; i++) {
+		if (!elist[i]->head)
+			continue;
+		rc = check_exclude(elist[i], fname, is_dir, rc);
+		if (rc > 0)
+			return 0;
+	}
+	return rc < 0;
 }
 
 /* used by the one_file_system code */
@@ -944,8 +952,12 @@ void send_file_name(int f, struct file_l
 	if (recursive && S_ISDIR(file->mode)
 	    && !(file->flags & FLAG_MOUNT_POINT)) {
 		struct exclude_list_struct last_list = local_exclude_list;
-		local_exclude_list.head = local_exclude_list.tail = NULL;
+		struct exclude_list_struct sub_list = perdir_exclude_list;
+		clear_exclude_list(&local_exclude_list, NULL);
+		clear_exclude_list(&perdir_exclude_list, sub_list.head);
 		send_directory(f, flist, f_name_to(file, fbuf));
+		free_exclude_list(&perdir_exclude_list);
+		perdir_exclude_list = sub_list;
 		free_exclude_list(&local_exclude_list);
 		local_exclude_list = last_list;
 	}
@@ -991,6 +1003,18 @@ static void send_directory(int f, struct
 			io_error |= IOERR_GENERAL;
 			rprintf(FINFO,
 				"cannot cvs-exclude in long-named directory %s\n",
+				full_fname(fname));
+		}
+	}
+
+	if (perdir_exclude_from) {
+		if (strlcpy(p, perdir_exclude_from, MAXPATHLEN - offset)
+		    < MAXPATHLEN - offset)
+			add_exclude_file(&perdir_exclude_list, fname, 0);
+		else {
+			io_error |= IOERR_GENERAL;
+			rprintf(FINFO,
+				"cannot perdir-exclude in long-named directory %s\n",
 				full_fname(fname));
 		}
 	}
--- options.c	17 Apr 2004 17:07:23 -0000	1.147
+++ options.c	22 Apr 2004 10:32:53 -0000
@@ -70,6 +70,7 @@ int am_server = 0;
 int am_sender = 0;
 int am_generator = 0;
 char *files_from = NULL;
+char *perdir_exclude_from = NULL;
 int filesfrom_fd = -1;
 char *remote_filesfrom_file = NULL;
 int eol_nulls = 0;
@@ -273,6 +274,7 @@ void usage(enum logcode F)
   rprintf(F,"     --exclude-from=FILE     exclude patterns listed in FILE\n");
   rprintf(F,"     --include=PATTERN       don't exclude files matching PATTERN\n");
   rprintf(F,"     --include-from=FILE     don't exclude patterns listed in FILE\n");
+  rprintf(F,"     --perdir-exclude-from=F look in each dir for exclude-file F\n");
   rprintf(F,"     --files-from=FILE       read FILE for list of source-file names\n");
   rprintf(F," -0  --from0                 all *-from file lists are delimited by nulls\n");
   rprintf(F,"     --version               print version number\n");
@@ -335,6 +337,7 @@ static struct poptOption long_options[] 
   {"dry-run",         'n', POPT_ARG_NONE,   &dry_run, 0, 0, 0 },
   {"sparse",          'S', POPT_ARG_NONE,   &sparse_files, 0, 0, 0 },
   {"cvs-exclude",     'C', POPT_ARG_NONE,   &cvs_exclude, 0, 0, 0 },
+  {"perdir-exclude-from",0,POPT_ARG_STRING, &perdir_exclude_from, 0, 0, 0 },
   {"update",          'u', POPT_ARG_NONE,   &update_only, 0, 0, 0 },
   {"links",           'l', POPT_ARG_NONE,   &preserve_links, 0, 0, 0 },
   {"copy-links",      'L', POPT_ARG_NONE,   &copy_links, 0, 0, 0 },
--- proto.h	22 Apr 2004 09:58:09 -0000	1.189
+++ proto.h	22 Apr 2004 10:32:53 -0000
@@ -52,7 +52,10 @@ int daemon_main(void);
 void setup_protocol(int f_out,int f_in);
 int claim_connection(char *fname,int max_connections);
 void free_exclude_list(struct exclude_list_struct *listp);
-int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir);
+void clear_exclude_list(struct exclude_list_struct *listp,
+			struct exclude_struct *extra);
+int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir,
+		  int current_exclusion);
 void add_exclude(struct exclude_list_struct *listp, const char *pattern,
 		 int xflags);
 void add_exclude_file(struct exclude_list_struct *listp, const char *fname,
--- rsync.h	22 Apr 2004 09:58:24 -0000	1.198
+++ rsync.h	22 Apr 2004 10:32:53 -0000
@@ -502,6 +502,7 @@ struct exclude_struct {
 struct exclude_list_struct {
 	struct exclude_struct *head;
 	struct exclude_struct *tail;
+	struct exclude_struct *extra;
 	char *debug_type;
 };
 
--- rsync.yo	17 Apr 2004 18:40:16 -0000	1.159
+++ rsync.yo	22 Apr 2004 10:32:54 -0000
@@ -331,6 +331,7 @@ verb(
      --exclude-from=FILE     exclude patterns listed in FILE
      --include=PATTERN       don't exclude files matching PATTERN
      --include-from=FILE     don't exclude patterns listed in FILE
+     --perdir-exclude-from=F look in each dir for exclude file F
      --files-from=FILE       read FILE for list of source-file names
  -0  --from0                 all file lists are delimited by nulls
      --version               print version number
@@ -672,6 +673,15 @@ dit(bf(--include-from=FILE)) This specif
 from a file.
 If em(FILE) is bf(-) the list will be read from standard input.
 
+dit(bf(--perdir-exclude-from=FILE)) In any given directory, patterns
+listed in FILE (one per line) are excluded from the file lists
+associated with that directory and all of its subdirectories. Rules
+specified in a subdir take precedence over rules evaluated in a parent
+dir (think of each file's rules being prefixed to the prior rules).  If
+you don't want to inherit the parent-dir rules, start the FILE with a
+single exclamation mark ! to clear them out. You may prefix names with
+an explicit exclude, include, or super-include prefix (see below).
+
 dit(bf(--files-from=FILE)) Using this option allows you to specify the
 exact list of files to transfer (as read from the specified FILE or "-"
 for stdin).  It also tweaks the default behavior of rsync to make
@@ -711,7 +721,8 @@ was located on the remote "src" host.
 
 dit(bf(-0, --from0)) This tells rsync that the filenames it reads from a
 file are terminated by a null ('\0') character, not a NL, CR, or CR+LF.
-This affects --exclude-from, --include-from, and --files-from.
+This affects --exclude-from, --include-from, --perdir-exclude-from,
+and --files-from.
 It does not affect --cvs-exclude (since all names read from a .cvsignore
 file are split on whitespace).
 
@@ -998,6 +1009,12 @@ itemize(
   it() if the pattern starts with "- " (a minus followed by a space)
   then it is always considered an exclude pattern, even if specified as
   part of an include option. The prefix is discarded before matching.
+
+  it() if the pattern starts with "& " (an ampersand followed by a space)
+  then it is considered a super-include pattern.  A super-include will
+  override an exclude from another exclude list (a normal include only
+  overrides an exclusion from further down in the same list).  The
+  prefix is discarded before matching.
 
   it() if the pattern is a single exclamation mark ! then the current
   include/exclude list is reset, removing all previously defined patterns.
--- t_stub.c	22 Apr 2004 09:58:11 -0000	1.7
+++ t_stub.c	22 Apr 2004 10:32:54 -0000
@@ -46,7 +46,7 @@ struct exclude_list_struct server_exclud
 }
 
  int check_exclude(UNUSED(struct exclude_list_struct *listp), UNUSED(char *name),
-		   UNUSED(int name_is_dir))
+		   UNUSED(int name_is_dir), UNUSED(int current_exclusion))
 {
 	/* This function doesn't really get called in this test context, so
 	 * just return 0. */
--- util.c	22 Apr 2004 09:58:21 -0000	1.137
+++ util.c	22 Apr 2004 10:32:54 -0000
@@ -476,7 +476,7 @@ static int exclude_server_path(char *arg
 	if (server_exclude_list.head) {
 		for (s = arg; (s = strchr(s, '/')) != NULL; ) {
 			*s = '\0';
-			if (check_exclude(&server_exclude_list, arg, 1)) {
+			if (check_exclude(&server_exclude_list, arg, 1, 0) < 0) {
 				/* We must leave arg truncated! */
 				return 1;
 			}
