Fixed a failing hunk.
[rsync/rsync-patches.git] / fuzzy.diff
1 The changes to generator.c were greatly simplified, making the patch
2 easier to maintain and fixing the failing test in the testsuite.
3 Lightly tested.
4
5 Be sure to run "make proto" before "make".
6
7 --- orig/generator.c    2005-01-17 23:11:45
8 +++ generator.c 2005-01-16 02:16:38
9 @@ -44,6 +44,7 @@ extern int size_only;
10  extern OFF_T max_size;
11  extern int io_timeout;
12  extern int protocol_version;
13 +extern int fuzzy_basis;
14  extern int always_checksum;
15  extern char *partial_dir;
16  extern char *basis_dir[];
17 @@ -242,6 +243,83 @@ static void generate_and_send_sums(int f
18  }
19  
20  
21 +static unsigned int measure_name(const char *name, const char *basename,
22 +                                const char *ext)
23 +{
24 +       int namelen = strlen(name);
25 +       int extlen = strlen(ext);
26 +       unsigned int score = 0;
27 +
28 +       /* Extensions must match */
29 +       if (namelen <= extlen || strcmp(name + namelen - extlen, ext) != 0)
30 +               return 0;
31 +
32 +       /* Now score depends on similarity of prefix */
33 +       for (; *name == *basename && *name; name++, basename++)
34 +               score++;
35 +       return score;
36 +}
37 +
38 +
39 +static int find_fuzzy(const char *fname, char *buf, STRUCT_STAT *st_ptr)
40 +{
41 +       DIR *d;
42 +       struct dirent *di;
43 +       char *basename, *dirname, *slash;
44 +       char bestname[MAXPATHLEN];
45 +       unsigned int bestscore = 0;
46 +       const char *ext;
47 +
48 +       strlcpy(buf, fname, MAXPATHLEN);
49 +       if ((slash = strrchr(buf, '/')) != NULL) {
50 +               dirname = buf;
51 +               *slash = '\0';
52 +               basename = slash + 1;
53 +       } else {
54 +               basename = buf;
55 +               dirname = ".";
56 +       }
57 +
58 +       if (!(d = opendir(dirname))) {
59 +               rsyserr(FERROR, errno, "recv_generator opendir(%s)", dirname);
60 +               return -1;
61 +       }
62 +
63 +       /* Get final extension, eg. .gz; never full basename though. */
64 +       for (ext = basename; *ext == '.'; ext++) {}
65 +       if (!(ext = strrchr(ext, '.')))
66 +               ext = basename + strlen(basename); /* ext = "" */
67 +
68 +       while ((di = readdir(d)) != NULL) {
69 +               const char *dname = d_name(di);
70 +               unsigned int score;
71 +
72 +               if (dname[0] == '.' && (dname[1] == '\0'
73 +                   || (dname[1] == '.' && dname[2] == '\0')))
74 +                       continue;
75 +
76 +               score = measure_name(dname, basename, ext);
77 +               if (verbose > 4) {
78 +                       rprintf(FINFO, "fuzzy score for %s = %u\n",
79 +                               dname, score);
80 +               }
81 +               if (score > bestscore) {
82 +                       strlcpy(bestname, dname, sizeof bestname);
83 +                       bestscore = score;
84 +               }
85 +       }
86 +       closedir(d);
87 +
88 +       /* Found a candidate. */
89 +       if (bestscore != 0) {
90 +               strlcpy(basename, MAXPATHLEN - (basename - buf), bestname);
91 +               if (verbose > 2)
92 +                       rprintf(FINFO, "fuzzy match %s->%s\n", fname, buf);
93 +               return link_stat(buf, st_ptr, 0);
94 +       }
95 +       return -1;
96 +}
97 +
98  
99  /*
100   * Acts on file number @p i from @p flist, whose name is @p fname.
101 @@ -496,6 +574,15 @@ static void recv_generator(char *fname, 
102         } else
103                 partialptr = NULL;
104  
105 +       if (statret == -1 && fuzzy_basis) {
106 +               if (find_fuzzy(fname, fnamecmpbuf, &st) == 0
107 +                   && S_ISREG(st.st_mode)) {
108 +                       statret = 0;
109 +                       fnamecmp = fnamecmpbuf;
110 +                       fnamecmp_type = FNAMECMP_FUZZY;
111 +               }
112 +       }
113 +
114         if (statret == -1) {
115                 if (preserve_hard_links && hard_link_check(file, HL_SKIP))
116                         return;
117 @@ -524,6 +611,8 @@ static void recv_generator(char *fname, 
118  
119         if (!compare_dest && fnamecmp_type <= FNAMECMP_BASIS_DIR_HIGH)
120                 ;
121 +       else if (fnamecmp_type == FNAMECMP_FUZZY)
122 +               ;
123         else if (unchanged_file(fnamecmp, file, &st)) {
124                 if (fnamecmp_type == FNAMECMP_FNAME)
125                         set_perms(fname, file, &st, PERMS_REPORT);
126 @@ -598,8 +687,24 @@ notify_others:
127         write_int(f_out, i);
128         if (protocol_version >= 29 && inplace && !read_batch)
129                 write_byte(f_out, fnamecmp_type);
130 -       if (f_out_name >= 0)
131 +       if (f_out_name >= 0) {
132                 write_byte(f_out_name, fnamecmp_type);
133 +               if (fnamecmp_type == FNAMECMP_FUZZY) {
134 +                       uchar lenbuf[3], *lb = lenbuf;
135 +                       int len = strlen(fnamecmpbuf);
136 +                       if (len > 0x7F) {
137 +#if MAXPATHLEN > 0x7FFF
138 +                               *lb++ = len / 0x10000 + 0x80;
139 +                               *lb++ = len / 0x100;
140 +#else
141 +                               *lb++ = len / 0x100 + 0x80;
142 +#endif
143 +                       }
144 +                       *lb = len;
145 +                       write_buf(f_out_name, lenbuf, lb - lenbuf + 1);
146 +                       write_buf(f_out_name, fnamecmpbuf, len);
147 +               }
148 +       }
149  
150         if (dry_run || read_batch)
151                 return;
152 --- orig/main.c 2005-01-17 23:11:45
153 +++ main.c      2005-01-14 18:33:15
154 @@ -48,6 +48,7 @@ extern int keep_dirlinks;
155  extern int preserve_hard_links;
156  extern int protocol_version;
157  extern int recurse;
158 +extern int fuzzy_basis;
159  extern int relative_paths;
160  extern int rsync_port;
161  extern int whole_file;
162 @@ -464,7 +465,8 @@ static int do_recv(int f_in,int f_out,st
163         int pid;
164         int status = 0;
165         int error_pipe[2], name_pipe[2];
166 -       BOOL need_name_pipe = (basis_dir[0] || partial_dir) && !dry_run;
167 +       BOOL need_name_pipe = (basis_dir[0] || partial_dir || fuzzy_basis)
168 +                           && !dry_run;
169  
170         /* The receiving side mustn't obey this, or an existing symlink that
171          * points to an identical file won't be replaced by the referent. */
172 --- orig/options.c      2005-01-17 23:11:45
173 +++ options.c   2005-01-15 21:08:13
174 @@ -86,6 +86,7 @@ int copy_unsafe_links = 0;
175  int size_only = 0;
176  int daemon_bwlimit = 0;
177  int bwlimit = 0;
178 +int fuzzy_basis = 0;
179  size_t bwlimit_writemax = 0;
180  int delete_after = 0;
181  int only_existing = 0;
182 @@ -288,6 +289,7 @@ void usage(enum logcode F)
183    rprintf(F,"     --compare-dest=DIR      also compare destination files relative to DIR\n");
184    rprintf(F,"     --copy-dest=DIR         ... and include copies of unchanged files\n");
185    rprintf(F,"     --link-dest=DIR         hardlink to files in DIR when unchanged\n");
186 +  rprintf(F,"     --fuzzy                 find similar file for basis when no dest file\n");
187    rprintf(F," -P                          equivalent to --partial --progress\n");
188    rprintf(F," -z, --compress              compress file data\n");
189    rprintf(F," -C, --cvs-exclude           auto ignore files in the same way CVS does\n");
190 @@ -384,6 +386,7 @@ static struct poptOption long_options[] 
191    {"compare-dest",     0,  POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
192    {"copy-dest",        0,  POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
193    {"link-dest",        0,  POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
194 +  {"fuzzy",            0,  POPT_ARG_NONE,   &fuzzy_basis, 0, 0, 0 },
195    /* TODO: Should this take an optional int giving the compression level? */
196    {"compress",        'z', POPT_ARG_NONE,   &do_compression, 0, 0, 0 },
197    {"stats",            0,  POPT_ARG_NONE,   &do_stats, 0, 0, 0 },
198 @@ -1234,6 +1237,9 @@ void server_options(char **args,int *arg
199                         args[ac++] = "--no-relative";
200         }
201  
202 +       if (fuzzy_basis && am_sender)
203 +               args[ac++] = "--fuzzy";
204 +
205         *argc = ac;
206         return;
207  
208 --- orig/receiver.c     2005-01-17 23:11:45
209 +++ receiver.c  2005-01-15 21:21:02
210 @@ -324,6 +324,27 @@ static int receive_data(int f_in, char *
211  }
212  
213  
214 +static void read_gen_name(int fd, char *buf)
215 +{
216 +       int len = read_byte(fd);
217 +       if (len & 0x80) {
218 +#if MAXPATHLEN > 32767
219 +               uchar lenbuf[2];
220 +               read_buf(fd, (char *)lenbuf, 2);
221 +               len = (len & ~0x80) * 0x10000 + lenbuf[0] * 0x100 + lenbuf[1];
222 +#else
223 +               len = (len & ~0x80) * 0x100 + read_byte(fd);
224 +#endif
225 +       }
226 +       if (len >= MAXPATHLEN) {
227 +               rprintf(FERROR, "bogus data on generator name pipe\n");
228 +               exit_cleanup(RERR_PROTOCOL);
229 +       }
230 +
231 +       read_sbuf(fd, buf, len);
232 +}
233 +
234 +
235  static void discard_receive_data(int f_in, OFF_T length)
236  {
237         receive_data(f_in, NULL, -1, 0, NULL, -1, length);
238 @@ -454,6 +475,10 @@ int recv_files(int f_in, struct file_lis
239                         case FNAMECMP_BACKUP:
240                                 fnamecmp = get_backup_name(fname);
241                                 break;
242 +                       case FNAMECMP_FUZZY:
243 +                               read_gen_name(f_in_name, fnamecmpbuf);
244 +                               fnamecmp = fnamecmpbuf;
245 +                               break;
246                         default:
247                                 if (j >= basis_dir_cnt) {
248                                         rprintf(FERROR,
249 --- orig/rsync.h        2005-01-17 23:11:45
250 +++ rsync.h     2005-01-15 21:24:09
251 @@ -128,6 +128,7 @@
252  #define FNAMECMP_FNAME         0x80
253  #define FNAMECMP_PARTIAL_DIR   0x81
254  #define FNAMECMP_BACKUP        0x82
255 +#define FNAMECMP_FUZZY         0x83
256  
257  
258  /* Log-message categories.  FLOG is only used on the daemon side to
259 --- orig/rsync.yo       2005-01-17 23:11:46
260 +++ rsync.yo    2005-01-15 21:48:52
261 @@ -358,6 +358,7 @@ verb(
262       --compare-dest=DIR      also compare received files relative to DIR
263       --copy-dest=DIR         ... and include copies of unchanged files
264       --link-dest=DIR         hardlink to files in DIR when unchanged
265 +     --fuzzy                 find similar file for basis when no dest
266   -P                          equivalent to --partial --progress
267   -z, --compress              compress file data
268   -C, --cvs-exclude           auto ignore files in the same way CVS does
269 @@ -878,6 +879,11 @@ Note that rsync versions prior to 2.6.1 
270  (or implied by -a).  You can work-around this bug by avoiding the -o option
271  when sending to an old rsync.
272  
273 +dit(bf(--fuzzy)) This option tells rsync that it should look around for a
274 +basis file for any destination file that is missing.  The current algorithm
275 +looks for a similarly-named file in the same directory as the destination
276 +file, and, if found, uses that to try to speed up the transfer.
277 +
278  dit(bf(-z, --compress)) With this option, rsync compresses any data from
279  the files that it sends to the destination machine.  This
280  option is useful on slow connections.  The compression method used is the