Fixed failing hunks.
[rsync/rsync-patches.git] / detect-renamed.diff
CommitLineData
1fffd582
WD
1This patch adds the --detect-renamed option which makes rsync notice files
2that either (1) match in size & modify-time (plus the basename, if possible)
3or (2) match in size & checksum (when --checksum was also specified) and use
4each match as an alternate basis file to speed up the transfer.
5
6The algorithm attempts to scan the receiving-side's files in an efficient
7manner. If --delete[-before] is enabled, we'll take advantage of the
8pre-transfer delete pass to prepare any alternate-basis-file matches we
9might find. If --delete-before is not enabled, rsync does the rename scan
10during the regular file-sending scan (scanning each directory right before
11the generator starts updating files from that dir). In this latter mode,
12rsync might delay the updating of a file (if no alternate-basis match was
13yet found) until the full scan of the receiving side is complete, at which
14point any delayed files are processed.
15
16I chose to hard-link the alternate-basis files into a ".~tmp~" subdir that
17takes advantage of rsync's pre-existing partial-dir logic. This uses less
18memory than trying to keep track of the matches internally, and also allows
19any deletions or file-updates to occur normally without interfering with
20these alternate-basis discoveries.
21
03019e41 22To use this patch, run these commands for a successful build:
1fffd582 23
03019e41
WD
24 patch -p1 <patches/detect-renamed.diff
25 ./configure (optional if already run)
1fffd582
WD
26 make
27
28TODO:
29
30 We need to never return a match from fattr_find() that has a basis
31 file. This will ensure that we don't try to give a renamed file to
32 a file that can't use it, while missing out on giving it to a file
33 that could use it.
34
35--- old/flist.c
36+++ new/flist.c
fc068916 37@@ -56,6 +56,7 @@ extern int non_perishable_cnt;
1fffd582
WD
38 extern int prune_empty_dirs;
39 extern int copy_links;
40 extern int copy_unsafe_links;
41+extern int detect_renamed;
42 extern int protocol_version;
43 extern int sanitize_paths;
03019e41 44 extern struct stats stats;
fc068916 45@@ -86,6 +87,8 @@ static int64 tmp_dev, tmp_ino;
7b80cd0e 46 #endif
70891d26 47 static char tmp_sum[MD4_SUM_LENGTH];
1fffd582
WD
48
49+struct file_list the_fattr_list;
50+
51 static char empty_sum[MD4_SUM_LENGTH];
a47d1f86 52 static int flist_count_offset; /* for --delete --progress */
1fffd582 53
fc068916 54@@ -268,6 +271,45 @@ static mode_t from_wire_mode(int mode)
03019e41 55 return mode;
1fffd582
WD
56 }
57
58+static int fattr_compare(struct file_struct **file1, struct file_struct **file2)
59+{
60+ struct file_struct *f1 = *file1;
61+ struct file_struct *f2 = *file2;
a47d1f86 62+ int64 len1 = F_LENGTH(f1), len2 = F_LENGTH(f2);
1fffd582
WD
63+ int diff;
64+
a47d1f86
WD
65+ if (!f1->basename || !S_ISREG(f1->mode) || !len1) {
66+ if (!f2->basename || !S_ISREG(f2->mode) || !len2)
1fffd582
WD
67+ return 0;
68+ return 1;
69+ }
a47d1f86 70+ if (!f2->basename || !S_ISREG(f2->mode) || !len2)
1fffd582
WD
71+ return -1;
72+
73+ /* Don't use diff for values that are longer than an int. */
a47d1f86
WD
74+ if (len1 != len2)
75+ return len1 < len2 ? -1 : 1;
1fffd582
WD
76+
77+ if (always_checksum) {
70891d26 78+ diff = u_memcmp(F_SUM(f1), F_SUM(f2), checksum_len);
1fffd582
WD
79+ if (diff)
80+ return diff;
81+ } else if (f1->modtime != f2->modtime)
82+ return f1->modtime < f2->modtime ? -1 : 1;
83+
84+ diff = u_strcmp(f1->basename, f2->basename);
85+ if (diff)
86+ return diff;
87+
88+ if (f1->dirname == f2->dirname)
89+ return 0;
90+ if (!f1->dirname)
91+ return -1;
92+ if (!f2->dirname)
93+ return 1;
94+ return u_strcmp(f1->dirname, f2->dirname);
95+}
96+
fc068916
WD
97 static void send_directory(int f, struct file_list *flist, int ndx,
98 char *fbuf, int len, int flags);
1fffd582 99
dd0d95fa 100@@ -1706,6 +1748,25 @@ struct file_list *recv_file_list(int f)
1fffd582
WD
101
102 clean_flist(flist, relative_paths, 1);
103
104+ if (detect_renamed) {
105+ int j = flist->count;
106+ the_fattr_list.count = j;
107+ the_fattr_list.files = new_array(struct file_struct *, j);
108+ if (!the_fattr_list.files)
70891d26 109+ out_of_memory("recv_file_list");
1fffd582
WD
110+ memcpy(the_fattr_list.files, flist->files,
111+ j * sizeof (struct file_struct *));
112+ qsort(the_fattr_list.files, j,
fc068916 113+ sizeof the_fattr_list.files[0], (int (*)())fattr_compare);
1fffd582
WD
114+ the_fattr_list.low = 0;
115+ while (j-- > 0) {
116+ struct file_struct *fp = the_fattr_list.files[j];
a47d1f86 117+ if (fp->basename && S_ISREG(fp->mode) && F_LENGTH(fp))
1fffd582
WD
118+ break;
119+ }
120+ the_fattr_list.high = j;
121+ }
122+
dd0d95fa 123 if (inc_recurse) {
fc068916
WD
124 qsort(dir_flist->files + dstart, dir_flist->count - dstart,
125 sizeof dir_flist->files[0], (int (*)())file_compare);
1fffd582
WD
126--- old/generator.c
127+++ new/generator.c
fc068916 128@@ -79,6 +79,7 @@ extern char *basis_dir[];
1fffd582
WD
129 extern int compare_dest;
130 extern int copy_dest;
131 extern int link_dest;
132+extern int detect_renamed;
133 extern int whole_file;
134 extern int list_only;
03019e41 135 extern int new_root_dir;
fc068916 136@@ -95,6 +96,7 @@ extern char *backup_suffix;
1fffd582 137 extern int backup_suffix_len;
fc068916 138 extern struct file_list *cur_flist, *first_flist, *dir_flist;
1fffd582 139 extern struct filter_list_struct server_filter_list;
fc068916 140+extern struct file_list the_fattr_list;
1fffd582 141
d16b5fd6 142 int ignore_perishable = 0;
fc068916
WD
143 int non_perishable_cnt = 0;
144@@ -102,12 +104,14 @@ int maybe_ATTRS_REPORT = 0;
d16b5fd6 145
fc068916 146 static dev_t dev_zero;
1fffd582
WD
147 static int deletion_count = 0; /* used to implement --max-delete */
148+static int unexplored_dirs = 1;
1071853f
WD
149 static int deldelay_size = 0, deldelay_cnt = 0;
150 static char *deldelay_buf = NULL;
151 static int deldelay_fd = -1;
81172142 152 static BOOL solo_file = 0;
1fffd582 153
d16b5fd6
WD
154-/* For calling delete_item() and delete_dir_contents(). */
155+/* For calling delete_item(), delete_dir_contents(), and delete_in_dir(). */
156+#define DEL_NO_DELETIONS (1<<0)
87d0091c 157 #define DEL_RECURSE (1<<1) /* recurse */
d16b5fd6 158 #define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */
1fffd582 159
fc068916 160@@ -129,11 +133,120 @@ static int is_backup_file(char *fn)
1fffd582
WD
161 return k > 0 && strcmp(fn+k, backup_suffix) == 0;
162 }
163
164+/* Search for a regular file that matches either (1) the size & modified
165+ * time (plus the basename, if possible) or (2) the size & checksum. If
166+ * we find an exact match down to the dirname, return -1 because we found
167+ * an up-to-date file in the transfer, not a renamed file. */
a47d1f86 168+static int fattr_find(struct file_struct *f, char *fname)
1fffd582
WD
169+{
170+ int low = the_fattr_list.low, high = the_fattr_list.high;
171+ int mid, ok_match = -1, good_match = -1;
172+ struct file_struct *fmid;
173+ int diff;
174+
175+ while (low <= high) {
176+ mid = (low + high) / 2;
177+ fmid = the_fattr_list.files[mid];
a47d1f86
WD
178+ if (F_LENGTH(fmid) != F_LENGTH(f)) {
179+ if (F_LENGTH(fmid) < F_LENGTH(f))
1fffd582
WD
180+ low = mid + 1;
181+ else
182+ high = mid - 1;
183+ continue;
184+ }
185+ if (always_checksum) {
a47d1f86
WD
186+ /* We use the FLAG_SENT flag to indicate when we
187+ * have computed the checksum for an entry. */
188+ if (!(f->flags & FLAG_SENT)) {
1fffd582
WD
189+ if (fmid->modtime == f->modtime
190+ && f_name_cmp(fmid, f) == 0)
191+ return -1; /* assume we can't help */
a47d1f86
WD
192+ file_checksum(fname, (char*)F_SUM(f), F_LENGTH(f));
193+ f->flags |= FLAG_SENT;
1fffd582 194+ }
70891d26 195+ diff = u_memcmp(F_SUM(fmid), F_SUM(f), checksum_len);
1fffd582
WD
196+ if (diff) {
197+ if (diff < 0)
198+ low = mid + 1;
199+ else
200+ high = mid - 1;
201+ continue;
202+ }
203+ } else {
204+ if (fmid->modtime != f->modtime) {
205+ if (fmid->modtime < f->modtime)
206+ low = mid + 1;
207+ else
208+ high = mid - 1;
209+ continue;
210+ }
211+ }
212+ ok_match = mid;
213+ diff = u_strcmp(fmid->basename, f->basename);
214+ if (diff == 0) {
215+ good_match = mid;
216+ if (fmid->dirname == f->dirname)
217+ return -1; /* file is up-to-date */
218+ if (!fmid->dirname) {
219+ low = mid + 1;
220+ continue;
221+ }
222+ if (!f->dirname) {
223+ high = mid - 1;
224+ continue;
225+ }
226+ diff = u_strcmp(fmid->dirname, f->dirname);
227+ if (diff == 0)
228+ return -1; /* file is up-to-date */
229+ }
230+ if (diff < 0)
231+ low = mid + 1;
232+ else
233+ high = mid - 1;
234+ }
235+
236+ return good_match >= 0 ? good_match : ok_match;
237+}
238+
a47d1f86 239+static void look_for_rename(struct file_struct *file, char *fname)
1fffd582
WD
240+{
241+ struct file_struct *fp;
242+ char *partialptr, *fn;
243+ STRUCT_STAT st;
244+ int ndx;
245+
a47d1f86 246+ if ((ndx = fattr_find(file, fname)) < 0)
1fffd582
WD
247+ return;
248+
249+ fp = the_fattr_list.files[ndx];
250+ fn = f_name(fp, NULL);
251+ /* We don't provide an alternate-basis file if there is a basis file. */
252+ if (link_stat(fn, &st, 0) == 0)
253+ return;
254+ if ((partialptr = partial_dir_fname(fn)) == NULL
255+ || !handle_partial_dir(partialptr, PDIR_CREATE))
256+ return;
257+
258+ /* We only use the file if we can hard-link it into our tmp dir. */
259+ if (link(fname, partialptr) == 0) {
260+ if (verbose > 2) {
261+ rprintf(FINFO, "found renamed: %s => %s\n",
262+ fname, partialptr);
263+ }
264+ return;
265+ }
266+
267+ if (errno != EEXIST)
268+ handle_partial_dir(partialptr, PDIR_DELETE);
269+}
87d0091c
WD
270+
271 /* Delete a file or directory. If DEL_RECURSE is set in the flags, this will
272 * delete recursively.
1fffd582 273 *
f813befd 274 * Note that fbuf must point to a MAXPATHLEN buffer if the mode indicates it's
15894839
WD
275 * a directory! (The buffer is used for recursion, but returned unchanged.)
276+ *
277+ * Also note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
278 */
f813befd 279 static enum delret delete_item(char *fbuf, int mode, char *replace, int flags)
15894839 280 {
fc068916 281@@ -155,6 +268,8 @@ static enum delret delete_item(char *fbu
15894839
WD
282 goto check_ret;
283 /* OK: try to delete the directory. */
284 }
285+ if (flags & DEL_NO_DELETIONS)
286+ return DR_SUCCESS;
287
288 if (!replace && max_delete >= 0 && ++deletion_count > max_delete)
289 return DR_AT_LIMIT;
fc068916 290@@ -201,6 +316,8 @@ static enum delret delete_item(char *fbu
d16b5fd6
WD
291 * its contents, otherwise just checks for content. Returns DR_SUCCESS or
292 * DR_NOT_EMPTY. Note that fname must point to a MAXPATHLEN buffer! (The
293 * buffer is used for recursion, but returned unchanged.)
1fffd582 294+ *
87d0091c 295+ * Note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
1fffd582 296 */
87d0091c 297 static enum delret delete_dir_contents(char *fname, int flags)
1fffd582 298 {
fc068916 299@@ -220,7 +337,9 @@ static enum delret delete_dir_contents(c
a47d1f86
WD
300 save_filters = push_local_filters(fname, dlen);
301
302 non_perishable_cnt = 0;
7e27b6c0 303+ file_extra_cnt += SUM_EXTRA_CNT;
a47d1f86 304 dirlist = get_dirlist(fname, dlen, 0);
7e27b6c0 305+ file_extra_cnt -= SUM_EXTRA_CNT;
a47d1f86
WD
306 ret = non_perishable_cnt ? DR_NOT_EMPTY : DR_SUCCESS;
307
308 if (!dirlist->count)
fc068916 309@@ -257,6 +376,8 @@ static enum delret delete_dir_contents(c
d16b5fd6
WD
310 if (S_ISDIR(fp->mode)
311 && delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
312 ret = DR_NOT_EMPTY;
313+ if (detect_renamed && S_ISREG(fp->mode))
a47d1f86 314+ look_for_rename(fp, fname);
d16b5fd6
WD
315 if (delete_item(fname, fp->mode, NULL, flags) != DR_SUCCESS)
316 ret = DR_NOT_EMPTY;
317 }
fc068916 318@@ -409,13 +530,17 @@ static void do_delayed_deletions(char *d
1fffd582
WD
319 * all the --delete-WHEN options. Note that the fbuf pointer must point to a
320 * MAXPATHLEN buffer with the name of the directory in it (the functions we
321 * call will append names onto the end, but the old dir value will be restored
322- * on exit). */
323+ * on exit).
324+ *
325+ * Note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
326+ */
327 static void delete_in_dir(struct file_list *flist, char *fbuf,
fc068916
WD
328- struct file_struct *file, dev_t *fs_dev)
329+ struct file_struct *file, dev_t *fs_dev, int flags)
1fffd582 330 {
1fffd582
WD
331 static int already_warned = 0;
332 struct file_list *dirlist;
333- char delbuf[MAXPATHLEN];
334+ char *p, delbuf[MAXPATHLEN];
335+ unsigned remainder;
336 int dlen, i;
337
338 if (!flist) {
fc068916 339@@ -426,21 +551,28 @@ static void delete_in_dir(struct file_li
1fffd582
WD
340 if (verbose > 2)
341 rprintf(FINFO, "delete_in_dir(%s)\n", fbuf);
342
87d0091c 343+ flags |= DEL_RECURSE;
1fffd582
WD
344+
345 if (allowed_lull)
346 maybe_send_keepalive();
347
041d67b8 348 if (io_error && !ignore_errors) {
1fffd582
WD
349- if (already_warned)
350+ if (!already_warned) {
351+ rprintf(FINFO,
352+ "IO error encountered -- skipping file deletion\n");
353+ already_warned = 1;
354+ }
355+ if (!detect_renamed)
356 return;
357- rprintf(FINFO,
358- "IO error encountered -- skipping file deletion\n");
359- already_warned = 1;
360- return;
361+ flags |= DEL_NO_DELETIONS;
362 }
363
1fffd582 364 dlen = strlen(fbuf);
fc068916 365 change_local_filter_dir(fbuf, dlen, F_DEPTH(file));
1fffd582
WD
366
367+ if (detect_renamed)
368+ unexplored_dirs--;
369+
370 if (one_file_system) {
371 if (file->flags & FLAG_TOP_DIR)
fc068916
WD
372 filesystem_dev = *fs_dev;
373@@ -450,6 +582,11 @@ static void delete_in_dir(struct file_li
1fffd582
WD
374
375 dirlist = get_dirlist(fbuf, dlen, 0);
376
377+ p = fbuf + dlen;
378+ if (dlen != 1 || *fbuf != '/')
379+ *p++ = '/';
380+ remainder = MAXPATHLEN - (p - fbuf);
381+
382 /* If an item in dirlist is not found in flist, delete it
383 * from the filesystem. */
384 for (i = dirlist->count; i--; ) {
fc068916 385@@ -462,16 +599,23 @@ static void delete_in_dir(struct file_li
87d0091c 386 f_name(fp, NULL));
1fffd582 387 continue;
87d0091c 388 }
1fffd582
WD
389+ if (detect_renamed && S_ISREG(fp->mode)) {
390+ strlcpy(p, fp->basename, remainder);
a47d1f86 391+ look_for_rename(fp, fbuf);
1fffd582
WD
392+ }
393 if (flist_find(flist, fp) < 0) {
394 f_name(fp, delbuf);
1071853f 395- if (delete_during == 2) {
a47d1f86 396+ if (delete_during == 2 && !(flags & DEL_NO_DELETIONS)) {
1071853f
WD
397 if (!remember_delete(fp, delbuf))
398 break;
399 } else
f813befd 400- delete_item(delbuf, fp->mode, NULL, DEL_RECURSE);
1fffd582 401- }
f813befd 402+ delete_item(delbuf, fp->mode, NULL, flags);
1fffd582
WD
403+ } else if (detect_renamed && S_ISDIR(fp->mode))
404+ unexplored_dirs++;
405 }
406
407+ fbuf[dlen] = '\0';
408+
409 flist_free(dirlist);
410 }
411
fc068916 412@@ -501,9 +645,9 @@ static void do_delete_pass(struct file_l
1fffd582
WD
413 || !S_ISDIR(st.st_mode))
414 continue;
415
fc068916
WD
416- delete_in_dir(flist, fbuf, file, &st.st_dev);
417+ delete_in_dir(flist, fbuf, file, &st.st_dev, 0);
1fffd582 418 }
fc068916
WD
419- delete_in_dir(NULL, NULL, NULL, &dev_zero);
420+ delete_in_dir(NULL, NULL, NULL, &dev_zero, 0);
1fffd582
WD
421
422 if (do_progress && !am_server)
423 rprintf(FINFO, " \r");
fc068916 424@@ -1038,6 +1182,7 @@ static int try_dests_non(struct file_str
9a70b743 425 return j;
1fffd582
WD
426 }
427
428+static struct bitbag *delayed_bits = NULL;
429 static int phase = 0;
430
fc068916
WD
431 /* Acts on cur_flist->file's ndx'th item, whose name is fname. If a dir,
432@@ -1229,8 +1374,12 @@ static void recv_generator(char *fname,
433 }
434 }
435 else if (delete_during && f_out != -1 && !phase && dry_run < 2
70891d26 436- && (file->flags & FLAG_XFER_DIR))
fc068916 437- delete_in_dir(cur_flist, fname, file, &real_st.st_dev);
70891d26 438+ && (file->flags & FLAG_XFER_DIR)) {
9a70b743 439+ if (detect_renamed && real_ret != 0)
1fffd582 440+ unexplored_dirs++;
fc068916 441+ delete_in_dir(cur_flist, fname, file, &real_st.st_dev,
1fffd582
WD
442+ delete_during < 0 ? DEL_NO_DELETIONS : 0);
443+ }
444 return;
445 }
446
fc068916 447@@ -1494,8 +1643,14 @@ static void recv_generator(char *fname,
a47d1f86 448 if (preserve_hard_links && F_HLINK_NOT_LAST(file))
1fffd582 449 return;
81172142 450 #endif
1fffd582
WD
451- if (stat_errno == ENOENT)
452+ if (stat_errno == ENOENT) {
453+ if (detect_renamed && unexplored_dirs > 0
a47d1f86 454+ && F_LENGTH(file)) {
1fffd582
WD
455+ bitbag_set_bit(delayed_bits, ndx);
456+ return;
457+ }
458 goto notify_others;
459+ }
460 rsyserr(FERROR, stat_errno, "recv_generator: failed to stat %s",
461 full_fname(fname));
462 return;
dd0d95fa 463@@ -1717,6 +1872,12 @@ void generate_files(int f_out, char *loc
fc068916
WD
464 if (verbose > 2)
465 rprintf(FINFO, "generator starting pid=%ld\n", (long)getpid());
1fffd582
WD
466
467+ if (detect_renamed) {
468+ delayed_bits = bitbag_create(flist->count);
469+ if (!delete_before && !delete_during)
470+ delete_during = -1;
471+ }
472+
fc068916
WD
473 if (delete_before && !local_name && cur_flist->count > 0)
474 do_delete_pass(cur_flist);
1071853f 475 if (delete_during == 2) {
dd0d95fa 476@@ -1727,7 +1888,7 @@ void generate_files(int f_out, char *loc
1071853f 477 }
1fffd582
WD
478 do_progress = 0;
479
fc068916
WD
480- if (append_mode > 0 || whole_file < 0)
481+ if (append_mode > 0 || detect_renamed || whole_file < 0)
1fffd582
WD
482 whole_file = 0;
483 if (verbose >= 2) {
484 rprintf(FINFO, "delta-transmission %s\n",
dd0d95fa 485@@ -1752,7 +1913,7 @@ void generate_files(int f_out, char *loc
fc068916
WD
486 dirdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
487 } else
488 dirdev = MAKEDEV(0, 0);
489- delete_in_dir(cur_flist, f_name(fp, fbuf), fp, &dirdev);
490+ delete_in_dir(cur_flist, f_name(fp, fbuf), fp, &dirdev, 0);
491 }
492 }
493 for (i = cur_flist->low; i <= cur_flist->high; i++) {
dd0d95fa 494@@ -1798,7 +1959,21 @@ void generate_files(int f_out, char *loc
fc068916 495
dd0d95fa 496 if (!inc_recurse) {
fc068916
WD
497 if (delete_during)
498- delete_in_dir(NULL, NULL, NULL, &dev_zero);
499+ delete_in_dir(NULL, NULL, NULL, &dev_zero, 0);
500+ if (detect_renamed) {
501+ if (delete_during < 0)
502+ delete_during = 0;
503+ detect_renamed = 0;
1fffd582 504+
fc068916
WD
505+ for (i = -1; (i = bitbag_next_bit(delayed_bits, i)) >= 0; ) {
506+ struct file_struct *file = flist->files[i];
507+ if (local_name)
508+ strlcpy(fbuf, local_name, sizeof fbuf);
509+ else
510+ f_name(file, fbuf);
511+ recv_generator(fbuf, file, i, itemizing, code, f_out);
512+ }
513+ }
514 phase++;
515 if (verbose > 2) {
516 rprintf(FINFO, "generate_files phase=%d\n",
1fffd582
WD
517--- old/options.c
518+++ new/options.c
03019e41 519@@ -78,6 +78,7 @@ int am_generator = 0;
a94141d9 520 int am_starting_up = 1;
1fffd582
WD
521 int relative_paths = -1;
522 int implied_dirs = 1;
523+int detect_renamed = 0;
524 int numeric_ids = 0;
525 int allow_8bit_chars = 0;
526 int force_delete = 0;
fc068916 527@@ -343,6 +344,7 @@ void usage(enum logcode F)
1fffd582
WD
528 rprintf(F," --modify-window=NUM compare mod-times with reduced accuracy\n");
529 rprintf(F," -T, --temp-dir=DIR create temporary files in directory DIR\n");
530 rprintf(F," -y, --fuzzy find similar file for basis if no dest file\n");
531+ rprintf(F," --detect-renamed try to find renamed files to speed up the transfer\n");
532 rprintf(F," --compare-dest=DIR also compare destination files relative to DIR\n");
533 rprintf(F," --copy-dest=DIR ... and include copies of unchanged files\n");
534 rprintf(F," --link-dest=DIR hardlink to files in DIR when unchanged\n");
fc068916 535@@ -497,6 +499,7 @@ static struct poptOption long_options[]
1fffd582
WD
536 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
537 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
538 {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
539+ {"detect-renamed", 0, POPT_ARG_NONE, &detect_renamed, 0, 0, 0 },
540 {"fuzzy", 'y', POPT_ARG_NONE, &fuzzy_basis, 0, 0, 0 },
541 {"compress", 'z', POPT_ARG_NONE, 0, 'z', 0, 0 },
542 {"compress-level", 0, POPT_ARG_INT, &def_compress_level, 'z', 0, 0 },
fc068916 543@@ -1368,7 +1371,7 @@ int parse_arguments(int *argc, const cha
1fffd582
WD
544 inplace = 1;
545 }
546
547- if (delay_updates && !partial_dir)
548+ if ((delay_updates || detect_renamed) && !partial_dir)
549 partial_dir = tmp_partialdir;
550
551 if (inplace) {
fc068916 552@@ -1377,6 +1380,7 @@ int parse_arguments(int *argc, const cha
1fffd582
WD
553 snprintf(err_buf, sizeof err_buf,
554 "--%s cannot be used with --%s\n",
555 append_mode ? "append" : "inplace",
556+ detect_renamed ? "detect-renamed" :
557 delay_updates ? "delay-updates" : "partial-dir");
558 return 0;
559 }
fc068916 560@@ -1690,6 +1694,8 @@ void server_options(char **args,int *arg
a94141d9
WD
561 args[ac++] = "--super";
562 if (size_only)
563 args[ac++] = "--size-only";
564+ if (detect_renamed)
565+ args[ac++] = "--detect-renamed";
566 }
567
568 if (modify_window_set) {
1fffd582
WD
569--- old/rsync.yo
570+++ new/rsync.yo
f813befd 571@@ -364,6 +364,7 @@ to the detailed description below for a
1fffd582
WD
572 --modify-window=NUM compare mod-times with reduced accuracy
573 -T, --temp-dir=DIR create temporary files in directory DIR
574 -y, --fuzzy find similar file for basis if no dest file
575+ --detect-renamed try to find renamed files to speed the xfer
576 --compare-dest=DIR also compare received files relative to DIR
577 --copy-dest=DIR ... and include copies of unchanged files
578 --link-dest=DIR hardlink to files in DIR when unchanged
dd0d95fa 579@@ -1296,6 +1297,15 @@ Note that the use of the bf(--delete) op
1fffd582
WD
580 fuzzy-match files, so either use bf(--delete-after) or specify some
581 filename exclusions if you need to prevent this.
582
583+dit(bf(--detect-renamed)) This option tells rsync to scan the receiving
584+side for files that have been renamed, and to use any that are found as
585+alternate basis files to help speed up the transfer.
586+By default, alternate-basis files are hard-linked into a directory named
587+".~tmp~" in each file's destination directory, but if you've specified
588+the bf(--partial-dir) option, that directory will be used instead. These
589+potential alternate-basis files will be removed as the transfer progresses.
590+This option conflicts with bf(--inplace) and bf(--append).
591+
592 dit(bf(--compare-dest=DIR)) This option instructs rsync to use em(DIR) on
593 the destination machine as an additional hierarchy to compare destination
594 files against doing transfers (if the files are missing in the destination
595--- old/util.c
596+++ new/util.c
f813befd 597@@ -1027,6 +1027,32 @@ int handle_partial_dir(const char *fname
1fffd582
WD
598 return 1;
599 }
600
601+/* We need to supply our own strcmp function for file list comparisons
602+ * to ensure that signed/unsigned usage is consistent between machines. */
603+int u_strcmp(const char *p1, const char *p2)
604+{
605+ for ( ; *p1; p1++, p2++) {
606+ if (*p1 != *p2)
607+ break;
608+ }
609+
610+ return (int)*(uchar*)p1 - (int)*(uchar*)p2;
611+}
612+
613+/* We need a memcmp function compares unsigned-byte values. */
614+int u_memcmp(const void *p1, const void *p2, size_t len)
615+{
616+ const uchar *u1 = p1;
617+ const uchar *u2 = p2;
618+
619+ while (len--) {
620+ if (*u1 != *u2)
621+ return (int)*u1 - (int)*u2;
622+ }
623+
624+ return 0;
625+}
626+
627 /**
628 * Determine if a symlink points outside the current directory tree.
629 * This is considered "unsafe" because e.g. when mirroring somebody