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: d4fc18f3755987ccea6e7f43609f862aa0254c5a
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 @@ -38,6 +38,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 @@ -156,6 +157,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 && !local_server
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 @@ -63,6 +63,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 @@ -129,6 +130,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 @@ -275,6 +278,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 @@ -2635,6 +2677,25 @@ 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 + if (!the_fattr_list.files)
207 + out_of_memory("recv_file_list");
208 + memcpy(the_fattr_list.files, flist->files,
209 + j * sizeof (struct file_struct *));
210 + qsort(the_fattr_list.files, j,
211 + sizeof the_fattr_list.files[0], (int (*)())fattr_compare);
212 + the_fattr_list.low = 0;
214 + struct file_struct *fp = the_fattr_list.files[j];
215 + if (fp->basename && S_ISREG(fp->mode) && F_LENGTH(fp))
218 + the_fattr_list.high = j;
221 if (protocol_version < 30) {
222 /* Recv the io_error flag */
223 int err = read_int(f);
224 diff --git a/generator.c b/generator.c
227 @@ -80,6 +80,7 @@ extern char *partial_dir;
228 extern int compare_dest;
229 extern int copy_dest;
230 extern int link_dest;
231 +extern int detect_renamed;
232 extern int whole_file;
233 extern int list_only;
234 extern int read_batch;
235 @@ -98,11 +99,13 @@ extern char *tmpdir;
236 extern char *basis_dir[MAX_BASIS_DIRS+1];
237 extern struct file_list *cur_flist, *first_flist, *dir_flist;
238 extern filter_rule_list filter_list, daemon_filter_list;
239 +extern struct file_list the_fattr_list;
241 int maybe_ATTRS_REPORT = 0;
242 int maybe_ATTRS_SET_NANO = 0;
244 static dev_t dev_zero;
245 +static int unexplored_dirs = 1;
246 static int deldelay_size = 0, deldelay_cnt = 0;
247 static char *deldelay_buf = NULL;
248 static int deldelay_fd = -1;
249 @@ -273,13 +276,18 @@ static void do_delayed_deletions(char *delbuf)
250 * all the --delete-WHEN options. Note that the fbuf pointer must point to a
251 * MAXPATHLEN buffer with the name of the directory in it (the functions we
252 * call will append names onto the end, but the old dir value will be restored
254 -static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
257 + * Note: --detect-rename may use this routine with DEL_NO_DELETIONS set!
259 +static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev,
262 static int already_warned = 0;
263 struct file_list *dirlist;
264 - char delbuf[MAXPATHLEN];
266 + char *p, delbuf[MAXPATHLEN];
267 + unsigned remainder;
268 + int dlen, i, restore_dot = 0;
271 change_local_filter_dir(NULL, 0, 0);
272 @@ -293,17 +301,22 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
273 maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
275 if (io_error & IOERR_GENERAL && !ignore_errors) {
276 - if (already_warned)
277 + if (!already_warned) {
279 + "IO error encountered -- skipping file deletion\n");
280 + already_warned = 1;
282 + if (!detect_renamed)
285 - "IO error encountered -- skipping file deletion\n");
286 - already_warned = 1;
288 + del_flags |= DEL_NO_DELETIONS;
292 change_local_filter_dir(fbuf, dlen, F_DEPTH(file));
294 + if (detect_renamed)
297 if (one_file_system) {
298 if (file->flags & FLAG_TOP_DIR)
299 filesystem_dev = *fs_dev;
300 @@ -313,6 +326,14 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
302 dirlist = get_dirlist(fbuf, dlen, 0);
305 + if (dlen == 1 && *fbuf == '.') {
308 + } else if (dlen != 1 || *fbuf != '/')
310 + remainder = MAXPATHLEN - (p - fbuf);
312 /* If an item in dirlist is not found in flist, delete it
313 * from the filesystem. */
314 for (i = dirlist->used; i--; ) {
315 @@ -325,6 +346,10 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
319 + if (detect_renamed && S_ISREG(fp->mode)) {
320 + strlcpy(p, fp->basename, remainder);
321 + look_for_rename(fp, fbuf);
323 /* Here we want to match regardless of file type. Replacement
324 * of a file with one of another type is handled separately by
325 * a delete_item call with a DEL_MAKE_ROOM flag. */
326 @@ -333,14 +358,19 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
327 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
328 flags |= DEL_NO_UID_WRITE;
330 - if (delete_during == 2) {
331 - if (!remember_delete(fp, delbuf, flags))
332 + if (delete_during == 2 && !(del_flags & DEL_NO_DELETIONS)) {
333 + if (!remember_delete(fp, delbuf, del_flags | flags))
336 - delete_item(delbuf, fp->mode, flags);
338 + delete_item(delbuf, fp->mode, del_flags | flags);
339 + } else if (detect_renamed && S_ISDIR(fp->mode))
350 @@ -376,14 +406,125 @@ static void do_delete_pass(void)
351 || !S_ISDIR(st.st_mode))
354 - delete_in_dir(fbuf, file, &st.st_dev);
355 + delete_in_dir(fbuf, file, &st.st_dev, 0);
357 - delete_in_dir(NULL, NULL, &dev_zero);
358 + delete_in_dir(NULL, NULL, &dev_zero, 0);
360 if (INFO_GTE(FLIST, 2) && !am_server)
361 rprintf(FINFO, " \r");
364 +/* Search for a regular file that matches either (1) the size & modified
365 + * time (plus the basename, if possible) or (2) the size & checksum. If
366 + * we find an exact match down to the dirname, return -1 because we found
367 + * an up-to-date file in the transfer, not a renamed file. */
368 +static int fattr_find(struct file_struct *f, char *fname)
370 + int low = the_fattr_list.low, high = the_fattr_list.high;
371 + int mid, ok_match = -1, good_match = -1;
372 + struct file_struct *fmid;
375 + while (low <= high) {
376 + mid = (low + high) / 2;
377 + fmid = the_fattr_list.files[mid];
378 + if (F_LENGTH(fmid) != F_LENGTH(f)) {
379 + if (F_LENGTH(fmid) < F_LENGTH(f))
385 + if (always_checksum) {
386 + /* We use the FLAG_FILE_SENT flag to indicate when we
387 + * have computed the checksum for an entry. */
388 + if (!(f->flags & FLAG_FILE_SENT)) {
390 + if (fmid->modtime == f->modtime
391 + && f_name_cmp(fmid, f) == 0)
392 + return -1; /* assume we can't help */
393 + st.st_size = F_LENGTH(f);
394 + st.st_mtime = f->modtime;
395 + file_checksum(fname, &st, F_SUM(f));
396 + f->flags |= FLAG_FILE_SENT;
398 + diff = u_memcmp(F_SUM(fmid), F_SUM(f), flist_csum_len);
407 + if (fmid->modtime != f->modtime) {
408 + if (fmid->modtime < f->modtime)
416 + diff = u_strcmp(fmid->basename, f->basename);
419 + if (fmid->dirname == f->dirname)
420 + return -1; /* file is up-to-date */
421 + if (!fmid->dirname) {
429 + diff = u_strcmp(fmid->dirname, f->dirname);
431 + return -1; /* file is up-to-date */
439 + return good_match >= 0 ? good_match : ok_match;
442 +void look_for_rename(struct file_struct *file, char *fname)
444 + struct file_struct *fp;
445 + char *partialptr, *fn;
449 + if (!partial_dir || (ndx = fattr_find(file, fname)) < 0)
452 + fp = the_fattr_list.files[ndx];
453 + fn = f_name(fp, NULL);
454 + /* We don't provide an alternate-basis file if there is a basis file. */
455 + if (link_stat(fn, &st, 0) == 0)
459 + if ((partialptr = partial_dir_fname(fn)) == NULL
460 + || !handle_partial_dir(partialptr, PDIR_CREATE))
462 + /* We only use the file if we can hard-link it into our tmp dir. */
463 + if (link(fname, partialptr) != 0) {
464 + if (errno != EEXIST)
465 + handle_partial_dir(partialptr, PDIR_DELETE);
470 + /* I think this falls into the -vv category with "%s is uptodate", etc. */
471 + if (INFO_GTE(MISC, 2))
472 + rprintf(FINFO, "found renamed: %s => %s\n", fname, fn);
475 static inline int time_diff(STRUCT_STAT *stp, struct file_struct *file)
478 @@ -1162,6 +1303,7 @@ static void list_file_entry(struct file_struct *f)
482 +static struct bitbag *delayed_bits = NULL;
483 static int phase = 0;
484 static int dflt_perms;
486 @@ -1292,7 +1434,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
487 && do_stat(dn, &sx.st) < 0) {
489 goto parent_is_dry_missing;
490 - if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
491 + if (make_path(fname, ACCESSPERMS, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
492 rsyserr(FERROR_XFER, errno,
493 "recv_generator: mkdir %s failed",
495 @@ -1430,7 +1572,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
497 if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
498 if (!relative_paths || errno != ENOENT
499 - || make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
500 + || make_path(fname, ACCESSPERMS, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
501 || (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
502 rsyserr(FERROR_XFER, errno,
503 "recv_generator: mkdir %s failed",
504 @@ -1479,9 +1621,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
506 else if (delete_during && f_out != -1 && !phase
507 && !(file->flags & FLAG_MISSING_DIR)) {
508 - if (file->flags & FLAG_CONTENT_DIR)
509 - delete_in_dir(fname, file, &real_sx.st.st_dev);
511 + if (file->flags & FLAG_CONTENT_DIR) {
512 + if (detect_renamed && real_ret != 0)
514 + delete_in_dir(fname, file, &real_sx.st.st_dev,
515 + delete_during < 0 ? DEL_NO_DELETIONS : 0);
517 change_local_filter_dir(fname, strlen(fname), F_DEPTH(file));
519 prior_dir_file = file;
520 @@ -1762,8 +1907,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
524 - if (stat_errno == ENOENT)
525 + if (stat_errno == ENOENT) {
526 + if (detect_renamed && unexplored_dirs > 0
527 + && F_LENGTH(file)) {
528 + bitbag_set_bit(delayed_bits, ndx);
533 rsyserr(FERROR_XFER, stat_errno, "recv_generator: failed to stat %s",
536 @@ -2227,6 +2378,12 @@ void generate_files(int f_out, const char *local_name)
537 if (DEBUG_GTE(GENR, 1))
538 rprintf(FINFO, "generator starting pid=%d\n", (int)getpid());
540 + if (detect_renamed) {
541 + delayed_bits = bitbag_create(cur_flist->used);
542 + if (!delete_before && !delete_during)
543 + delete_during = -1;
546 if (delete_before && !solo_file && cur_flist->used > 0)
548 if (delete_during == 2) {
549 @@ -2237,7 +2394,7 @@ void generate_files(int f_out, const char *local_name)
551 info_levels[INFO_FLIST] = info_levels[INFO_PROGRESS] = 0;
553 - if (append_mode > 0 || whole_file < 0)
554 + if (append_mode > 0 || detect_renamed || whole_file < 0)
556 if (DEBUG_GTE(FLIST, 1)) {
557 rprintf(FINFO, "delta-transmission %s\n",
558 @@ -2273,7 +2430,7 @@ void generate_files(int f_out, const char *local_name)
559 dirdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
561 dirdev = MAKEDEV(0, 0);
562 - delete_in_dir(fbuf, fp, &dirdev);
563 + delete_in_dir(fbuf, fp, &dirdev, 0);
565 change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(fp));
567 @@ -2320,7 +2477,21 @@ void generate_files(int f_out, const char *local_name)
568 } while ((cur_flist = cur_flist->next) != NULL);
571 - delete_in_dir(NULL, NULL, &dev_zero);
572 + delete_in_dir(NULL, NULL, &dev_zero, 0);
573 + if (detect_renamed) {
574 + if (delete_during < 0)
576 + detect_renamed = 0;
578 + for (i = -1; (i = bitbag_next_bit(delayed_bits, i)) >= 0; ) {
579 + struct file_struct *file = cur_flist->files[i];
581 + strlcpy(fbuf, local_name, sizeof fbuf);
583 + f_name(file, fbuf);
584 + recv_generator(fbuf, file, i, itemizing, code, f_out);
588 if (DEBUG_GTE(GENR, 1))
589 rprintf(FINFO, "generate_files phase=%d\n", phase);
590 diff --git a/options.c b/options.c
593 @@ -86,6 +86,7 @@ int am_server = 0;
595 int am_starting_up = 1;
596 int relative_paths = -1;
597 +int detect_renamed = 0;
598 int implied_dirs = 1;
599 int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
601 @@ -894,6 +895,7 @@ static struct poptOption long_options[] = {
602 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
603 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
604 {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
605 + {"detect-renamed", 0, POPT_ARG_NONE, &detect_renamed, 0, 0, 0 },
606 {"fuzzy", 'y', POPT_ARG_NONE, 0, 'y', 0, 0 },
607 {"no-fuzzy", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
608 {"no-y", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
609 @@ -2289,7 +2291,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
613 - if (delay_updates && !partial_dir)
614 + if ((delay_updates || detect_renamed) && !partial_dir)
615 partial_dir = tmp_partialdir;
618 @@ -2298,6 +2300,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
619 snprintf(err_buf, sizeof err_buf,
620 "--%s cannot be used with --%s\n",
621 append_mode ? "append" : "inplace",
622 + detect_renamed ? "detect-renamed" :
623 delay_updates ? "delay-updates" : "partial-dir");
626 @@ -2700,6 +2703,8 @@ void server_options(char **args, int *argc_p)
627 args[ac++] = "--super";
629 args[ac++] = "--size-only";
630 + if (detect_renamed)
631 + args[ac++] = "--detect-renamed";
633 args[ac++] = "--stats";
635 diff --git a/receiver.c b/receiver.c
638 @@ -217,7 +217,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
639 * information should have been previously transferred, but that may
640 * not be the case with -R */
641 if (fd == -1 && relative_paths && errno == ENOENT
642 - && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
643 + && make_path(fnametmp, ACCESSPERMS, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
644 /* Get back to name with XXXXXX in it. */
645 get_tmpname(fnametmp, fname, False);
646 fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
647 diff --git a/rsync.1.md b/rsync.1.md
650 @@ -417,6 +417,7 @@ detailed description below for a complete description.
651 --modify-window=NUM, -@ set the accuracy for mod-time comparisons
652 --temp-dir=DIR, -T create temporary files in directory DIR
653 --fuzzy, -y find similar file for basis if no dest file
654 +--detect-renamed try to find renamed files to speed the xfer
655 --compare-dest=DIR also compare destination files relative to DIR
656 --copy-dest=DIR ... and include copies of unchanged files
657 --link-dest=DIR hardlink to files in DIR when unchanged
658 @@ -2157,6 +2158,22 @@ your home directory (remove the '=' for that).
659 fuzzy-match files, so either use `--delete-after` or specify some filename
660 exclusions if you need to prevent this.
662 +0. `--detect-renamed`
664 + With this option, for each new source file (call it `src/S`), rsync looks
665 + for a file `dest/D` anywhere in the destination that passes the quick check
666 + with `src/S`. If such a `dest/D` is found, rsync uses it as an alternate
667 + basis for transferring `S`. The idea is that if `src/S` was renamed from
668 + `src/D` (as opposed to `src/S` passing the quick check with `dest/D` by
669 + coincidence), the delta-transfer algorithm will find that all the data
670 + matches between `src/S` and `dest/D`, and the transfer will be really fast.
672 + By default, alternate-basis files are hard-linked into a directory named
673 + ".~tmp~" in each file's destination directory, but if you've specified the
674 + `--partial-dir` option, that directory will be used instead. These
675 + otential alternate-basis files will be removed as the transfer progresses.
676 + This option conflicts with `--inplace` and `--append`.
678 0. `--compare-dest=DIR`
680 This option instructs rsync to use _DIR_ on the destination machine as an
681 diff --git a/rsync.h b/rsync.h
684 @@ -265,7 +265,7 @@ enum msgcode {
685 #define NDX_DEL_STATS -3
686 #define NDX_FLIST_OFFSET -101
688 -/* For calling delete_item() and delete_dir_contents(). */
689 +/* For calling delete_item(), delete_dir_contents(), and delete_in_dir(). */
690 #define DEL_NO_UID_WRITE (1<<0) /* file/dir has our uid w/o write perm */
691 #define DEL_RECURSE (1<<1) /* if dir, delete all contents */
692 #define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */
693 @@ -275,6 +275,7 @@ enum msgcode {
694 #define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */
695 #define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */
696 #define DEL_FOR_BACKUP (1<<8) /* the delete is for a backup operation */
697 +#define DEL_NO_DELETIONS (1<<9) /* just check for renames w/o deleting */
699 #define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL)
701 diff --git a/util.c b/util.c
704 @@ -182,7 +182,7 @@ int set_times(const char *fname, STRUCT_STAT *stp)
705 /* Create any necessary directories in fname. Any missing directories are
706 * created with default permissions. Returns < 0 on error, or the number
707 * of directories created. */
708 -int make_path(char *fname, int flags)
709 +int make_path(char *fname, mode_t mode, int flags)
713 @@ -213,7 +213,7 @@ int make_path(char *fname, int flags)
717 - } else if (do_mkdir(fname, ACCESSPERMS) == 0) {
718 + } else if (do_mkdir(fname, mode) == 0) {
722 @@ -252,7 +252,7 @@ int make_path(char *fname, int flags)
724 if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */
726 - if (do_mkdir(fname, ACCESSPERMS) < 0)
727 + if (do_mkdir(fname, mode) < 0)
731 @@ -1174,6 +1174,32 @@ char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr)
735 +/* We need to supply our own strcmp function for file list comparisons
736 + * to ensure that signed/unsigned usage is consistent between machines. */
737 +int u_strcmp(const char *p1, const char *p2)
739 + for ( ; *p1; p1++, p2++) {
744 + return (int)*(uchar*)p1 - (int)*(uchar*)p2;
747 +/* We need a memcmp function compares unsigned-byte values. */
748 +int u_memcmp(const void *p1, const void *p2, size_t len)
750 + const uchar *u1 = p1;
751 + const uchar *u2 = p2;
755 + return (int)*u1 - (int)*u2;
762 * Return a quoted string with the full pathname of the indicated filename.
763 * The string " (in MODNAME)" may also be appended. The returned pointer
764 @@ -1267,7 +1293,7 @@ int handle_partial_dir(const char *fname, int create)
768 - if (statret < 0 && do_mkdir(dir, 0700) < 0) {
769 + if (statret < 0 && make_path(dir, 0700, 0) < 0) {