7fbcbc692de84716e904dcefb1280430598e28fa
[rsync-patches.git] / ignore-case.diff
1 This adds the --ignore-case option, which makes rsync compare filenames
2 in a case-insensitive manner.
3
4 To use this patch, run these commands for a successful build:
5
6     patch -p1 <patches/ignore-case.diff
7     ./configure                            (optional if already run)
8     make
9
10 TODO:
11
12  - Make this code handle multibyte character encodings, and honor the
13    --iconv setting when converting case.
14
15 diff --git a/exclude.c b/exclude.c
16 --- a/exclude.c
17 +++ b/exclude.c
18 @@ -21,6 +21,7 @@
19   */
20  
21  #include "rsync.h"
22 +#include "ifuncs.h"
23  
24  extern int verbose;
25  extern int am_server;
26 @@ -580,16 +581,15 @@ static int rule_matches(char *fname, struct filter_struct *ex, int name_is_dir)
27                 if (litmatch_array(pattern, strings, slash_handling))
28                         return ret_match;
29         } else if (anchored_match) {
30 -               if (strcmp(name, pattern) == 0)
31 +               if (ic_strEQ(name, pattern))
32                         return ret_match;
33         } else {
34                 int l1 = strlen(name);
35                 int l2 = strlen(pattern);
36 -               if (l2 <= l1 &&
37 -                   strcmp(name+(l1-l2),pattern) == 0 &&
38 -                   (l1==l2 || name[l1-(l2+1)] == '/')) {
39 +               if (l2 <= l1
40 +                && ic_strEQ(name + (l1-l2), pattern)
41 +                && (l1 == l2 || name[l1 - (l2+1)] == '/'))
42                         return ret_match;
43 -               }
44         }
45  
46         return !ret_match;
47 diff --git a/flist.c b/flist.c
48 --- a/flist.c
49 +++ b/flist.c
50 @@ -35,6 +35,7 @@ extern int inc_recurse;
51  extern int do_progress;
52  extern int always_checksum;
53  extern int module_id;
54 +extern int ignore_case;
55  extern int ignore_errors;
56  extern int numeric_ids;
57  extern int recurse;
58 @@ -2617,6 +2618,7 @@ int f_name_cmp(const struct file_struct *f1, const struct file_struct *f2)
59  {
60         int dif;
61         const uchar *c1, *c2;
62 +       uchar ch1, ch2;
63         enum fnc_state state1, state2;
64         enum fnc_type type1, type2;
65         enum fnc_type t_path = protocol_version >= 29 ? t_PATH : t_ITEM;
66 @@ -2727,7 +2729,15 @@ int f_name_cmp(const struct file_struct *f1, const struct file_struct *f2)
67                         if (type1 != type2)
68                                 return type1 == t_PATH ? 1 : -1;
69                 }
70 -       } while ((dif = (int)*c1++ - (int)*c2++) == 0);
71 +               ch1 = *c1++;
72 +               ch2 = *c2++;
73 +               if (ignore_case) {
74 +                       if (isupper(ch1))
75 +                               ch1 = tolower(ch1);
76 +                       if (isupper(ch2))
77 +                               ch2 = tolower(ch2);
78 +               }
79 +       } while ((dif = (int)ch1 - (int)ch2) == 0);
80  
81         return dif;
82  }
83 diff --git a/ifuncs.h b/ifuncs.h
84 --- a/ifuncs.h
85 +++ b/ifuncs.h
86 @@ -98,3 +98,38 @@ toUpper(const char *ptr)
87  {
88         return toupper(*(unsigned char *)ptr);
89  }
90 +
91 +static inline int
92 +strEQ(const char *s1, const char *s2)
93 +{
94 +       return strcmp(s1, s2) == 0;
95 +}
96 +
97 +static inline int
98 +strnEQ(const char *s1, const char *s2, size_t n)
99 +{
100 +       return strncmp(s1, s2, n) == 0;
101 +}
102 +
103 +static inline int
104 +ic_strEQ(const char *s1, const char *s2)
105 +{
106 +       extern int ignore_case;
107 +       if (ignore_case)
108 +               return strcasecmp(s1, s2) == 0;
109 +       return strcmp(s1, s2) == 0;
110 +}
111 +
112 +static inline int
113 +ic_strnEQ(const char *s1, const char *s2, size_t n)
114 +{
115 +       extern int ignore_case;
116 +       if (ignore_case)
117 +               return strncasecmp(s1, s2, n) == 0;
118 +       return strncmp(s1, s2, n) == 0;
119 +}
120 +
121 +#define strNE(s1,s2) (!strEQ(s1,s2))
122 +#define strnNE(s1,s2,n) (!strnEQ(s1,s2,n))
123 +#define ic_strNE(s1,s2) (!ic_strEQ(s1,s2))
124 +#define ic_strnNE(s1,s2) (!ic_strnEQ(s1,s2,n))
125 diff --git a/lib/wildmatch.c b/lib/wildmatch.c
126 --- a/lib/wildmatch.c
127 +++ b/lib/wildmatch.c
128 @@ -53,6 +53,8 @@
129  #define ISUPPER(c) (ISASCII(c) && isupper(c))
130  #define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
131  
132 +extern int ignore_case;
133 +
134  #ifdef WILD_TEST_ITERATIONS
135  int wildmatch_iteration_count;
136  #endif
137 @@ -72,6 +74,8 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
138      for ( ; (p_ch = *p) != '\0'; text++, p++) {
139         int matched, special;
140         uchar t_ch, prev_ch;
141 +       if (ignore_case && ISUPPER(p_ch))
142 +           p_ch = tolower(p_ch);
143         while ((t_ch = *text) == '\0') {
144             if (*a == NULL) {
145                 if (p_ch != '*')
146 @@ -237,12 +241,21 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
147   * of "text" and any strings in array "a". */
148  static int doliteral(const uchar *s, const uchar *text, const uchar*const *a)
149  {
150 +    uchar s_ch, t_ch;
151      for ( ; *s != '\0'; text++, s++) {
152         while (*text == '\0') {
153             if ((text = *a++) == NULL)
154                 return FALSE;
155         }
156 -       if (*text != *s)
157 +       s_ch = *s;
158 +       t_ch = *text;
159 +       if (ignore_case) {
160 +           if (ISUPPER(s_ch))
161 +               s_ch = tolower(s_ch);
162 +           if (ISUPPER(t_ch))
163 +               t_ch = tolower(t_ch);
164 +       }
165 +       if (t_ch != s_ch)
166             return FALSE;
167      }
168  
169 @@ -288,10 +301,14 @@ static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)
170  int wildmatch(const char *pattern, const char *text)
171  {
172      static const uchar *nomore[1]; /* A NULL pointer. */
173 +    int ret;
174  #ifdef WILD_TEST_ITERATIONS
175      wildmatch_iteration_count = 0;
176  #endif
177 -    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
178 +    force_lower_case = ignore_case;
179 +    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
180 +    force_lower_case = 0;
181 +    return ret;
182  }
183  
184  /* Match the "pattern" against the forced-to-lower-case "text" string. */
185 @@ -331,12 +348,14 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)
186      if (!text)
187         return FALSE;
188  
189 +    force_lower_case = ignore_case;
190 +
191      if ((matched = dowild(p, text, a)) != TRUE && where < 0
192       && matched != ABORT_ALL) {
193         while (1) {
194             if (*text == '\0') {
195                 if ((text = (uchar*)*a++) == NULL)
196 -                   return FALSE;
197 +                   break;
198                 continue;
199             }
200             if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE
201 @@ -344,6 +363,9 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)
202                 break;
203         }
204      }
205 +
206 +    force_lower_case = 0;
207 +
208      return matched == TRUE;
209  }
210  
211 diff --git a/options.c b/options.c
212 --- a/options.c
213 +++ b/options.c
214 @@ -117,6 +117,7 @@ OFF_T max_size = 0;
215  OFF_T min_size = 0;
216  int ignore_errors = 0;
217  int modify_window = 0;
218 +int ignore_case = 0;
219  int blocking_io = -1;
220  int checksum_seed = 0;
221  int inplace = 0;
222 @@ -407,6 +408,7 @@ void usage(enum logcode F)
223    rprintf(F,"     --files-from=FILE       read list of source-file names from FILE\n");
224    rprintf(F," -0, --from0                 all *-from/filter files are delimited by 0s\n");
225    rprintf(F," -s, --protect-args          no space-splitting; only wildcard special-chars\n");
226 +  rprintf(F,"     --ignore-case           ignore case when comparing filenames\n");
227    rprintf(F,"     --address=ADDRESS       bind address for outgoing socket to daemon\n");
228    rprintf(F,"     --port=PORT             specify double-colon alternate port number\n");
229    rprintf(F,"     --sockopts=OPTIONS      specify custom TCP options\n");
230 @@ -602,6 +604,8 @@ static struct poptOption long_options[] = {
231    {"read-batch",       0,  POPT_ARG_STRING, &batch_name, OPT_READ_BATCH, 0, 0 },
232    {"write-batch",      0,  POPT_ARG_STRING, &batch_name, OPT_WRITE_BATCH, 0, 0 },
233    {"only-write-batch", 0,  POPT_ARG_STRING, &batch_name, OPT_ONLY_WRITE_BATCH, 0, 0 },
234 +  {"ignore-case",      0,  POPT_ARG_VAL,    &ignore_case, 1, 0, 0 },
235 +  {"no-ignore-case",   0,  POPT_ARG_VAL,    &ignore_case, 0, 0, 0 },
236    {"files-from",       0,  POPT_ARG_STRING, &files_from, 0, 0, 0 },
237    {"from0",           '0', POPT_ARG_VAL,    &eol_nulls, 1, 0, 0},
238    {"no-from0",         0,  POPT_ARG_VAL,    &eol_nulls, 0, 0, 0},
239 @@ -1939,6 +1943,9 @@ void server_options(char **args, int *argc_p)
240                 args[ac++] = arg;
241         }
242  
243 +       if (ignore_case)
244 +               args[ac++] = "--ignore-case";
245 +
246         if (partial_dir && am_sender) {
247                 if (partial_dir != tmp_partialdir) {
248                         args[ac++] = "--partial-dir";
249 diff --git a/rsync.yo b/rsync.yo
250 --- a/rsync.yo
251 +++ b/rsync.yo
252 @@ -402,6 +402,7 @@ to the detailed description below for a complete description.  verb(
253       --files-from=FILE       read list of source-file names from FILE
254   -0, --from0                 all *from/filter files are delimited by 0s
255   -s, --protect-args          no space-splitting; wildcard chars only
256 +     --ignore-case           ignore case when comparing filenames
257       --address=ADDRESS       bind address for outgoing socket to daemon
258       --port=PORT             specify double-colon alternate port number
259       --sockopts=OPTIONS      specify custom TCP options
260 @@ -1425,6 +1426,10 @@ If you use this option with bf(--iconv), the args will also be translated
261  from the local to the remote character-set.  The translation happens before
262  wild-cards are expanded.  See also the bf(--files-from) option.
263  
264 +dit(bf(--ignore-case)) This option tells rsync to ignore upper-/lower-case
265 +differences when comparing filenames.  This can avoid problems when sending
266 +files to a filesystem that ignores these differences.
267 +
268  dit(bf(-T, --temp-dir=DIR)) This option instructs rsync to use DIR as a
269  scratch directory when creating temporary copies of the files transferred
270  on the receiving side.  The default behavior is to create each temporary
271 diff --git a/wildtest.c b/wildtest.c
272 --- a/wildtest.c
273 +++ b/wildtest.c
274 @@ -31,6 +31,7 @@ int fnmatch_errors = 0;
275  #endif
276  
277  int wildmatch_errors = 0;
278 +int ignore_case = 0;
279  
280  typedef char bool;
281