Re: [PATCH] patch: handle files with no final newline

Denys Vlasenko via busybox <[email protected]>
Newsgroups gmane.linux.busybox
Message-ID <CAK1hOcP_47i7pmE9z=eLRw=dQx=ntD_RS1vmfGocXQT0R9qPiA@mail.gmail.com>
Applied, thank you.

On Sun, Apr 19, 2026 at 11:03 AM Ron Yorston via busybox
<[email protected]> wrote:
>
> 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
>
> _______________________________________________
> busybox mailing list
> [email protected]
> https://lists.busybox.net/mailman/listinfo/busybox
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.