Fixed a build problem.
[rsync/rsync-patches.git] / link-by-hash.diff
CommitLineData
27e96866 1After applying this patch, run these commands for a successful build:
8a529471 2
27e96866
WD
3 ./prepare-source
4 ./configure (optional if already run)
5 make
8a529471
WD
6
7Jason M. Felice writes:
2eb075b2
WD
8
9This patch adds the --link-by-hash=DIR option, which hard links received
10files in a link farm arranged by MD4 file hash. The result is that the system
11will only store one copy of the unique contents of each file, regardless of
12the file's name.
13
2eb075b2 14
9a7eef96
WD
15--- old/Makefile.in
16+++ new/Makefile.in
ff55cce0 17@@ -34,7 +34,7 @@ OBJS1=rsync.o generator.o receiver.o cle
8a529471
WD
18 main.o checksum.o match.o syscall.o log.o backup.o
19 OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
610969d1 20 fileio.o batch.o clientname.o chmod.o
8a529471
WD
21-OBJS3=progress.o pipe.o
22+OBJS3=progress.o pipe.o hashlink.o
23 DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
24 popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
25 popt/popthelp.o popt/poptparse.o
9a7eef96
WD
26--- old/hashlink.c
27+++ new/hashlink.c
47841496 28@@ -0,0 +1,340 @@
c57f4101
WD
29+/*
30+ Copyright (C) Cronosys, LLC 2004
31+
32+ This program is free software; you can redistribute it and/or modify
33+ it under the terms of the GNU General Public License as published by
34+ the Free Software Foundation; either version 2 of the License, or
35+ (at your option) any later version.
36+
37+ This program is distributed in the hope that it will be useful,
38+ but WITHOUT ANY WARRANTY; without even the implied warranty of
39+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40+ GNU General Public License for more details.
41+
42+ You should have received a copy of the GNU General Public License
43+ along with this program; if not, write to the Free Software
44+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
45+*/
46+
47+/* This file contains code used by the --link-by-hash option. */
48+
49+#include "rsync.h"
50+
51+extern char *link_by_hash_dir;
52+
79f132a1 53+#if HAVE_LINK
c57f4101
WD
54+
55+char* make_hash_name(struct file_struct *file)
56+{
57+ char hash[33], *dst;
58+ unsigned char *src;
59+ unsigned char c;
60+ int i;
61+
62+ src = (unsigned char*)file->u.sum;
63+ for (dst = hash, i = 0; i < 4; i++, src++) {
64+ c = *src >> 4;
65+ *(dst++) = (c >= 10) ? (c - 10 + 'a') : (c + '0');
66+ c = *src & 0x0f;
67+ *(dst++) = (c >= 10) ? (c - 10 + 'a') : (c + '0');
68+ }
69+ *dst++ = '/';
70+ for (i = 0; i < 12; i++, src++) {
71+ c = *src >> 4;
72+ *(dst++) = (c >= 10) ? (c - 10 + 'a') : (c + '0');
73+ c = *src & 0x0f;
74+ *(dst++) = (c >= 10) ? (c - 10 + 'a') : (c + '0');
75+ }
76+ *dst = 0;
77+
78+ asprintf(&dst,"%s/%s",link_by_hash_dir,hash);
79+ return dst;
80+}
81+
82+
83+void kill_hashfile(struct hashfile_struct *hashfile)
84+{
85+ if (!hashfile)
86+ return;
87+ free(hashfile->name);
88+ close(hashfile->fd);
89+ free(hashfile);
90+}
91+
92+
93+void kill_hashfiles(struct hashfile_struct *hashfiles)
94+{
95+ struct hashfile_struct *iter, *next;
96+ if ((iter = hashfiles) != NULL) {
97+ do {
98+ next = iter->next;
99+ kill_hashfile(iter);
100+ iter = next;
101+ } while (iter != hashfiles);
102+ }
103+}
104+
105+
106+struct hashfile_struct *find_hashfiles(char *hashname, int64 size, long *fnbr)
107+{
108+ DIR *d;
109+ struct dirent *di;
110+ struct hashfile_struct *hashfiles = NULL, *hashfile;
111+ STRUCT_STAT st;
112+ long this_fnbr;
113+
114+ *fnbr = 0;
47841496 115+
c57f4101
WD
116+ /* Build a list of potential candidates and open
117+ * them. */
118+ if ((d = opendir(hashname)) == NULL) {
d0320a46 119+ rsyserr(FERROR, errno, "opendir failed: \"%s\"", hashname);
c57f4101
WD
120+ free(hashname);
121+ return NULL;
122+ }
123+ while ((di = readdir(d)) != NULL) {
124+ if (!strcmp(di->d_name,".") || !strcmp(di->d_name,"..")) {
125+ continue;
126+ }
127+
128+ /* We need to have the largest fnbr in case we need to store
129+ * a new file. */
130+ this_fnbr = atol(di->d_name);
131+ if (this_fnbr > *fnbr)
132+ *fnbr = this_fnbr;
133+
39cc637d 134+ hashfile = new_array(struct hashfile_struct, 1);
c57f4101
WD
135+ asprintf(&hashfile->name,"%s/%s",hashname,
136+ di->d_name);
137+ if (do_stat(hashfile->name,&st) == -1) {
d0320a46 138+ rsyserr(FERROR, errno, "stat failed: %s", hashfile->name);
c57f4101
WD
139+ kill_hashfile(hashfile);
140+ continue;
141+ }
142+ if (st.st_size != size) {
143+ kill_hashfile(hashfile);
144+ continue;
145+ }
146+ hashfile->nlink = st.st_nlink;
147+ hashfile->fd = open(hashfile->name,O_RDONLY|O_BINARY);
148+ if (hashfile->fd == -1) {
d0320a46 149+ rsyserr(FERROR, errno, "open failed: %s", hashfile->name);
c57f4101
WD
150+ kill_hashfile(hashfile);
151+ continue;
152+ }
153+ if (hashfiles == NULL)
154+ hashfiles = hashfile->next = hashfile->prev = hashfile;
155+ else {
156+ hashfile->next = hashfiles;
157+ hashfile->prev = hashfiles->prev;
158+ hashfile->next->prev = hashfile;
159+ hashfile->prev->next = hashfile;
160+ }
161+ }
162+ closedir(d);
163+
164+ return hashfiles;
165+}
166+
167+
168+struct hashfile_struct *compare_hashfiles(int fd,struct hashfile_struct *files)
169+{
170+ int amt, hamt;
171+ char buffer[BUFSIZ], cmpbuffer[BUFSIZ];
172+ struct hashfile_struct *iter, *next, *best;
173+ uint32 nlink;
174+
175+ if (!files)
176+ return NULL;
177+
178+ iter = files; /* in case files are 0 bytes */
179+ while ((amt = read(fd, buffer, BUFSIZ)) > 0) {
180+ iter = files;
181+ do {
182+ /* Icky bit to resync when we steal the first node. */
183+ if (!files)
184+ files = iter;
185+
186+ next = iter->next;
187+
188+ hamt = read(iter->fd, cmpbuffer, BUFSIZ);
189+ if (amt != hamt || memcmp(buffer, cmpbuffer, amt)) {
190+ if (iter == files) {
191+ files = files->prev;
192+ }
193+ if (iter->next == iter) {
194+ files = next = NULL;
195+ } else {
196+ next = iter->next;
197+ if (iter == files) {
198+ /* So we know to resync */
199+ files = NULL;
200+ }
201+ }
202+ iter->next->prev = iter->prev;
203+ iter->prev->next = iter->next;
204+ kill_hashfile(iter);
205+ }
206+
207+ iter = next;
208+ } while (iter != files);
209+
210+ if (iter == NULL && files == NULL) {
211+ /* There are no matches. */
212+ return NULL;
213+ }
c57f4101
WD
214+ }
215+
216+ if (amt == -1) {
d0320a46 217+ rsyserr(FERROR, errno, "read failed in compare_hashfiles()");
c57f4101
WD
218+ kill_hashfiles(files);
219+ return NULL;
220+ }
221+
222+ /* If we only have one file left, use it. */
223+ if (files == files->next) {
224+ return files;
225+ }
226+
227+ /* All files which remain in the list are identical and should have
228+ * the same size. We pick the one with the lowest link count (we
229+ * may have rolled over because we hit the maximum link count for
230+ * the filesystem). */
231+ best = iter = files;
232+ nlink = iter->nlink;
233+ do {
234+ if (iter->nlink < nlink) {
235+ nlink = iter->nlink;
236+ best = iter;
237+ }
238+ iter = iter->next;
239+ } while (iter != files);
240+
241+ best->next->prev = best->prev;
242+ best->prev->next = best->next;
243+ if (files == best)
244+ files = files->next;
245+ kill_hashfiles(files);
246+ return best;
247+}
248+
249+
250+int link_by_hash(char *fnametmp,char *fname,struct file_struct *file)
251+{
252+ STRUCT_STAT st;
47841496 253+ char *hashname = make_hash_name(file);
c57f4101
WD
254+ int first = 0, rc;
255+ char *linkname;
256+ long last_fnbr;
257+
258+ if (file->length == 0) {
259+ return robust_rename(fnametmp,fname,0644);
260+ }
261+
262+ if (do_stat(hashname, &st) == -1) {
263+ char *dirname;
264+
265+ /* Directory does not exist. */
266+ dirname = strdup(hashname);
267+ *strrchr(dirname,'/') = 0;
268+ if (do_mkdir(dirname, 0755) == -1 && errno != EEXIST) {
d0320a46 269+ rsyserr(FERROR, errno, "mkdir failed: %s", dirname);
c57f4101
WD
270+ free(hashname);
271+ free(dirname);
272+ return robust_rename(fnametmp,fname,0644);
273+ }
274+ free(dirname);
275+
276+ if (do_mkdir(hashname, 0755) == -1 && errno != EEXIST) {
d0320a46 277+ rsyserr(FERROR, errno, "mkdir failed: %s", hashname);
c57f4101
WD
278+ free(hashname);
279+ return robust_rename(fnametmp,fname,0644);
280+ }
281+
282+ first = 1;
283+ asprintf(&linkname,"%s/0",hashname);
284+ rprintf(FINFO, "(1) linkname = %s\n", linkname);
c57f4101
WD
285+ } else {
286+ struct hashfile_struct *hashfiles, *hashfile;
c57f4101
WD
287+
288+ if (do_stat(fnametmp,&st) == -1) {
d0320a46 289+ rsyserr(FERROR, errno, "stat failed: %s", fname);
c57f4101
WD
290+ return -1;
291+ }
292+ hashfiles = find_hashfiles(hashname, st.st_size, &last_fnbr);
293+
294+ if (hashfiles == NULL) {
295+ first = 1;
296+ asprintf(&linkname,"%s/0",hashname);
297+ rprintf(FINFO, "(2) linkname = %s\n", linkname);
298+ } else {
47841496 299+ int fd;
c57f4101
WD
300+ /* Search for one identical to us. */
301+ if ((fd = open(fnametmp,O_RDONLY|O_BINARY)) == -1) {
d0320a46 302+ rsyserr(FERROR, errno, "open failed: %s", fnametmp);
c57f4101
WD
303+ kill_hashfiles(hashfiles);
304+ return -1;
305+ }
306+ hashfile = compare_hashfiles(fd, hashfiles);
307+ hashfiles = NULL;
47841496 308+ close(fd);
c57f4101
WD
309+
310+ if (hashfile) {
311+ first = 0;
312+ linkname = strdup(hashfile->name);
313+ rprintf(FINFO, "(3) linkname = %s\n", linkname);
314+ kill_hashfile(hashfile);
315+ } else {
316+ first = 1;
317+ asprintf(&linkname, "%s/%ld", hashname,
318+ last_fnbr + 1);
319+ rprintf(FINFO, "(4) linkname = %s\n", linkname);
320+ }
321+ }
322+ }
323+
324+ if (!first) {
325+ rprintf(FINFO, "link-by-hash (existing): \"%s\" -> %s\n",
326+ linkname, full_fname(fname));
cad12f62 327+ robust_unlink(fname);
c57f4101
WD
328+ rc = do_link(linkname, fname);
329+ if (rc == -1) {
330+ if (errno == EMLINK) {
331+ first = 1;
332+ free(linkname);
333+ asprintf(&linkname,"%s/%ld",hashname,
334+ last_fnbr + 1);
335+ rprintf(FINFO, "(5) linkname = %s\n", linkname);
336+ rprintf(FINFO,"link-by-hash: max link count exceeded, starting new file \"%s\".\n", linkname);
337+ } else {
fe6407b5
WD
338+ rsyserr(FERROR, errno, "link \"%s\" -> \"%s\"",
339+ linkname, full_fname(fname));
c57f4101
WD
340+ rc = robust_rename(fnametmp,fname,0644);
341+ }
342+ } else {
343+ do_unlink(fnametmp);
344+ }
345+ }
346+
347+ if (first) {
348+ rprintf(FINFO, "link-by-hash (new): %s -> \"%s\"\n",
349+ full_fname(fname),linkname);
350+
351+ rc = robust_rename(fnametmp,fname,0644);
352+ if (rc != 0) {
fe6407b5
WD
353+ rsyserr(FERROR, errno, "rename \"%s\" -> \"%s\"",
354+ full_fname(fnametmp), full_fname(fname));
c57f4101
WD
355+ }
356+ rc = do_link(fname,linkname);
357+ if (rc != 0) {
fe6407b5
WD
358+ rsyserr(FERROR, errno, "link \"%s\" -> \"%s\"",
359+ full_fname(fname), linkname);
c57f4101
WD
360+ }
361+ }
362+
363+ free(linkname);
364+ free(hashname);
365+ return rc;
366+}
367+
368+#endif
9a7eef96
WD
369--- old/options.c
370+++ new/options.c
afcb578c 371@@ -145,6 +145,7 @@ char *backup_suffix = NULL;
e0e47893
WD
372 char *tmpdir = NULL;
373 char *partial_dir = NULL;
374 char *basis_dir[MAX_BASIS_DIRS+1];
c57f4101 375+char *link_by_hash_dir = NULL;
e0e47893
WD
376 char *config_file = NULL;
377 char *shell_cmd = NULL;
378 char *log_format = NULL;
afcb578c 379@@ -338,6 +339,7 @@ void usage(enum logcode F)
c57f4101 380 rprintf(F," --compare-dest=DIR also compare destination files relative to DIR\n");
6ba1be7d 381 rprintf(F," --copy-dest=DIR ... and include copies of unchanged files\n");
0808daa5 382 rprintf(F," --link-dest=DIR hardlink to files in DIR when unchanged\n");
b78a6aba
WD
383+ rprintf(F," --link-by-hash=DIR create hardlinks by hash into DIR\n");
384 rprintf(F," -z, --compress compress file data during the transfer\n");
610969d1 385 rprintf(F," --compress-level=NUM explicitly set compression level\n");
79f132a1 386 rprintf(F," -C, --cvs-exclude auto-ignore files the same way CVS does\n");
afcb578c 387@@ -385,7 +387,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OP
93ca4d27 388 OPT_FILTER, OPT_COMPARE_DEST, OPT_COPY_DEST, OPT_LINK_DEST, OPT_HELP,
0ca6aebe 389 OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
5398d042 390 OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
4a65fe72
WD
391- OPT_NO_D,
392+ OPT_NO_D, OPT_LINK_BY_HASH,
0ca6aebe 393 OPT_SERVER, OPT_REFUSED_BASE = 9000};
c57f4101 394
70c5d149 395 static struct poptOption long_options[] = {
afcb578c 396@@ -480,6 +482,7 @@ static struct poptOption long_options[]
0808daa5 397 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
6ba1be7d 398 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
0808daa5 399 {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
5388f859 400+ {"link-by-hash", 0, POPT_ARG_STRING, 0, OPT_LINK_BY_HASH, 0, 0},
09fb8f03 401 {"fuzzy", 'y', POPT_ARG_NONE, &fuzzy_basis, 0, 0, 0 },
610969d1
WD
402 {"compress", 'z', POPT_ARG_NONE, 0, 'z', 0, 0 },
403 {"compress-level", 0, POPT_ARG_INT, &def_compress_level, 'z', 0, 0 },
afcb578c 404@@ -1060,6 +1063,21 @@ int parse_arguments(int *argc, const cha
0ca6aebe
WD
405 usage(FINFO);
406 exit_cleanup(0);
c57f4101
WD
407
408+ case OPT_LINK_BY_HASH:
409+#if HAVE_LINK
d0320a46
WD
410+ arg = poptGetOptArg(pc);
411+ if (sanitize_paths)
412+ arg = sanitize_path(NULL, arg, NULL, 0);
413+ link_by_hash_dir = (char *)arg;
c57f4101
WD
414+ break;
415+#else
416+ snprintf(err_buf, sizeof err_buf,
417+ "hard links are not supported on this %s\n",
418+ am_server ? "server" : "client");
419+ rprintf(FERROR, "ERROR: %s", err_buf);
420+ return 0;
421+#endif
422+
423 default:
424 /* A large opt value means that set_refuse_options()
27a7053c 425 * turned this option off. */
afcb578c 426@@ -1708,6 +1726,11 @@ void server_options(char **args,int *arg
e0e47893 427 }
7b675ff5
WD
428 }
429
c57f4101
WD
430+ if (link_by_hash_dir && am_sender) {
431+ args[ac++] = "--link-by-hash";
432+ args[ac++] = link_by_hash_dir;
7b675ff5
WD
433+ }
434+
def2ace9
WD
435 if (files_from && (!am_sender || filesfrom_host)) {
436 if (filesfrom_host) {
7b675ff5 437 args[ac++] = "--files-from";
9a7eef96
WD
438--- old/receiver.c
439+++ new/receiver.c
70c5d149 440@@ -54,6 +54,7 @@ extern int delay_updates;
33840e0c
WD
441 extern struct stats stats;
442 extern char *log_format;
f6c3b300 443 extern char *tmpdir;
b4023df5 444+extern char *link_by_hash_dir;
afbebe13 445 extern char *partial_dir;
0808daa5 446 extern char *basis_dir[];
18ae7b87 447 extern struct file_list *the_file_list;
afcb578c 448@@ -125,12 +126,13 @@ static int get_tmpname(char *fnametmp, c
c57f4101
WD
449
450
dc3ae351
WD
451 static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
452- char *fname, int fd, OFF_T total_size)
453+ char *fname, int fd, OFF_T total_size, char *md4)
c57f4101 454 {
dc3ae351
WD
455 static char file_sum1[MD4_SUM_LENGTH];
456 static char file_sum2[MD4_SUM_LENGTH];
457 struct map_struct *mapbuf;
c57f4101
WD
458 struct sum_struct sum;
459+ struct mdfour mdfour_data;
2c2d83dc 460 int32 len;
c57f4101
WD
461 OFF_T offset = 0;
462 OFF_T offset2;
afcb578c 463@@ -150,6 +152,9 @@ static int receive_data(int f_in, char *
dc3ae351
WD
464 } else
465 mapbuf = NULL;
7b675ff5 466
c57f4101
WD
467+ if (md4)
468+ mdfour_begin(&mdfour_data);
7b675ff5
WD
469+
470 sum_init(checksum_seed);
c57f4101 471
ff55cce0 472 if (append_mode) {
afcb578c 473@@ -192,6 +197,8 @@ static int receive_data(int f_in, char *
c57f4101
WD
474 cleanup_got_literal = 1;
475
2c2d83dc 476 sum_update(data, i);
c57f4101
WD
477+ if (md4)
478+ mdfour_update(&mdfour_data,data,i);
479
afbebe13
WD
480 if (fd != -1 && write_file(fd,data,i) != i)
481 goto report_write_error;
afcb578c 482@@ -218,6 +225,8 @@ static int receive_data(int f_in, char *
c57f4101
WD
483
484 see_token(map, len);
2c2d83dc 485 sum_update(map, len);
c57f4101
WD
486+ if (md4)
487+ mdfour_update(&mdfour_data,map,len);
488 }
489
afbebe13 490 if (inplace) {
afcb578c 491@@ -258,6 +267,8 @@ static int receive_data(int f_in, char *
c57f4101
WD
492 }
493
494 sum_end(file_sum1);
495+ if (md4)
496+ mdfour_result(&mdfour_data, (unsigned char*)md4);
497
dc3ae351
WD
498 if (mapbuf)
499 unmap_file(mapbuf);
afcb578c 500@@ -273,7 +284,7 @@ static int receive_data(int f_in, char *
5823d322
WD
501
502 static void discard_receive_data(int f_in, OFF_T length)
503 {
dc3ae351
WD
504- receive_data(f_in, NULL, -1, 0, NULL, -1, length);
505+ receive_data(f_in, NULL, -1, 0, NULL, -1, length, NULL);
5823d322
WD
506 }
507
36bbf3d1 508 static void handle_delayed_updates(struct file_list *flist, char *local_name)
afcb578c 509@@ -605,8 +616,12 @@ int recv_files(int f_in, struct file_lis
93ca4d27 510 rprintf(FINFO, "%s\n", fname);
c57f4101
WD
511
512 /* recv file data */
79f132a1 513+#if HAVE_LINK
7b675ff5 514+ if (link_by_hash_dir)
39cc637d 515+ file->u.sum = new_array(char, MD4_SUM_LENGTH);
c57f4101 516+#endif
dc3ae351
WD
517 recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size,
518- fname, fd2, file->length);
519+ fname, fd2, file->length, file->u.sum);
c57f4101 520
5a56905b 521 if (!log_before_transfer)
6ba1be7d 522 log_item(file, &initial_stats, iflags, NULL);
9a7eef96
WD
523--- old/rsync.c
524+++ new/rsync.c
afcb578c 525@@ -50,6 +50,7 @@ extern int inplace;
52f25864 526 extern int keep_dirlinks;
c57f4101 527 extern int make_backups;
c968d24c 528 extern struct stats stats;
c968d24c 529+extern char *link_by_hash_dir;
52f25864 530
27e96866
WD
531 #if defined HAVE_ICONV_OPEN && defined HAVE_ICONV_H
532 iconv_t ic_chck = (iconv_t)-1;
afcb578c 533@@ -266,8 +267,15 @@ void finish_transfer(char *fname, char *
93ca4d27
WD
534 /* move tmp file over real file */
535 if (verbose > 2)
536 rprintf(FINFO, "renaming %s to %s\n", fnametmp, fname);
4a65fe72
WD
537- ret = robust_rename(fnametmp, fname, partialptr,
538- file->mode & INITACCESSPERMS);
79f132a1 539+#if HAVE_LINK
2eb075b2 540+ if (link_by_hash_dir)
7b675ff5 541+ ret = link_by_hash(fnametmp, fname, file);
2eb075b2 542+ else
c57f4101 543+#endif
4a65fe72
WD
544+ {
545+ ret = robust_rename(fnametmp, fname, partialptr,
546+ file->mode & INITACCESSPERMS);
547+ }
54691942 548 if (ret < 0) {
fe6407b5 549 rsyserr(FERROR, errno, "%s %s -> \"%s\"",
93ca4d27 550 ret == -2 ? "copy" : "rename",
9a7eef96
WD
551--- old/rsync.h
552+++ new/rsync.h
e0e47893 553@@ -640,6 +640,14 @@ struct stats {
c57f4101
WD
554 int current_file_index;
555 };
556
557+struct hashfile_struct {
558+ struct hashfile_struct *next;
559+ struct hashfile_struct *prev;
560+ char *name;
561+ int fd;
562+ uint32 nlink;
563+};
564+
4a65fe72 565 struct chmod_mode_struct;
c57f4101 566
a7219d20 567 #include "byteorder.h"
9a7eef96
WD
568--- old/rsync.yo
569+++ new/rsync.yo
4a65fe72 570@@ -361,6 +361,7 @@ to the detailed description below for a
79f132a1 571 --compare-dest=DIR also compare received files relative to DIR
6ba1be7d 572 --copy-dest=DIR ... and include copies of unchanged files
79f132a1 573 --link-dest=DIR hardlink to files in DIR when unchanged
b78a6aba
WD
574+ --link-by-hash=DIR create hardlinks by hash into DIR
575 -z, --compress compress file data during the transfer
610969d1 576 --compress-level=NUM explicitly set compression level
79f132a1 577 -C, --cvs-exclude auto-ignore files in the same way CVS does