Skip to content

Fix textconv producing empty diffs on Linux (use cat instead of git-crypt diff)#332

Open
mischadiehm wants to merge 1 commit intoAGWA:masterfrom
narrowin:upstream-textconv-cat
Open

Fix textconv producing empty diffs on Linux (use cat instead of git-crypt diff)#332
mischadiehm wants to merge 1 commit intoAGWA:masterfrom
narrowin:upstream-textconv-cat

Conversation

@mischadiehm
Copy link
Copy Markdown

Problem

On Linux with git 2.43 (and likely other recent versions), git diff between two commits shows no output for encrypted files. The diff is completely empty — not "Binary files differ", just nothing.

This makes git log -p, git diff <commit> <commit>, and git blame useless for encrypted files on Linux. macOS is unaffected.

Root cause

configure_git_filters() sets diff.git-crypt.textconv to git-crypt diff. But git applies the smudge filter (decrypt) to blob content before passing it to the textconv command. By the time git-crypt diff receives the file, it's already plaintext.

After loading the repo key, git-crypt diff sees no \0GITCRYPT\0 header, hits the "not encrypted" path, and copies the content through unchanged. This makes the configured textconv a redundant identity transform that unnecessarily depends on git-crypt key loading, repo discovery, and header probing. On some git versions this redundant round-trip produces empty output instead of a plaintext diff.

Verified by tracing the textconv invocation:

CWD: /tmp/tmp.xxx
FILE: /tmp/git-blob-xxx/secret.txt
FILE_HEX: 6f72 6967 696e 616c 0a    # "original\n" — already decrypted

The temp file contains plaintext, not the encrypted blob.

Fix

Replace git-crypt diff with cat in both the named-key and default-key paths of configure_git_filters():

-		git_config(std::string("diff.git-crypt-") + key_name + ".textconv",
-		           escaped_git_crypt_path + " diff --key-name=" + key_name);
+		git_config(std::string("diff.git-crypt-") + key_name + ".textconv", "cat");
-		git_config("diff.git-crypt.textconv", escaped_git_crypt_path + " diff");
+		git_config("diff.git-crypt.textconv", "cat");

cat is correct because:

  1. The smudge filter already decrypted the content
  2. cat just passes the plaintext through to git's diff machinery
  3. cat is POSIX and avoids invoking git-crypt a second time during diff

What about locked repos?

Under the normal lock/unlock flow, locked repos do not have this textconv configured: git-crypt lock calls deconfigure_git_filters, which removes both the smudge filter and the diff textconv. So the supported path does not rely on cat handling encrypted content.

Tested

Built the exact upstream PR branch commit on macOS Apple Silicon with Apple Git 2.50.1 and Homebrew OpenSSL 3:

CXXFLAGS="-I$(brew --prefix openssl@3)/include" \
LDFLAGS="-L$(brew --prefix openssl@3)/lib" \
make

Also built and tested the same commit in an Ubuntu 24.04.4 container with Git 2.43.0. The container copied the PR worktree, installed g++ make libssl-dev git, built git-crypt, and ran the same focused checks below.

Focused behavioral checks against the built binaries:

  • default key: diff.git-crypt.textconv is configured as cat
  • default key: git diff HEAD~1 HEAD -- secret.txt shows plaintext -original secret / +modified secret
  • named key: diff.git-crypt-team.textconv is configured as cat
  • named key: commit-to-commit diff shows the same plaintext diff
  • lock/unlock: git-crypt lock removes the diff textconv config; git-crypt unlock KEYFILE restores it as cat
  • traced textconv input: Git passed plaintext temp files to textconv (original / modified bytes), not blobs beginning with \0GITCRYPT\0

Also verified the fork's broader smoke test suite with the same textconv behavior:

bash scripts/smoke-test.sh ./git-crypt

Result: 12/12 tests passed, including the diff-driver test.

Existing users

Users who already have git-crypt configured in a repo keep the old textconv setting until they re-run git-crypt unlock. This is expected — unlock calls configure_git_filters which updates the config.

Files changed

  • commands.cpp — two lines in configure_git_filters()

Git applies the smudge filter before invoking the diff textconv command, so encrypted blobs have already been decrypted by the time textconv runs. The textconv command only needs to pass that plaintext through to Git's diff machinery.

Using cat avoids invoking git-crypt diff redundantly during diff generation.
Copilot AI review requested due to automatic review settings April 24, 2026 06:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes empty diffs for git-crypt–encrypted files on Linux by adjusting the configured Git textconv command to avoid re-invoking git-crypt diff on content that Git has already decrypted via the smudge filter.

Changes:

  • Update configure_git_filters() to set diff.*.textconv to cat (both default-key and named-key drivers).
  • Remove dependence on git-crypt diff during Git diff/log/blame operations, preventing the observed “empty diff” behavior on some Git/Linux combinations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mischadiehm added a commit to narrowin/git-crypt that referenced this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants