Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix diff parsing to support mnemonicPrefix configuration
Fixes #2013

When diff.mnemonicPrefix=true is set in git config, git uses different
prefixes for diff paths instead of the standard a/ and b/:
- c/ for commit
- w/ for worktree
- i/ for index
- o/ for object
- h/ for HEAD

Previously, the diff regex and decode_path() function only accepted
a/ and b/ prefixes, causing create_patch=True diffs to fail parsing.

Changes:
- Update re_header regex to accept [abciwoh]/ prefixes
- Update decode_path() assertion to accept all valid mnemonic prefixes
- Add test case for mnemonicPrefix-style diffs
  • Loading branch information
MirrorDNA-Reflection-Protocol committed Dec 7, 2025
commit 6ef056f842fe7a302ae7bf7f332eec60802929ea
9 changes: 7 additions & 2 deletions git/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
path = _octal_byte_re.sub(_octal_repl, path)

if has_ab_prefix:
assert path.startswith(b"a/") or path.startswith(b"b/")
# Support standard (a/b) and mnemonicPrefix (c/w/i/o/h) prefixes
# See git-config diff.mnemonicPrefix documentation
valid_prefixes = (b"a/", b"b/", b"c/", b"w/", b"i/", b"o/", b"h/")
assert any(path.startswith(p) for p in valid_prefixes), f"Unexpected path prefix: {path[:10]}"
path = path[2:]

return path
Expand Down Expand Up @@ -367,10 +370,12 @@ class Diff:
"""

# Precompiled regex.
# Note: The path prefixes support both default (a/b) and mnemonicPrefix mode
# which can use prefixes like c/ (commit), w/ (worktree), i/ (index), o/ (object)
re_header = re.compile(
rb"""
^diff[ ]--git
[ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
[ ](?P<a_path_fallback>"?[abciwoh]/.+?"?)[ ](?P<b_path_fallback>"?[abciwoh]/.+?"?)\n
(?:^old[ ]mode[ ](?P<old_mode>\d+)\n
^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
(?:^similarity[ ]index[ ]\d+%\n
Expand Down
32 changes: 32 additions & 0 deletions test/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,38 @@ def test_diff_unsafe_paths(self):
self.assertEqual(res[13].a_path, 'a/"with-quotes"')
self.assertEqual(res[13].b_path, 'b/"with even more quotes"')

def test_diff_mnemonic_prefix(self):
"""Test that diff parsing works with mnemonicPrefix enabled.

When diff.mnemonicPrefix=true is set in git config, git uses different
prefixes for diff paths:
- c/ for commit
- w/ for worktree
- i/ for index
- o/ for object

This addresses issue #2013 where the regex only matched [ab]/ prefixes.
"""
# Create a diff with mnemonicPrefix-style c/ and w/ prefixes
# Using valid 40-char hex SHAs
diff_mnemonic = b"""diff --git c/.vscode/launch.json w/.vscode/launch.json
index 1234567890abcdef1234567890abcdef12345678..abcdef1234567890abcdef1234567890abcdef12 100644
--- c/.vscode/launch.json
+++ w/.vscode/launch.json
@@ -1,3 +1,3 @@
-old content
+new content
"""
diff_proc = StringProcessAdapter(diff_mnemonic)
diffs = Diff._index_from_patch_format(self.rorepo, diff_proc)

# Should parse successfully (previously would fail or return empty)
self.assertEqual(len(diffs), 1)
diff = diffs[0]
# The path should be extracted correctly (without the c/ or w/ prefix)
self.assertEqual(diff.a_path, ".vscode/launch.json")
self.assertEqual(diff.b_path, ".vscode/launch.json")

def test_diff_patch_format(self):
# Test all of the 'old' format diffs for completeness - it should at least be
# able to deal with it.
Expand Down
Loading