fix(pm): isolate per-package pin failures instead of aborting the run

_apply_pins let any exception from _pin_artifacts propagate: one broken
pin - a package's resolution bug (gh's 3-part target split) or one
target's upstream pool skew (Termux rolling pool 404 while nodejs.org
is ahead) - aborted the entire run, blocked every unrelated package,
and left lockfile.save() unreached, so any failure meant zero progress.

Catch per package, pin everything that succeeds, install the pinned
set, print a 'pinned X, failed Y' summary, and exit non-zero when
anything failed. A run where every pin fails still leaves the lockfile
untouched.

Fixes #125386
This commit is contained in:
Yuan Li
2026-09-27 23:44:48 +08:00
committed by Teknium
parent 4b6884aaa7
commit 87bfb9426e

View File

@@ -589,15 +589,33 @@ def _apply_pins(changed: list, lockfile) -> int:
if not changed:
print("pm update: nothing to update")
return 0
pinned: list[str] = []
failed: list[str] = []
for d in changed:
package = get_package(d.name)
artifacts = _pin_artifacts(package, d, lockfile.pinned_artifacts(d.name))
try:
artifacts = _pin_artifacts(package, d, lockfile.pinned_artifacts(d.name))
except Exception as e:
# One broken pin — a package's resolution bug or one target's upstream
# pool skew (rolling Termux pool 404 while nodejs.org is ahead) — must
# not zero out the whole run. Pin the rest, report the failure (#125386).
print(f"✗ {d.name} pin failed: {e}")
failed.append(d.name)
continue
lockfile.set_pin(d.name, d.version, artifacts)
pinned.append(d.name)
print(f"✓ {d.name} pinned {d.locked or '—'} → {d.version}")
lockfile.save()
if _install_names([d.name for d in changed]):
if not pinned:
print("pm update: every pin failed; lockfile untouched")
return 1
return 0 if _sync_venv_step() else 1
lockfile.save()
if _install_names(pinned):
return 1
rc = 0 if _sync_venv_step() else 1
if failed:
print(f"pm update: pinned {len(pinned)}, failed {len(failed)}: {', '.join(failed)}")
return 1
return rc
def _refresh_uv_lock() -> int: