From: Johannes Schindelin Date: Sun, 07 Jun 2026 14:36:15 GMT Subject: Re: [PATCH v3 4/6] diff: add long-running diff process via diff..process Message-ID: In-Reply-To: Hi Michael, I stumbled about this patch when it broke CI in Git for Windows, where we do _not_ use `NO_PYTHON`, even though Python is unavailable in the build/test CI jobs. The existing tests handle this situation gracefully, this here patch does not: On Sun, 7 Jun 2026, Michael Montalbo via GitGitGadget wrote: > diff --git a/t/t4080-diff-process.sh b/t/t4080-diff-process.sh > new file mode 100755 > index 0000000000..f159cd86d8 > --- /dev/null > +++ b/t/t4080-diff-process.sh > @@ -0,0 +1,538 @@ > +#!/bin/sh > + > +test_description='diff process via long-running process' > + > +. ./test-lib.sh > + > +if test_have_prereq PYTHON > +then > + PYTHON_PATH=$(command -v python3) || PYTHON_PATH=$(command -v python) When neither `python3` nor `python` are available (which is the case in the minimal Git for Windows SDK used in Git's CI runs), this fails under `set -e`. Before even running the first test case. Resulting in an unexpected TAP format error. Now, we could "fix" this by imitating what `lib-p4` does (see https://github.com/dscho/git/commit/bd0b5570c744f678911a67a62da63f30655f20d8 which demonstrates that it is indeed a work-around on Windows): -- snip -- diff --git a/t/t4080-diff-process.sh b/t/t4080-diff-process.sh index fdf6da1c341e67..bd22c247ff3856 100755 --- a/t/t4080-diff-process.sh +++ b/t/t4080-diff-process.sh @@ -4,9 +4,10 @@ test_description='diff process via long-running process' . ./test-lib.sh -if test_have_prereq PYTHON +if ! test_have_prereq PYTHON || ! test -x "$PYTHON_PATH" then - PYTHON_PATH=$(command -v python3) || PYTHON_PATH=$(command -v python) + skip_all='python interpreter not available' + test_done fi # -- snap -- Of course, this uncovers _another_ problem with the Python script: It uses Python3-only `f"..."` format strings, which cannot be handled by the Python2 to which the `PYTHON_PATH` variable in `linux-TEST-vars` points. So this requires _another follow-up (see also https://github.com/dscho/git/commit/c12a9f4c80e5ce8db0fe370fac46fb45be2b775f): -- snip -- diff --git a/t/t4080-diff-process.sh b/t/t4080-diff-process.sh index bd22c247ff3856..ba14682a9086e4 100755 --- a/t/t4080-diff-process.sh +++ b/t/t4080-diff-process.sh @@ -39,7 +39,8 @@ setup_backend () { def write_pkt(line): data = (line + "\n").encode() - sys.stdout.buffer.write(f"{len(data)+4:04x}".encode() + data) + hdr = "{:04x}".format(len(data) + 4).encode() + sys.stdout.buffer.write(hdr + data) sys.stdout.buffer.flush() def write_flush(): @@ -98,7 +99,8 @@ setup_backend () { new = read_content() old_first = old.split(b"\n")[0].decode(errors="replace") if old else "" new_first = new.split(b"\n")[0].decode(errors="replace") if new else "" - log(f"command={cmd} pathname={pathname} old={old_first} new={new_first}") + log("command={} pathname={} old={} new={}".format( + cmd, pathname, old_first, new_first)) if mode == "error": write_flush() @@ -130,7 +132,7 @@ setup_backend () { else: ol = old.count(b"\n") nl = new.count(b"\n") - write_pkt(f"hunk 1 {ol} 1 {nl}") + write_pkt("hunk 1 {} 1 {}".format(ol, nl)) write_flush() write_pkt("status=success") write_flush() -- snap -- And this is still not enough to make it work with Python2, see https://github.com/dscho/git/actions/runs/27091523842/job/79955895737: -- snip -- [...] + git -c diff.cdiff.process=./diff-process-backend --mode=fixed-hunk diff boundary.c Traceback (most recent call last): File "/__w/git/git/t/trash directory.t4080-diff-process/diff-process-backend.py", line 45, in assert read_pkt() == "git-diff-client" File "/__w/git/git/t/trash directory.t4080-diff-process/diff-process-backend.py", line 4, in read_pkt hdr = sys.stdin.buffer.read(4) AttributeError: 'file' object has no attribute 'buffer' -- snap -- I have experienced similar patterns in my career, where a single decision required multiple follow-up fixes _just_ to avoid having to revert that decision. This kind of doubling down has never ended well. Therefore I would like to take a step back, and ask: Is it _really_ a good idea to use Python here? Are we certain that we want to _require_ Python to run this test and skip it if Python isn't available (as is the case in the Windows-related parts of Git's very own CI) even if Python has nothing at all to do with the feature that is being tested? I don't want to be doomed to repeat history, and we can very well learn e.g. from prior art in this very project, where the tests for the clean/smudge filters (which _also_ want to speak pkt-line over stdio) needlessly incurred Perl as a requirement to run the tests. It was Matheus's heroic work in 52917a998ef3a (t0021: implementation the rot13-filter.pl script in C, 2022-08-14) and 4d1d843be7a15 (tests: use the new C rot13-filter helper to avoid PERL prereq, 2022-08-14) that avoided that unnecessary prerequisite. Likewise, there is `test-tool pkt-line` intended for driving the pkt-line protocol via simple shell scripts. So the conscious project direction has been: fold pkt-line test backends into `test-tool` and drop the scripting-language prereq. Reintroducing the same shape in Python would walk this back. Patrick's careful effort in 27bd8ee311719 (Merge branch 'ps/fewer-perl', 2025-04-29) has been another clear sign that the Git project is actively _removing_ scripting-language dependencies from the build and test infrastructure, not adding new ones. The clear prior art in Git's own tests for what t4080 wants to do, as of today, is `t/helper/test-rot13-filter.c`, which could be imitated here instead of (re-)introducing a dependency on a scripting language other than Unix shell in Git's test suite. The `PYTHON` prereq exists in exactly five files today, all `git p4` related (where Python is an intrinsic prerequisite given that `git-p4.py` _is_ written in Python): `t/lib-git-p4.sh`, `t/t9802-git-p4-filetype.sh`, `t/t9810-git-p4-rcs.sh`, `t/t9835-git-p4-metadata-encoding-python2.sh`, and `t/t9836-git-p4-metadata-encoding-python3.sh`. After 7cdbff14d482 (remove merge-recursive-old, 2006-11-20), this here patch would be the first one, after almost 20 years, to re-introduce Python as a dependency outside `git p4`. And it would also be the first ever to embed a Python script as a heredoc: > +fi > + > +# > +# A single parametric diff process. > +# Usage: diff-process-backend --mode= [--log=] > +# > +# Modes: > +# whole-file - report all lines as changed (default) > +# fixed-hunk - always report hunk 5 2 5 2 > +# bad-hunk - report out-of-bounds hunk 999 1 999 1 > +# bad-sync - report hunk with mismatched unchanged totals > +# overlap - report two overlapping hunks > +# no-hunks - return no hunks (files considered equivalent) > +# error - return status=error for every request > +# abort - return status=abort for every request > +# crash - read one request then exit without responding > +# > +setup_backend () { > + cat >"$TRASH_DIRECTORY/diff-process-backend.py" <<-\PYEOF > + import sys, os > + > + def read_pkt(): > + hdr = sys.stdin.buffer.read(4) > + if len(hdr) < 4: return None > + length = int(hdr, 16) > + if length == 0: return "" > + data = sys.stdin.buffer.read(length - 4) > + return data.decode().rstrip("\n") > + > + def write_pkt(line): > + data = (line + "\n").encode() > + sys.stdout.buffer.write(f"{len(data)+4:04x}".encode() + data) > + sys.stdout.buffer.flush() > + > + def write_flush(): > + sys.stdout.buffer.write(b"0000") > + sys.stdout.buffer.flush() > + > + def read_content(): > + chunks = [] > + while True: > + hdr = sys.stdin.buffer.read(4) > + if len(hdr) < 4: break > + length = int(hdr, 16) > + if length == 0: break > + chunks.append(sys.stdin.buffer.read(length - 4)) > + return b"".join(chunks) > + > + mode = "whole-file" > + logfile = None > + for arg in sys.argv[1:]: > + if arg.startswith("--mode="): > + mode = arg[7:] > + elif arg.startswith("--log="): > + logfile = open(arg[6:], "a") > + > + def log(msg): > + if logfile: > + logfile.write(msg + "\n") > + logfile.flush() > + > + # Handshake > + assert read_pkt() == "git-diff-client" > + assert read_pkt() == "version=1" > + read_pkt() > + write_pkt("git-diff-server") > + write_pkt("version=1") > + write_flush() > + while True: > + p = read_pkt() > + if p == "": break > + write_pkt("capability=hunks") > + write_flush() > + > + log("ready") > + > + while True: > + cmd = None > + pathname = None > + while True: > + p = read_pkt() > + if p is None: sys.exit(0) > + if p == "": break > + if p.startswith("command="): cmd = p.split("=",1)[1] > + if p.startswith("pathname="): pathname = p.split("=",1)[1] > + if cmd is None: sys.exit(0) > + old = read_content() > + new = read_content() > + old_first = old.split(b"\n")[0].decode(errors="replace") if old else "" > + new_first = new.split(b"\n")[0].decode(errors="replace") if new else "" > + log(f"command={cmd} pathname={pathname} old={old_first} new={new_first}") > + > + if mode == "error": > + write_flush() > + write_pkt("status=error") > + write_flush() > + continue > + > + if mode == "abort": > + write_flush() > + write_pkt("status=abort") > + write_flush() > + continue > + > + if mode == "crash": > + sys.exit(1) > + > + if cmd == "hunks": > + if mode == "fixed-hunk": > + write_pkt("hunk 5 2 5 2") > + elif mode == "bad-hunk": > + write_pkt("hunk 999 1 999 1") > + elif mode == "bad-sync": > + write_pkt("hunk 1 2 1 1") > + elif mode == "overlap": > + write_pkt("hunk 1 5 1 5") > + write_pkt("hunk 3 2 3 2") > + elif mode == "no-hunks": > + pass > + else: > + ol = old.count(b"\n") > + nl = new.count(b"\n") > + write_pkt(f"hunk 1 {ol} 1 {nl}") > + write_flush() > + write_pkt("status=success") > + write_flush() > + else: > + write_flush() > + write_pkt("status=error") > + write_flush() > + PYEOF The existing pattern is to provide larger scripts as fixtures in associated `t/tNNNN/` directories, not as heredoc, see e.g. `t/t1509/prepare-chroot.sh`. Writing scripts, especially lengthy ones, in heredoc strings makes it virtually impossible to use static code analysis or syntax highlighting to fend off banal errors. Given the complexity of what t4080 tries to test (error, abort, crash, bad-sync, no-hunks, multiple files in one session, capability negotiation), it would unfortunately be infeasible to use `test-tool pkt-line` from a shell script implementing that `diff.*.process` protocol. So I've spiked a demo how the `test-tool diff-process-backend` could look like (letting Opus do the menial typing, so that I can enjoy at least part of a sunny Sunday outside), which also passes the CI build and test: https://github.com/dscho/git/commit/b6e3c93381b00929476c3a00155f7cf7334a22e6 That commit is of course not intended to be used as-is; Feel free to pick code parts of it and integrate them into your topic branch. Or write your own test-tool helper from scratch if that's more your jam. Ciao, Johannes > + write_script diff-process-backend <<-SHEOF > + exec "$PYTHON_PATH" "$TRASH_DIRECTORY/diff-process-backend.py" "\$@" > + SHEOF > +} > + > +BACKEND="./diff-process-backend" > + > +test_expect_success PYTHON 'setup' ' > + setup_backend && > + echo "*.c diff=cdiff" >.gitattributes && > + git add .gitattributes && > + > + # boundary.c: 10 lines, changes at 5-6 and 9-10. > + # Used by: hunk boundaries, error fallback, crash, bad hunks, overlap. > + cat >boundary.c <<-\EOF && > + line1 > + line2 > + line3 > + line4 > + OLD5 > + OLD6 > + line7 > + line8 > + OLD9 > + OLD10 > + EOF > + git add boundary.c && > + > + # worddiff.c: single-line function, value changes 1 -> 999. > + # Used by: word-diff, --diff-algorithm, --no-ext-diff, --stat. > + cat >worddiff.c <<-\EOF && > + int value(void) { return 1; } > + EOF > + git add worddiff.c && > + > + # newfile.c: single-line function, value changes 42 -> 99. > + # Used by: new file, --exit-code, multiple drivers. > + cat >newfile.c <<-\EOF && > + int new_func(void) { return 42; } > + EOF > + git add newfile.c && > + > + # logtest.c: single-line function for log/format-patch tests. > + # Needs two commits so log -1 has a diff. > + cat >logtest.c <<-\EOF && > + int logfunc(void) { return 1; } > + EOF > + git add logtest.c && > + > + # two.c/one.c: two-file pair for error/abort/startup-failure tests. > + cat >one.c <<-\EOF && > + int first(void) { return 1; } > + EOF > + cat >two.c <<-\EOF && > + int second(void) { return 2; } > + EOF > + git add one.c two.c && > + > + git commit -m "initial" && > + > + # Second commit for logtest.c (so log -1 has something to show). > + cat >logtest.c <<-\EOF && > + int logfunc(void) { return 2; } > + EOF > + git add logtest.c && > + git commit -m "change logtest.c" && > + > + # Working tree modifications (not committed). > + cat >boundary.c <<-\EOF && > + line1 > + line2 > + line3 > + line4 > + NEW5 > + NEW6 > + line7 > + line8 > + NEW9 > + NEW10 > + EOF > + > + cat >worddiff.c <<-\EOF && > + int value(void) { return 999; } > + EOF > + > + cat >newfile.c <<-\EOF && > + int new_func(void) { return 99; } > + EOF > + > + cat >one.c <<-\EOF && > + int first(void) { return 10; } > + EOF > + > + cat >two.c <<-\EOF > + int second(void) { return 20; } > + EOF > +' > + > +# > +# Core behavior: the tool controls which lines are marked as changed. > +# > + > +test_expect_success PYTHON 'diff process hunk boundaries affect output' ' > + # The file has changes at lines 5-6 and 9-10, but fixed-hunk > + # only reports lines 5-6 as changed. Lines 9-10 should not > + # appear as changed in the output. > + git -c diff.cdiff.process="$BACKEND --mode=fixed-hunk" \ > + diff boundary.c >actual && > + test_grep "^-OLD5" actual && > + test_grep "^-OLD6" actual && > + test_grep "^+NEW5" actual && > + test_grep "^+NEW6" actual && > + test_grep ! "^-OLD9" actual && > + test_grep ! "^-OLD10" actual && > + test_grep ! "^+NEW9" actual && > + test_grep ! "^+NEW10" actual > +' > + > +test_expect_success PYTHON 'diff process works with new file' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff -- newfile.c >actual 2>stderr && > + test_grep "return 99" actual && > + test_grep "pathname=newfile.c" backend.log && > + test_must_be_empty stderr > +' > + > +test_expect_success PYTHON 'diff process works with added file (empty old side)' ' > + cat >added.c <<-\EOF && > + int added(void) { return 1; } > + EOF > + git add added.c && > + > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff --cached -- added.c >actual 2>stderr && > + test_grep "added" actual && > + test_grep "pathname=added.c" backend.log && > + test_must_be_empty stderr > +' > + > +test_expect_success PYTHON 'diff process skipped for binary files' ' > + printf "\\0binary" >binary.c && > + git add binary.c && > + git commit -m "add binary" && > + printf "\\0changed" >binary.c && > + > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff -- binary.c >actual && > + test_grep "Binary files" actual && > + test_path_is_missing backend.log > +' > + > +test_expect_success PYTHON 'diff process not consulted for unmatched driver' ' > + echo "not tracked by cdiff" >unmatched.txt && > + git add unmatched.txt && > + git commit -m "add unmatched.txt" && > + > + echo "modified" >unmatched.txt && > + > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff -- unmatched.txt >actual && > + test_grep "modified" actual && > + test_path_is_missing backend.log > +' > + > +test_expect_success PYTHON 'multiple drivers use separate processes' ' > + echo "*.h diff=hdiff" >>.gitattributes && > + git add .gitattributes && > + > + cat >multi.h <<-\EOF && > + int header(void) { return 1; } > + EOF > + git add multi.h && > + git commit -m "add multi.h" && > + > + cat >multi.h <<-\EOF && > + int header(void) { return 2; } > + EOF > + > + rm -f backend-c.log backend-h.log && > + git -c diff.cdiff.process="$BACKEND --log=backend-c.log" \ > + -c diff.hdiff.process="$BACKEND --log=backend-h.log" \ > + diff -- newfile.c multi.h >actual 2>stderr && > + test_grep "pathname=newfile.c" backend-c.log && > + test_grep "pathname=multi.h" backend-h.log && > + test_must_be_empty stderr > +' > + > +test_expect_success PYTHON 'diff process works alongside textconv' ' > + write_script uppercase-filter <<-\EOF && > + tr "a-z" "A-Z" <"$1" > + EOF > + > + cat >textconv.c <<-\EOF && > + hello world > + EOF > + git add textconv.c && > + git commit -m "add textconv.c" && > + > + cat >textconv.c <<-\EOF && > + goodbye world > + EOF > + > + rm -f backend.log && > + git -c diff.cdiff.textconv="./uppercase-filter" \ > + -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff -- textconv.c >actual 2>stderr && > + # The diff process receives textconv-transformed (uppercase) content. > + test_grep "pathname=textconv.c" backend.log && > + test_grep "old=HELLO WORLD" backend.log && > + test_grep "new=GOODBYE WORLD" backend.log && > + test_must_be_empty stderr > +' > + > +# > +# Downstream features: word diff, log, equivalent files, exit code. > +# > + > +test_expect_success PYTHON 'diff process with --word-diff' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff --word-diff worddiff.c >actual 2>stderr && > + test_grep "\[-1;-\]" actual && > + test_grep "{+999;+}" actual && > + test_grep "pathname=worddiff.c" backend.log && > + test_must_be_empty stderr > +' > + > +test_expect_success PYTHON 'diff process works with git log -p' ' > + # With no-hunks mode, the tool says the files are equivalent, > + # so log -p should show the commit but no diff content. > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --mode=no-hunks --log=backend.log" \ > + log -1 -p -- logtest.c >actual 2>stderr && > + test_grep "change logtest.c" actual && > + test_grep ! "return 2" actual && > + test_grep "command=hunks pathname=logtest.c" backend.log && > + test_must_be_empty stderr > +' > + > +test_expect_success PYTHON 'diff process no hunks suppresses diff output' ' > + cat >nohunks.c <<-\EOF && > + int zero(void) { return 0; } > + EOF > + git add nohunks.c && > + git commit -m "add nohunks.c" && > + > + cat >nohunks.c <<-\EOF && > + int zero(void) { return 999; } > + EOF > + > + git -c diff.cdiff.process="$BACKEND --mode=no-hunks" \ > + diff nohunks.c >actual && > + test_must_be_empty actual > +' > + > +test_expect_success PYTHON 'diff process no hunks with --exit-code returns success' ' > + git -c diff.cdiff.process="$BACKEND --mode=no-hunks" \ > + diff --exit-code nohunks.c > +' > + > +test_expect_success PYTHON 'diff process with --exit-code and hunks returns failure' ' > + test_expect_code 1 git -c diff.cdiff.process="$BACKEND" \ > + diff --exit-code newfile.c > +' > + > +# > +# Bypass mechanisms: flags and commands that skip the diff process. > +# > + > +test_expect_success PYTHON 'diff process bypassed by --diff-algorithm' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff --diff-algorithm=patience worddiff.c >actual && > + test_grep "return 999" actual && > + test_path_is_missing backend.log > +' > + > +test_expect_success PYTHON 'diff process not used by --stat' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --log=backend.log" \ > + diff --stat worddiff.c >actual && > + test_grep "worddiff.c" actual && > + test_path_is_missing backend.log > +' > + > +# > +# Error handling and fallback. > +# > + > +test_expect_success PYTHON 'diff process fallback on tool error status' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --mode=error --log=backend.log" \ > + diff boundary.c >actual 2>stderr && > + # Fallback produces the full builtin diff (both change regions). > + test_grep "^-OLD5" actual && > + test_grep "^+NEW5" actual && > + test_grep "^-OLD9" actual && > + test_grep "^+NEW9" actual && > + # Tool was contacted (it replied with error, not crash). > + test_grep "command=hunks pathname=boundary.c" backend.log && > + test_grep "diff process.*failed" stderr > +' > + > +test_expect_success PYTHON 'diff process error keeps tool available for next file' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --mode=error --log=backend.log" \ > + diff -- one.c two.c >actual 2>stderr && > + # Unlike abort, error keeps the tool available: both files > + # are sent to the tool (and both fall back). > + test_grep "pathname=one.c" backend.log && > + test_grep "pathname=two.c" backend.log && > + test_grep "return 10" actual && > + test_grep "return 20" actual > +' > + > +test_expect_success PYTHON 'diff process abort disables for session' ' > + rm -f backend.log && > + git -c diff.cdiff.process="$BACKEND --mode=abort --log=backend.log" \ > + diff -- one.c two.c >actual && > + # Both files should still produce diff output via fallback. > + test_grep "return 10" actual && > + test_grep "return 20" actual && > + # The tool aborts on the first file and git clears its > + # capability. The second file never contacts the tool. > + test_grep "pathname=one.c" backend.log && > + test_grep ! "pathname=two.c" backend.log > +' > + > +test_expect_success PYTHON 'diff process fallback on tool crash' ' > + git -c diff.cdiff.process="$BACKEND --mode=crash" \ > + diff boundary.c >actual 2>stderr && > + test_grep "^-OLD5" actual && > + test_grep "^+NEW5" actual && > + test_grep "^-OLD9" actual && > + test_grep "^+NEW9" actual && > + # Crash is a communication failure, so a warning is emitted. > + test_grep "diff process.*failed" stderr > +' > + > +test_expect_success PYTHON 'diff process startup failure only warns once' ' > + git -c diff.cdiff.process="/nonexistent/tool" \ > + diff -- one.c two.c >actual 2>stderr && > + # Both files produce diff output via fallback. > + test_grep "return 10" actual && > + test_grep "return 20" actual && > + # Sentinel prevents repeated warnings: only one, not one per file. > + test_grep "diff process.*failed" stderr >warnings && > + test_line_count = 1 warnings > +' > + > +test_expect_success PYTHON 'diff process fallback on bad hunks' ' > + git -c diff.cdiff.process="$BACKEND --mode=bad-hunk" \ > + diff boundary.c >actual 2>stderr && > + test_grep "^-OLD5" actual && > + test_grep "^+NEW5" actual && > + test_grep "^-OLD9" actual && > + test_grep "^+NEW9" actual && > + # Invalid hunks are caught by xdiff validation, not the > + # protocol layer, so no warning is emitted. > + test_must_be_empty stderr > +' > + > +test_expect_success PYTHON 'diff process fallback on mismatched unchanged totals' ' > + cat >synctest.c <<-\EOF && > + line1 > + line2 > + line3 > + EOF > + git add synctest.c && > + git commit -m "add synctest.c" && > + > + cat >synctest.c <<-\EOF && > + line1 > + changed > + line3 > + EOF > + > + # bad-sync reports hunk 1 2 1 1: marks 2 old lines and 1 new > + # line as changed, leaving 1 unchanged old vs 2 unchanged new. > + # The synchronization invariant fails and git falls back. > + git -c diff.cdiff.process="$BACKEND --mode=bad-sync" \ > + diff synctest.c >actual 2>stderr && > + test_grep "changed" actual > +' > + > +test_expect_success PYTHON 'diff process fallback on overlapping hunks' ' > + # boundary.c has 10 lines, so both hunks are in bounds > + # but they overlap at lines 3-5, triggering the ordering check. > + git -c diff.cdiff.process="$BACKEND --mode=overlap" \ > + diff boundary.c >actual 2>stderr && > + test_grep "NEW5" actual > +' > + > +test_done > diff --git a/userdiff.h b/userdiff.h > index 51c26e0d41..a98eabe377 100644 > --- a/userdiff.h > +++ b/userdiff.h > @@ -3,6 +3,7 @@ > > #include "notes-cache.h" > > +struct diff_subprocess; > struct index_state; > struct repository; > > @@ -33,6 +34,8 @@ struct userdiff_driver { > int textconv_want_cache; > const char *process; > char *process_owned; > + struct diff_subprocess *diff_subprocess; > + unsigned diff_process_failed : 1; > }; > enum userdiff_driver_type { > USERDIFF_DRIVER_TYPE_BUILTIN = 1<<0, > -- > gitgitgadget > > >