Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

save_zip can emit one companion twice under two spellings when the source folder folds case and the temp dir does not

オープン
#35 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
静か
技術スタック
python
領域
tooling

調査の方向性

src/sil_lift/_zip.py の save_zip から始め、_write_zip を追って、コピーされた companion ファイルとレンダリングされた companion ファイルがどのように staging ディレクトリに入るのかを理解します。Issue に記載された case-folding source と case-sensitive TMPDIR の条件を再現し、その後、アーカイブにレンダリング済みの bytes を持つ .lift-ranges メンバーが 1 つだけ含まれ、古い大文字小文字違いの variant が含まれていないことを確認します。

索引モデルが issue の本文から書いたものです。

説明

bug

Lexicon.save_zip() can produce an archive containing the same .lift-ranges twice — once stale, once current — under names differing only in case.

Preconditions

Both must hold, which is why this is not seen in ordinary use:

  1. The LIFT folder is on a case-folding filesystem, and its .lift and companion disagree in case (Dict.LIFT beside Dict.lift-ranges). This is what makes RangesFile.path carry a spelling that is not the on-disk name — _existing_file returns the candidate's spelling when the exact stat succeeds, and on a folding filesystem it succeeds for a case variant.
  2. TMPDIR is on a case-sensitive filesystem. On macOS that is a case-sensitive APFS volume; on Linux, a LIFT folder on ciopfs / exfat / casefold-enabled ext4 with /tmp on ext4.

Normally these are the same filesystem — the divergence needs folding, and staging happens on the same machine — so the second write lands on the first file and nothing goes wrong.

Mechanism

save_zip (src/sil_lift/_zip.py) stages into a TemporaryDirectory, copies the whole source package in, then overwrites the rendered files by name:

shutil.copytree(source_root, content, dirs_exist_ok=True)   # brings in Dict.lift-ranges
...
name = ranges_file.path.name if ranges_file.path is not None else Path(key).name
(content / name).write_bytes(render_ranges_document(ranges_file))   # writes Dict.LIFT-ranges

_write_zip then walks the staging directory, so members come from the filesystem rather than from the names. Where staging folds case, the second write overwrites the copied file and leaves its directory entry alone: one file, one member, correct bytes. Where staging does not fold, it creates a second file, and the archive gets both — the stale copytree'd bytes under the on-disk spelling and the rendered bytes under the href's spelling.

On read-back the duplicate is not rejected: _select_lift_member only guards against multiple .lift members, and two case-variant .lift-ranges are resolved the way any such folder is — one wins.

Fix options

  • Narrow, in save_zip: before writing, look for a file already in staging whose name folds onto name and write to that spelling instead. Keeps the change local to packaging.
  • At the root, in _resolve_ranges: hand RangesFile.load the canonical path so RangesFile.path always matches what is on disk. Tempting one-liner, but the canonical path is absolute and symlink-collapsed, so it changes a public attribute's value and what RangesFile.save() targets for every caller — a much wider blast radius than the defect.

The first looks right; the second is worth ruling out deliberately rather than by omission.

Notes

Pre-existing: _resolve_ranges passed the candidate's spelling to RangesFile.load before #19 as well. Split out of review discussion on #19, which kept its scope to companion resolution.

主要言語
Python
スター
1
フォーク
0
平均マージ
11日 8時間
マージ済み PR(30日)
3

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

sillsdev/python-sil-lift のほかの issue

sillsdev/python-sil-lift の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。