Skip to content

Commit ed770ba

Browse files
Fix cache file length (#4176)
- Ensure total file length stays under 96 - Hash the path only if it's too long - Proceed normally (with a warning) if the cache can't be read Fixes #4172
1 parent 659c29a commit ed770ba

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
<!-- Changes to how Black can be configured -->
2020

21+
- Shorten the length of the name of the cache file to fix crashes on file systems that
22+
do not support long paths (#4176)
23+
2124
### Packaging
2225

2326
<!-- Changes to how Black is packaged, such as dependency requirements -->

src/black/cache.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from _black_version import version as __version__
1515
from black.mode import Mode
16+
from black.output import err
1617

1718
if sys.version_info >= (3, 11):
1819
from typing import Self
@@ -64,7 +65,13 @@ def read(cls, mode: Mode) -> Self:
6465
resolve the issue.
6566
"""
6667
cache_file = get_cache_file(mode)
67-
if not cache_file.exists():
68+
try:
69+
exists = cache_file.exists()
70+
except OSError as e:
71+
# Likely file too long; see #4172 and #4174
72+
err(f"Unable to read cache file {cache_file} due to {e}")
73+
return cls(mode, cache_file)
74+
if not exists:
6875
return cls(mode, cache_file)
6976

7077
with cache_file.open("rb") as fobj:

src/black/mode.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ class Deprecated(UserWarning):
192192
"""Visible deprecation warning."""
193193

194194

195+
_MAX_CACHE_KEY_PART_LENGTH: Final = 32
196+
197+
195198
@dataclass
196199
class Mode:
197200
target_versions: Set[TargetVersion] = field(default_factory=set)
@@ -228,6 +231,19 @@ def get_cache_key(self) -> str:
228231
)
229232
else:
230233
version_str = "-"
234+
if len(version_str) > _MAX_CACHE_KEY_PART_LENGTH:
235+
version_str = sha256(version_str.encode()).hexdigest()[
236+
:_MAX_CACHE_KEY_PART_LENGTH
237+
]
238+
features_and_magics = (
239+
",".join(sorted(f.name for f in self.enabled_features))
240+
+ "@"
241+
+ ",".join(sorted(self.python_cell_magics))
242+
)
243+
if len(features_and_magics) > _MAX_CACHE_KEY_PART_LENGTH:
244+
features_and_magics = sha256(features_and_magics.encode()).hexdigest()[
245+
:_MAX_CACHE_KEY_PART_LENGTH
246+
]
231247
parts = [
232248
version_str,
233249
str(self.line_length),
@@ -236,10 +252,7 @@ def get_cache_key(self) -> str:
236252
str(int(self.is_ipynb)),
237253
str(int(self.skip_source_first_line)),
238254
str(int(self.magic_trailing_comma)),
239-
sha256(
240-
(",".join(sorted(f.name for f in self.enabled_features))).encode()
241-
).hexdigest(),
242255
str(int(self.preview)),
243-
sha256((",".join(sorted(self.python_cell_magics))).encode()).hexdigest(),
256+
features_and_magics,
244257
]
245258
return ".".join(parts)

tests/test_black.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from black import re_compile_maybe_verbose as compile_pattern
4545
from black.cache import FileData, get_cache_dir, get_cache_file
4646
from black.debug import DebugVisitor
47+
from black.mode import Mode, Preview
4748
from black.output import color_diff, diff
4849
from black.report import Report
4950

@@ -2065,6 +2066,30 @@ def test_get_cache_dir(
20652066
monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
20662067
assert get_cache_dir().parent == workspace2
20672068

2069+
def test_cache_file_length(self) -> None:
2070+
cases = [
2071+
DEFAULT_MODE,
2072+
# all of the target versions
2073+
Mode(target_versions=set(TargetVersion)),
2074+
# all of the features
2075+
Mode(enabled_features=set(Preview)),
2076+
# all of the magics
2077+
Mode(python_cell_magics={f"magic{i}" for i in range(500)}),
2078+
# all of the things
2079+
Mode(
2080+
target_versions=set(TargetVersion),
2081+
enabled_features=set(Preview),
2082+
python_cell_magics={f"magic{i}" for i in range(500)},
2083+
),
2084+
]
2085+
for case in cases:
2086+
cache_file = get_cache_file(case)
2087+
# Some common file systems enforce a maximum path length
2088+
# of 143 (issue #4174). We can't do anything if the directory
2089+
# path is too long, but ensure the name of the cache file itself
2090+
# doesn't get too crazy.
2091+
assert len(cache_file.name) <= 96
2092+
20682093
def test_cache_broken_file(self) -> None:
20692094
mode = DEFAULT_MODE
20702095
with cache_dir() as workspace:

0 commit comments

Comments
 (0)