You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A configured external diff driver no longer makes a dirty tree look clean. With diff.external set, or GIT_EXTERNAL_DIFF exported, git hands the diff to that program and writes nothing to stdout while exiting 0 — so list showed no hunks, count returned 0, and check passed vacuously. Not a crash and not a parse error: a wrong answer shaped exactly like a clean tree, which an agent has no way to distinguish. Every git diff call site now passes --no-ext-diff (which defeats both the config key and the environment variable), including the --no-index path used for untracked files.
A diff.<driver>.textconv filter no longer leaks converted text into the hunk listing. Hunks on such a path showed the driver's rendering of the blob rather than its real content, and the patch rebuilt from that rendering was rejected by git apply ("patch does not apply") — add, reset, restore and commit all failed on those paths. Diff calls now pass --no-textconv, so textconv paths behave like any other.
Redirecting stdout to a regular file no longer overwrites what preceded it. The stdout writer was positional and started at offset 0, ignoring the offset the shell had already put on the descriptor, so git hunk list >> log truncated the log and { echo header; git hunk list; } > out overwrote the header. Output is now streamed.
Added
list --verbose and count --verbose now name changed paths that produced no hunk, with the reason and the git add <path> that stages them. A submodule pointer bump, a mode change and a rename with no content change are all deliberate skips — they have no line-level content for a hash to address — but the skip was silent, so a tree whose only change was one of them read here as having nothing to stage. A mode change is reported even when the same file also has content hunks, since the mode is unstageable either way. Derived from the diff text the parser consumed rather than a parallel copy of the skip rules, so a future skip cannot go unreported.
Changed
git hunk commit builds its temporary index under TMPDIR when set, rather than always under /tmp — the same rule the shell and mktemp follow, so a caller that has isolated its temp directory keeps that isolation.
The man page now carries the version in its .TH line, so man git-hunk names the release it documents and a packaging step that shipped a stale copy leaves a trace. zig build docs must therefore be re-run on a version bump; CI's existing drift check enforces it.
Test coverage for classes that were previously unpinned: the \ No newline at end of file marker end to end (all four positions across add, reset, restore, commit, stash, and line-spec selection, asserting file and blob bytes); non-UTF-8 file content as distinct from filenames; line-ending normalisation under core.autocrlf true/input, core.eol with text=auto, and none — each pinned against git's own answer (git add, git checkout --) rather than against the pre-edit bytes; submodule and mode-change skips; an inherited GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE (which hooks, rebases and CI all set); and stdout redirected to a regular file.
Releases are now packaged by tools/package-release.sh, which CI runs too, and the resulting tarball is read back by tests/check-release-artifact.sh — the binary, the man page and all four completion files must be present, the binary must run, and the shipped man page and completions must cover every command the shipped binary lists. Previously nothing verified a release's contents, so a workflow edit could drop the man page or a completion file silently.
The whole integration suite can be re-run under hostile git configuration: tests/run-hostile.sh applies a profile through GIT_CONFIG_GLOBAL (so it reaches every git invocation in every test, including the ones git-hunk spawns) and requires the same assertion count as a default-config run — a profile that silently emptied every diff would otherwise pass by asserting nothing. Eight profiles cover external diff drivers, textconv, path prefixes and diff.relative, color.ui and color.diff separately, diff algorithm and blank-line suppression, core.quotePath, and pagers with an order file. CI runs one job per profile, with the matrix generated from the profile list so adding a profile adds a job. Nothing previously set a hostile config anywhere, so every flag the tool passes to defend itself was unverified.
The test harness no longer sets pipefail. The suite is built on echo "$OUT" | grep -q X, where grep -q exits on its first match and echo then dies of SIGPIPE with status 141; under pipefail that 141 became the pipeline's status, so a passing assertion failed at random. Reproduced 200/200 with a payload larger than the pipe buffer, and observed on CI as one grep in a pair failing while its neighbour passed. The status these pipelines want is the consumer's, which is what they return without pipefail.
The test harness now scrubs an inherited git environment and observes repos through git --no-ext-diff --no-textconv --no-color with colour keys forced off. Without the first, a caller's GIT_DIR redirected the whole suite at another repository; without the second, a test asserting that something is present fails under a hostile diff driver while its negation passes vacuously.