[PATCH] patch: handle files with no final newline

Ron Yorston via busybox <[email protected]> Sun, 19 Apr 2026 10:03:24 +0100
Newsgroups gmane.linux.busybox
Message-ID <69e49a5c.6on5uZJ9HGFbVESa%[email protected]>
GNU and BSD patch both handle patches which include the annotation
'\ No newline at end of file'.  BusyBox patch doesn't, even though
its diff emits it.

Implement this feature and add some tests.

function                                             old     new   delta
patch_main                                          1912    2002     +90
do_line                                               88     116     +28
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 118/0)             Total: 118 bytes

Signed-off-by: Ron Yorston <[email protected]>
---
 editors/patch.c       | 33 ++++++++++++++++++++---
 testsuite/patch.tests | 63 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+), 4 deletions(-)

diff --git a/editors/patch.c b/editors/patch.c
index 5a768b23f..ee0ccd384 100644
--- a/editors/patch.c
+++ b/editors/patch.c
@@ -57,6 +57,7 @@ struct double_list {
 	struct double_list *next;
 	struct double_list *prev;
 	char *data;
+	int no_newline;
 };
 
 // Free all the elements of a linked list
@@ -76,7 +77,7 @@ static void dlist_free(struct double_list *list, void (*freeit)(void *data))
 static struct double_list *dlist_add(struct double_list **list, char *data)
 {
 	struct double_list *llist;
-	struct double_list *line = xmalloc(sizeof(*line));
+	struct double_list *line = xzalloc(sizeof(*line));
 
 	line->data = data;
 	llist = *list;
@@ -140,7 +141,8 @@ static void do_line(void *data)
 
 	if (TT.state > 1 && *dlist->data != TT.state)
 		fdprintf(TT.state == 2 ? 2 : TT.fileout,
-			"%s\n", dlist->data + (TT.state > 3 ? 1 : 0));
+			dlist->no_newline && TT.state != 2 ? "%s" : "%s\n",
+			dlist->data + (TT.state > 3 ? 1 : 0));
 
 	if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data);
 
@@ -433,7 +435,17 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
 
 		// Are we assembling a hunk?
 		if (state >= 2) {
-			if (*patchline == ' ' || *patchline == '+' || *patchline == '-') {
+			switch (*patchline) {
+			case '\\':
+				// '\ No newline at end of file' detected, mark
+				// previous line, if it exists.
+				if (TT.current_hunk->prev)
+					TT.current_hunk->prev->no_newline = TRUE;
+				free(patchline);
+				continue;
+			case ' ':
+			case '+':
+			case '-':
 				dlist_add(&TT.current_hunk, patchline);
 
 				if (*patchline != '+') oldlen--;
@@ -445,7 +457,20 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
 
 				// If we've consumed all expected hunk lines, apply the hunk.
 
-				if (!oldlen && !newlen) state = apply_one_hunk();
+				if (!oldlen && !newlen) {
+					// Peek ahead for '\ No newline at end of file', mark
+					// previous line, if it exists.
+					int c = getchar();
+					ungetc(c, stdin);
+					if (c == '\\') {
+						if (TT.current_hunk->prev)
+							TT.current_hunk->prev->no_newline = TRUE;
+						do {
+							c = getchar();
+						} while (c != EOF && c != '\n');
+					}
+					state = apply_one_hunk();
+				}
 				continue;
 			}
 			fail_hunk();
diff --git a/testsuite/patch.tests b/testsuite/patch.tests
index 1d48e90be..1a786aa71 100755
--- a/testsuite/patch.tests
+++ b/testsuite/patch.tests
@@ -287,6 +287,69 @@ bar
 2.9.2
 " \
 
+# testing "test name" "command(s)" "expected result" "file input" "stdin"
+testing "patch file with no last newline" \
+	'patch 2>&1; cat input' \
+"\
+patching file input
+first line
+last line with newline
+" \
+"\
+first line
+last line with no newline" \
+"\
+--- input
++++ input
+@@ -1,2 +1,2 @@
+ first line
+-last line with no newline
+\ No newline at end of file
++last line with newline
+" \
+
+# testing "test name" "command(s)" "expected result" "file input" "stdin"
+testing "patch -R to revert previous test" \
+	'patch -R 2>&1; cat input' \
+"\
+patching file input
+first line
+last line with no newline" \
+"\
+first line
+last line with newline
+" \
+"\
+--- input
++++ input
+@@ -1,2 +1,2 @@
+ first line
+-last line with no newline
+\ No newline at end of file
++last line with newline
+" \
+
+# testing "test name" "command(s)" "expected result" "file input" "stdin"
+testing "patch file so it has no last newline" \
+	'patch 2>&1; cat input' \
+"\
+patching file input
+first line
+last line with no newline" \
+"\
+first line
+last line with newline
+" \
+"\
+--- input
++++ input
+@@ -1,2 +1,2 @@
+ first line
+-last line with newline
++last line with no newline
+\ No newline at end of file
+" \
+
 rm input.orig 2>/dev/null
 
 exit $FAILCOUNT
-- 
2.53.0