mirror of
https://github.com/git/git.git
synced 2026-09-28 17:12:46 +02:00
The provider chain so far holds the diff-hunks store in front of the terminal builtin computation. Open it to external processes: a pair on a path whose driver configures diff.<driver>.process is answered by a long-running process speaking a pkt-line protocol (following the filter process protocol), registered at the head of the chain and consulted before the store and before any blob is loaded. The protocol starts with the smallest request that can carry an answer: object names alone. A request is the pathname and the pair's old-oid/new-oid, with no content. The process answers with hunk lines, with a zero-hunk success that asserts the blobs equivalent (trailing newlines included), or with status=need-content, on which the pair falls through to the builtin answer. This serves the two shapes that need no content pushed to them: a cache keyed on the blob pair, and a process that fetches the blobs itself (for example over "git cat-file --batch"). A pair whose side is not a stored blob carries a NULL id; the provider sends no request and passes it. Because Git holds no content for the exchange, the answer is used as sent: hunks are validated for order, overlap, lockstep alignment, and magnitude, then replayed without the normalization xdiff applies to diffs it computes itself. The magnitude bound is the blobs' sizes, read from the object database without loading content: a blob of N bytes holds at most N lines. Because the process's answer is authoritative, it outranks the store, and its head-of-chain position says so. A pair the process answers never reaches the store and is never recorded, so nothing it produces enters the store, which holds the builtin answer only. A request it does not answer, whether need-content, a missing capability, or a missing id, passes down the chain to the builtin answer, which is what the store serves, so the store may serve such a pair and a warming run may record it. Entries recorded before a process was configured are not purged; a pair the process answers ignores them, and "git diff-hunks clear" discards them. The provider gates itself per request. The driver is looked up by the old-side path, so a renamed file resolves to the same driver, and by the repository-relative path, so a diff.relative run from a subdirectory names the pair the same way. Options the process is never told about select no process: the whitespace-ignoring options, -I, --anchored, and an algorithm forced by option or configuration (blame routes its algorithm through xdl_opts, so --histogram is covered). The request gains its last field, the path; the consumers change only by filling it, and neither names the process. The provider's state is its repository's pool of running processes, keyed by the configured command, so drivers sharing a command share a process, a submodule speaks to its own, and releasing the provider (from repo_clear()) stops them. The pool owns a copy of each command string, so an entry outlives a config re-read. A command that fails stays as an entry that is not retried: its request and every later one pass, so the store may serve the path for the rest of the command. A protocol error in a response never kills the command. The response is read through a packet reader gentle about framing, so an error takes one path: a single warning, the process stopped and marked failed, and the builtin diff for the rest of the command. That covers garbage bytes, a truncated response, an empty packet, a bare status, and an unrecognized status. Semantically invalid coordinates cost only their pair: the response is drained, the pair is computed, and the process stays alive. A path the protocol cannot carry (an embedded newline, or one too long for a packet) falls back per path rather than costing the command its process. The handshake keeps one fatal check: a process that announces a capability Git did not request aborts the command, as the long-running filter protocol does. Consulting is allowed per command, following the allow_textconv precedent. "git diff", "git log" and "git show", and "git blame" set allow_diff_process; the plumbing diff commands and the interactive-patch machinery never set it, so scripted and staging output stays builtin. The options adjust the flag: - --no-ext-diff clears it and --ext-diff sets it; - --diff-process and --no-diff-process set and clear it alone, leaving external diff drivers as they were; - format-patch clears it unconditionally, so a generated patch applies for recipients without the process; - range-diff passes --no-ext-diff to the "git log" it compares. git blame and the summary formats consult the process. For blame, a pair reported equivalent emits no hunks, so the whole commit passes to its parent. In the stat formats such a pair sums to a zero-count entry, which the "nothing changed" rule omits, as under -w. The subprocess is long-running: one startup cost across a traversal, one round-trip per consulted pair. Answers travel in struct xdl_hunk, new in xdiff-interface.h, holding xdiff's 1-based coordinates; nothing feeds them back to xdiff, since only coordinate consumers consult. A content-carrying request is the natural extension: it would serve sides that are not stored blobs and processes that want content pushed to them, and bring patch output and log -L's range tracking to the same answer. As it stands, a process's answers show in blame and the summary formats while patch output stays builtin. t4080 exercises the protocol, the per-command gate, and the error paths: - each adversarial response shape warns and falls back to builtin, the request log proving which failures disable the process and which keep it alive (a malformed hunk line, coordinates past the blob size, a count overflowing strtol(), overlapping or misaligned hunks, an unrecognized status, a bare status, an empty packet, a mid-response crash, and raw garbage); - a capability-less process and status=abort degrade without noise, and a failed start warns once and returns the path to the store; - a trailing token on a hunk line is ignored, pinning field appendability; - positive consults for git diff, git show, and diff-tree under --ext-diff and --diff-process; textconv output and gitlink sides are never identified; a diff.relative run consults by the repo-relative path; - the equivalence answer is pinned from both consumers, and a warming run past a deferring process records the pair for a later read. Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Michael Montalbo <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
594 lines
23 KiB
Bash
Executable File
594 lines
23 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
test_description='diff.<driver>.process: oid-only hunk requests'
|
|
|
|
TEST_PASSES_SANITIZE_LEAK=true
|
|
. ./test-lib.sh
|
|
|
|
# See t/helper/test-diff-process-backend.c for the process implementation
|
|
# and available --mode= options.
|
|
|
|
BACKEND="test-tool diff-process-backend"
|
|
|
|
test_expect_success 'setup' '
|
|
echo "*.c diff=cdiff" >.gitattributes &&
|
|
git add .gitattributes &&
|
|
|
|
# 10 lines, changes at 5-6 and 9-10 between the two commits.
|
|
cat >pair.c <<-\EOF &&
|
|
line1
|
|
line2
|
|
line3
|
|
line4
|
|
original5
|
|
original6
|
|
line7
|
|
line8
|
|
line9
|
|
line10
|
|
EOF
|
|
git add pair.c &&
|
|
git commit -m "add pair.c" &&
|
|
|
|
cat >pair.c <<-\EOF &&
|
|
line1
|
|
line2
|
|
line3
|
|
line4
|
|
changed5
|
|
changed6
|
|
line7
|
|
line8
|
|
changed9
|
|
changed10
|
|
EOF
|
|
git add pair.c &&
|
|
git commit -m "change pair.c"
|
|
'
|
|
|
|
test_expect_success 'an oid-capable process answers blame by object names alone' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
ORIG=$(git rev-parse --short HEAD~1) &&
|
|
CHANGE=$(git rev-parse --short HEAD) &&
|
|
# The process reports only lines 5-6 as changed, so blame attributes
|
|
# lines 9-10 to the original commit even though the builtin diff
|
|
# would show them as changed.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
blame pair.c >actual &&
|
|
sed -n "9p" actual >line9 &&
|
|
sed -n "10p" actual >line10 &&
|
|
test_grep "$ORIG" line9 &&
|
|
test_grep "$ORIG" line10 &&
|
|
sed -n "5p" actual >line5 &&
|
|
test_grep "$CHANGE" line5 &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'an oid-capable process answers --numstat by object names alone' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
log -1 --format= --numstat -- pair.c >actual &&
|
|
printf "2\t2\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'need-content falls through to the builtin diff' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-need-content --log=backend.log" \
|
|
log -1 --format= --numstat -- pair.c >actual &&
|
|
printf "4\t4\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'a warmed hunk store does not override process hunks' '
|
|
test_when_finished "git diff-hunks clear" &&
|
|
ORIG=$(git rev-parse --short HEAD~1) &&
|
|
GIT_DIFF_HUNKS_WRITE=1 git log -2 --stat -- pair.c >/dev/null &&
|
|
|
|
# Control: without a process, blame is served from the store.
|
|
git blame --show-stats pair.c >stats &&
|
|
test_grep "num precomputed hits: 1" stats &&
|
|
|
|
# The store holds the builtin hunks, but a process-capable driver
|
|
# makes the process authoritative, so blame must reflect the
|
|
# process hunks (only lines 5-6), not a store hit.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed" \
|
|
blame pair.c >actual &&
|
|
sed -n "9p" actual >line9 &&
|
|
test_grep "$ORIG" line9
|
|
'
|
|
|
|
test_expect_success 'a worktree side is not asked by object names' '
|
|
test_when_finished "rm -f backend.log && git checkout -- pair.c" &&
|
|
echo "worktree edit" >>pair.c &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
diff --numstat -- pair.c >actual &&
|
|
printf "1\t0\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'diff process bypassed by --no-ext-diff' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
log -1 --format= --numstat --no-ext-diff -- pair.c >actual &&
|
|
printf "4\t4\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'format-patch keeps its diffstat off the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
# format-patch emits a diffstat, and a diffstat consults the
|
|
# process, but the gate keeps it builtin so a generated patch
|
|
# applies for recipients without the process.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
format-patch -1 --stdout --stat -- pair.c >actual &&
|
|
test_grep "^+changed9" actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'format-patch --ext-diff keeps its diffstat off the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
# The gate holds even when --ext-diff enables the external command.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
format-patch -1 --stdout --ext-diff --stat -- pair.c >actual &&
|
|
test_grep "^+changed9" actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'diff process not consulted by plumbing diff commands' '
|
|
test_when_finished "rm -f backend.log && git checkout -f HEAD -- pair.c" &&
|
|
# diff-tree diffs the two commits, a real pair a defeated gate would
|
|
# consult the process for.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
diff-tree --numstat HEAD >actual &&
|
|
test_grep "pair.c" actual &&
|
|
# diff-index needs a change to diff, or there is no pair and a
|
|
# missing log proves nothing; stage one and diff it against HEAD.
|
|
echo "staged change" >>pair.c &&
|
|
git add pair.c &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
diff-index --cached --numstat HEAD -- pair.c >actual &&
|
|
test_grep "pair.c" actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'add -p stages from the builtin diff with a process configured' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
cat >gate.c <<-\EOF &&
|
|
int gate(void) { return 1; }
|
|
EOF
|
|
git add gate.c &&
|
|
git commit -m "add gate.c" &&
|
|
cat >gate.c <<-\EOF &&
|
|
int gate(void) { return 2; }
|
|
EOF
|
|
# add -p builds its hunks from patch text, which is not a provider
|
|
# consumer today, so a configured process cannot shape what it
|
|
# offers. This pins that interactive patch stays builtin for the
|
|
# current consumers, rather than exercising the plumbing gate.
|
|
test_write_lines y |
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
add -p gate.c &&
|
|
git diff --cached -- gate.c >staged &&
|
|
test_grep "return 2" staged &&
|
|
test_path_is_missing backend.log &&
|
|
git commit -m "gate.c v2"
|
|
'
|
|
|
|
test_expect_success 'blame withholds identity for the working-tree pair' '
|
|
test_when_finished "rm -f backend.log && git checkout -- pair.c" &&
|
|
echo "uncommitted" >>pair.c &&
|
|
wt=$(git hash-object pair.c) &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
blame pair.c >actual &&
|
|
# The dirty working-tree side is not a stored blob: no request
|
|
# may name its bytes by object id.
|
|
test_grep ! "new-oid=$wt" backend.log
|
|
'
|
|
|
|
test_expect_success 'a replaced blob makes the process step aside' '
|
|
new_blob=$(git rev-parse HEAD:pair.c) &&
|
|
test_when_finished "rm -f backend.log && git replace -d $new_blob" &&
|
|
# Replacing the new-side blob redirects the content the diff reads
|
|
# under the id the process would be sent, so a raw-id request would
|
|
# name bytes other than the ones diffed. Identity is withheld: the
|
|
# process is not consulted and the builtin computes the pair from
|
|
# the replaced content.
|
|
repl=$(printf "just one line\n" | git hash-object -w --stdin) &&
|
|
git replace "$new_blob" "$repl" &&
|
|
git log -1 --format= --numstat -- pair.c >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
log -1 --format= --numstat -- pair.c >actual &&
|
|
test_cmp expect actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'an equivalence answer omits the pair from the stat' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-equal --log=backend.log" \
|
|
log -1 --format= --numstat -- pair.c >actual &&
|
|
# An equivalent pair sums to a zero-count entry, and the stat
|
|
# code omits zero-count modified entries, the same way a
|
|
# whitespace-only pair prints nothing under -w. The builtin
|
|
# diff would print nonzero counts here, and the log proves the
|
|
# process was consulted.
|
|
test_must_be_empty actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'blame passes equivalent pairs through to the boundary' '
|
|
ORIG=$(git rev-parse --short ":/add pair.c") &&
|
|
# The process asserts every consulted pair equal, so no line is
|
|
# ever treated as changed: every line passes through to the
|
|
# commit that added the file, marked as the blame boundary.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-equal" \
|
|
blame pair.c >actual &&
|
|
sed -n "5p" actual >line5 &&
|
|
test_grep "^\^$ORIG" line5 &&
|
|
sed -n "10p" actual >line10 &&
|
|
test_grep "^\^$ORIG" line10
|
|
'
|
|
|
|
test_expect_success 'a warming run records a pair the process defers' '
|
|
test_when_finished "git diff-hunks clear" &&
|
|
git diff-hunks clear &&
|
|
# The process owns the path but defers this pair with
|
|
# need-content, so the pair gets the builtin diff; that is the
|
|
# result the store holds, so the warming run records it and a
|
|
# later read is served from the store.
|
|
GIT_DIFF_HUNKS_WRITE=1 git -c core.diffHunks=true \
|
|
-c diff.cdiff.process="$BACKEND --mode=oid-need-content" \
|
|
log -1 --format= --stat -- pair.c >/dev/null &&
|
|
git -c core.diffHunks=true blame --show-stats pair.c >stats 2>&1 &&
|
|
test_grep "num precomputed hits: 1" stats
|
|
'
|
|
|
|
# The protocol error paths: each adversarial response shape must warn,
|
|
# fall back to the builtin output, and either keep the process alive
|
|
# (a per-pair rejection) or disable it for the rest of the command (a
|
|
# protocol error). The request log tells the two apart: the log walk
|
|
# below consults two pairs (gate.c first, then pair.c), so a disabled
|
|
# process shows one logged request and a live one shows two.
|
|
|
|
test_expect_success 'a malformed hunk line disables the process for the command' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-malformed --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "disabling it for the remainder" err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'coordinates past the blob size skip the pair, process stays alive' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-huge --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "past the end" err &&
|
|
test_line_count = 2 backend.log
|
|
'
|
|
|
|
test_expect_success 'a count that overflows long skips the pair, process stays alive' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-erange --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "out-of-range coordinates" err &&
|
|
test_line_count = 2 backend.log
|
|
'
|
|
|
|
test_expect_success 'overlapping hunks are rejected per pair' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-overlap --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "overlapping hunks" err &&
|
|
test_line_count = 2 backend.log
|
|
'
|
|
|
|
test_expect_success 'misaligned hunks are rejected per pair' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-misaligned --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "misaligned" err &&
|
|
test_line_count = 2 backend.log
|
|
'
|
|
|
|
test_expect_success 'a start of zero with a nonzero count is rejected per pair' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
# A start of 0 names an empty side, so a nonzero count beside it
|
|
# names no line; the coordinate is rejected and the pair falls back
|
|
# to the builtin diff while the process stays alive.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-badstart --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "out-of-range coordinates" err &&
|
|
test_line_count = 2 backend.log
|
|
'
|
|
|
|
test_expect_success 'an unrecognized status disables the process for the command' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-unknown-status --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "unrecognized status .frobnicate." err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'status=abort withdraws the capability without a warning' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-abort --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep ! "disabling" err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'a bare status without the hunk-section flush is a protocol error' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-bare-status --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "disabling it for the remainder" err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'an empty packet in the hunk section is a protocol error' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-empty-packet --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "disabling it for the remainder" err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'a process that dies mid-response fails the command over to builtin' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-crash --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "disabling it for the remainder" err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'garbage bytes on stdout fail the command over to builtin' '
|
|
test_when_finished "rm -f backend.log err" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-garbage --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
test_grep "disabling it for the remainder" err &&
|
|
test_line_count = 1 backend.log
|
|
'
|
|
|
|
test_expect_success 'a process announcing no capability is never asked' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git log --format= --numstat -- "*.c" >expect &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=cap-none --log=backend.log" \
|
|
log --format= --numstat -- "*.c" >actual &&
|
|
test_cmp expect actual &&
|
|
test_must_be_empty backend.log
|
|
'
|
|
|
|
test_expect_success 'a trailing token on a hunk line is ignored' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-trailing --log=backend.log" \
|
|
log -1 --format= --numstat -- pair.c >actual &&
|
|
printf "2\t2\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'a failed start warns once and the store may serve the path' '
|
|
git init failrepo &&
|
|
(
|
|
cd failrepo &&
|
|
echo "*.c diff=cdiff" >.gitattributes &&
|
|
git add .gitattributes &&
|
|
test_commit f1 f.c "one" &&
|
|
test_commit f2 f.c "one
|
|
two" &&
|
|
test_commit f3 f.c "one
|
|
two
|
|
three" &&
|
|
GIT_DIFF_HUNKS_WRITE=1 git log --format= --stat -- f.c >/dev/null &&
|
|
# The command has no shell metacharacters, so it fails at
|
|
# exec time; a shell-wrapped command would fail at the
|
|
# handshake, which the gentle handshake in the base
|
|
# (061a68e443) likewise degrades to the builtin diff.
|
|
git blame f.c >expect &&
|
|
git -c diff.cdiff.process=/does-not-exist-diff-backend \
|
|
blame f.c >actual 2>err &&
|
|
test_cmp expect actual &&
|
|
# One warning even though the blame consults two pairs.
|
|
test $(grep -c "failed to start" err) = 1 &&
|
|
# A failure is a non-answer like any other: the request
|
|
# that observes it and every later request pass to the
|
|
# store, so the warmed store serves both pairs.
|
|
git -c diff.cdiff.process=/does-not-exist-diff-backend \
|
|
blame --show-stats f.c >stats 2>&1 &&
|
|
test_grep "num precomputed hits: 2" stats
|
|
)
|
|
'
|
|
|
|
test_expect_success 'the store serves a pair the process defers' '
|
|
test_when_finished "git diff-hunks clear" &&
|
|
git diff-hunks clear &&
|
|
GIT_DIFF_HUNKS_WRITE=1 git log --format= --stat -- pair.c >/dev/null &&
|
|
git blame pair.c >expect &&
|
|
# need-content defers the pair to the builtin diff, which is
|
|
# what the store holds, so the walk continues past the process
|
|
# and the store serves the pair.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-need-content" \
|
|
blame pair.c >actual &&
|
|
test_cmp expect actual &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-need-content" \
|
|
blame --show-stats pair.c >stats 2>&1 &&
|
|
test_grep "num precomputed hits: 1" stats
|
|
'
|
|
|
|
test_expect_success 'git diff between commits consults the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
ORIG=$(git rev-parse ":/add pair.c") &&
|
|
CHANGE=$(git rev-parse ":/change pair.c") &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
diff --numstat $ORIG $CHANGE -- pair.c >actual &&
|
|
printf "2\t2\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'git show consults the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
CHANGE=$(git rev-parse ":/change pair.c") &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
show --format= --numstat $CHANGE -- pair.c >actual &&
|
|
printf "2\t2\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'diff-tree --ext-diff consults the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
CHANGE=$(git rev-parse ":/change pair.c") &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
diff-tree --ext-diff --no-commit-id --numstat $CHANGE >actual &&
|
|
printf "2\t2\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success '--no-diff-process forbids consulting alone' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
log -1 --no-diff-process --format= --numstat -- pair.c >actual &&
|
|
printf "4\t4\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success '--diff-process allows plumbing to consult' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
CHANGE=$(git rev-parse ":/change pair.c") &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
diff-tree --diff-process --no-commit-id --numstat $CHANGE >actual &&
|
|
printf "2\t2\tpair.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=pair.c" backend.log
|
|
'
|
|
|
|
test_expect_success 'a forced blame diff algorithm bypasses the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
CHANGE=$(git rev-parse --short ":/change pair.c") &&
|
|
# The process would attribute lines 9-10 to the original commit
|
|
# (see the oid-fixed blame test above); a forced builtin
|
|
# algorithm must produce the builtin attribution and never start
|
|
# the process.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
blame --histogram pair.c >actual &&
|
|
sed -n "9p" actual >line9 &&
|
|
test_grep "$CHANGE" line9 &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'textconv output is never identified to the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
echo "*.tcv diff=tcv" >>.gitattributes &&
|
|
git add .gitattributes &&
|
|
git commit -m tcv-attr &&
|
|
test_config diff.tcv.textconv cat &&
|
|
test_commit tcv1 file.tcv "alpha" &&
|
|
test_commit tcv2 file.tcv "alpha
|
|
beta" &&
|
|
git blame file.tcv >expect &&
|
|
git -c diff.tcv.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
blame file.tcv >actual &&
|
|
test_cmp expect actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'a gitlink side is never identified to the process' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
echo "sub diff=cdiff" >>.gitattributes &&
|
|
git add .gitattributes &&
|
|
git commit -m sub-attr &&
|
|
C1=$(git rev-parse HEAD) &&
|
|
C2=$(git rev-parse HEAD~1) &&
|
|
git update-index --add --cacheinfo 160000,$C1,sub &&
|
|
git commit -m sub-1 &&
|
|
git update-index --add --cacheinfo 160000,$C2,sub &&
|
|
git commit -m sub-2 &&
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
log -1 --format= --numstat -- sub >actual &&
|
|
printf "1\t1\tsub\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_path_is_missing backend.log
|
|
'
|
|
|
|
test_expect_success 'a relative diff consults by the repo-relative path' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
echo "reldir/*.rel diff=rdrv" >>.gitattributes &&
|
|
git add .gitattributes &&
|
|
git commit -m rel-attr &&
|
|
mkdir reldir &&
|
|
test_write_lines line1 line2 line3 line4 original5 original6 \
|
|
line7 line8 line9 line10 >reldir/x.rel &&
|
|
git add reldir/x.rel &&
|
|
git commit -m "add x.rel" &&
|
|
test_write_lines line1 line2 line3 line4 changed5 changed6 \
|
|
line7 line8 changed9 changed10 >reldir/x.rel &&
|
|
git add reldir/x.rel &&
|
|
git commit -m "change x.rel" &&
|
|
# diff.relative strips the prefix from the displayed name; the
|
|
# driver lookup and the request pathname must still use the
|
|
# repo-relative path, or the directory-scoped attribute above
|
|
# would not match and the process would never be consulted.
|
|
# The process runs at the repository root, so its log lands there.
|
|
(
|
|
cd reldir &&
|
|
git -c diff.relative=true \
|
|
-c diff.rdrv.process="$BACKEND --mode=oid-fixed --log=backend.log" \
|
|
log -1 --format= --numstat -- x.rel
|
|
) >actual &&
|
|
printf "2\t2\tx.rel\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=reldir/x.rel" backend.log
|
|
'
|
|
|
|
test_expect_success 'an empty file side is answered with a start of zero' '
|
|
test_when_finished "rm -f backend.log" &&
|
|
>empty.c &&
|
|
git add empty.c &&
|
|
git commit -m "add empty.c" &&
|
|
printf "x\ny\nz\n" >empty.c &&
|
|
git add empty.c &&
|
|
git commit -m "fill empty.c" &&
|
|
# The process addresses the empty old side with a start of 0 and a
|
|
# count of 0, and claims two of the three added lines. The answer is
|
|
# used as sent, so the stat shows the two lines it named, not the
|
|
# three the builtin would.
|
|
git -c diff.cdiff.process="$BACKEND --mode=oid-empty --log=backend.log" \
|
|
log -1 --format= --numstat -- empty.c >actual &&
|
|
printf "2\t0\tempty.c\n" >expect &&
|
|
test_cmp expect actual &&
|
|
test_grep "command=hunks-by-oid pathname=empty.c" backend.log
|
|
'
|
|
|
|
test_done
|