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
This commit is contained in:
parent
659c29a41c
commit
ed770ba4dd
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
<!-- Changes to how Black can be configured -->
|
<!-- Changes to how Black can be configured -->
|
||||||
|
|
||||||
|
- Shorten the length of the name of the cache file to fix crashes on file systems that
|
||||||
|
do not support long paths (#4176)
|
||||||
|
|
||||||
### Packaging
|
### Packaging
|
||||||
|
|
||||||
<!-- Changes to how Black is packaged, such as dependency requirements -->
|
<!-- Changes to how Black is packaged, such as dependency requirements -->
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
from _black_version import version as __version__
|
from _black_version import version as __version__
|
||||||
from black.mode import Mode
|
from black.mode import Mode
|
||||||
|
from black.output import err
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
if sys.version_info >= (3, 11):
|
||||||
from typing import Self
|
from typing import Self
|
||||||
@ -64,7 +65,13 @@ def read(cls, mode: Mode) -> Self:
|
|||||||
resolve the issue.
|
resolve the issue.
|
||||||
"""
|
"""
|
||||||
cache_file = get_cache_file(mode)
|
cache_file = get_cache_file(mode)
|
||||||
if not cache_file.exists():
|
try:
|
||||||
|
exists = cache_file.exists()
|
||||||
|
except OSError as e:
|
||||||
|
# Likely file too long; see #4172 and #4174
|
||||||
|
err(f"Unable to read cache file {cache_file} due to {e}")
|
||||||
|
return cls(mode, cache_file)
|
||||||
|
if not exists:
|
||||||
return cls(mode, cache_file)
|
return cls(mode, cache_file)
|
||||||
|
|
||||||
with cache_file.open("rb") as fobj:
|
with cache_file.open("rb") as fobj:
|
||||||
|
@ -192,6 +192,9 @@ class Deprecated(UserWarning):
|
|||||||
"""Visible deprecation warning."""
|
"""Visible deprecation warning."""
|
||||||
|
|
||||||
|
|
||||||
|
_MAX_CACHE_KEY_PART_LENGTH: Final = 32
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Mode:
|
class Mode:
|
||||||
target_versions: Set[TargetVersion] = field(default_factory=set)
|
target_versions: Set[TargetVersion] = field(default_factory=set)
|
||||||
@ -228,6 +231,19 @@ def get_cache_key(self) -> str:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
version_str = "-"
|
version_str = "-"
|
||||||
|
if len(version_str) > _MAX_CACHE_KEY_PART_LENGTH:
|
||||||
|
version_str = sha256(version_str.encode()).hexdigest()[
|
||||||
|
:_MAX_CACHE_KEY_PART_LENGTH
|
||||||
|
]
|
||||||
|
features_and_magics = (
|
||||||
|
",".join(sorted(f.name for f in self.enabled_features))
|
||||||
|
+ "@"
|
||||||
|
+ ",".join(sorted(self.python_cell_magics))
|
||||||
|
)
|
||||||
|
if len(features_and_magics) > _MAX_CACHE_KEY_PART_LENGTH:
|
||||||
|
features_and_magics = sha256(features_and_magics.encode()).hexdigest()[
|
||||||
|
:_MAX_CACHE_KEY_PART_LENGTH
|
||||||
|
]
|
||||||
parts = [
|
parts = [
|
||||||
version_str,
|
version_str,
|
||||||
str(self.line_length),
|
str(self.line_length),
|
||||||
@ -236,10 +252,7 @@ def get_cache_key(self) -> str:
|
|||||||
str(int(self.is_ipynb)),
|
str(int(self.is_ipynb)),
|
||||||
str(int(self.skip_source_first_line)),
|
str(int(self.skip_source_first_line)),
|
||||||
str(int(self.magic_trailing_comma)),
|
str(int(self.magic_trailing_comma)),
|
||||||
sha256(
|
|
||||||
(",".join(sorted(f.name for f in self.enabled_features))).encode()
|
|
||||||
).hexdigest(),
|
|
||||||
str(int(self.preview)),
|
str(int(self.preview)),
|
||||||
sha256((",".join(sorted(self.python_cell_magics))).encode()).hexdigest(),
|
features_and_magics,
|
||||||
]
|
]
|
||||||
return ".".join(parts)
|
return ".".join(parts)
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
from black import re_compile_maybe_verbose as compile_pattern
|
from black import re_compile_maybe_verbose as compile_pattern
|
||||||
from black.cache import FileData, get_cache_dir, get_cache_file
|
from black.cache import FileData, get_cache_dir, get_cache_file
|
||||||
from black.debug import DebugVisitor
|
from black.debug import DebugVisitor
|
||||||
|
from black.mode import Mode, Preview
|
||||||
from black.output import color_diff, diff
|
from black.output import color_diff, diff
|
||||||
from black.report import Report
|
from black.report import Report
|
||||||
|
|
||||||
@ -2065,6 +2066,30 @@ def test_get_cache_dir(
|
|||||||
monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
|
monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
|
||||||
assert get_cache_dir().parent == workspace2
|
assert get_cache_dir().parent == workspace2
|
||||||
|
|
||||||
|
def test_cache_file_length(self) -> None:
|
||||||
|
cases = [
|
||||||
|
DEFAULT_MODE,
|
||||||
|
# all of the target versions
|
||||||
|
Mode(target_versions=set(TargetVersion)),
|
||||||
|
# all of the features
|
||||||
|
Mode(enabled_features=set(Preview)),
|
||||||
|
# all of the magics
|
||||||
|
Mode(python_cell_magics={f"magic{i}" for i in range(500)}),
|
||||||
|
# all of the things
|
||||||
|
Mode(
|
||||||
|
target_versions=set(TargetVersion),
|
||||||
|
enabled_features=set(Preview),
|
||||||
|
python_cell_magics={f"magic{i}" for i in range(500)},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for case in cases:
|
||||||
|
cache_file = get_cache_file(case)
|
||||||
|
# Some common file systems enforce a maximum path length
|
||||||
|
# of 143 (issue #4174). We can't do anything if the directory
|
||||||
|
# path is too long, but ensure the name of the cache file itself
|
||||||
|
# doesn't get too crazy.
|
||||||
|
assert len(cache_file.name) <= 96
|
||||||
|
|
||||||
def test_cache_broken_file(self) -> None:
|
def test_cache_broken_file(self) -> None:
|
||||||
mode = DEFAULT_MODE
|
mode = DEFAULT_MODE
|
||||||
with cache_dir() as workspace:
|
with cache_dir() as workspace:
|
||||||
|
Loading…
Reference in New Issue
Block a user