Skip to content
Merged
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
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
29 changes: 29 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,34 @@ def test_blame_incremental(self, git):
),
)

def test_blame_incremental_preserves_historical_paths(self):
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
# Tree entries allow historical names that cannot be checked out on every platform.
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\0' "$blob" "${paths[$i]}" | git mktree -z)
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