1

Problem

Some versions of diff (notably, busybox diff in every build of BusyBox I've seen) only produce "unified" diff format output, they don't produce the output that GNU diff produces by default.

When I try to run diff-refine-hunk in a vc-diff buffer (git repository, if that matters), it fails, and prints the following error in the minibuffer:

Unexpected patch hunk header: --- /tmp/diff*

(Where the asterisk is some number of randomly generated characters, obviously.)

Question

Is there a way I can get diff-refine-hunk to handle a diff-command whose output is always just unified diffs?

Detailed Example

Steps

I start with this dummy file, version-controlled in git:

00 00
11 11
22 22
33 33
55 55
66 66
77 77
88 88
99 99
bb bb
cc cc
dd dd
ee ee
ff ff

I edit/replace it with this, and save it to disk (but don't stage/commit in git):

00 00
22 22
33 33
44 44
55 55
66 66
7x 77
88 x8
99 99
aa aa
bx bb
dd dd
ex ee
ff ff

Run vc-diff with C-x v =, so far so good.

Run diff-refine-hunk with C-c C-b. And if the invoked diff command is BusyBox diff (or GNU diff with the -u option) it fails with the "Unexpected patch hunk header" error. If the diff command is just regular GNU diff without the default output, it works fine.

Example Output emacs sees from various diffs

From what I've gathered since I first posted this, diff-refine-hunk runs diff-command on several temporary files (one for each hunk?), and parses the output from each to refine the hunks. What follows are the output diff-command produces for one of those hunks (the one with the "77" and "88" 'words'). In the case of the failure cases, this hunk is the one diff-refine-hunk stops at (in the success case, it keeps calling diff-command on other hunks).

GNU diff output

When called by diff-refine-hunk in the setup above, this isthe output of GNU diff (normal, successful case):

2,3c2,3
< 77
< 77
---
> 7
> x
12,13c12,13
< 88
< 88
---
> x
> 8

GNU diff -u output

When called by diff-refine-hunk in the setup above, this is the output of GNU diff -u (failure case):

--- /tmp/diff122272NoN  2016-11-26 00:43:54.796744201 +0000
+++ /tmp/diff222272ayT  2016-11-26 00:43:54.832744689 +0000
@@ -1,6 +1,6 @@
 -
-77
-77
+7
+x

 77
 77
@@ -9,6 +9,6 @@
 88
 88

-88
-88
+x
+8

BusyBox diff output

When called by diff-refine-hunk in the setup above, this is the output of Busybox diff (failure case - same output except no datetime in header):

--- /tmp/diff122272bly
+++ /tmp/diff222272NvB
@@ -1,6 +1,6 @@
 -
-77
-77
+7
+x

 77
 77
@@ -9,6 +9,6 @@
 88
 88

-88
-88
+x
+8

How I captured the above outputs

I created a script like this and pointed diff-command in emacs to it to test each variant and capture the outputs of each background run of diff (unlike a prior edit to this question, this script captures each diff-command run in a separate file, since diff-refine-hunk can call the diff backend program multiple times.

#!/bin/sh
exec busybox diff "$@" | tee "`mktemp /tmp/busybox_diff.XXXXXX`"
#exec diff -u "$@" | tee "`mktemp /tmp/gnu_diff-u.XXXXXX`"
#exec diff "$@" | tee "`mktemp /tmp/gnu_diff.XXXXXX`"

Question - Re-Worded

Is there a way to get diff-refine-hunk to work correctly when the underlying diff command that it calls in the background on temporary files always produces output in that unified format?

10
  • 2
    I suggest you M-x report-emacs-bug and give an actual problematic diff sample. The author of diff-mode (yours truly) mostly uses unified diffs, so this format should actually work better rather than worse.
    – Stefan
    Nov 25, 2016 at 23:23
  • 1
    A very quick look at diff-refine-hunk shows that it tries to detect the diff format by calling diff-hunk-style which in turn uses a fairly complex regexp diff-hunk-header-re. Is that not detecting the hunks from the busybox diff output as being in the unified style?
    – phils
    Dec 12, 2016 at 9:47
  • 1
    @mtraceur: Indeed I had mis-understood the problem. It's not a bug in diff-mode, but rather either a "misfeature" of busybox's diff, or a missing functionality in the refinement code (which is shared between diff-mode and smerge-mode).
    – Stefan
    Dec 12, 2016 at 12:53
  • 1
    Ah, that specific error Unexpected patch hunk header: --- /tmp/diff* appears to come from either smerge-refine-subst (seems likely) or smerge-apply-resolution-patch (identical code, presumably not the trigger in this case). A grep says no other elisp produces that error message. It is indeed parsing the output of a call to diff-command with no -u argument, and consequently expecting non-unified output, as you ascertained. I think there was some confusion over where the error was coming from, though.
    – phils
    Dec 13, 2016 at 4:51
  • 1
    @mtraceur: Indeed, I don't think Busybox would be interested in this use case (and since they support my favorite format, I think they made a pretty good decision). I see one option tho: supplement diff-mode with a diff-unified->normal (which isn't very high priority, but would be mildly useful in itself), and then use that in the refinement code when the output is in unified format.
    – Stefan
    Dec 13, 2016 at 13:06

1 Answer 1

1

Documenting my own crude solution for now, in case it helps anyone. I'll happily accept another answer with a better solution.

My solution took the form of two parts:

  1. Wrapper shell script around BusyBox's diff, converting its unified output into traditional/"normal" output. (It's now in one of the directories in my PATH as bnudiff. I put it on GitHub it here in case anyone wants to look).

  2. Emacs function advice around smerge-refine-subst (I initially wrapped diff-refine-hunk, but then learned from the comments that the actual refining function is the smerge one, and is presumably shared by one or more other modes as well) to make it use bnudiff instead of diff as the backend diff-command:

    (defun smerge-refine-subst-wrapper (smerge-refine-subst-real &rest arguments)
     (let ((diff-command "bnudiff"))
      (apply smerge-refine-subst-real arguments)
     )
    )
    (advice-add 'smerge-refine-subst :around #'smerge-refine-subst-wrapper)
    

This works perfectly (though I'm not in love with the inherently kludgey nature of it).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.