- Fixed a couple problems with the daemon's path-sanitizing.
[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 }
045caa90 168@@ -96,22 +185,154 @@ 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;
a55d21aa 282+ }
d8af8661 283+ lp->tail = NULL; /* Switch any local rules to inherited. */
eabda998 284+ if (strlcpy(dirbuf + dirbuf_offset, ex->pattern,
d8af8661
WD
285+ MAXPATHLEN - dirbuf_offset) < MAXPATHLEN - dirbuf_offset)
286+ add_exclude_file(lp, dirbuf, flags);
524989ae 287+ else {
a55d21aa
WD
288+ io_error |= IOERR_GENERAL;
289+ rprintf(FINFO,
290+ "cannot add local excludes in long-named directory %s\n",
d8af8661 291+ full_fname(dirbuf));
a55d21aa
WD
292+ }
293+ }
294+
524989ae 295+ return (void*)push;
a55d21aa
WD
296+}
297+
eabda998 298+void pop_local_excludes(void *mem)
a55d21aa 299+{
eabda998
WD
300+ struct mergelist_save_struct *pop = (struct mergelist_save_struct*)mem;
301+ struct exclude_list_struct *ap;
302+ int i;
d8af8661 303+
066e3b3e 304+ for (i = mergelist_cnt; i-- > 0; ) {
eabda998
WD
305+ struct exclude_struct *ex = mergelist_parents[i];
306+ struct exclude_list_struct *lp = ex->u.mergelist;
524989ae 307+
524989ae
WD
308+ if (verbose > 2) {
309+ rprintf(FINFO, "[%s] popping %sexclude list\n",
d8af8661 310+ who_am_i(), lp->debug_type);
524989ae 311+ }
d8af8661
WD
312+
313+ clear_exclude_list(lp);
a55d21aa 314+ }
d8af8661 315+
eabda998
WD
316+ mergelist_cnt = pop->count;
317+ for (i = 0, ap = pop->array; i < mergelist_cnt; i++) {
318+ memcpy(mergelist_parents[i]->u.mergelist, ap++,
319+ sizeof (struct exclude_list_struct));
320+ }
d8af8661
WD
321+
322+ free(pop->array);
323+ free(pop);
7cb7ae4e
WD
324+}
325+
a55d21aa 326 static int check_one_exclude(char *name, struct exclude_struct *ex,
7cb7ae4e
WD
327 int name_is_dir)
328 {
045caa90 329@@ -122,7 +343,7 @@ static int check_one_exclude(char *name,
bc95f62b
WD
330 /* If the pattern does not have any slashes AND it does not have
331 * a "**" (which could match a slash), then we just match the
332 * name portion of the path. */
333- if (!ex->slash_cnt && !(ex->match_flags & MATCHFLG_WILD2)) {
334+ if (!ex->u.slash_cnt && !(ex->match_flags & MATCHFLG_WILD2)) {
335 if ((p = strrchr(name,'/')) != NULL)
336 name = p+1;
337 }
045caa90
WD
338@@ -133,7 +354,8 @@ static int check_one_exclude(char *name,
339 name = full_name;
340 }
341
342- if (!name[0]) return 0;
343+ if (!name[0])
344+ return 0;
345
346 if (ex->match_flags & MATCHFLG_DIRECTORY && !name_is_dir)
347 return 0;
348@@ -148,9 +370,9 @@ static int check_one_exclude(char *name,
bc95f62b
WD
349 if (ex->match_flags & MATCHFLG_WILD) {
350 /* A non-anchored match with an infix slash and no "**"
351 * needs to match the last slash_cnt+1 name elements. */
0c7d1fd8
WD
352- if (!match_start && ex->slash_cnt
353+ if (!match_start && ex->u.slash_cnt
354 && !(ex->match_flags & MATCHFLG_WILD2)) {
bc95f62b
WD
355- int cnt = ex->slash_cnt + 1;
356+ int cnt = ex->u.slash_cnt + 1;
357 for (p = name + strlen(name) - 1; p >= name; p--) {
358 if (*p == '/' && !--cnt)
359 break;
045caa90 360@@ -221,6 +443,13 @@ int check_exclude(struct exclude_list_st
a55d21aa
WD
361 struct exclude_struct *ent;
362
363 for (ent = listp->head; ent; ent = ent->next) {
524989ae 364+ if (ent->match_flags & MATCHFLG_MERGE_FILE) {
eabda998 365+ int rc = check_exclude(ent->u.mergelist, name,
d8af8661 366+ name_is_dir);
a55d21aa
WD
367+ if (rc)
368+ return rc;
369+ continue;
370+ }
371 if (check_one_exclude(name, ent, name_is_dir)) {
372 report_exclude_result(name, ent, name_is_dir,
373 listp->debug_type);
045caa90 374@@ -253,11 +482,16 @@ static const char *get_exclude_tok(const
a55d21aa
WD
375 p = (const char *)s;
376 }
377
378- /* Is this a '+' or '-' followed by a space (not whitespace)? */
379+ /* Is this a +/-/. followed by a space (not whitespace)? */
ee1af13c 380 if (!(xflags & XFLG_WORDS_ONLY)
a55d21aa 381- && (*s == '-' || *s == '+') && s[1] == ' ') {
a55d21aa 382+ && (*s == '-' || *s == '+' || *s == '.') && s[1] == ' ') {
0c7d1fd8
WD
383 if (*s == '+')
384 mflags |= MATCHFLG_INCLUDE;
a55d21aa 385+ else if (*s == '.') {
524989ae 386+ mflags |= MATCHFLG_MERGE_FILE;
a55d21aa
WD
387+ if (xflags & XFLG_DEF_INCLUDE)
388+ mflags |= MATCHFLG_INCLUDE;
389+ }
390 s += 2;
0c7d1fd8
WD
391 } else if (xflags & XFLG_DEF_INCLUDE)
392 mflags |= MATCHFLG_INCLUDE;
045caa90
WD
393@@ -292,6 +526,7 @@ void add_exclude(struct exclude_list_str
394 cp = pattern;
395 pat_len = 0;
396 while (1) {
397+ /* Remember that the returned string is NOT '\0' terminated! */
398 cp = get_exclude_tok(cp + pat_len, &pat_len, &mflags, xflags);
399 if (!pat_len)
400 break;
401@@ -306,11 +541,57 @@ void add_exclude(struct exclude_list_str
0c7d1fd8
WD
402 continue;
403 }
a55d21aa 404
524989ae 405+ if (mflags & MATCHFLG_MERGE_FILE) {
a55d21aa 406+ char name[MAXPATHLEN];
bc95f62b
WD
407+ if (pat_len >= sizeof name) {
408+ rprintf(FERROR,
409+ "merge filename too long: %s\n", cp);
410+ continue;
411+ }
045caa90 412+ /* We need a null-terminated version of the filename. */
a55d21aa 413+ strlcpy(name, cp, pat_len+1);
1e476835
WD
414+ if (!(xflags & XFLG_PERDIR_MERGE)
415+ && (inherit_exclude_levels == -1
416+ || strchr(name, '/') != NULL)) {
c4e46f36
WD
417+ char *mem = NULL;
418+ if (*name == '/') {
419+ if (sanitize_paths) {
420+ mem = alloc_sanitize_path(name,
421+ curr_dir);
422+ cp = mem;
423+ } else
424+ cp = name;
425+ } else {
426+ if (sanitize_paths) {
427+ dirbuf[dirbuf_offset] = '\0';
428+ sanitize_path(name, dirbuf);
429+ }
045caa90
WD
430+ if (!dirbuf_offset)
431+ cp = name;
432+ else if (strlcpy(dirbuf + dirbuf_offset,
a55d21aa 433+ name, MAXPATHLEN - dirbuf_offset)
bc95f62b
WD
434+ >= MAXPATHLEN - dirbuf_offset) {
435+ rprintf(FERROR,
436+ "merge filename too long: %s...\n",
437+ dirbuf);
438+ continue;
045caa90
WD
439+ } else
440+ cp = dirbuf;
a55d21aa
WD
441+ }
442+ add_exclude_file(listp, cp,
c4e46f36
WD
443+ xflags | XFLG_FATAL_ERRORS);
444+ if (mem)
445+ free(mem);
a55d21aa 446+ continue;
0c7d1fd8
WD
447+ }
448+ }
a55d21aa 449+
0c7d1fd8
WD
450 make_exclude(listp, cp, pat_len, mflags);
451
452 if (verbose > 2) {
453- rprintf(FINFO, "[%s] add_exclude(%.*s, %s%sclude)\n",
a55d21aa 454+ rprintf(FINFO, "[%s] add_exclude(%.*s, %s%s%sclude)\n",
eabda998 455 who_am_i(), (int)pat_len, cp, listp->debug_type,
524989ae 456+ mflags & MATCHFLG_MERGE_FILE ? "FILE " : "",
0c7d1fd8 457 mflags & MATCHFLG_INCLUDE ? "in" : "ex");
a55d21aa 458 }
0c7d1fd8 459 }
045caa90 460@@ -343,6 +624,11 @@ void add_exclude_file(struct exclude_lis
c4e46f36
WD
461 return;
462 }
463
464+ if (verbose > 2) {
1e476835
WD
465+ rprintf(FINFO, "[%s] add_exclude_file(%s,%d)\n",
466+ who_am_i(), fname, xflags);
c4e46f36
WD
467+ }
468+
469 while (1) {
470 char *s = line;
471 int ch, overflow = 0;
045caa90 472@@ -402,7 +688,11 @@ void send_exclude_list(int f)
0c7d1fd8 473 if (ent->match_flags & MATCHFLG_INCLUDE) {
a55d21aa
WD
474 write_int(f, l + 2);
475 write_buf(f, "+ ", 2);
476- } else if ((*p == '-' || *p == '+') && p[1] == ' ') {
524989ae 477+ } else if (ent->match_flags & MATCHFLG_MERGE_FILE) {
a55d21aa
WD
478+ write_int(f, l + 2);
479+ write_buf(f, ". ", 2);
480+ } else if ((*p == '-' || *p == '+' || *p == '.')
481+ && p[1] == ' ') {
482 write_int(f, l + 2);
483 write_buf(f, "- ", 2);
484 } else
045caa90
WD
485@@ -411,6 +701,13 @@ void send_exclude_list(int f)
486 }
487
488 write_int(f, 0);
489+
490+ /* If we're the receiver and we don't need the excludes, dump them. */
491+ if (!am_sender && (!delete_mode || delete_excluded)) {
492+ clear_exclude_list(&exclude_list);
493+ inherit_exclude_levels = 0;
494+ mergelist_cnt = 0;
495+ }
496 }
497
498
499@@ -443,6 +740,7 @@ void add_cvs_excludes(void)
a55d21aa
WD
500 char fname[MAXPATHLEN];
501 char *p;
502
1e476835 503+ add_exclude(&exclude_list, ". .cvsignore", XFLG_PERDIR_MERGE);
a55d21aa 504 add_exclude(&exclude_list, default_cvsignore,
ee1af13c 505 XFLG_WORD_SPLIT | XFLG_WORDS_ONLY);
a55d21aa 506
045caa90
WD
507--- orig/flist.c 2004-07-17 15:20:05
508+++ flist.c 2004-08-03 17:14:17
a55d21aa
WD
509@@ -39,8 +39,6 @@ extern int module_id;
510 extern int ignore_errors;
511 extern int numeric_ids;
512
513-extern int cvs_exclude;
514-
515 extern int recurse;
516 extern char curr_dir[MAXPATHLEN];
517 extern char *files_from;
7628f156 518@@ -59,6 +57,7 @@ extern int implied_dirs;
56babefa
WD
519 extern int copy_links;
520 extern int copy_unsafe_links;
521 extern int protocol_version;
522+extern int inherit_exclude_levels;
523 extern int sanitize_paths;
7628f156
WD
524 extern int delete_excluded;
525 extern int orig_umask;
9be39c35 526@@ -66,7 +65,6 @@ extern int list_only;
a55d21aa
WD
527
528 extern struct exclude_list_struct exclude_list;
529 extern struct exclude_list_struct server_exclude_list;
530-extern struct exclude_list_struct local_exclude_list;
531
532 int io_error;
533
9be39c35 534@@ -221,8 +219,6 @@ int link_stat(const char *path, STRUCT_S
a55d21aa
WD
535 */
536 static int check_exclude_file(char *fname, int is_dir, int exclude_level)
537 {
538- int rc;
539-
540 #if 0 /* This currently never happens, so avoid a useless compare. */
541 if (exclude_level == NO_EXCLUDES)
542 return 0;
9be39c35 543@@ -244,10 +240,7 @@ static int check_exclude_file(char *fnam
a55d21aa
WD
544 if (exclude_level != ALL_EXCLUDES)
545 return 0;
546 if (exclude_list.head
547- && (rc = check_exclude(&exclude_list, fname, is_dir)) != 0)
548- return rc < 0;
549- if (local_exclude_list.head
550- && check_exclude(&local_exclude_list, fname, is_dir) < 0)
551+ && check_exclude(&exclude_list, fname, is_dir) < 0)
552 return 1;
553 return 0;
554 }
9be39c35 555@@ -954,15 +947,7 @@ void send_file_name(int f, struct file_l
a55d21aa
WD
556
557 if (recursive && S_ISDIR(file->mode)
558 && !(file->flags & FLAG_MOUNT_POINT)) {
559- struct exclude_list_struct last_list = local_exclude_list;
560- local_exclude_list.head = local_exclude_list.tail = NULL;
561 send_directory(f, flist, f_name_to(file, fbuf));
7cb7ae4e
WD
562- if (verbose > 2) {
563- rprintf(FINFO, "[%s] popping %sexclude list\n",
564- who_am_i(), local_exclude_list.debug_type);
565- }
d8af8661 566- clear_exclude_list(&local_exclude_list);
a55d21aa
WD
567- local_exclude_list = last_list;
568 }
569 }
570
9be39c35 571@@ -973,6 +958,7 @@ static void send_directory(int f, struct
a55d21aa
WD
572 struct dirent *di;
573 char fname[MAXPATHLEN];
574 unsigned int offset;
575+ void *save_excludes;
576 char *p;
577
578 d = opendir(dir);
045caa90 579@@ -996,18 +982,13 @@ static void send_directory(int f, struct
a55d21aa
WD
580 offset++;
581 }
582
583- if (cvs_exclude) {
584- if (strlcpy(p, ".cvsignore", MAXPATHLEN - offset)
585- < MAXPATHLEN - offset) {
586- add_exclude_file(&local_exclude_list, fname,
ee1af13c 587- XFLG_WORD_SPLIT | XFLG_WORDS_ONLY);
a55d21aa
WD
588- } else {
589- io_error |= IOERR_GENERAL;
590- rprintf(FINFO,
591- "cannot cvs-exclude in long-named directory %s\n",
592- full_fname(fname));
593- }
045caa90 594- }
56babefa
WD
595+ if (inherit_exclude_levels > 0) {
596+ pre_inherit_files(fname, offset);
597+ inherit_exclude_levels = 0;
045caa90
WD
598+ } else if (inherit_exclude_levels < -1)
599+ inherit_exclude_levels = -1;
56babefa 600+
045caa90
WD
601+ save_excludes = push_local_excludes(fname, offset);
602
a55d21aa
WD
603 for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) {
604 char *dname = d_name(di);
045caa90 605@@ -1028,6 +1009,8 @@ static void send_directory(int f, struct
bc95f62b 606 rsyserr(FERROR, errno, "readdir(%s)", dir);
a55d21aa 607 }
a55d21aa 608
eabda998
WD
609+ pop_local_excludes(save_excludes);
610+
a55d21aa
WD
611 closedir(d);
612 }
eabda998 613
045caa90
WD
614--- orig/options.c 2004-08-03 15:41:32
615+++ options.c 2004-08-03 15:41:39
616@@ -51,6 +51,7 @@ int preserve_gid = 0;
1e476835
WD
617 int preserve_times = 0;
618 int update_only = 0;
619 int cvs_exclude = 0;
620+int inherit_exclude_levels = -9999;
621 int dry_run = 0;
622 int local_server = 0;
623 int ignore_times = 0;
045caa90 624@@ -319,7 +320,7 @@ void usage(enum logcode F)
1e476835
WD
625
626 enum {OPT_VERSION = 1000, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
627 OPT_DELETE_AFTER, OPT_DELETE_EXCLUDED, OPT_LINK_DEST,
628- OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW,
629+ OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_INHERIT, OPT_MODIFY_WINDOW,
125d7fca 630 OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_TIMEOUT,
1e476835
WD
631 OPT_REFUSED_BASE = 9000};
632
045caa90 633@@ -344,6 +345,7 @@ static struct poptOption long_options[]
1e476835
WD
634 {"include", 0, POPT_ARG_STRING, 0, OPT_INCLUDE, 0, 0 },
635 {"exclude-from", 0, POPT_ARG_STRING, 0, OPT_EXCLUDE_FROM, 0, 0 },
636 {"include-from", 0, POPT_ARG_STRING, 0, OPT_INCLUDE_FROM, 0, 0 },
637+ {"inherit", 0, POPT_ARG_STRING, 0, OPT_INHERIT, 0, 0 },
638 {"safe-links", 0, POPT_ARG_NONE, &safe_symlinks, 0, 0, 0 },
639 {"help", 'h', POPT_ARG_NONE, 0, 'h', 0, 0 },
640 {"backup", 'b', POPT_ARG_NONE, &make_backups, 0, 0, 0 },
045caa90 641@@ -566,6 +568,31 @@ int parse_arguments(int *argc, const cha
1e476835
WD
642 XFLG_FATAL_ERRORS | XFLG_DEF_INCLUDE);
643 break;
644
645+ case OPT_INHERIT:
646+ arg = poptGetOptArg(pc);
647+ if (isdigit(*arg))
648+ inherit_exclude_levels = atoi(arg);
649+ else if (*arg == '.') {
dc3ae351 650+ if (!*++arg)
1e476835 651+ inherit_exclude_levels = 0;
dc3ae351
WD
652+ else if (*arg == '.') {
653+ inherit_exclude_levels = 1;
654+ arg++;
655+ while (strncmp(arg, "/..", 3) == 0) {
1e476835
WD
656+ inherit_exclude_levels++;
657+ arg += 3;
dc3ae351 658+ }
1e476835
WD
659+ if (*arg)
660+ inherit_exclude_levels = -1;
661+ }
662+ }
663+ if (inherit_exclude_levels < 0) {
664+ snprintf(err_buf, sizeof err_buf,
665+ "Invalid argument given to --inherit.\n");
666+ return 0;
667+ }
668+ break;
669+
670 case 'h':
671 usage(FINFO);
672 exit_cleanup(0);
045caa90 673@@ -969,6 +996,12 @@ void server_options(char **args,int *arg
1e476835
WD
674 if (x != 1)
675 args[ac++] = argstr;
676
677+ if (inherit_exclude_levels >= 0) {
678+ if (asprintf(&arg, "--inherit=%d", inherit_exclude_levels) < 0)
679+ goto oom;
680+ args[ac++] = arg;
681+ }
682+
683 if (block_size) {
684 if (asprintf(&arg, "-B%u", block_size) < 0)
685 goto oom;
045caa90 686--- orig/rsync.h 2004-08-03 15:41:32
13bed3dd 687+++ rsync.h 2004-07-03 20:21:27
1e476835
WD
688@@ -108,6 +108,7 @@
689 #define XFLG_DEF_INCLUDE (1<<1)
690 #define XFLG_WORDS_ONLY (1<<2)
691 #define XFLG_WORD_SPLIT (1<<3)
692+#define XFLG_PERDIR_MERGE (1<<4)
693
694 #define PERMS_REPORT (1<<0)
695 #define PERMS_SKIP_MTIME (1<<1)
045caa90 696@@ -499,11 +500,16 @@ struct map_struct {
0c7d1fd8
WD
697 #define MATCHFLG_INCLUDE (1<<4) /* this is an include, not an exclude */
698 #define MATCHFLG_DIRECTORY (1<<5) /* this matches only directories */
699 #define MATCHFLG_CLEAR_LIST (1<<6) /* this item is the "!" token */
524989ae 700+#define MATCHFLG_MERGE_FILE (1<<7) /* specifies a file to merge */
a55d21aa
WD
701+#define MATCHFLG_CVSIGNORE (1<<8) /* parse this as a .cvsignore file */
702 struct exclude_struct {
703 struct exclude_struct *next;
704 char *pattern;
0c7d1fd8 705 unsigned int match_flags;
bc95f62b 706- int slash_cnt;
bc95f62b
WD
707+ union {
708+ int slash_cnt;
eabda998 709+ struct exclude_list_struct *mergelist;
bc95f62b 710+ } u;
a55d21aa
WD
711 };
712
713 struct exclude_list_struct {
045caa90
WD
714--- orig/rsync.yo 2004-08-03 15:34:32
715+++ rsync.yo 2004-08-03 15:34:44
716@@ -334,6 +334,7 @@ verb(
1e476835
WD
717 --exclude-from=FILE exclude patterns listed in FILE
718 --include=PATTERN don't exclude files matching PATTERN
719 --include-from=FILE don't exclude patterns listed in FILE
720+ --inherit=DEPTH make per-dir merge files inherited
721 --files-from=FILE read FILE for list of source-file names
722 -0 --from0 all file lists are delimited by nulls
723 --version print version number
045caa90 724@@ -711,6 +712,28 @@ dit(bf(--include-from=FILE)) This specif
1e476835 725 from a file.
f6c3b300 726 If em(FILE) is "-" the list will be read from standard input.
1e476835
WD
727
728+dit(bf(--inherit=DEPTH)) Using this option allows you to specify that the
729+contents of per-directory merge files is inherited by the subdirectories of
730+the spot where the rules were read in. If a subdirectory has its own
731+per-directory merge file, its contents are prefixed to the inherited rules,
732+which gives them higher priority.
733+
734+The DEPTH value tells rsync how much deeper than the root directory of the
735+transfer should be scanned for merge files. If you don't need any higher
736+directories scanned, use "." (dot). If you want the parent directory
737+scanned, specify ".." (dot dot) or "1" (i.e. one directory higher). You
738+can continue to specify either more ".."s (separated by slashes) or simply
739+supply the count of how many parent-directory levels should be scanned.
740+The reason this is useful is that you may wish to transfer just a small
741+portion of a larger tree of files, but to be sure to get all the
742+appropriate exclude rules, you need to be sure that rsync reads in all the
743+merge files from the top of the tree of related files.
744+
745+Note also that you can eliminate all the inherited rules for the current
746+per-directory ruleset by putting the list-clearing token (!) in the file.
747+This only clears the rules in the current per-directory sub-list for the
748+current directory and its subdirectories.
749+
750 dit(bf(--files-from=FILE)) Using this option allows you to specify the
751 exact list of files to transfer (as read from the specified FILE or "-"
f6c3b300 752 for standard input). It also tweaks the default behavior of rsync to make
045caa90 753@@ -1086,6 +1109,11 @@ itemize(
1e476835
WD
754 then it is always considered an exclude pattern, even if specified as
755 part of an include option. The prefix is discarded before matching.
756
757+ it() if the pattern starts with ". " (a dot followed by a space) then it
758+ its pattern is taken to be a merge file that is read in to supplement the
759+ current rules. See the section on MERGING EXCLUDE FILES for more
760+ information.
761+
762 it() if the pattern is a single exclamation mark ! then the current
763 include/exclude list is reset, removing all previously defined patterns.
764 )
045caa90 765@@ -1138,6 +1166,67 @@ itemize(
524989ae
WD
766 it would be excluded by the "*")
767 )
768
769+manpagesection(MERGING EXCLUDE FILES)
770+
771+You can merge whole files into an exclude file using a rule that starts
772+with a ". " (a dot followed by a space) and has a filename in place of the
773+pattern. There are two types of merge rules, single-instance and
774+per-directory:
775+
776+itemize(
777+ it() If the filename has no slashes in it, it is a per-directory merge;
bc95f62b 778+ rsync scans every directory that it traverses for the named file, merging
1e476835
WD
779+ its contents (when it exists) at the start of this per-directory
780+ sub-list.
524989ae
WD
781+
782+ it() If a filename has a slash in it, it is a single-instance merge; the
783+ named file's contents will be merged into the current exclude file,
784+ replacing the merge rule. Thus, you should use the name ./foo instead of
1e476835
WD
785+ foo if you don't want to scan for "foo" in all the subdirectories in the
786+ transferred tree of directories.
524989ae
WD
787+)
788+
bc95f62b
WD
789+Here's an example exclude file (which you'd specify via the normal
790+--exclude-from option):
524989ae
WD
791+
792+verb(
793+ . /home/user/.global_excludes
794+ - *.gz
795+ . .excl
796+ + *.[ch]
797+ - *.o
798+)
799+
800+This will merge the contents of the /home/user/.global_excludes file at the
801+start of the list and also turns the ".excl" filename into a per-directory
802+exclude file whose local contents will be merged into the list in place of
803+the .excl line.
804+
805+Additionally, you can affect where the --cvs-exclude (-C) option's
806+inclusion of a per-directory .cvsignore file gets placed into your rules by
bc95f62b 807+adding an explicit merge rule for ".cvsignore". For instance, specifying
524989ae
WD
808+this:
809+
810+verb(
811+ rsync -avC --exclude='. .cvsignore' --exclude-from=foo a/ b
812+)
813+
814+will merge all the per-directory .cvsignore rules at the start of your list
815+rather than at the end. This allows their dir-specific rules to supersede
816+your rules instead of being subservient to them. (The global rules taken
817+from the $HOME/.cvsignore file and from $CVSIGNORE are not affected by
818+this.)
819+
820+Note also that the parsing of any merge-file named ".cvsignore" is always
dc5cce3c
WD
821+done in a CVS-compatible manner, even if -C wasn't specified (i.e. the
822+rules are always exclude rules (even when specified by an include option);
1e476835
WD
823+they are split on whitespace; and no special prefixes, list-clearing
824+tokens, or comment characters are honored).
825+
826+See the --inherit option for how to make the rules read from a
827+per-directory merge file inherited by all the subdirectories of the
828+spot where the per-directory rule file was found.
524989ae
WD
829+
830 manpagesection(BATCH MODE)
831
832 bf(Note:) Batch mode should be considered experimental in this version
13bed3dd
WD
833--- orig/testsuite/exclude.test 2004-05-29 21:25:45
834+++ testsuite/exclude.test 2004-07-03 20:21:27
c4e46f36 835@@ -23,19 +23,47 @@ export HOME CVSIGNORE
7d31425d
WD
836 makepath "$fromdir/foo/down/to/you"
837 makepath "$fromdir/bar/down/to/foo/too"
838 makepath "$fromdir/mid/for/foo/and/that/is/who"
c4e46f36 839+cat >"$fromdir/.excl" <<EOF
7d31425d
WD
840+.excl
841+*.bak
842+*.old
843+*.junk
c4e46f36 844+EOF
7d31425d
WD
845 echo kept >"$fromdir/foo/file1"
846 echo removed >"$fromdir/foo/file2"
847 echo cvsout >"$fromdir/foo/file2.old"
c4e46f36 848+cat >"$fromdir/foo/.excl" <<EOF
7d31425d
WD
849++ .excl
850+- file1
c4e46f36
WD
851+EOF
852+cat >"$fromdir/bar/.excl" <<EOF
7d31425d
WD
853+home-cvs-exclude
854+. .excl2
855++ to
c4e46f36 856+EOF
7d31425d 857 echo cvsout >"$fromdir/bar/down/to/home-cvs-exclude"
c4e46f36 858+cat >"$fromdir/bar/down/to/.excl2" <<EOF
7d31425d 859+.excl2
c4e46f36 860+EOF
7d31425d
WD
861 echo keeper >"$fromdir/bar/down/to/foo/file1"
862 echo cvsout >"$fromdir/bar/down/to/foo/file1.bak"
863 echo gone >"$fromdir/bar/down/to/foo/file3"
864 echo lost >"$fromdir/bar/down/to/foo/file4"
865 echo cvsout >"$fromdir/bar/down/to/foo/file4.junk"
866 echo smashed >"$fromdir/bar/down/to/foo/to"
c4e46f36 867+cat >"$fromdir/bar/down/to/foo/.excl2" <<EOF
7d31425d 868++ *.junk
c4e46f36 869+EOF
7d31425d 870+# This one should be ineffectual
c4e46f36 871+cat >"$fromdir/mid/.excl2" <<EOF
7d31425d 872+extra
c4e46f36 873+EOF
7d31425d
WD
874 echo cvsout >"$fromdir/mid/one-in-one-out"
875 echo one-in-one-out >"$fromdir/mid/.cvsignore"
876 echo cvsin >"$fromdir/mid/one-for-all"
c4e46f36 877+cat >"$fromdir/mid/.excl" <<EOF
7d31425d 878+. .cvsignore
c4e46f36 879+EOF
7d31425d
WD
880 echo cvsin >"$fromdir/mid/for/one-in-one-out"
881 echo expunged >"$fromdir/mid/for/foo/extra"
882 echo retained >"$fromdir/mid/for/foo/keep"
c4e46f36
WD
883@@ -100,5 +128,24 @@ $RSYNC -av --existing --include='*/' --e
884 checkit "$RSYNC -avvC --exclude-from=\"$excl\" \
885 --delete-excluded \"$fromdir/\" \"$todir/\"" "$chkdir" "$todir"
7d31425d
WD
886
887+# Modify the chk dir for our merge-exclude test and then tweak the dir times.
888+
889+rm "$chkdir"/.excl
890+rm "$chkdir"/foo/file1
891+rm "$chkdir"/bar/.excl
892+rm "$chkdir"/bar/down/to/.excl2
893+rm "$chkdir"/bar/down/to/foo/.excl2
894+rm "$chkdir"/mid/.excl
895+cp -p "$fromdir"/bar/down/to/foo/*.junk "$chkdir"/bar/down/to/foo
896+cp -p "$fromdir"/bar/down/to/foo/to "$chkdir"/bar/down/to/foo
897+
898+$RSYNC -av --existing --include='*/' --exclude='*' "$fromdir/" "$chkdir/"
899+
900+# Now, test if rsync excludes the same files, this time with a merge-exclude
901+# file.
902+
1e476835 903+checkit "$RSYNC -avv --exclude='. .excl' --exclude-from=\"$excl\" --inherit=. \
c4e46f36 904+ --delete-excluded \"$fromdir/\" \"$todir/\"" "$chkdir" "$todir"
7d31425d
WD
905+
906 # The script would have aborted on error, so getting here means we've won.
907 exit 0