mirror of
https://github.com/akadeepesh/contextzip.git
synced 2026-10-20 08:36:20 +02:00
fix(applier): stop skipping wrapper-strip when project has a same-named folder
apply-zip's wrapper-folder detection refused to strip a zip's outer folder whenever the project already had a real directory of that same name — which backfires on the single most common shape of AI-returned zip: the whole project wrapped in a folder named after the project (or its own package dir), e.g. 'project/' or 'contextzip/contextzip/...'. Since the project's real package dir almost always shares that name, the guard treated the wrapper as legitimate structure and dumped a nested copy at the project root instead of updating the real files. Now, whenever a manifest is available, the decision is made by comparing manifest match-rate with vs. without stripping (ground truth about what paths this project actually has) instead of inferring intent from what merely exists on disk. Falls back to the old on-disk heuristic only when no manifest exists to check against. Fixes the case where apply-zip silently mis-applies a returned zip.
This commit is contained in:
+21
-3
@@ -446,8 +446,24 @@ def build_plan(
|
||||
strip_prefix: str | None = None
|
||||
wrapper_note: str | None = None
|
||||
candidate = _detect_common_wrapper(raw_names)
|
||||
if candidate and not (project_dir / candidate).is_dir():
|
||||
if candidate:
|
||||
if manifest_files:
|
||||
# Decide using the manifest — actual ground truth about what
|
||||
# paths this project has — rather than whether a directory of
|
||||
# that name merely happens to exist on disk. That on-disk check
|
||||
# used to gate this whole branch, which backfired on the most
|
||||
# common real case: an AI tool wraps the whole project in a
|
||||
# folder named after the project itself (e.g. "contextzip/"),
|
||||
# and the project's own top-level package/dir happens to share
|
||||
# that exact name — so the wrapper looked "legitimate" and was
|
||||
# never stripped, dumping a nested copy of the project instead
|
||||
# of updating the real files. Comparing manifest match-rates
|
||||
# with vs without stripping gets both cases right: a genuine
|
||||
# wrapper matches far better once stripped, while a zip whose
|
||||
# top-level folder is an intentional part of the project (e.g.
|
||||
# updates confined to an existing "docs/" folder) matches
|
||||
# better *unstripped*, since those paths are already in the
|
||||
# manifest as-is.
|
||||
unstripped_rate = _match_rate(raw_names, manifest_files)
|
||||
stripped_names = [n[len(candidate) + 1:] for n in raw_names]
|
||||
stripped_rate = _match_rate(stripped_names, manifest_files)
|
||||
@@ -462,10 +478,12 @@ def build_plan(
|
||||
f"project manifest after stripping vs {unstripped_rate:.0%} "
|
||||
"before."
|
||||
)
|
||||
else:
|
||||
elif not (project_dir / candidate).is_dir():
|
||||
# No manifest to confirm against, so this is a softer call —
|
||||
# still strip (matches how `tar` and GitHub's own zip downloads
|
||||
# behave), but say so plainly since it's not manifest-verified.
|
||||
# behave) as long as the project doesn't already have a real
|
||||
# folder of that name, but say so plainly since it's not
|
||||
# manifest-verified.
|
||||
strip_prefix = candidate
|
||||
wrapper_note = (
|
||||
f"Removed wrapping folder '{candidate}/' present in every zip "
|
||||
|
||||
Reference in New Issue
Block a user