Commit Diff


commit - cf536071bc57734308f29cda79d67c88abb3b9f0
commit + 684a9a6c2461ac3b30d1f0445ec45c94add21931
blob - 6c31c4b5e3788850c30c78ed552e90f1eddd3982
blob + 6227992d23f21048e0238cc7a3bfe018b85c9cf5
--- lib/patch.c
+++ lib/patch.c
@@ -586,6 +586,10 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp)
 			return got_error(GOT_ERR_PATCH_MALFORMED);
 		return apply_hunk(orig, tmp, h, &lineno, 0);
 	}
+
+	/* When deleting binary files there are no hunks to apply. */
+	if (p->new == NULL && STAILQ_EMPTY(&p->head))
+		return NULL;
 
 	if (fstat(fileno(orig), &sb) == -1)
 		return got_error_from_errno("fstat");
blob - becf27d3e00feb1fd2b32823931dc0d1b580cfb6
blob + d0f6b68a23b13b9ec9d5271bb00d093a979e05cb
--- libexec/got-read-patch/got-read-patch.c
+++ libexec/got-read-patch/got-read-patch.c
@@ -129,6 +129,44 @@ filename(const char *at, char **name)
 }
 
 static int
+binary_deleted(const char *line)
+{
+	const char *prefix = "Binary files ";
+	const char *suffix = " and /dev/null differ\n";
+	size_t len, d;
+
+	if (strncmp(line, prefix, strlen(prefix)) != 0)
+		return 0;
+	line += strlen(prefix);
+
+	len = strlen(line);
+	if (len <= strlen(suffix))
+		return 0;
+	d = len - strlen(suffix);
+	return (strcmp(line + d, suffix) == 0);
+}
+
+static const struct got_error *
+binaryfilename(const char *at, char **name)
+{
+	const char *suffix = " and /dev/null differ\n";
+	size_t len, d;
+
+	len = strlen(at);
+	if (len <= strlen(suffix))
+		return NULL;
+
+	d = len - strlen(suffix);
+	if (strcmp(at + d, suffix) != 0)
+		return NULL;
+
+	*name = strndup(at, d);
+	if (*name == NULL)
+		return got_error_from_errno("strndup");
+	return NULL;
+}
+
+static int
 filexbit(const char *line)
 {
 	char *m;
@@ -187,7 +225,8 @@ patch_start(int *git, char **cid, FILE *fp)
 				break;
 		} else if (!strncmp(line, "--- ", 4) ||
 		    !strncmp(line, "+++ ", 4) ||
-		    !strncmp(line, "blob - ", 7)) {
+		    !strncmp(line, "blob - ", 7) ||
+		    binary_deleted(line)) {
 			/* rewind to previous line */
 			if (fseeko(fp, -linelen, SEEK_CUR) == -1)
 				err = got_error_from_errno("fseeko");
@@ -212,7 +251,7 @@ find_diff(int *done, int *next, FILE *fp, int git, con
 	char	*line = NULL;
 	size_t	 linesize = 0;
 	ssize_t	 linelen;
-	int	 create, rename = 0, xbit = 0;
+	int	 create, delete_binary = 0, rename = 0, xbit = 0;
 
 	*done = 0;
 	*next = 0;
@@ -237,6 +276,10 @@ find_diff(int *done, int *next, FILE *fp, int git, con
 		} else if (!git && !strncmp(line, "blob - ", 7)) {
 			free(blob);
 			err = blobid(line + 7, &blob, git);
+		} else if (!strncmp(line, "Binary files ", 13)) {
+			delete_binary = 1;
+			free(old);
+			err = binaryfilename(line + 13, &old);
 		} else if (rename && !strncmp(line, "rename to ", 10)) {
 			free(new);
 			err = filename(line + 10, &new);
@@ -264,6 +307,16 @@ find_diff(int *done, int *next, FILE *fp, int git, con
 		 * line.
 		 */
 		if (rename && old != NULL && new != NULL) {
+			*done = 1;
+			err = send_patch(old, new, commitid,
+			    blob, xbit, git);
+			break;
+		}
+
+		/*
+		 * Diffs that remove binary files have no hunks.
+		 */
+		if (delete_binary && old != NULL) {
 			*done = 1;
 			err = send_patch(old, new, commitid,
 			    blob, xbit, git);
blob - 9e3d4639d01241422b50bdddf316137ceb10d945
blob + 17d3810a7849186809369d7245dafdba62ab2db7
--- regress/cmdline/patch.sh
+++ regress/cmdline/patch.sh
@@ -1880,7 +1880,79 @@ EOF
 
 	test_done "$testroot" 0
 }
+
+test_patch_remove_binary_file() {
+	local testroot=`test_init patch_remove_binary_file`
+
+	if ! got checkout $testroot/repo $testroot/wt >/dev/null; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	dd if=/dev/zero of=$testroot/wt/x bs=1 count=16 2>/dev/null >&2
+	(cd $testroot/wt && got add x && got commit -m +x) >/dev/null
+
+	(cd $testroot/wt && \
+		got branch demo && \
+		got rm x && \
+		got ci -m -x &&
+		got up -b master) >/dev/null
+
+	echo 'D  x' > $testroot/stdout.expected
 
+	(cd $testroot/wt && got log -c demo -l 1 -p >patch)
+
+	(cd $testroot/wt && got patch <patch) > $testroot/stdout
+	if [ $? -ne 0 ]; then
+		echo 'patch failed' >&2
+		test_done $testroot 1
+		return 1
+	fi
+
+	if ! cmp -s $testroot/stdout.expected $testroot/stdout; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done $testroot 1
+		return 1
+	fi
+
+	# try again using a git produced diff
+	(cd $testroot/wt && got revert x) >/dev/null
+
+	(cd $testroot/repo && git show demo) >$testroot/wt/patch
+
+	(cd $testroot/wt && got patch <patch) > $testroot/stdout
+	if [ $? -ne 0 ]; then
+		echo 'patch failed' >&2
+		test_done $testroot 1
+		return 1
+	fi
+
+	if ! cmp -s $testroot/stdout.expected $testroot/stdout; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done $testroot 1
+		return 1
+	fi
+
+	# try again using a diff(1) style patch
+	(cd $testroot/wt && got revert x) >/dev/null
+
+	echo "Binary files x and /dev/null differ" >$testroot/wt/patch
+	(cd $testroot/wt && got patch <patch) >$testroot/stdout
+	if [ $? -ne 0 ]; then
+		echo 'patch failed' >&2
+		test_done $testroot 1
+		return 1
+	fi
+
+	if ! cmp -s $testroot/stdout.expected $testroot/stdout; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done $testroot 1
+		return 1
+	fi
+
+	test_done $testroot 0
+}
+
 test_parseargs "$@"
 run_test test_patch_basic
 run_test test_patch_dont_apply
@@ -1910,3 +1982,4 @@ run_test test_patch_merge_reverse
 run_test test_patch_newfile_xbit_got_diff
 run_test test_patch_newfile_xbit_git_diff
 run_test test_patch_umask
+run_test test_patch_remove_binary_file