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: 7fb08531e0ee267e4a41e209be4cb5a24d461a2d
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 @@ -155,6 +156,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 @@ -79,6 +79,7 @@ extern int always_checksum;
228 extern int flist_csum_len;
229 extern char *partial_dir;
230 extern int alt_dest_type;
231 +extern int detect_renamed;
232 extern int whole_file;
233 extern int list_only;
234 extern int read_batch;
235 @@ -97,11 +98,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_ACCURATE_TIME = 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 @@ -269,14 +272,19 @@ 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 static struct hashtable *dev_tbl;
264 struct file_list *dirlist;
265 - char delbuf[MAXPATHLEN];
267 + char *p, delbuf[MAXPATHLEN];
268 + unsigned remainder;
269 + int dlen, i, restore_dot = 0;
272 change_local_filter_dir(NULL, 0, 0);
273 @@ -290,17 +298,22 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
274 maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
276 if (io_error & IOERR_GENERAL && !ignore_errors) {
277 - if (already_warned)
278 + if (!already_warned) {
280 + "IO error encountered -- skipping file deletion\n");
281 + already_warned = 1;
283 + if (!detect_renamed)
286 - "IO error encountered -- skipping file deletion\n");
287 - already_warned = 1;
289 + del_flags |= DEL_NO_DELETIONS;
293 change_local_filter_dir(fbuf, dlen, F_DEPTH(file));
295 + if (detect_renamed)
298 if (one_file_system) {
300 dev_tbl = hashtable_create(16, HT_KEY64);
301 @@ -316,6 +329,14 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
303 dirlist = get_dirlist(fbuf, dlen, 0);
306 + if (dlen == 1 && *fbuf == '.') {
309 + } else if (dlen != 1 || *fbuf != '/')
311 + remainder = MAXPATHLEN - (p - fbuf);
313 /* If an item in dirlist is not found in flist, delete it
314 * from the filesystem. */
315 for (i = dirlist->used; i--; ) {
316 @@ -328,6 +349,10 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
320 + if (detect_renamed && S_ISREG(fp->mode)) {
321 + strlcpy(p, fp->basename, remainder);
322 + look_for_rename(fp, fbuf);
324 /* Here we want to match regardless of file type. Replacement
325 * of a file with one of another type is handled separately by
326 * a delete_item call with a DEL_MAKE_ROOM flag. */
327 @@ -336,14 +361,19 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
328 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
329 flags |= DEL_NO_UID_WRITE;
331 - if (delete_during == 2) {
332 - if (!remember_delete(fp, delbuf, flags))
333 + if (delete_during == 2 && !(del_flags & DEL_NO_DELETIONS)) {
334 + if (!remember_delete(fp, delbuf, del_flags | flags))
337 - delete_item(delbuf, fp->mode, flags);
339 + delete_item(delbuf, fp->mode, del_flags | flags);
340 + } else if (detect_renamed && S_ISDIR(fp->mode))
351 @@ -379,14 +409,125 @@ static void do_delete_pass(void)
352 || !S_ISDIR(st.st_mode))
355 - delete_in_dir(fbuf, file, &st.st_dev);
356 + delete_in_dir(fbuf, file, &st.st_dev, 0);
358 - delete_in_dir(NULL, NULL, &dev_zero);
359 + delete_in_dir(NULL, NULL, &dev_zero, 0);
361 if (INFO_GTE(FLIST, 2) && !am_server)
362 rprintf(FINFO, " \r");
365 +/* Search for a regular file that matches either (1) the size & modified
366 + * time (plus the basename, if possible) or (2) the size & checksum. If
367 + * we find an exact match down to the dirname, return -1 because we found
368 + * an up-to-date file in the transfer, not a renamed file. */
369 +static int fattr_find(struct file_struct *f, char *fname)
371 + int low = the_fattr_list.low, high = the_fattr_list.high;
372 + int mid, ok_match = -1, good_match = -1;
373 + struct file_struct *fmid;
376 + while (low <= high) {
377 + mid = (low + high) / 2;
378 + fmid = the_fattr_list.files[mid];
379 + if (F_LENGTH(fmid) != F_LENGTH(f)) {
380 + if (F_LENGTH(fmid) < F_LENGTH(f))
386 + if (always_checksum) {
387 + /* We use the FLAG_FILE_SENT flag to indicate when we
388 + * have computed the checksum for an entry. */
389 + if (!(f->flags & FLAG_FILE_SENT)) {
391 + if (fmid->modtime == f->modtime
392 + && f_name_cmp(fmid, f) == 0)
393 + return -1; /* assume we can't help */
394 + st.st_size = F_LENGTH(f);
395 + st.st_mtime = f->modtime;
396 + file_checksum(fname, &st, F_SUM(f));
397 + f->flags |= FLAG_FILE_SENT;
399 + diff = u_memcmp(F_SUM(fmid), F_SUM(f), flist_csum_len);
408 + if (fmid->modtime != f->modtime) {
409 + if (fmid->modtime < f->modtime)
417 + diff = u_strcmp(fmid->basename, f->basename);
420 + if (fmid->dirname == f->dirname)
421 + return -1; /* file is up-to-date */
422 + if (!fmid->dirname) {
430 + diff = u_strcmp(fmid->dirname, f->dirname);
432 + return -1; /* file is up-to-date */
440 + return good_match >= 0 ? good_match : ok_match;
443 +void look_for_rename(struct file_struct *file, char *fname)
445 + struct file_struct *fp;
446 + char *partialptr, *fn;
450 + if (!partial_dir || (ndx = fattr_find(file, fname)) < 0)
453 + fp = the_fattr_list.files[ndx];
454 + fn = f_name(fp, NULL);
455 + /* We don't provide an alternate-basis file if there is a basis file. */
456 + if (link_stat(fn, &st, 0) == 0)
460 + if ((partialptr = partial_dir_fname(fn)) == NULL
461 + || !handle_partial_dir(partialptr, PDIR_CREATE))
463 + /* We only use the file if we can hard-link it into our tmp dir. */
464 + if (link(fname, partialptr) != 0) {
465 + if (errno != EEXIST)
466 + handle_partial_dir(partialptr, PDIR_DELETE);
471 + /* I think this falls into the -vv category with "%s is uptodate", etc. */
472 + if (INFO_GTE(MISC, 2))
473 + rprintf(FINFO, "found renamed: %s => %s\n", fname, fn);
476 static inline int mtime_differs(STRUCT_STAT *stp, struct file_struct *file)
479 @@ -1164,6 +1305,7 @@ static void list_file_entry(struct file_struct *f)
483 +static struct bitbag *delayed_bits = NULL;
484 static int phase = 0;
485 static int dflt_perms;
487 @@ -1294,7 +1436,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
488 && do_stat(dn, &sx.st) < 0) {
490 goto parent_is_dry_missing;
491 - if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
492 + if (make_path(fname, ACCESSPERMS, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
493 rsyserr(FERROR_XFER, errno,
494 "recv_generator: mkdir %s failed",
496 @@ -1430,7 +1572,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
498 if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
499 if (!relative_paths || errno != ENOENT
500 - || make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
501 + || make_path(fname, ACCESSPERMS, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
502 || (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
503 rsyserr(FERROR_XFER, errno,
504 "recv_generator: mkdir %s failed",
505 @@ -1478,9 +1620,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
507 else if (delete_during && f_out != -1 && !phase
508 && !(file->flags & FLAG_MISSING_DIR)) {
509 - if (file->flags & FLAG_CONTENT_DIR)
510 - delete_in_dir(fname, file, &real_sx.st.st_dev);
512 + if (file->flags & FLAG_CONTENT_DIR) {
513 + if (detect_renamed && real_ret != 0)
515 + delete_in_dir(fname, file, &real_sx.st.st_dev,
516 + delete_during < 0 ? DEL_NO_DELETIONS : 0);
518 change_local_filter_dir(fname, strlen(fname), F_DEPTH(file));
520 prior_dir_file = file;
521 @@ -1757,8 +1902,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
525 - if (stat_errno == ENOENT)
526 + if (stat_errno == ENOENT) {
527 + if (detect_renamed && unexplored_dirs > 0
528 + && F_LENGTH(file)) {
529 + bitbag_set_bit(delayed_bits, ndx);
534 rsyserr(FERROR_XFER, stat_errno, "recv_generator: failed to stat %s",
537 @@ -2222,6 +2373,12 @@ void generate_files(int f_out, const char *local_name)
538 if (DEBUG_GTE(GENR, 1))
539 rprintf(FINFO, "generator starting pid=%d\n", (int)getpid());
541 + if (detect_renamed) {
542 + delayed_bits = bitbag_create(cur_flist->used);
543 + if (!delete_before && !delete_during)
544 + delete_during = -1;
547 if (delete_before && !solo_file && cur_flist->used > 0)
549 if (delete_during == 2) {
550 @@ -2232,7 +2389,7 @@ void generate_files(int f_out, const char *local_name)
552 info_levels[INFO_FLIST] = info_levels[INFO_PROGRESS] = 0;
554 - if (append_mode > 0 || whole_file < 0)
555 + if (append_mode > 0 || detect_renamed || whole_file < 0)
557 if (DEBUG_GTE(FLIST, 1)) {
558 rprintf(FINFO, "delta-transmission %s\n",
559 @@ -2268,7 +2425,7 @@ void generate_files(int f_out, const char *local_name)
560 dirdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
562 dirdev = MAKEDEV(0, 0);
563 - delete_in_dir(fbuf, fp, &dirdev);
564 + delete_in_dir(fbuf, fp, &dirdev, 0);
566 change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(fp));
568 @@ -2315,7 +2472,21 @@ void generate_files(int f_out, const char *local_name)
569 } while ((cur_flist = cur_flist->next) != NULL);
572 - delete_in_dir(NULL, NULL, &dev_zero);
573 + delete_in_dir(NULL, NULL, &dev_zero, 0);
574 + if (detect_renamed) {
575 + if (delete_during < 0)
577 + detect_renamed = 0;
579 + for (i = -1; (i = bitbag_next_bit(delayed_bits, i)) >= 0; ) {
580 + struct file_struct *file = cur_flist->files[i];
582 + strlcpy(fbuf, local_name, sizeof fbuf);
584 + f_name(file, fbuf);
585 + recv_generator(fbuf, file, i, itemizing, code, f_out);
589 if (DEBUG_GTE(GENR, 1))
590 rprintf(FINFO, "generate_files phase=%d\n", phase);
591 diff --git a/options.c b/options.c
594 @@ -86,6 +86,7 @@ int am_server = 0;
596 int am_starting_up = 1;
597 int relative_paths = -1;
598 +int detect_renamed = 0;
599 int implied_dirs = 1;
600 int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
602 @@ -933,6 +934,7 @@ static struct poptOption long_options[] = {
603 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
604 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
605 {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
606 + {"detect-renamed", 0, POPT_ARG_NONE, &detect_renamed, 0, 0, 0 },
607 {"fuzzy", 'y', POPT_ARG_NONE, 0, 'y', 0, 0 },
608 {"no-fuzzy", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
609 {"no-y", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
610 @@ -2347,7 +2349,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
614 - if (delay_updates && !partial_dir)
615 + if ((delay_updates || detect_renamed) && !partial_dir)
616 partial_dir = tmp_partialdir;
619 @@ -2356,6 +2358,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
620 snprintf(err_buf, sizeof err_buf,
621 "--%s cannot be used with --%s\n",
622 append_mode ? "append" : "inplace",
623 + detect_renamed ? "detect-renamed" :
624 delay_updates ? "delay-updates" : "partial-dir");
627 @@ -2758,6 +2761,8 @@ void server_options(char **args, int *argc_p)
628 args[ac++] = "--super";
630 args[ac++] = "--size-only";
631 + if (detect_renamed)
632 + args[ac++] = "--detect-renamed";
634 args[ac++] = "--stats";
636 diff --git a/receiver.c b/receiver.c
639 @@ -217,7 +217,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
640 * information should have been previously transferred, but that may
641 * not be the case with -R */
642 if (fd == -1 && relative_paths && errno == ENOENT
643 - && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
644 + && make_path(fnametmp, ACCESSPERMS, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
645 /* Get back to name with XXXXXX in it. */
646 get_tmpname(fnametmp, fname, False);
647 fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
648 diff --git a/rsync.1.md b/rsync.1.md
651 @@ -418,6 +418,7 @@ detailed description below for a complete description.
652 --modify-window=NUM, -@ set the accuracy for mod-time comparisons
653 --temp-dir=DIR, -T create temporary files in directory DIR
654 --fuzzy, -y find similar file for basis if no dest file
655 +--detect-renamed try to find renamed files to speed the xfer
656 --compare-dest=DIR also compare destination files relative to DIR
657 --copy-dest=DIR ... and include copies of unchanged files
658 --link-dest=DIR hardlink to files in DIR when unchanged
659 @@ -2169,6 +2170,22 @@ your home directory (remove the '=' for that).
660 fuzzy-match files, so either use `--delete-after` or specify some filename
661 exclusions if you need to prevent this.
663 +0. `--detect-renamed`
665 + With this option, for each new source file (call it `src/S`), rsync looks
666 + for a file `dest/D` anywhere in the destination that passes the quick check
667 + with `src/S`. If such a `dest/D` is found, rsync uses it as an alternate
668 + basis for transferring `S`. The idea is that if `src/S` was renamed from
669 + `src/D` (as opposed to `src/S` passing the quick check with `dest/D` by
670 + coincidence), the delta-transfer algorithm will find that all the data
671 + matches between `src/S` and `dest/D`, and the transfer will be really fast.
673 + By default, alternate-basis files are hard-linked into a directory named
674 + ".~tmp~" in each file's destination directory, but if you've specified the
675 + `--partial-dir` option, that directory will be used instead. These
676 + otential alternate-basis files will be removed as the transfer progresses.
677 + This option conflicts with `--inplace` and `--append`.
679 0. `--compare-dest=DIR`
681 This option instructs rsync to use _DIR_ on the destination machine as an
682 diff --git a/rsync.h b/rsync.h
685 @@ -269,7 +269,7 @@ enum msgcode {
686 #define NDX_DEL_STATS -3
687 #define NDX_FLIST_OFFSET -101
689 -/* For calling delete_item() and delete_dir_contents(). */
690 +/* For calling delete_item(), delete_dir_contents(), and delete_in_dir(). */
691 #define DEL_NO_UID_WRITE (1<<0) /* file/dir has our uid w/o write perm */
692 #define DEL_RECURSE (1<<1) /* if dir, delete all contents */
693 #define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */
694 @@ -279,6 +279,7 @@ enum msgcode {
695 #define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */
696 #define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */
697 #define DEL_FOR_BACKUP (1<<8) /* the delete is for a backup operation */
698 +#define DEL_NO_DELETIONS (1<<9) /* just check for renames w/o deleting */
700 #define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL)
702 diff --git a/util.c b/util.c
705 @@ -182,7 +182,7 @@ int set_times(const char *fname, STRUCT_STAT *stp)
706 /* Create any necessary directories in fname. Any missing directories are
707 * created with default permissions. Returns < 0 on error, or the number
708 * of directories created. */
709 -int make_path(char *fname, int flags)
710 +int make_path(char *fname, mode_t mode, int flags)
714 @@ -213,7 +213,7 @@ int make_path(char *fname, int flags)
718 - } else if (do_mkdir(fname, ACCESSPERMS) == 0) {
719 + } else if (do_mkdir(fname, mode) == 0) {
723 @@ -252,7 +252,7 @@ int make_path(char *fname, int flags)
725 if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */
727 - if (do_mkdir(fname, ACCESSPERMS) < 0)
728 + if (do_mkdir(fname, mode) < 0)
732 @@ -1171,6 +1171,32 @@ char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr)
736 +/* We need to supply our own strcmp function for file list comparisons
737 + * to ensure that signed/unsigned usage is consistent between machines. */
738 +int u_strcmp(const char *p1, const char *p2)
740 + for ( ; *p1; p1++, p2++) {
745 + return (int)*(uchar*)p1 - (int)*(uchar*)p2;
748 +/* We need a memcmp function compares unsigned-byte values. */
749 +int u_memcmp(const void *p1, const void *p2, size_t len)
751 + const uchar *u1 = p1;
752 + const uchar *u2 = p2;
756 + return (int)*u1 - (int)*u2;
763 * Return a quoted string with the full pathname of the indicated filename.
764 * The string " (in MODNAME)" may also be appended. The returned pointer
765 @@ -1264,7 +1290,7 @@ int handle_partial_dir(const char *fname, int create)
769 - if (statret < 0 && do_mkdir(dir, 0700) < 0) {
770 + if (statret < 0 && make_path(dir, 0700, 0) < 0) {