Skip to content

fix(vcs): VcsArchive.Update re-fetches and atomically replaces dest - #206

Open
gmoigneu wants to merge 1 commit into
mainfrom
fix/124-archive-update-reextract
Open

fix(vcs): VcsArchive.Update re-fetches and atomically replaces dest#206
gmoigneu wants to merge 1 commit into
mainfrom
fix/124-archive-update-reextract

Conversation

@gmoigneu

@gmoigneu gmoigneu commented May 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extends the `VCS.Update` interface signature with a `url` parameter so the archive backend has access to the upstream URL it needs to re-fetch.
  • `VcsArchive.Update` now re-downloads and re-extracts using the same staging-temp-dir + `RemoveAll` + `Rename` pattern as skill: install atomically (stage + rename) and remove stale destination files #121, so a crash leaves either the previous or the new tree intact — never a half-written hybrid.
  • Files removed upstream disappear from the destination on the next sync (overlay extraction never deleted them before).
  • Returns an error if called without a URL — defensive guard so a forgotten plumbing change can't silently regress to no-op.

Why

`VcsArchive.Update` was `return nil` with a TODO comment. Combined with `repo.Manager.syncOne` only calling `Clone` when `!IsCloned(path)`, every sync after the first was a silent no-op for archive-backed repos. Users pinning to an archive URL got permanently stale content with no signal.

Test plan

  • `go build ./...`
  • `go vet ./...`
  • `go test ./...` — full suite green
  • New `TestVcsArchive_Update_RefetchesAndReplaces` exercises the regression: install v1, swap upstream, re-Update, assert v2 content + removed file gone + new file present.
  • Updated `internal/repo` and `internal/engine` tests that previously relied on the no-op now stand up a local `httptest` server.
  • Manual e2e: change archive URL in config, re-run sync, verify new content.

Closes #124

VcsArchive.Update used to return nil without doing anything. Combined
with repo.Manager.syncOne only calling Clone when !IsCloned, every
sync after the first was a silent no-op for archive sources — even
when the upstream archive content (or the configured version: prefix)
had changed.

Fix:
- Extend the VCS.Update interface signature with a url string. Other
  backends (git, hg, svn, bzr) ignore it because their on-disk
  metadata already records origin; the archive backend needs it
  because nothing on disk does.
- Plumb cfg.URL through repo.Manager.syncOne and skill.Manager.
- VcsArchive.Update re-fetches and re-extracts, staging into a
  sibling temp directory and atomically swapping (RemoveAll + Rename)
  on success — same pattern as #121. A crash mid-extract leaves the
  previous tree untouched (or the new one fully installed); never a
  half-written hybrid. Files removed upstream disappear because dst
  is replaced wholesale.
- VcsArchive.Update returns an error when called without a url
  (defensive guard against callers that forget to pass it).

Tests:
- Replace TestVcsArchive_Update_NoOp with TestVcsArchive_Update_RequiresURL
  and TestVcsArchive_Update_RefetchesAndReplaces (the latter is the
  regression test for #124).
- Update repo + engine tests that relied on the no-op to drive a
  local httptest server instead.
- Mechanical sed pass for git/hg/svn/bzr/archive _test.go call sites
  inserting "" as the new url argument.

Closes #124
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vcs: VcsArchive.Update silently no-ops, freezing archive repos after first sync

1 participant