Fixed rules being inherited without --inherit.
[rsync/rsync-patches.git] / filter.diff
CommitLineData
8a529471
WD
1After applying this patch and running configure, you MUST run this
2command before "make":
3
4 make proto
5
524989ae
WD
6This patch adds the ability to merge rules into your excludes/includes
7using a ". FILE" idiom. If you specify a name without slashes, that
8filename will be looked for in every subdirectory that rsync visits,
0c7d1fd8 9and the rules found in that subdirectory's file will affect that dir
56babefa 10and (optionally) its subdirectories.
a55d21aa
WD
11
12For example:
13
56babefa 14 rsync -av --exclude='. .excl' --inherit=. from/ to
a55d21aa
WD
15
16The above will look for a file named ".excl" in every directory of the
524989ae
WD
17hierarchy that rsync visits, and it will exclude (by default) names
18based on the rules found therein. If one of the .excl files contains
19this:
a55d21aa
WD
20
21 + *.c
524989ae
WD
22 . .excl2
23 . ./.excl3
24 *.o
a55d21aa 25
524989ae 26Then the file ".excl2" will also be read in the current dir, and all
56babefa
WD
27subdirs of the current dir (due to the --inherit option). The file
28".excl3" would just be read in for the current dir because its name
29contained a slash.
a55d21aa
WD
30
31..wayne..
32
13bed3dd 33--- orig/exclude.c 2004-05-22 19:30:03
045caa90
WD
34+++ exclude.c 2004-08-03 17:06:22
35@@ -27,16 +27,65 @@
36 #include "rsync.h"
37
38 extern int verbose;
39+extern int am_sender;
a55d21aa
WD
40 extern int eol_nulls;
41 extern int list_only;
42 extern int recurse;
43+extern int io_error;
56babefa 44+extern int module_id;
045caa90
WD
45+extern int delete_mode;
46+extern int delete_excluded;
1e476835 47+extern int inherit_exclude_levels;
56babefa 48+extern int sanitize_paths;
a55d21aa
WD
49
50 extern char curr_dir[];
56babefa 51+extern unsigned int curr_dir_len;
a55d21aa 52
d8af8661 53 struct exclude_list_struct exclude_list = { 0, 0, "" };
524989ae 54-struct exclude_list_struct local_exclude_list = { 0, 0, "per-dir .cvsignore " };
d8af8661 55 struct exclude_list_struct server_exclude_list = { 0, 0, "server " };
a55d21aa 56 char *exclude_path_prefix = NULL;
eabda998
WD
57+struct exclude_struct **mergelist_parents;
58+int mergelist_cnt = 0;
59+int mergelist_size = 0;
d8af8661 60+
eabda998 61+struct mergelist_save_struct {
d1e7c1c8 62+ struct exclude_list_struct *array;
d8af8661
WD
63+ int count;
64+};
524989ae 65+
a55d21aa
WD
66+static char dirbuf[MAXPATHLEN];
67+static unsigned int dirbuf_offset = 0;
a55d21aa 68+
d1e7c1c8
WD
69+/* Each exclude_list_struct describes a singly-linked list by keeping track
70+ * of both the head and tail pointers. The list is slightly unusual in that
71+ * a parent-dir's content can be appended to the end of the local list in a
72+ * special way: the last item in the local list has its "next" pointer set
0c7d1fd8 73+ * to point to the inherited list, but the local list's tail pointer points
d1e7c1c8 74+ * at the end of the local list. Thus, if the local list is empty, the head
0c7d1fd8 75+ * will be pointing at the inherited content but the tail will be NULL. To
d1e7c1c8
WD
76+ * help you visualize this, here are the possible list arrangements:
77+ *
78+ * Completely Empty Local Content Only
79+ * ================================== ====================================
80+ * head -> NULL head -> Local1 -> Local2 -> NULL
81+ * tail -> NULL tail -------------^
82+ *
83+ * Inherited Content Only Both Local and Inherited Content
84+ * ================================== ====================================
85+ * head -> Parent1 -> Parent2 -> NULL head -> L1 -> L2 -> P1 -> P2 -> NULL
86+ * tail -> NULL tail ---------^
87+ *
88+ * This means that anyone wanting to traverse the whole list to USE it just
89+ * needs to start at the head and use the "next" pointers until it goes
90+ * NULL. To add new local content, we insert the item after the tail item
91+ * and update the tail (obviously, if "tail" was NULL, we insert it at the
92+ * head). To clear the local list, WE MUST NOT FREE THE INHERITED CONTENT
bc95f62b
WD
93+ * because it is shared between the current list and our parent list(s).
94+ * The easiest way to handle this is to simply truncate the list after the
95+ * tail item and then free the local list from the head. When inheriting
96+ * the list for a new local dir, we just save off the exclude_list_struct
0c7d1fd8 97+ * values (so we can pop back to them later) and set the tail to NULL.
d1e7c1c8 98+ */
d8af8661 99
0c7d1fd8
WD
100 /** Build an exclude structure given an exclude pattern. */
101 static void make_exclude(struct exclude_list_struct *listp, const char *pat,
045caa90 102@@ -46,6 +95,24 @@ static void make_exclude(struct exclude_
a55d21aa 103 const char *cp;
0c7d1fd8
WD
104 unsigned int ex_len;
105
524989ae 106+ if (mflags & MATCHFLG_MERGE_FILE) {
eabda998 107+ int i;
a55d21aa 108+ /* If the local include file was already mentioned, don't
524989ae 109+ * add it again. */
eabda998
WD
110+ for (i = 0; i < mergelist_cnt; i++) {
111+ struct exclude_struct *ex = mergelist_parents[i];
112+ if (strlen(ex->pattern) == pat_len
d8af8661 113+ && memcmp(ex->pattern, pat, pat_len) == 0)
a55d21aa
WD
114+ return;
115+ }
524989ae
WD
116+ if ((pat_len == 10 || (pat_len > 10 && pat[pat_len-11] == '/'))
117+ && strncmp(pat+pat_len-10, ".cvsignore", 10) == 0) {
a55d21aa
WD
118+ mflags |= MATCHFLG_CVSIGNORE;
119+ mflags &= ~MATCHFLG_INCLUDE;
120+ } else
121+ mflags &= ~MATCHFLG_CVSIGNORE;
122+ }
0c7d1fd8 123+
a55d21aa
WD
124 ret = new(struct exclude_struct);
125 if (!ret)
126 out_of_memory("make_exclude");
045caa90 127@@ -81,14 +148,36 @@ static void make_exclude(struct exclude_
7b22909b 128 mflags |= MATCHFLG_DIRECTORY;
a55d21aa
WD
129 }
130
7b22909b 131- for (cp = ret->pattern; (cp = strchr(cp, '/')) != NULL; cp++)
bc95f62b 132- ret->slash_cnt++;
524989ae 133+ if (mflags & MATCHFLG_MERGE_FILE) {
d8af8661
WD
134+ struct exclude_list_struct *lp
135+ = new_array(struct exclude_list_struct, 1);
136+ if (!lp)
a55d21aa 137+ out_of_memory("make_exclude");
7cb7ae4e 138+ lp->head = lp->tail = NULL;
524989ae 139+ if (asprintf(&lp->debug_type, "per-dir %s ", ret->pattern) < 0)
a55d21aa 140+ out_of_memory("make_exclude");
eabda998
WD
141+ ret->u.mergelist = lp;
142+ if (mergelist_cnt == mergelist_size) {
143+ mergelist_size += 5;
144+ mergelist_parents = realloc_array(mergelist_parents,
145+ struct exclude_struct *,
146+ mergelist_size);
147+ if (!mergelist_parents)
148+ out_of_memory("make_exclude");
149+ }
150+ mergelist_parents[mergelist_cnt++] = ret;
7b22909b
WD
151+ } else {
152+ for (cp = ret->pattern; (cp = strchr(cp, '/')) != NULL; cp++)
153+ ret->u.slash_cnt++;
abe86b1f 154+ }
7b22909b 155
0c7d1fd8 156 ret->match_flags = mflags;
a55d21aa 157
7b22909b
WD
158- if (!listp->tail)
159+ if (!listp->tail) {
160+ ret->next = listp->head;
161 listp->head = listp->tail = ret;
162- else {
163+ } else {
164+ ret->next = listp->tail->next;
165 listp->tail->next = ret;
166 listp->tail = ret;
167 }
08a2fca5 168@@ -96,22 +185,156 @@ static void make_exclude(struct exclude_
d8af8661
WD
169
170 static void free_exclude(struct exclude_struct *ex)
171 {
172+ if (ex->match_flags & MATCHFLG_MERGE_FILE) {
eabda998
WD
173+ free(ex->u.mergelist->debug_type);
174+ free(ex->u.mergelist);
d8af8661
WD
175+ }
176 free(ex->pattern);
a55d21aa
WD
177 free(ex);
178 }
179
d8af8661
WD
180-void clear_exclude_list(struct exclude_list_struct *listp)
181+static void clear_exclude_list(struct exclude_list_struct *listp)
a55d21aa 182 {
7cb7ae4e
WD
183- struct exclude_struct *ent, *next;
184-
185- for (ent = listp->head; ent; ent = next) {
186- next = ent->next;
187- free_exclude(ent);
188+ if (listp->tail) {
189+ struct exclude_struct *ent, *next;
bc95f62b 190+ /* Truncate any inherited items from the local list. */
7cb7ae4e 191+ listp->tail->next = NULL;
dc5cce3c 192+ /* Now free everything that is left. */
7cb7ae4e
WD
193+ for (ent = listp->head; ent; ent = next) {
194+ next = ent->next;
195+ free_exclude(ent);
196+ }
524989ae
WD
197 }
198
7cb7ae4e
WD
199 listp->head = listp->tail = NULL;
200 }
a55d21aa 201
56babefa 202+void pre_inherit_files(char *fname, unsigned int len)
1e476835 203+{
56babefa
WD
204+ char path[MAXPATHLEN], save[MAXPATHLEN];
205+ unsigned int limit;
1e476835
WD
206+ int i;
207+
56babefa
WD
208+ if (sanitize_paths)
209+ limit = strlen(lp_path(module_id));
210+ else
211+ limit = 0;
212+ fname[len] = '\0';
213+ if (*fname == '/')
045caa90
WD
214+ strcpy(path, fname); /* safe */
215+ else if (len == 2 && *fname == '.' && fname[1] == '/')
216+ len = strlcpy(path, curr_dir, sizeof path);
56babefa
WD
217+ else
218+ len = pathjoin(path, sizeof path, curr_dir, fname);
219+
220+ if (len >= MAXPATHLEN || len == 0)
1e476835
WD
221+ return;
222+
56babefa
WD
223+ for (i = inherit_exclude_levels, len--; i && len > limit; ) {
224+ if (path[--len] == '/')
225+ i--;
226+ }
227+ for (len++; i < inherit_exclude_levels; i++) {
228+ char *s = path + len;
229+ strcpy(save, s);
230+ push_local_excludes(path, len);
231+ strcpy(s, save);
232+ if (!(s = strchr(s, '/')))
233+ break; /* Impossible ... */
234+ len = s - path + 1;
1e476835
WD
235+ }
236+}
237+
eabda998 238+void *push_local_excludes(char *fname, unsigned int offset)
a55d21aa 239+{
eabda998
WD
240+ struct mergelist_save_struct *push;
241+ struct exclude_list_struct *ap;
242+ int i;
243+
244+ /* Make it easy to construct the full path for a merge-file that was
245+ * specified with a relative path by saving off the current dir. */
246+ memcpy(dirbuf, fname, offset);
247+ dirbuf_offset = offset;
045caa90
WD
248+ if (dirbuf_offset == 2 && *dirbuf == '.')
249+ dirbuf_offset = 0;
eabda998
WD
250+
251+ if (!(push = new_array(struct mergelist_save_struct, 1)))
252+ out_of_memory("push_local_excludes");
253+
254+ push->count = mergelist_cnt;
255+ push->array = new_array(struct exclude_list_struct, mergelist_cnt);
256+ if (!push->array)
257+ out_of_memory("push_local_excludes");
524989ae 258+
eabda998
WD
259+ for (i = 0, ap = push->array; i < mergelist_cnt; i++) {
260+ memcpy(ap++, mergelist_parents[i]->u.mergelist,
261+ sizeof (struct exclude_list_struct));
262+ }
263+
264+ /* Note: add_exclude_file() might increase mergelist_cnt, so keep
265+ * this loop separate from the above loop. */
266+ for (i = 0; i < mergelist_cnt; i++) {
267+ struct exclude_struct *ex = mergelist_parents[i];
268+ struct exclude_list_struct *lp = ex->u.mergelist;
a55d21aa 269+ int flags;
524989ae
WD
270+
271+ if (verbose > 2) {
272+ rprintf(FINFO, "[%s] pushing %sexclude list\n",
d8af8661 273+ who_am_i(), lp->debug_type);
524989ae 274+ }
d8af8661 275+
eabda998 276+ if (ex->match_flags & MATCHFLG_CVSIGNORE) {
d8af8661 277+ lp->head = NULL; /* CVS doesn't inherit rules. */
ee1af13c 278+ flags = XFLG_WORD_SPLIT | XFLG_WORDS_ONLY;
a55d21aa 279+ } else {
eabda998 280+ flags = ex->match_flags & MATCHFLG_INCLUDE
524989ae 281+ ? XFLG_DEF_INCLUDE : 0;
08a2fca5
WD
282+ if (inherit_exclude_levels < 0)
283+ lp->head = NULL;
a55d21aa 284+ }
d8af8661 285+ lp->tail = NULL; /* Switch any local rules to inherited. */
eabda998 286+ if (strlcpy(dirbuf + dirbuf_offset, ex->pattern,
d8af8661
WD
287+ MAXPATHLEN - dirbuf_offset) < MAXPATHLEN - dirbuf_offset)
288+ add_exclude_file(lp, dirbuf, flags);
524989ae 289+ else {
a55d21aa
WD
290+ io_error |= IOERR_GENERAL;
291+ rprintf(FINFO,
292+ "cannot add local excludes in long-named directory %s\n",
d8af8661 293+ full_fname(dirbuf));
a55d21aa
WD
294+ }
295+ }
296+
524989ae 297+ return (void*)push;
a55d21aa
WD
298+}
299+
eabda998 300+void pop_local_excludes(void *mem)
a55d21aa 301+{
eabda998
WD
302+ struct mergelist_save_struct *pop = (struct mergelist_save_struct*)mem;
303+ struct exclude_list_struct *ap;
304+ int i;
d8af8661 305+
066e3b3e 306+ for (i = mergelist_cnt; i-- > 0; ) {
eabda998
WD
307+ struct exclude_struct *ex = mergelist_parents[i];
308+ struct exclude_list_struct *lp = ex->u.mergelist;
524989ae 309+
524989ae
WD
310+ if (verbose > 2) {
311+ rprintf(FINFO, "[%s] popping %sexclude list\n",
d8af8661 312+ who_am_i(), lp->debug_type);
524989ae 313+ }
d8af8661
WD
314+
315+ clear_exclude_list(lp);
a55d21aa 316+ }
d8af8661 317+
eabda998
WD
318+ mergelist_cnt = pop->count;
319+ for (i = 0, ap = pop->array; i < mergelist_cnt; i++) {
320+ memcpy(mergelist_parents[i]->u.mergelist, ap++,
321+ sizeof (struct exclude_list_struct));
322+ }
d8af8661
WD
323+
324+ free(pop->array);
325+ free(pop);
7cb7ae4e
WD
326+}
327+
a55d21aa 328 static int check_one_exclude(char *name, struct exclude_struct *ex,
7cb7ae4e
WD
329 int name_is_dir)
330 {
08a2fca5 331@@ -122,7 +345,7 @@ static int check_one_exclude(char *name,
bc95f62b
WD
332 /* If the pattern does not have any slashes AND it does not have
333 * a "**" (which could match a slash), then we just match the
334 * name portion of the path. */
335- if (!ex->slash_cnt && !(ex->match_flags & MATCHFLG_WILD2)) {
336+ if (!ex->u.slash_cnt && !(ex->match_flags & MATCHFLG_WILD2)) {
337 if ((p = strrchr(name,'/')) != NULL)
338 name = p+1;
339 }
08a2fca5 340@@ -133,7 +356,8 @@ static int check_one_exclude(char *name,
045caa90
WD
341 name = full_name;
342 }
343
344- if (!name[0]) return 0;
345+ if (!name[0])
346+ return 0;
347
348 if (ex->match_flags & MATCHFLG_DIRECTORY && !name_is_dir)
349 return 0;
08a2fca5 350@@ -148,9 +372,9 @@ static int check_one_exclude(char *name,
bc95f62b
WD
351 if (ex->match_flags & MATCHFLG_WILD) {
352 /* A non-anchored match with an infix slash and no "**"
353 * needs to match the last slash_cnt+1 name elements. */
0c7d1fd8
WD
354- if (!match_start && ex->slash_cnt
355+ if (!match_start && ex->u.slash_cnt
356 && !(ex->match_flags & MATCHFLG_WILD2)) {
bc95f62b
WD
357- int cnt = ex->slash_cnt + 1;
358+ int cnt = ex->u.slash_cnt + 1;
359 for (p = name + strlen(name) - 1; p >= name; p--) {
360 if (*p == '/' && !--cnt)
361 break;
08a2fca5 362@@ -221,6 +445,13 @@ int check_exclude(struct exclude_list_st
a55d21aa
WD
363 struct exclude_struct *ent;
364
365 for (ent = listp->head; ent; ent = ent->next) {
524989ae 366+ if (ent->match_flags & MATCHFLG_MERGE_FILE) {
eabda998 367+ int rc = check_exclude(ent->u.mergelist, name,
d8af8661 368+ name_is_dir);
a55d21aa
WD
369+ if (rc)
370+ return rc;
371+ continue;
372+ }
373 if (check_one_exclude(name, ent, name_is_dir)) {
374 report_exclude_result(name, ent, name_is_dir,
375 listp->debug_type);
08a2fca5 376@@ -253,11 +484,16 @@ static const char *get_exclude_tok(const
a55d21aa
WD
377 p = (const char *)s;
378 }
379
380- /* Is this a '+' or '-' followed by a space (not whitespace)? */
381+ /* Is this a +/-/. followed by a space (not whitespace)? */
ee1af13c 382 if (!(xflags & XFLG_WORDS_ONLY)
a55d21aa 383- && (*s == '-' || *s == '+') && s[1] == ' ') {
a55d21aa 384+ && (*s == '-' || *s == '+' || *s == '.') && s[1] == ' ') {
0c7d1fd8
WD
385 if (*s == '+')
386 mflags |= MATCHFLG_INCLUDE;
a55d21aa 387+ else if (*s == '.') {
524989ae 388+ mflags |= MATCHFLG_MERGE_FILE;
a55d21aa
WD
389+ if (xflags & XFLG_DEF_INCLUDE)
390+ mflags |= MATCHFLG_INCLUDE;
391+ }
392 s += 2;
0c7d1fd8
WD
393 } else if (xflags & XFLG_DEF_INCLUDE)
394 mflags |= MATCHFLG_INCLUDE;
08a2fca5 395@@ -292,6 +528,7 @@ void add_exclude(struct exclude_list_str
045caa90
WD
396 cp = pattern;
397 pat_len = 0;
398 while (1) {
399+ /* Remember that the returned string is NOT '\0' terminated! */
400 cp = get_exclude_tok(cp + pat_len, &pat_len, &mflags, xflags);
401 if (!pat_len)
402 break;
08a2fca5 403@@ -306,11 +543,57 @@ void add_exclude(struct exclude_list_str
0c7d1fd8
WD
404 continue;
405 }
a55d21aa 406
524989ae 407+ if (mflags & MATCHFLG_MERGE_FILE) {
a55d21aa 408+ char name[MAXPATHLEN];
bc95f62b
WD
409+ if (pat_len >= sizeof name) {
410+ rprintf(FERROR,
411+ "merge filename too long: %s\n", cp);
412+ continue;
413+ }
045caa90 414+ /* We need a null-terminated version of the filename. */
a55d21aa 415+ strlcpy(name, cp, pat_len+1);
1e476835
WD
416+ if (!(xflags & XFLG_PERDIR_MERGE)
417+ && (inherit_exclude_levels == -1
418+ || strchr(name, '/') != NULL)) {
c4e46f36
WD
419+ char *mem = NULL;
420+ if (*name == '/') {
421+ if (sanitize_paths) {
422+ mem = alloc_sanitize_path(name,
423+ curr_dir);
424+ cp = mem;
425+ } else
426+ cp = name;
427+ } else {
428+ if (sanitize_paths) {
429+ dirbuf[dirbuf_offset] = '\0';
430+ sanitize_path(name, dirbuf);
431+ }
045caa90
WD
432+ if (!dirbuf_offset)
433+ cp = name;
434+ else if (strlcpy(dirbuf + dirbuf_offset,
a55d21aa 435+ name, MAXPATHLEN - dirbuf_offset)
bc95f62b
WD
436+ >= MAXPATHLEN - dirbuf_offset) {
437+ rprintf(FERROR,
438+ "merge filename too long: %s...\n",
439+ dirbuf);
440+ continue;
045caa90
WD
441+ } else
442+ cp = dirbuf;
a55d21aa
WD
443+ }
444+ add_exclude_file(listp, cp,
c4e46f36
WD
445+ xflags | XFLG_FATAL_ERRORS);
446+ if (mem)
447+ free(mem);
a55d21aa 448+ continue;
0c7d1fd8
WD
449+ }
450+ }
a55d21aa 451+
0c7d1fd8
WD
452 make_exclude(listp, cp, pat_len, mflags);
453
454 if (verbose > 2) {
455- rprintf(FINFO, "[%s] add_exclude(%.*s, %s%sclude)\n",
a55d21aa 456+ rprintf(FINFO, "[%s] add_exclude(%.*s, %s%s%sclude)\n",
eabda998 457 who_am_i(), (int)pat_len, cp, listp->debug_type,
524989ae 458+ mflags & MATCHFLG_MERGE_FILE ? "FILE " : "",
0c7d1fd8 459 mflags & MATCHFLG_INCLUDE ? "in" : "ex");
a55d21aa 460 }
0c7d1fd8 461 }
08a2fca5 462@@ -343,6 +626,11 @@ void add_exclude_file(struct exclude_lis
c4e46f36
WD
463 return;
464 }
465
466+ if (verbose > 2) {
1e476835
WD
467+ rprintf(FINFO, "[%s] add_exclude_file(%s,%d)\n",
468+ who_am_i(), fname, xflags);
c4e46f36
WD
469+ }
470+
471 while (1) {
472 char *s = line;
473 int ch, overflow = 0;
08a2fca5 474@@ -402,7 +690,11 @@ void send_exclude_list(int f)
0c7d1fd8 475 if (ent->match_flags & MATCHFLG_INCLUDE) {
a55d21aa
WD
476 write_int(f, l + 2);
477 write_buf(f, "+ ", 2);
478- } else if ((*p == '-' || *p == '+') && p[1] == ' ') {
524989ae 479+ } else if (ent->match_flags & MATCHFLG_MERGE_FILE) {
a55d21aa
WD
480+ write_int(f, l + 2);
481+ write_buf(f, ". ", 2);
482+ } else if ((*p == '-' || *p == '+' || *p == '.')
483+ && p[1] == ' ') {
484 write_int(f, l + 2);
485 write_buf(f, "- ", 2);
486 } else
08a2fca5 487@@ -411,6 +703,13 @@ void send_exclude_list(int f)
045caa90
WD
488 }
489
490 write_int(f, 0);
491+
492+ /* If we're the receiver and we don't need the excludes, dump them. */
493+ if (!am_sender && (!delete_mode || delete_excluded)) {
494+ clear_exclude_list(&exclude_list);
495+ inherit_exclude_levels = 0;
496+ mergelist_cnt = 0;
497+ }
498 }
499
500
08a2fca5 501@@ -443,6 +742,7 @@ void add_cvs_excludes(void)
a55d21aa
WD
502 char fname[MAXPATHLEN];
503 char *p;
504
1e476835 505+ add_exclude(&exclude_list, ". .cvsignore", XFLG_PERDIR_MERGE);
a55d21aa 506 add_exclude(&exclude_list, default_cvsignore,
ee1af13c 507 XFLG_WORD_SPLIT | XFLG_WORDS_ONLY);
a55d21aa 508
045caa90
WD
509--- orig/flist.c 2004-07-17 15:20:05
510+++ flist.c 2004-08-03 17:14:17
a55d21aa
WD
511@@ -39,8 +39,6 @@ extern int module_id;
512 extern int ignore_errors;
513 extern int numeric_ids;
514
515-extern int cvs_exclude;
516-
517 extern int recurse;
518 extern char curr_dir[MAXPATHLEN];
519 extern char *files_from;
7628f156 520@@ -59,6 +57,7 @@ extern int implied_dirs;
56babefa
WD
521 extern int copy_links;
522 extern int copy_unsafe_links;
523 extern int protocol_version;
524+extern int inherit_exclude_levels;
525 extern int sanitize_paths;
7628f156
WD
526 extern int delete_excluded;
527 extern int orig_umask;
9be39c35 528@@ -66,7 +65,6 @@ extern int list_only;
a55d21aa
WD
529
530 extern struct exclude_list_struct exclude_list;
531 extern struct exclude_list_struct server_exclude_list;
532-extern struct exclude_list_struct local_exclude_list;
533
534 int io_error;
535
9be39c35 536@@ -221,8 +219,6 @@ int link_stat(const char *path, STRUCT_S
a55d21aa
WD
537 */
538 static int check_exclude_file(char *fname, int is_dir, int exclude_level)
539 {
540- int rc;
541-
542 #if 0 /* This currently never happens, so avoid a useless compare. */
543 if (exclude_level == NO_EXCLUDES)
544 return 0;
9be39c35 545@@ -244,10 +240,7 @@ static int check_exclude_file(char *fnam
a55d21aa
WD
546 if (exclude_level != ALL_EXCLUDES)
547 return 0;
548 if (exclude_list.head
549- && (rc = check_exclude(&exclude_list, fname, is_dir)) != 0)
550- return rc < 0;
551- if (local_exclude_list.head
552- && check_exclude(&local_exclude_list, fname, is_dir) < 0)
553+ && check_exclude(&exclude_list, fname, is_dir) < 0)
554 return 1;
555 return 0;
556 }
9be39c35 557@@ -954,15 +947,7 @@ void send_file_name(int f, struct file_l
a55d21aa
WD
558
559 if (recursive && S_ISDIR(file->mode)
560 && !(file->flags & FLAG_MOUNT_POINT)) {
561- struct exclude_list_struct last_list = local_exclude_list;
562- local_exclude_list.head = local_exclude_list.tail = NULL;
563 send_directory(f, flist, f_name_to(file, fbuf));
7cb7ae4e
WD
564- if (verbose > 2) {
565- rprintf(FINFO, "[%s] popping %sexclude list\n",
566- who_am_i(), local_exclude_list.debug_type);
567- }
d8af8661 568- clear_exclude_list(&local_exclude_list);
a55d21aa
WD
569- local_exclude_list = last_list;
570 }
571 }
572
9be39c35 573@@ -973,6 +958,7 @@ static void send_directory(int f, struct
a55d21aa
WD
574 struct dirent *di;
575 char fname[MAXPATHLEN];
576 unsigned int offset;
577+ void *save_excludes;
578 char *p;
579
580 d = opendir(dir);
045caa90 581@@ -996,18 +982,13 @@ static void send_directory(int f, struct
a55d21aa
WD
582 offset++;
583 }
584
585- if (cvs_exclude) {
586- if (strlcpy(p, ".cvsignore", MAXPATHLEN - offset)
587- < MAXPATHLEN - offset) {
588- add_exclude_file(&local_exclude_list, fname,
ee1af13c 589- XFLG_WORD_SPLIT | XFLG_WORDS_ONLY);
a55d21aa
WD
590- } else {
591- io_error |= IOERR_GENERAL;
592- rprintf(FINFO,
593- "cannot cvs-exclude in long-named directory %s\n",
594- full_fname(fname));
595- }
045caa90 596- }
56babefa
WD
597+ if (inherit_exclude_levels > 0) {
598+ pre_inherit_files(fname, offset);
599+ inherit_exclude_levels = 0;
045caa90
WD
600+ } else if (inherit_exclude_levels < -1)
601+ inherit_exclude_levels = -1;
56babefa 602+
045caa90
WD
603+ save_excludes = push_local_excludes(fname, offset);
604
a55d21aa
WD
605 for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) {
606 char *dname = d_name(di);
045caa90 607@@ -1028,6 +1009,8 @@ static void send_directory(int f, struct
bc95f62b 608 rsyserr(FERROR, errno, "readdir(%s)", dir);
a55d21aa 609 }
a55d21aa 610
eabda998
WD
611+ pop_local_excludes(save_excludes);
612+
a55d21aa
WD
613 closedir(d);
614 }
eabda998 615
045caa90 616--- orig/options.c 2004-08-03 15:41:32
623f2443 617+++ options.c 2004-08-04 08:36:48
045caa90 618@@ -51,6 +51,7 @@ int preserve_gid = 0;
1e476835
WD
619 int preserve_times = 0;
620 int update_only = 0;
621 int cvs_exclude = 0;
622+int inherit_exclude_levels = -9999;
623 int dry_run = 0;
624 int local_server = 0;
625 int ignore_times = 0;
623f2443
WD
626@@ -287,6 +288,7 @@ void usage(enum logcode F)
627 rprintf(F," --exclude-from=FILE exclude patterns listed in FILE\n");
628 rprintf(F," --include=PATTERN don't exclude files matching PATTERN\n");
629 rprintf(F," --include-from=FILE don't exclude patterns listed in FILE\n");
630+ rprintf(F," --inherit=DEPTH inherit contents of per-dir merge files\n");
631 rprintf(F," --files-from=FILE read FILE for list of source-file names\n");
632 rprintf(F," -0, --from0 all *-from file lists are delimited by nulls\n");
633 rprintf(F," --version print version number\n");
634@@ -319,7 +321,7 @@ void usage(enum logcode F)
1e476835
WD
635
636 enum {OPT_VERSION = 1000, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
637 OPT_DELETE_AFTER, OPT_DELETE_EXCLUDED, OPT_LINK_DEST,
638- OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW,
639+ OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_INHERIT, OPT_MODIFY_WINDOW,
125d7fca 640 OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_TIMEOUT,
1e476835
WD
641 OPT_REFUSED_BASE = 9000};
642
623f2443 643@@ -344,6 +346,7 @@ static struct poptOption long_options[]
1e476835
WD
644 {"include", 0, POPT_ARG_STRING, 0, OPT_INCLUDE, 0, 0 },
645 {"exclude-from", 0, POPT_ARG_STRING, 0, OPT_EXCLUDE_FROM, 0, 0 },
646 {"include-from", 0, POPT_ARG_STRING, 0, OPT_INCLUDE_FROM, 0, 0 },
647+ {"inherit", 0, POPT_ARG_STRING, 0, OPT_INHERIT, 0, 0 },
648 {"safe-links", 0, POPT_ARG_NONE, &safe_symlinks, 0, 0, 0 },
649 {"help", 'h', POPT_ARG_NONE, 0, 'h', 0, 0 },
650 {"backup", 'b', POPT_ARG_NONE, &make_backups, 0, 0, 0 },
623f2443 651@@ -566,6 +569,31 @@ int parse_arguments(int *argc, const cha
1e476835
WD
652 XFLG_FATAL_ERRORS | XFLG_DEF_INCLUDE);
653 break;
654
655+ case OPT_INHERIT:
656+ arg = poptGetOptArg(pc);
657+ if (isdigit(*arg))
658+ inherit_exclude_levels = atoi(arg);
659+ else if (*arg == '.') {
dc3ae351 660+ if (!*++arg)
1e476835 661+ inherit_exclude_levels = 0;
dc3ae351
WD
662+ else if (*arg == '.') {
663+ inherit_exclude_levels = 1;
664+ arg++;
665+ while (strncmp(arg, "/..", 3) == 0) {
1e476835
WD
666+ inherit_exclude_levels++;
667+ arg += 3;
dc3ae351 668+ }
1e476835
WD
669+ if (*arg)
670+ inherit_exclude_levels = -1;
671+ }
672+ }
673+ if (inherit_exclude_levels < 0) {
674+ snprintf(err_buf, sizeof err_buf,
675+ "Invalid argument given to --inherit.\n");
676+ return 0;
677+ }
678+ break;
679+
680 case 'h':
681 usage(FINFO);
682 exit_cleanup(0);
623f2443 683@@ -969,6 +997,12 @@ void server_options(char **args,int *arg
1e476835
WD
684 if (x != 1)
685 args[ac++] = argstr;
686
687+ if (inherit_exclude_levels >= 0) {
688+ if (asprintf(&arg, "--inherit=%d", inherit_exclude_levels) < 0)
689+ goto oom;
690+ args[ac++] = arg;
691+ }
692+
693 if (block_size) {
694 if (asprintf(&arg, "-B%u", block_size) < 0)
695 goto oom;
045caa90 696--- orig/rsync.h 2004-08-03 15:41:32
13bed3dd 697+++ rsync.h 2004-07-03 20:21:27
1e476835
WD
698@@ -108,6 +108,7 @@
699 #define XFLG_DEF_INCLUDE (1<<1)
700 #define XFLG_WORDS_ONLY (1<<2)
701 #define XFLG_WORD_SPLIT (1<<3)
702+#define XFLG_PERDIR_MERGE (1<<4)
703
704 #define PERMS_REPORT (1<<0)
705 #define PERMS_SKIP_MTIME (1<<1)
045caa90 706@@ -499,11 +500,16 @@ struct map_struct {
0c7d1fd8
WD
707 #define MATCHFLG_INCLUDE (1<<4) /* this is an include, not an exclude */
708 #define MATCHFLG_DIRECTORY (1<<5) /* this matches only directories */
709 #define MATCHFLG_CLEAR_LIST (1<<6) /* this item is the "!" token */
524989ae 710+#define MATCHFLG_MERGE_FILE (1<<7) /* specifies a file to merge */
a55d21aa
WD
711+#define MATCHFLG_CVSIGNORE (1<<8) /* parse this as a .cvsignore file */
712 struct exclude_struct {
713 struct exclude_struct *next;
714 char *pattern;
0c7d1fd8 715 unsigned int match_flags;
bc95f62b 716- int slash_cnt;
bc95f62b
WD
717+ union {
718+ int slash_cnt;
eabda998 719+ struct exclude_list_struct *mergelist;
bc95f62b 720+ } u;
a55d21aa
WD
721 };
722
723 struct exclude_list_struct {
045caa90 724--- orig/rsync.yo 2004-08-03 15:34:32
623f2443 725+++ rsync.yo 2004-08-04 08:36:18
045caa90 726@@ -334,6 +334,7 @@ verb(
1e476835
WD
727 --exclude-from=FILE exclude patterns listed in FILE
728 --include=PATTERN don't exclude files matching PATTERN
729 --include-from=FILE don't exclude patterns listed in FILE
623f2443 730+ --inherit=DEPTH inherit contents of per-dir merge files
1e476835
WD
731 --files-from=FILE read FILE for list of source-file names
732 -0 --from0 all file lists are delimited by nulls
733 --version print version number
045caa90 734@@ -711,6 +712,28 @@ dit(bf(--include-from=FILE)) This specif
1e476835 735 from a file.
f6c3b300 736 If em(FILE) is "-" the list will be read from standard input.
1e476835
WD
737
738+dit(bf(--inherit=DEPTH)) Using this option allows you to specify that the
739+contents of per-directory merge files is inherited by the subdirectories of
740+the spot where the rules were read in. If a subdirectory has its own
741+per-directory merge file, its contents are prefixed to the inherited rules,
623f2443 742+which gives the newest rules a higher priority than the inherited rules.
1e476835
WD
743+
744+The DEPTH value tells rsync how much deeper than the root directory of the
745+transfer should be scanned for merge files. If you don't need any higher
746+directories scanned, use "." (dot). If you want the parent directory
747+scanned, specify ".." (dot dot) or "1" (i.e. one directory higher). You
748+can continue to specify either more ".."s (separated by slashes) or simply
749+supply the count of how many parent-directory levels should be scanned.
750+The reason this is useful is that you may wish to transfer just a small
751+portion of a larger tree of files, but to be sure to get all the
623f2443 752+appropriate exclude rules, you need to make rsync read in all the
1e476835
WD
753+merge files from the top of the tree of related files.
754+
755+Note also that you can eliminate all the inherited rules for the current
756+per-directory ruleset by putting the list-clearing token (!) in the file.
757+This only clears the rules in the current per-directory sub-list for the
758+current directory and its subdirectories.
759+
760 dit(bf(--files-from=FILE)) Using this option allows you to specify the
761 exact list of files to transfer (as read from the specified FILE or "-"
f6c3b300 762 for standard input). It also tweaks the default behavior of rsync to make
045caa90 763@@ -1086,6 +1109,11 @@ itemize(
1e476835
WD
764 then it is always considered an exclude pattern, even if specified as
765 part of an include option. The prefix is discarded before matching.
766
623f2443
WD
767+ it() if the pattern starts with ". " (a dot followed by a space) then its
768+ pattern is taken to be a merge file that is read in to supplement the
1e476835
WD
769+ current rules. See the section on MERGING EXCLUDE FILES for more
770+ information.
771+
772 it() if the pattern is a single exclamation mark ! then the current
773 include/exclude list is reset, removing all previously defined patterns.
774 )
045caa90 775@@ -1138,6 +1166,67 @@ itemize(
524989ae
WD
776 it would be excluded by the "*")
777 )
778
779+manpagesection(MERGING EXCLUDE FILES)
780+
781+You can merge whole files into an exclude file using a rule that starts
782+with a ". " (a dot followed by a space) and has a filename in place of the
783+pattern. There are two types of merge rules, single-instance and
784+per-directory:
785+
786+itemize(
787+ it() If the filename has no slashes in it, it is a per-directory merge;
bc95f62b 788+ rsync scans every directory that it traverses for the named file, merging
1e476835
WD
789+ its contents (when it exists) at the start of this per-directory
790+ sub-list.
524989ae
WD
791+
792+ it() If a filename has a slash in it, it is a single-instance merge; the
793+ named file's contents will be merged into the current exclude file,
794+ replacing the merge rule. Thus, you should use the name ./foo instead of
1e476835
WD
795+ foo if you don't want to scan for "foo" in all the subdirectories in the
796+ transferred tree of directories.
524989ae
WD
797+)
798+
bc95f62b
WD
799+Here's an example exclude file (which you'd specify via the normal
800+--exclude-from option):
524989ae
WD
801+
802+verb(
803+ . /home/user/.global_excludes
804+ - *.gz
805+ . .excl
806+ + *.[ch]
807+ - *.o
808+)
809+
810+This will merge the contents of the /home/user/.global_excludes file at the
811+start of the list and also turns the ".excl" filename into a per-directory
812+exclude file whose local contents will be merged into the list in place of
813+the .excl line.
814+
815+Additionally, you can affect where the --cvs-exclude (-C) option's
816+inclusion of a per-directory .cvsignore file gets placed into your rules by
bc95f62b 817+adding an explicit merge rule for ".cvsignore". For instance, specifying
524989ae
WD
818+this:
819+
820+verb(
821+ rsync -avC --exclude='. .cvsignore' --exclude-from=foo a/ b
822+)
823+
824+will merge all the per-directory .cvsignore rules at the start of your list
825+rather than at the end. This allows their dir-specific rules to supersede
826+your rules instead of being subservient to them. (The global rules taken
827+from the $HOME/.cvsignore file and from $CVSIGNORE are not affected by
828+this.)
829+
830+Note also that the parsing of any merge-file named ".cvsignore" is always
dc5cce3c
WD
831+done in a CVS-compatible manner, even if -C wasn't specified (i.e. the
832+rules are always exclude rules (even when specified by an include option);
623f2443
WD
833+they are split on whitespace; the contents is never inherited; and no
834+prefixes, list-clearing tokens, or comment characters are honored).
1e476835
WD
835+
836+See the --inherit option for how to make the rules read from a
837+per-directory merge file inherited by all the subdirectories of the
838+spot where the per-directory rule file was found.
524989ae
WD
839+
840 manpagesection(BATCH MODE)
841
842 bf(Note:) Batch mode should be considered experimental in this version
13bed3dd
WD
843--- orig/testsuite/exclude.test 2004-05-29 21:25:45
844+++ testsuite/exclude.test 2004-07-03 20:21:27
c4e46f36 845@@ -23,19 +23,47 @@ export HOME CVSIGNORE
7d31425d
WD
846 makepath "$fromdir/foo/down/to/you"
847 makepath "$fromdir/bar/down/to/foo/too"
848 makepath "$fromdir/mid/for/foo/and/that/is/who"
c4e46f36 849+cat >"$fromdir/.excl" <<EOF
7d31425d
WD
850+.excl
851+*.bak
852+*.old
853+*.junk
c4e46f36 854+EOF
7d31425d
WD
855 echo kept >"$fromdir/foo/file1"
856 echo removed >"$fromdir/foo/file2"
857 echo cvsout >"$fromdir/foo/file2.old"
c4e46f36 858+cat >"$fromdir/foo/.excl" <<EOF
7d31425d
WD
859++ .excl
860+- file1
c4e46f36
WD
861+EOF
862+cat >"$fromdir/bar/.excl" <<EOF
7d31425d
WD
863+home-cvs-exclude
864+. .excl2
865++ to
c4e46f36 866+EOF
7d31425d 867 echo cvsout >"$fromdir/bar/down/to/home-cvs-exclude"
c4e46f36 868+cat >"$fromdir/bar/down/to/.excl2" <<EOF
7d31425d 869+.excl2
c4e46f36 870+EOF
7d31425d
WD
871 echo keeper >"$fromdir/bar/down/to/foo/file1"
872 echo cvsout >"$fromdir/bar/down/to/foo/file1.bak"
873 echo gone >"$fromdir/bar/down/to/foo/file3"
874 echo lost >"$fromdir/bar/down/to/foo/file4"
875 echo cvsout >"$fromdir/bar/down/to/foo/file4.junk"
876 echo smashed >"$fromdir/bar/down/to/foo/to"
c4e46f36 877+cat >"$fromdir/bar/down/to/foo/.excl2" <<EOF
7d31425d 878++ *.junk
c4e46f36 879+EOF
7d31425d 880+# This one should be ineffectual
c4e46f36 881+cat >"$fromdir/mid/.excl2" <<EOF
7d31425d 882+extra
c4e46f36 883+EOF
7d31425d
WD
884 echo cvsout >"$fromdir/mid/one-in-one-out"
885 echo one-in-one-out >"$fromdir/mid/.cvsignore"
886 echo cvsin >"$fromdir/mid/one-for-all"
c4e46f36 887+cat >"$fromdir/mid/.excl" <<EOF
7d31425d 888+. .cvsignore
c4e46f36 889+EOF
7d31425d
WD
890 echo cvsin >"$fromdir/mid/for/one-in-one-out"
891 echo expunged >"$fromdir/mid/for/foo/extra"
892 echo retained >"$fromdir/mid/for/foo/keep"
c4e46f36
WD
893@@ -100,5 +128,24 @@ $RSYNC -av --existing --include='*/' --e
894 checkit "$RSYNC -avvC --exclude-from=\"$excl\" \
895 --delete-excluded \"$fromdir/\" \"$todir/\"" "$chkdir" "$todir"
7d31425d
WD
896
897+# Modify the chk dir for our merge-exclude test and then tweak the dir times.
898+
899+rm "$chkdir"/.excl
900+rm "$chkdir"/foo/file1
901+rm "$chkdir"/bar/.excl
902+rm "$chkdir"/bar/down/to/.excl2
903+rm "$chkdir"/bar/down/to/foo/.excl2
904+rm "$chkdir"/mid/.excl
905+cp -p "$fromdir"/bar/down/to/foo/*.junk "$chkdir"/bar/down/to/foo
906+cp -p "$fromdir"/bar/down/to/foo/to "$chkdir"/bar/down/to/foo
907+
908+$RSYNC -av --existing --include='*/' --exclude='*' "$fromdir/" "$chkdir/"
909+
910+# Now, test if rsync excludes the same files, this time with a merge-exclude
911+# file.
912+
1e476835 913+checkit "$RSYNC -avv --exclude='. .excl' --exclude-from=\"$excl\" --inherit=. \
c4e46f36 914+ --delete-excluded \"$fromdir/\" \"$todir/\"" "$chkdir" "$todir"
7d31425d
WD
915+
916 # The script would have aborted on error, so getting here means we've won.
917 exit 0