Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions git/repo/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,8 @@ def blame_incremental(
If you combine all line number ranges outputted by this command, you should get
a continuous range spanning all line numbers in the file.
"""
from git.diff import _unquote_path

if not allow_unsafe_options:
Git.check_unsafe_options(
options=Git._option_candidates([rev], kwargs),
Expand Down Expand Up @@ -1375,6 +1377,9 @@ def blame_incremental(
orig_filename = value
break

if orig_filename.startswith(b'"') and orig_filename.endswith(b'"'):
orig_filename = _unquote_path(orig_filename[1:-1])

yield BlameEntry(
commits[hexsha],
range(lineno, lineno + num_lines),
Expand Down
30 changes: 30 additions & 0 deletions test/test_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
Submodule,
Tree,
)
from git.compat import safe_decode
from git.exc import BadObject, UnsafeOptionError, UnsafeProtocolError
from git.repo.fun import find_worktree_git_dir
from git.util import bin_to_hex, cwd, rmfile, rmtree
Expand Down Expand Up @@ -754,6 +755,35 @@ def test_blame_incremental(self, git):
),
)

def test_blame_incremental_preserves_historical_paths(self):
self.maxDiff = None
paths = (
b"plain.txt",
b" spaced name.txt ",
"café.txt".encode(),
b"byte-\xff.txt",
b'"literal-\\303\\251.txt"',
b'quote"name.txt',
b"back\\slash.txt",
b"control\a\b\t\n\v\f\r.txt",
)
for quote_path in ("true", "false"):
for i, path in enumerate(paths):
with self.subTest(quote_path=quote_path, path=path), fixture_repo(
"blame-paths.sh", quote_path, subdir=str(i)
) as repo:
entries = list(repo.blame_incremental("HEAD", "current.txt"))
original = repo.commit("HEAD~2")
self.assertEqual(
sorted(entries, key=lambda e: e.linenos.start),
[
(original, range(1, 2), safe_decode(path), range(1, 2)),
(repo.head.commit, range(2, 3), "current.txt", range(2, 3)),
(original, range(3, 4), safe_decode(path), range(3, 4)),
],
)
self.assertEqual(list(repo.blame("HEAD", "current.txt", incremental=True)), entries)

@mock.patch.object(Git, "_call_process")
def test_blame_complex_revision(self, git):
git.return_value = fixture("blame_complex_revision")
Expand Down
20 changes: 20 additions & 0 deletions tests/fixtures/blame-paths.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -eu
# C-quoted tree input preserves control bytes through Windows fixture creation.
paths=('plain.txt' ' spaced name.txt ' '"caf\303\251.txt"' '"byte-\377.txt"' '"\"literal-\\303\\251.txt\""' '"quote\"name.txt"' '"back\\slash.txt"' '"control\a\b\t\n\v\f\r.txt"')
for i in "${!paths[@]}"; do
git init -q --initial-branch=main "$i"
(
cd "$i"
git config core.quotePath "$1"
blob=$(printf 'first\nmiddle\nlast\n' | git hash-object -w --stdin)
tree=$(printf '100644 blob %s\t%s\n' "$blob" "${paths[$i]}" | git mktree)
original=$(git commit-tree "$tree" -m original)
tree=$(printf '100644 blob %s\tcurrent.txt\0' "$blob" | git mktree -z)
renamed=$(git commit-tree "$tree" -p "$original" -m rename)
blob=$(printf 'first\nchanged\nlast\n' | git hash-object -w --stdin)
tree=$(printf '100644 blob %s\tcurrent.txt\0' "$blob" | git mktree -z)
edited=$(git commit-tree "$tree" -p "$renamed" -m edit)
git update-ref refs/heads/main "$edited"
)
done
Loading