Skip to content

Commit 79a34a0

Browse files
committed
fix(repo): decode quoted untracked paths with Unicode correctly
With `core.quotePath=false`, Git can emit raw UTF-8 characters alongside C-style escapes in a quoted status path. `Repo.untracked_files` forced that path through ASCII before unquoting it, raising `UnicodeEncodeError` for a filename such as `café\x7f.txt`. Reuse the existing `_unquote_path` byte decoder, as `Repo.ignored` already does. The regression uses real Git output with both `core.quotePath` settings and also checks the `Repo.is_dirty` caller. Validation: the regression failed before the change. Related filename tests passed (4 passed, 1 skipped, 2 subtests passed), and Ruff lint, formatting, and `git diff --check` passed.
1 parent 9658d0f commit 79a34a0

2 files changed

Lines changed: 13 additions & 3 deletions

File tree

‎git/repo/base.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,9 +1141,7 @@ def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]:
11411141
filename = line[len(prefix) :].rstrip("\n")
11421142
# Special characters are escaped
11431143
if filename[0] == filename[-1] == '"':
1144-
filename = filename[1:-1]
1145-
# WHATEVER ... it's a mess, but works for me
1146-
filename = filename.encode("ascii").decode("unicode_escape").encode("latin1").decode(defenc)
1144+
filename = _unquote_path(filename[1:-1].encode(defenc)).decode(defenc)
11471145
untracked_files.append(filename)
11481146
finalize_process(proc)
11491147
return untracked_files

‎test/test_repo.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,18 @@ def test_untracked_files(self, rwrepo):
905905
self.assertEqual(len(rwrepo.untracked_files), (num_recently_untracked - len(files)))
906906
# END for each run
907907

908+
@with_rw_directory
909+
def test_untracked_files_with_unquoted_unicode_and_escapes(self, rw_dir):
910+
with Repo.init(rw_dir) as repo:
911+
paths = {"plain.txt", "caf\u00e9.txt", "caf\u00e9\x7f.txt", " leading.txt"}
912+
for path in paths:
913+
Path(rw_dir, path).touch()
914+
for quote_path in ("true", "false"):
915+
with self.subTest(quote_path=quote_path):
916+
repo.git.config("core.quotePath", quote_path)
917+
assert set(repo.untracked_files) == paths
918+
assert repo.is_dirty(untracked_files=True)
919+
908920
def test_config_reader(self):
909921
reader = self.rorepo.config_reader() # All config files.
910922
assert reader.read_only

0 commit comments

Comments
 (0)