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