Commit a92a20426c3078772e9cc8671b4c58ed9a07b9bf

Omar Polo 2022-07-02T21:16:13

got patch: handle mangled whitespaces This makes 'got patch' try to ignore whitespaces when trying to match a hunk. Discused with and ok stsp@

diff --git a/got/got.c b/got/got.c
index 5f7f000..4e69daf 100644
--- a/got/got.c
+++ b/got/got.c
@@ -7758,7 +7758,7 @@ static const struct got_error *
 patch_progress(void *arg, const char *old, const char *new,
     unsigned char status, const struct got_error *error, long old_from,
     long old_lines, long new_from, long new_lines, long offset,
-    const struct got_error *hunk_err)
+    int ws_mangled, const struct got_error *hunk_err)
 {
 	const char *path = new == NULL ? old : new;
 
@@ -7771,13 +7771,15 @@ patch_progress(void *arg, const char *old, const char *new,
 	if (error != NULL)
 		fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
 
-	if (offset != 0 || hunk_err != NULL) {
+	if (offset != 0 || hunk_err != NULL || ws_mangled) {
 		printf("@@ -%ld,%ld +%ld,%ld @@ ", old_from,
 		    old_lines, new_from, new_lines);
 		if (hunk_err != NULL)
 			printf("%s\n", hunk_err->msg);
-		else
+		else if (offset != 0)
 			printf("applied with offset %ld\n", offset);
+		else
+			printf("hunk contains mangled whitespace\n");
 	}
 
 	return NULL;
diff --git a/include/got_patch.h b/include/got_patch.h
index bc2e95b..e72ded7 100644
--- a/include/got_patch.h
+++ b/include/got_patch.h
@@ -22,7 +22,7 @@
  */
 typedef const struct got_error *(*got_patch_progress_cb)(void *,
     const char *, const char *, unsigned char, const struct got_error *,
-    long, long, long, long, long, const struct got_error *);
+    long, long, long, long, long, int, const struct got_error *);
 
 /*
  * Apply the (already opened) patch to the repository and register the
diff --git a/lib/patch.c b/lib/patch.c
index 2cd5efa..f12c747 100644
--- a/lib/patch.c
+++ b/lib/patch.c
@@ -26,6 +26,7 @@
 #include <sys/stat.h>
 #include <sys/uio.h>
 
+#include <ctype.h>
 #include <errno.h>
 #include <limits.h>
 #include <sha1.h>
@@ -58,6 +59,7 @@
 struct got_patch_hunk {
 	STAILQ_ENTRY(got_patch_hunk) entries;
 	const struct got_error *err;
+	int	ws_mangled;
 	int	offset;
 	int	old_nonl;
 	int	new_nonl;
@@ -401,6 +403,30 @@ locate_hunk(FILE *orig, struct got_patch_hunk *h, off_t *pos, int *lineno)
 	return err;
 }
 
+static int
+linecmp(const char *a, const char *b, int *mangled)
+{
+	int c;
+
+	*mangled = 0;
+	c = strcmp(a, b);
+	if (c == 0)
+		return c;
+
+	*mangled = 1;
+	for (;;) {
+		while (isspace(*a))
+			a++;
+		while (isspace(*b))
+			b++;
+		if (*a == '\0' || *b == '\0' || *a != *b)
+			break;
+		a++, b++;
+	}
+
+	return *a - *b;
+}
+
 static const struct got_error *
 test_hunk(FILE *orig, struct got_patch_hunk *h)
 {
@@ -408,6 +434,7 @@ test_hunk(FILE *orig, struct got_patch_hunk *h)
 	char *line = NULL;
 	size_t linesize = 0, i = 0;
 	ssize_t linelen;
+	int mangled;
 
 	for (i = 0; i < h->len; ++i) {
 		switch (*h->lines[i]) {
@@ -426,10 +453,12 @@ test_hunk(FILE *orig, struct got_patch_hunk *h)
 			}
 			if (line[linelen - 1] == '\n')
 				line[linelen - 1] = '\0';
-			if (strcmp(h->lines[i] + 1, line)) {
+			if (linecmp(h->lines[i] + 1, line, &mangled)) {
 				err = got_error(GOT_ERR_HUNK_FAILED);
 				goto done;
 			}
+			if (mangled)
+				h->ws_mangled = 1;
 			break;
 		}
 	}
@@ -440,32 +469,61 @@ done:
 }
 
 static const struct got_error *
-apply_hunk(FILE *tmp, struct got_patch_hunk *h, int *lineno)
+apply_hunk(FILE *orig, FILE *tmp, struct got_patch_hunk *h, int *lineno,
+    off_t from)
 {
-	size_t i, new = 0;
+	const struct got_error *err = NULL;
+	const char *t;
+	size_t linesize = 0, i, new = 0;
+	char *line = NULL;
+	char mode;
+	ssize_t linelen;
+
+	if (orig != NULL && fseeko(orig, from, SEEK_SET) == -1)
+		return got_error_from_errno("fseeko");
 
 	for (i = 0; i < h->len; ++i) {
-		switch (*h->lines[i]) {
-		case ' ':
-			if (fprintf(tmp, "%s\n", h->lines[i] + 1) < 0)
-				return got_error_from_errno("fprintf");
-			/* fallthrough */
+		switch (mode = *h->lines[i]) {
 		case '-':
+		case ' ':
 			(*lineno)++;
+			if (orig != NULL) {
+				linelen = getline(&line, &linesize, orig);
+				if (linelen == -1) {
+					err = got_error_from_errno("getline");
+					goto done;
+				}
+				if (line[linelen - 1] == '\n')
+					line[linelen - 1] = '\0';
+				t = line;
+			} else
+				t = h->lines[i] + 1;
+			if (mode == '-')
+				continue;
+			if (fprintf(tmp, "%s\n", t) < 0) {
+				err = got_error_from_errno("fprintf");
+				goto done;
+			}
 			break;
 		case '+':
 			new++;
-			if (fprintf(tmp, "%s", h->lines[i] + 1) < 0)
-				return got_error_from_errno("fprintf");
+			if (fprintf(tmp, "%s", h->lines[i] + 1) < 0) {
+				err = got_error_from_errno("fprintf");
+				goto done;
+			}
 			if (new != h->new_lines || !h->new_nonl) {
-				if (fprintf(tmp, "\n") < 0)
-					return got_error_from_errno(
-					    "fprintf");
+				if (fprintf(tmp, "\n") < 0) {
+					err = got_error_from_errno("fprintf");
+					goto done;
+				}
 			}
 			break;
 		}
 	}
-	return NULL;
+
+done:
+	free(line);
+	return err;
 }
 
 static const struct got_error *
@@ -484,7 +542,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp)
 		h = STAILQ_FIRST(&p->head);
 		if (h == NULL || STAILQ_NEXT(h, entries) != NULL)
 			return got_error(GOT_ERR_PATCH_MALFORMED);
-		return apply_hunk(tmp, h, &lineno);
+		return apply_hunk(orig, tmp, h, &lineno, 0);
 	}
 
 	if (fstat(fileno(orig), &sb) == -1)
@@ -523,7 +581,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp)
 		if (lineno + 1 != h->old_from)
 			h->offset = lineno + 1 - h->old_from;
 
-		err = apply_hunk(tmp, h, &lineno);
+		err = apply_hunk(orig, tmp, h, &lineno, pos);
 		if (err != NULL)
 			return err;
 
@@ -550,17 +608,17 @@ report_progress(struct patch_args *pa, const char *old, const char *new,
 	struct got_patch_hunk *h;
 
 	err = pa->progress_cb(pa->progress_arg, old, new, status,
-	    orig_error, 0, 0, 0, 0, 0, NULL);
+	    orig_error, 0, 0, 0, 0, 0, 0, NULL);
 	if (err)
 		return err;
 
 	STAILQ_FOREACH(h, pa->head, entries) {
-		if (h->offset == 0 && h->err == NULL)
+		if (h->offset == 0 && !h->ws_mangled && h->err == NULL)
 			continue;
 
 		err = pa->progress_cb(pa->progress_arg, old, new, 0, NULL,
 		    h->old_from, h->old_lines, h->new_from, h->new_lines,
-		    h->offset, h->err);
+		    h->offset, h->ws_mangled, h->err);
 		if (err)
 			return err;
 	}
diff --git a/regress/cmdline/patch.sh b/regress/cmdline/patch.sh
index c16e62c..7901777 100755
--- a/regress/cmdline/patch.sh
+++ b/regress/cmdline/patch.sh
@@ -1277,6 +1277,91 @@ EOF
 	test_done $testroot 0
 }
 
+test_patch_whitespace() {
+	local testroot=`test_init patch_whitespace`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	trailing="		"
+
+	cat <<EOF > $testroot/wt/hello.c
+#include <stdio.h>
+
+int
+main(void)
+{
+	/* the trailing whitespace is on purpose */
+	printf("hello, world\n");$trailing
+	return 0;
+}
+EOF
+
+	(cd $testroot/wt && got add hello.c && got ci -m '+hello.c') \
+		> /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# test with a diff with various whitespace corruptions
+	cat <<EOF > $testroot/wt/patch
+--- hello.c
++++ hello.c
+@@ -5,5 +5,5 @@
+ {
+ /* the trailing whitespace is on purpose */
+	printf("hello, world\n");
+-    return 0;
++    return 5; /* always fails */
+ }
+EOF
+
+	(cd $testroot/wt && got patch patch) \
+		2>$testroot/stderr >$testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "failed to apply diff" >&2
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo 'M  hello.c' > $testroot/stdout.expected
+	echo '@@ -5,5 +5,5 @@ hunk contains mangled whitespace' \
+		>> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done $testroot $ret
+		return 1
+	fi
+
+	cat <<EOF > $testroot/wt/hello.c.expected
+#include <stdio.h>
+
+int
+main(void)
+{
+	/* the trailing whitespace is on purpose */
+	printf("hello, world\n");$trailing
+    return 5; /* always fails */
+}
+EOF
+
+	cmp -s $testroot/wt/hello.c.expected $testroot/wt/hello.c
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/wt/hello.c.expected $testroot/wt/hello.c
+	fi
+	test_done $testroot $ret
+}
+
 test_patch_relative_paths() {
 	local testroot=`test_init patch_relative_paths`
 
@@ -1811,6 +1896,7 @@ run_test test_patch_with_offset
 run_test test_patch_prefer_new_path
 run_test test_patch_no_newline
 run_test test_patch_strip
+run_test test_patch_whitespace
 run_test test_patch_relative_paths
 run_test test_patch_with_path_prefix
 run_test test_patch_relpath_with_path_prefix