1 This patch adds the --detect-renamed option which makes rsync notice files
2 that either (1) match in size & modify-time (plus the basename, if possible)
3 or (2) match in size & checksum (when --checksum was also specified) and use
4 each match as an alternate basis file to speed up the transfer.
6 The algorithm attempts to scan the receiving-side's files in an efficient
7 manner. If --delete[-before] is enabled, we'll take advantage of the
8 pre-transfer delete pass to prepare any alternate-basis-file matches we
9 might find. If --delete-before is not enabled, rsync does the rename scan
10 during the regular file-sending scan (scanning each directory right before
11 the generator starts updating files from that dir). In this latter mode,
12 rsync might delay the updating of a file (if no alternate-basis match was
13 yet found) until the full scan of the receiving side is complete, at which
14 point any delayed files are processed.
16 I chose to hard-link the alternate-basis files into a ".~tmp~" subdir that
17 takes advantage of rsync's pre-existing partial-dir logic. This uses less
18 memory than trying to keep track of the matches internally, and also allows
19 any deletions or file-updates to occur normally without interfering with
20 these alternate-basis discoveries.
22 To use this patch, run these commands for a successful build:
24 patch -p1 <patches/detect-renamed.diff
25 ./configure (optional if already run)
30 The routine that makes missing directories for files that get renamed
31 down into a new sub-hierarchy doesn't properly handle the case where some
32 path elements might exist but not be a dir yet. We need to either change
33 our stash-ahead algorithm (to not require unknown path elements) or we
34 need to create a better path-making routine.
36 We need to never return a match from fattr_find() that has a basis
37 file. This will ensure that we don't try to give a renamed file to
38 a file that can't use it, while missing out on giving it to a file
41 based-on: 6c8ca91c731b7bf2b081694bda85b7dadc2b7aff
42 diff --git a/backup.c b/backup.c
45 @@ -162,7 +162,7 @@ char *get_backup_name(const char *fname)
47 if (backup_dir_len > 1)
48 backup_dir_buf[backup_dir_len-1] = '\0';
49 - ret = make_path(backup_dir_buf, 0);
50 + ret = make_path(backup_dir_buf, ACCESSPERMS, 0);
51 if (backup_dir_len > 1)
52 backup_dir_buf[backup_dir_len-1] = '/';
54 diff --git a/compat.c b/compat.c
57 @@ -40,6 +40,7 @@ extern int checksum_seed;
58 extern int basis_dir_cnt;
59 extern int prune_empty_dirs;
60 extern int protocol_version;
61 +extern int detect_renamed;
62 extern int protect_args;
63 extern int preserve_uid;
64 extern int preserve_gid;
65 @@ -172,6 +173,7 @@ void set_allow_inc_recurse(void)
66 allow_inc_recurse = 0;
68 && (delete_before || delete_after
70 || delay_updates || prune_empty_dirs))
71 allow_inc_recurse = 0;
72 else if (am_server && strchr(client_info, 'i') == NULL)
73 diff --git a/delete.c b/delete.c
78 extern int make_backups;
79 extern int max_delete;
80 +extern int detect_renamed;
81 extern char *backup_dir;
82 extern char *backup_suffix;
83 extern int backup_suffix_len;
84 @@ -44,6 +45,8 @@ static inline int is_backup_file(char *fn)
85 * its contents, otherwise just checks for content. Returns DR_SUCCESS or
86 * DR_NOT_EMPTY. Note that fname must point to a MAXPATHLEN buffer! (The
87 * buffer is used for recursion, but returned unchanged.)
89 + * Note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
91 static enum delret delete_dir_contents(char *fname, uint16 flags)
93 @@ -63,7 +66,9 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
94 save_filters = push_local_filters(fname, dlen);
96 non_perishable_cnt = 0;
97 + file_extra_cnt += SUM_EXTRA_CNT;
98 dirlist = get_dirlist(fname, dlen, 0);
99 + file_extra_cnt -= SUM_EXTRA_CNT;
100 ret = non_perishable_cnt ? DR_NOT_EMPTY : DR_SUCCESS;
103 @@ -103,7 +108,8 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
104 if (S_ISDIR(fp->mode)) {
105 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
108 + } else if (detect_renamed && S_ISREG(fp->mode))
109 + look_for_rename(fp, fname);
110 if (delete_item(fname, fp->mode, flags) != DR_SUCCESS)
113 @@ -126,6 +132,8 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
115 * Note that fbuf must point to a MAXPATHLEN buffer if the mode indicates it's
116 * a directory! (The buffer is used for recursion, but returned unchanged.)
118 + * Also note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
120 enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
122 @@ -153,6 +161,9 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
123 /* OK: try to delete the directory. */
126 + if (flags & DEL_NO_DELETIONS)
129 if (!(flags & DEL_MAKE_ROOM) && max_delete >= 0 && stats.deleted_files >= max_delete) {
132 diff --git a/flist.c b/flist.c
135 @@ -64,6 +64,7 @@ extern int non_perishable_cnt;
136 extern int prune_empty_dirs;
137 extern int copy_links;
138 extern int copy_unsafe_links;
139 +extern int detect_renamed;
140 extern int protocol_version;
141 extern int sanitize_paths;
142 extern int munge_symlinks;
143 @@ -132,6 +133,8 @@ static int64 tmp_dev = -1, tmp_ino;
145 static char tmp_sum[MAX_DIGEST_LEN];
147 +struct file_list the_fattr_list;
149 static char empty_sum[MAX_DIGEST_LEN];
150 static int flist_count_offset; /* for --delete --progress */
151 static int show_filelist_progress;
152 @@ -280,6 +283,45 @@ static inline int is_excluded(const char *fname, int is_dir, int filter_level)
153 return name_is_excluded(fname, is_dir ? NAME_IS_DIR : NAME_IS_FILE, filter_level);
156 +static int fattr_compare(struct file_struct **file1, struct file_struct **file2)
158 + struct file_struct *f1 = *file1;
159 + struct file_struct *f2 = *file2;
160 + int64 len1 = F_LENGTH(f1), len2 = F_LENGTH(f2);
163 + if (!f1->basename || !S_ISREG(f1->mode) || !len1) {
164 + if (!f2->basename || !S_ISREG(f2->mode) || !len2)
168 + if (!f2->basename || !S_ISREG(f2->mode) || !len2)
171 + /* Don't use diff for values that are longer than an int. */
173 + return len1 < len2 ? -1 : 1;
175 + if (always_checksum) {
176 + diff = u_memcmp(F_SUM(f1), F_SUM(f2), flist_csum_len);
179 + } else if (f1->modtime != f2->modtime)
180 + return f1->modtime < f2->modtime ? -1 : 1;
182 + diff = u_strcmp(f1->basename, f2->basename);
186 + if (f1->dirname == f2->dirname)
192 + return u_strcmp(f1->dirname, f2->dirname);
195 static void send_directory(int f, struct file_list *flist,
196 char *fbuf, int len, int flags);
198 @@ -2722,6 +2764,23 @@ struct file_list *recv_file_list(int f, int dir_ndx)
199 * for a non-relative transfer in recv_file_entry(). */
200 flist_sort_and_clean(flist, relative_paths);
202 + if (detect_renamed) {
203 + int j = flist->used;
204 + the_fattr_list.used = j;
205 + the_fattr_list.files = new_array(struct file_struct *, j);
206 + memcpy(the_fattr_list.files, flist->files,
207 + j * sizeof (struct file_struct *));
208 + qsort(the_fattr_list.files, j,
209 + sizeof the_fattr_list.files[0], (int (*)())fattr_compare);
210 + the_fattr_list.low = 0;
212 + struct file_struct *fp = the_fattr_list.files[j];
213 + if (fp->basename && S_ISREG(fp->mode) && F_LENGTH(fp))
216 + the_fattr_list.high = j;
219 if (protocol_version < 30) {
220 /* Recv the io_error flag */
221 int err = read_int(f);
222 diff --git a/generator.c b/generator.c
225 @@ -81,6 +81,7 @@ extern int always_checksum;
226 extern int flist_csum_len;
227 extern char *partial_dir;
228 extern int alt_dest_type;
229 +extern int detect_renamed;
230 extern int whole_file;
231 extern int list_only;
232 extern int read_batch;
233 @@ -99,11 +100,13 @@ extern char *tmpdir;
234 extern char *basis_dir[MAX_BASIS_DIRS+1];
235 extern struct file_list *cur_flist, *first_flist, *dir_flist;
236 extern filter_rule_list filter_list, daemon_filter_list;
237 +extern struct file_list the_fattr_list;
239 int maybe_ATTRS_REPORT = 0;
240 int maybe_ATTRS_ACCURATE_TIME = 0;
242 static dev_t dev_zero;
243 +static int unexplored_dirs = 1;
244 static int deldelay_size = 0, deldelay_cnt = 0;
245 static char *deldelay_buf = NULL;
246 static int deldelay_fd = -1;
247 @@ -268,14 +271,19 @@ static void do_delayed_deletions(char *delbuf)
248 * all the --delete-WHEN options. Note that the fbuf pointer must point to a
249 * MAXPATHLEN buffer with the name of the directory in it (the functions we
250 * call will append names onto the end, but the old dir value will be restored
252 -static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev)
255 + * Note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
257 +static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev,
260 static int already_warned = 0;
261 static struct hashtable *dev_tbl;
262 struct file_list *dirlist;
263 - char delbuf[MAXPATHLEN];
265 + char *p, delbuf[MAXPATHLEN];
266 + unsigned remainder;
267 + int dlen, i, restore_dot = 0;
270 change_local_filter_dir(NULL, 0, 0);
271 @@ -289,17 +297,22 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev)
272 maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
274 if (io_error & IOERR_GENERAL && !ignore_errors) {
275 - if (already_warned)
276 + if (!already_warned) {
278 + "IO error encountered -- skipping file deletion\n");
279 + already_warned = 1;
281 + if (!detect_renamed)
284 - "IO error encountered -- skipping file deletion\n");
285 - already_warned = 1;
287 + del_flags |= DEL_NO_DELETIONS;
291 change_local_filter_dir(fbuf, dlen, F_DEPTH(file));
293 + if (detect_renamed)
296 if (one_file_system) {
298 dev_tbl = hashtable_create(16, HT_KEY64);
299 @@ -315,6 +328,14 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev)
301 dirlist = get_dirlist(fbuf, dlen, 0);
304 + if (dlen == 1 && *fbuf == '.') {
307 + } else if (dlen != 1 || *fbuf != '/')
309 + remainder = MAXPATHLEN - (p - fbuf);
311 /* If an item in dirlist is not found in flist, delete it
312 * from the filesystem. */
313 for (i = dirlist->used; i--; ) {
314 @@ -327,6 +348,10 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev)
318 + if (detect_renamed && S_ISREG(fp->mode)) {
319 + strlcpy(p, fp->basename, remainder);
320 + look_for_rename(fp, fbuf);
322 /* Here we want to match regardless of file type. Replacement
323 * of a file with one of another type is handled separately by
324 * a delete_item call with a DEL_MAKE_ROOM flag. */
325 @@ -335,14 +360,19 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev)
326 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
327 flags |= DEL_NO_UID_WRITE;
329 - if (delete_during == 2) {
330 - if (!remember_delete(fp, delbuf, flags))
331 + if (delete_during == 2 && !(del_flags & DEL_NO_DELETIONS)) {
332 + if (!remember_delete(fp, delbuf, del_flags | flags))
335 - delete_item(delbuf, fp->mode, flags);
337 + delete_item(delbuf, fp->mode, del_flags | flags);
338 + } else if (detect_renamed && S_ISDIR(fp->mode))
349 @@ -378,14 +408,125 @@ static void do_delete_pass(void)
350 || !S_ISDIR(st.st_mode))
353 - delete_in_dir(fbuf, file, st.st_dev);
354 + delete_in_dir(fbuf, file, st.st_dev, 0);
356 - delete_in_dir(NULL, NULL, dev_zero);
357 + delete_in_dir(NULL, NULL, dev_zero, 0);
359 if (INFO_GTE(FLIST, 2) && !am_server)
360 rprintf(FINFO, " \r");
363 +/* Search for a regular file that matches either (1) the size & modified
364 + * time (plus the basename, if possible) or (2) the size & checksum. If
365 + * we find an exact match down to the dirname, return -1 because we found
366 + * an up-to-date file in the transfer, not a renamed file. */
367 +static int fattr_find(struct file_struct *f, char *fname)
369 + int low = the_fattr_list.low, high = the_fattr_list.high;
370 + int mid, ok_match = -1, good_match = -1;
371 + struct file_struct *fmid;
374 + while (low <= high) {
375 + mid = (low + high) / 2;
376 + fmid = the_fattr_list.files[mid];
377 + if (F_LENGTH(fmid) != F_LENGTH(f)) {
378 + if (F_LENGTH(fmid) < F_LENGTH(f))
384 + if (always_checksum) {
385 + /* We use the FLAG_FILE_SENT flag to indicate when we
386 + * have computed the checksum for an entry. */
387 + if (!(f->flags & FLAG_FILE_SENT)) {
389 + if (fmid->modtime == f->modtime
390 + && f_name_cmp(fmid, f) == 0)
391 + return -1; /* assume we can't help */
392 + st.st_size = F_LENGTH(f);
393 + st.st_mtime = f->modtime;
394 + file_checksum(fname, &st, F_SUM(f));
395 + f->flags |= FLAG_FILE_SENT;
397 + diff = u_memcmp(F_SUM(fmid), F_SUM(f), flist_csum_len);
406 + if (fmid->modtime != f->modtime) {
407 + if (fmid->modtime < f->modtime)
415 + diff = u_strcmp(fmid->basename, f->basename);
418 + if (fmid->dirname == f->dirname)
419 + return -1; /* file is up-to-date */
420 + if (!fmid->dirname) {
428 + diff = u_strcmp(fmid->dirname, f->dirname);
430 + return -1; /* file is up-to-date */
438 + return good_match >= 0 ? good_match : ok_match;
441 +void look_for_rename(struct file_struct *file, char *fname)
443 + struct file_struct *fp;
444 + char *partialptr, *fn;
448 + if (!partial_dir || (ndx = fattr_find(file, fname)) < 0)
451 + fp = the_fattr_list.files[ndx];
452 + fn = f_name(fp, NULL);
453 + /* We don't provide an alternate-basis file if there is a basis file. */
454 + if (link_stat(fn, &st, 0) == 0)
458 + if ((partialptr = partial_dir_fname(fn)) == NULL
459 + || !handle_partial_dir(partialptr, PDIR_CREATE))
461 + /* We only use the file if we can hard-link it into our tmp dir. */
462 + if (link(fname, partialptr) != 0) {
463 + if (errno != EEXIST)
464 + handle_partial_dir(partialptr, PDIR_DELETE);
469 + /* I think this falls into the -vv category with "%s is uptodate", etc. */
470 + if (INFO_GTE(MISC, 2))
471 + rprintf(FINFO, "found renamed: %s => %s\n", fname, fn);
474 static inline int mtime_differs(STRUCT_STAT *stp, struct file_struct *file)
477 @@ -1182,6 +1323,7 @@ static void list_file_entry(struct file_struct *f)
481 +static struct bitbag *delayed_bits = NULL;
482 static int phase = 0;
483 static int dflt_perms;
485 @@ -1318,7 +1460,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
486 && do_stat(dn, &sx.st) < 0) {
488 goto parent_is_dry_missing;
489 - if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
490 + if (make_path(fname, ACCESSPERMS, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
491 rsyserr(FERROR_XFER, errno,
492 "recv_generator: mkdir %s failed",
494 @@ -1471,7 +1613,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
496 if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
497 if (!relative_paths || errno != ENOENT
498 - || make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
499 + || make_path(fname, ACCESSPERMS, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
500 || (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
501 rsyserr(FERROR_XFER, errno,
502 "recv_generator: mkdir %s failed",
503 @@ -1519,9 +1661,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
505 else if (delete_during && f_out != -1 && !phase
506 && !(file->flags & FLAG_MISSING_DIR)) {
507 - if (file->flags & FLAG_CONTENT_DIR)
508 - delete_in_dir(fname, file, real_sx.st.st_dev);
510 + if (file->flags & FLAG_CONTENT_DIR) {
511 + if (detect_renamed && real_ret != 0)
513 + delete_in_dir(fname, file, real_sx.st.st_dev,
514 + delete_during < 0 ? DEL_NO_DELETIONS : 0);
516 change_local_filter_dir(fname, strlen(fname), F_DEPTH(file));
518 prior_dir_file = file;
519 @@ -1789,8 +1934,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
523 - if (stat_errno == ENOENT)
524 + if (stat_errno == ENOENT) {
525 + if (detect_renamed && unexplored_dirs > 0
526 + && F_LENGTH(file)) {
527 + bitbag_set_bit(delayed_bits, ndx);
532 rsyserr(FERROR_XFER, stat_errno, "recv_generator: failed to stat %s",
535 @@ -2256,6 +2407,12 @@ void generate_files(int f_out, const char *local_name)
536 if (DEBUG_GTE(GENR, 1))
537 rprintf(FINFO, "generator starting pid=%d\n", (int)getpid());
539 + if (detect_renamed) {
540 + delayed_bits = bitbag_create(cur_flist->used);
541 + if (!delete_before && !delete_during)
542 + delete_during = -1;
545 if (delete_before && !solo_file && cur_flist->used > 0)
547 if (delete_during == 2) {
548 @@ -2264,7 +2421,7 @@ void generate_files(int f_out, const char *local_name)
550 info_levels[INFO_FLIST] = info_levels[INFO_PROGRESS] = 0;
552 - if (append_mode > 0 || whole_file < 0)
553 + if (append_mode > 0 || detect_renamed || whole_file < 0)
555 if (DEBUG_GTE(FLIST, 1)) {
556 rprintf(FINFO, "delta-transmission %s\n",
557 @@ -2300,7 +2457,7 @@ void generate_files(int f_out, const char *local_name)
558 dirdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
560 dirdev = MAKEDEV(0, 0);
561 - delete_in_dir(fbuf, fp, dirdev);
562 + delete_in_dir(fbuf, fp, dirdev, 0);
564 change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(fp));
566 @@ -2347,7 +2504,21 @@ void generate_files(int f_out, const char *local_name)
567 } while ((cur_flist = cur_flist->next) != NULL);
570 - delete_in_dir(NULL, NULL, dev_zero);
571 + delete_in_dir(NULL, NULL, dev_zero, 0);
572 + if (detect_renamed) {
573 + if (delete_during < 0)
575 + detect_renamed = 0;
577 + for (i = -1; (i = bitbag_next_bit(delayed_bits, i)) >= 0; ) {
578 + struct file_struct *file = cur_flist->files[i];
580 + strlcpy(fbuf, local_name, sizeof fbuf);
582 + f_name(file, fbuf);
583 + recv_generator(fbuf, file, i, itemizing, code, f_out);
587 if (DEBUG_GTE(GENR, 1))
588 rprintf(FINFO, "generate_files phase=%d\n", phase);
589 diff --git a/main.c b/main.c
592 @@ -726,7 +726,7 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
594 if (mkpath_dest_arg && statret < 0 && (cp || file_total > 1)) {
595 int save_errno = errno;
596 - int ret = make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
597 + int ret = make_path(dest_path, ACCESSPERMS, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
600 if (ret && (INFO_GTE(NAME, 1) || stdout_format_has_i)) {
601 diff --git a/options.c b/options.c
604 @@ -91,6 +91,7 @@ int am_server = 0;
606 int am_starting_up = 1;
607 int relative_paths = -1;
608 +int detect_renamed = 0;
609 int implied_dirs = 1;
610 int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
612 @@ -743,6 +744,7 @@ static struct poptOption long_options[] = {
613 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
614 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
615 {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
616 + {"detect-renamed", 0, POPT_ARG_NONE, &detect_renamed, 0, 0, 0 },
617 {"fuzzy", 'y', POPT_ARG_NONE, 0, 'y', 0, 0 },
618 {"no-fuzzy", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
619 {"no-y", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
620 @@ -2400,7 +2402,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
624 - if (delay_updates && !partial_dir)
625 + if ((delay_updates || detect_renamed) && !partial_dir)
626 partial_dir = tmp_partialdir;
629 @@ -2409,6 +2411,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
630 snprintf(err_buf, sizeof err_buf,
631 "--%s cannot be used with --%s\n",
632 append_mode ? "append" : "inplace",
633 + detect_renamed ? "detect-renamed" :
634 delay_updates ? "delay-updates" : "partial-dir");
637 @@ -2835,6 +2838,8 @@ void server_options(char **args, int *argc_p)
638 args[ac++] = "--super";
640 args[ac++] = "--size-only";
641 + if (detect_renamed)
642 + args[ac++] = "--detect-renamed";
644 args[ac++] = "--stats";
646 diff --git a/receiver.c b/receiver.c
649 @@ -220,7 +220,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
650 * information should have been previously transferred, but that may
651 * not be the case with -R */
652 if (fd == -1 && relative_paths && errno == ENOENT
653 - && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
654 + && make_path(fnametmp, ACCESSPERMS, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
655 /* Get back to name with XXXXXX in it. */
656 get_tmpname(fnametmp, fname, False);
657 fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
658 diff --git a/rsync.1.md b/rsync.1.md
661 @@ -507,6 +507,7 @@ has its own detailed description later in this manpage.
662 --modify-window=NUM, -@ set the accuracy for mod-time comparisons
663 --temp-dir=DIR, -T create temporary files in directory DIR
664 --fuzzy, -y find similar file for basis if no dest file
665 +--detect-renamed try to find renamed files to speed the xfer
666 --compare-dest=DIR also compare destination files relative to DIR
667 --copy-dest=DIR ... and include copies of unchanged files
668 --link-dest=DIR hardlink to files in DIR when unchanged
669 @@ -2635,6 +2636,22 @@ expand it.
670 potential fuzzy-match files, so either use [`--delete-after`](#opt) or
671 specify some filename exclusions if you need to prevent this.
673 +0. `--detect-renamed`
675 + With this option, for each new source file (call it `src/S`), rsync looks
676 + for a file `dest/D` anywhere in the destination that passes the quick check
677 + with `src/S`. If such a `dest/D` is found, rsync uses it as an alternate
678 + basis for transferring `S`. The idea is that if `src/S` was renamed from
679 + `src/D` (as opposed to `src/S` passing the quick check with `dest/D` by
680 + coincidence), the delta-transfer algorithm will find that all the data
681 + matches between `src/S` and `dest/D`, and the transfer will be really fast.
683 + By default, alternate-basis files are hard-linked into a directory named
684 + ".~tmp~" in each file's destination directory, but if you've specified the
685 + [`--partial-dir`](#opt) option, that directory will be used instead. These
686 + otential alternate-basis files will be removed as the transfer progresses.
687 + This option conflicts with [`--inplace`](#opt) and [`--append`](#opt).
689 0. `--compare-dest=DIR`
691 This option instructs rsync to use _DIR_ on the destination machine as an
692 diff --git a/rsync.h b/rsync.h
695 @@ -286,7 +286,7 @@ enum filetype {
696 #define NDX_DEL_STATS -3
697 #define NDX_FLIST_OFFSET -101
699 -/* For calling delete_item() and delete_dir_contents(). */
700 +/* For calling delete_item(), delete_dir_contents(), and delete_in_dir(). */
701 #define DEL_NO_UID_WRITE (1<<0) /* file/dir has our uid w/o write perm */
702 #define DEL_RECURSE (1<<1) /* if dir, delete all contents */
703 #define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */
704 @@ -296,6 +296,7 @@ enum filetype {
705 #define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */
706 #define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */
707 #define DEL_FOR_BACKUP (1<<8) /* the delete is for a backup operation */
708 +#define DEL_NO_DELETIONS (1<<9) /* just check for renames w/o deleting */
710 #define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL)
712 diff --git a/util1.c b/util1.c
715 @@ -183,7 +183,7 @@ int set_times(const char *fname, STRUCT_STAT *stp)
716 /* Create any necessary directories in fname. Any missing directories are
717 * created with default permissions. Returns < 0 on error, or the number
718 * of directories created. */
719 -int make_path(char *fname, int flags)
720 +int make_path(char *fname, mode_t mode, int flags)
724 @@ -214,7 +214,7 @@ int make_path(char *fname, int flags)
728 - } else if (do_mkdir(fname, ACCESSPERMS) == 0) {
729 + } else if (do_mkdir(fname, mode) == 0) {
733 @@ -253,7 +253,7 @@ int make_path(char *fname, int flags)
735 if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */
737 - if (do_mkdir(fname, ACCESSPERMS) < 0)
738 + if (do_mkdir(fname, mode) < 0)
742 @@ -1196,6 +1196,32 @@ char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr)
746 +/* We need to supply our own strcmp function for file list comparisons
747 + * to ensure that signed/unsigned usage is consistent between machines. */
748 +int u_strcmp(const char *p1, const char *p2)
750 + for ( ; *p1; p1++, p2++) {
755 + return (int)*(uchar*)p1 - (int)*(uchar*)p2;
758 +/* We need a memcmp function compares unsigned-byte values. */
759 +int u_memcmp(const void *p1, const void *p2, size_t len)
761 + const uchar *u1 = p1;
762 + const uchar *u2 = p2;
766 + return (int)*u1 - (int)*u2;
773 * Return a quoted string with the full pathname of the indicated filename.
774 * The string " (in MODNAME)" may also be appended. The returned pointer
775 @@ -1289,7 +1315,7 @@ int handle_partial_dir(const char *fname, int create)
779 - if (statret < 0 && do_mkdir(dir, 0700) < 0) {
780 + if (statret < 0 && make_path(dir, 0700, 0) < 0) {