Depends-On-Patch: g2r-basis-filename.diff

The changes to generator.c were greatly simplified, making the patch
easier to maintain and fixing the failing test in the testsuite.
Very lightly tested.

Be sure to run "make proto" before "make".

--- orig/generator.c	2004-07-28 10:14:15
+++ generator.c	2004-07-28 10:23:12
@@ -41,6 +41,7 @@ extern int ignore_times;
 extern int size_only;
 extern int io_timeout;
 extern int protocol_version;
+extern int fuzzy;
 extern int always_checksum;
 extern char *partial_dir;
 extern char *compare_dest;
@@ -249,6 +250,94 @@ static void generate_and_send_sums(int f
 }
 
 
+static void split_names(char *fname, char **dirname, char **basename)
+{
+	char *slash = strrchr(fname, '/');
+	if (slash) {
+		*dirname = fname;
+		*slash = '\0';
+		*basename = slash+1;
+	} else {
+		*basename = fname;
+		*dirname = ".";
+	}
+}
+
+
+static unsigned int measure_name(const char *name, const char *basename,
+				 const char *ext)
+{
+	int namelen = strlen(name);
+	int extlen = strlen(ext);
+	unsigned int score = 0;
+
+	/* Extensions must match */
+	if (namelen <= extlen || strcmp(name + namelen - extlen, ext) != 0)
+		return 0;
+
+	/* Now score depends on similarity of prefix */
+	for (; *name == *basename && *name; name++, basename++)
+		score++;
+	return score;
+}
+
+
+static int find_fuzzy(char **fname_ptr, char *buf, STRUCT_STAT *st_ptr)
+{
+	DIR *d;
+	struct dirent *di;
+	char *basename, *dirname;
+	char mangled_name[MAXPATHLEN];
+	char bestname[MAXPATHLEN];
+	unsigned int bestscore = 0;
+	const char *ext;
+
+	strlcpy(mangled_name, *fname_ptr, sizeof mangled_name);
+
+	split_names(mangled_name, &dirname, &basename);
+	if (!(d = opendir(dirname))) {
+		rsyserr(FERROR, errno, "recv_generator opendir(%s)", dirname);
+		return -1;
+	}
+
+	/* Get final extension, eg. .gz; never full basename though. */
+	ext = strrchr(basename + 1, '.');
+	if (!ext)
+		ext = basename + strlen(basename); /* ext = "" */
+
+	while ((di = readdir(d)) != NULL) {
+		const char *dname = d_name(di);
+		unsigned int score;
+
+		if (dname[0] == '.' && (dname[1] == '\0'
+		    || (dname[1] == '.' && dname[2] == '\0')))
+			continue;
+
+		score = measure_name(dname, basename, ext);
+		if (verbose > 4) {
+			rprintf(FINFO, "[%s] fuzzy score for %s = %u\n",
+				who_am_i(), dname, score);
+		}
+		if (score > bestscore) {
+			strlcpy(bestname, dname, sizeof bestname);
+			bestscore = score;
+		}
+	}
+	closedir(d);
+
+	/* Found a candidate. */
+	if (bestscore != 0) {
+		pathjoin(buf, MAXPATHLEN, dirname, bestname);
+		if (verbose > 2) {
+			rprintf(FINFO, "[%s] fuzzy match %s->%s\n",
+				who_am_i(), *fname_ptr, buf);
+		}
+		*fname_ptr = buf;
+		return link_stat(buf, st_ptr, 0);
+	}
+	return -1;
+}
+
 
 /*
  * Acts on file number @p i from @p flist, whose name is @p fname.
@@ -263,7 +352,7 @@ static void recv_generator(char *fname, 
 {
 	int fd = -1;
 	STRUCT_STAT st;
-	int statret, stat_errno;
+	int statret, stat_errno, fuzzy_file = 0;
 	char *fnamecmp;
 	char fnamecmpbuf[MAXPATHLEN];
 
@@ -448,6 +537,14 @@ static void recv_generator(char *fname, 
 	} else
 		*fnamecmpbuf = '\0';
 
+	if (statret == -1 && fuzzy) {
+		statret = find_fuzzy(&fnamecmp, fnamecmpbuf, &st);
+		if (!S_ISREG(st.st_mode))
+			statret = -1;
+		else
+			fuzzy_file = 1;
+	}
+
 	if (statret == 0 && !S_ISREG(st.st_mode)) {
 		if (delete_file(fname) != 0)
 			return;
@@ -481,7 +578,7 @@ static void recv_generator(char *fname, 
 		return;
 	}
 
-	if (skip_file(fname, file, &st)) {
+	if (!fuzzy_file && skip_file(fname, file, &st)) {
 		if (!*fnamecmpbuf)
 			set_perms(fname, file, &st, PERMS_REPORT);
 		return;
--- orig/main.c	2004-07-22 00:10:43
+++ main.c	2004-07-22 00:32:31
@@ -47,6 +47,7 @@ extern int keep_dirlinks;
 extern int preserve_hard_links;
 extern int protocol_version;
 extern int recurse;
+extern int fuzzy;
 extern int relative_paths;
 extern int rsync_port;
 extern int whole_file;
@@ -458,7 +459,7 @@ static int do_recv(int f_in,int f_out,st
 	int pid;
 	int status = 0;
 	int error_pipe[2], name_pipe[2];
-	BOOL need_name_pipe = compare_dest && !dry_run;
+	BOOL need_name_pipe = (compare_dest || fuzzy) && !dry_run;
 
 	if (preserve_hard_links)
 		init_hard_links(flist);
--- orig/options.c	2004-07-29 16:08:03
+++ options.c	2004-07-16 20:14:12
@@ -85,6 +85,7 @@ int safe_symlinks = 0;
 int copy_unsafe_links = 0;
 int size_only = 0;
 int bwlimit = 0;
+int fuzzy = 0;
 size_t bwlimit_writemax = 0;
 int delete_after = 0;
 int only_existing = 0;
@@ -279,6 +280,7 @@ void usage(enum logcode F)
   rprintf(F," -T, --temp-dir=DIR          create temporary files in directory DIR\n");
   rprintf(F,"     --compare-dest=DIR      also compare destination files relative to DIR\n");
   rprintf(F,"     --link-dest=DIR         create hardlinks to DIR for unchanged files\n");
+  rprintf(F,"     --fuzzy                 use similar file as basis if basis doesn't exist\n");
   rprintf(F," -P                          equivalent to --partial --progress\n");
   rprintf(F," -z, --compress              compress file data\n");
   rprintf(F," -C, --cvs-exclude           auto ignore files in the same way CVS does\n");
@@ -378,6 +380,7 @@ static struct poptOption long_options[] 
   {"temp-dir",        'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
   {"compare-dest",     0,  POPT_ARG_STRING, &compare_dest, 0, 0, 0 },
   {"link-dest",        0,  POPT_ARG_STRING, &compare_dest,  OPT_LINK_DEST, 0, 0 },
+  {"fuzzy",            0,  POPT_ARG_NONE,   &fuzzy, 0, 0, 0 },
   /* TODO: Should this take an optional int giving the compression level? */
   {"compress",        'z', POPT_ARG_NONE,   &do_compression, 0, 0, 0 },
   {"daemon",           0,  POPT_ARG_NONE,   &daemon_opt, 0, 0, 0 },
@@ -1042,6 +1045,9 @@ void server_options(char **args,int *arg
 		}
 	}
 
+	if (fuzzy && am_sender)
+		args[ac++] = "--fuzzy";
+
 	*argc = ac;
 	return;
 
--- orig/receiver.c	2004-07-23 21:59:07
+++ receiver.c	2004-07-23 22:08:03
@@ -39,7 +39,6 @@ extern int cvs_exclude;
 extern int io_error;
 extern char *tmpdir;
 extern char *partial_dir;
-extern char *compare_dest;
 extern int make_backups;
 extern int do_progress;
 extern char *backup_dir;
--- orig/rsync.yo	2004-07-29 16:08:04
+++ rsync.yo	2004-07-03 19:27:25
@@ -327,6 +327,7 @@ verb(
  -T  --temp-dir=DIR          create temporary files in directory DIR
      --compare-dest=DIR      also compare received files relative to DIR
      --link-dest=DIR         create hardlinks to DIR for unchanged files
+     --fuzzy                 use similar file as basis if basis is gone
  -P                          equivalent to --partial --progress
  -z, --compress              compress file data
  -C, --cvs-exclude           auto ignore files in the same way CVS does
