fix(updates): add --force to git fetch so re-pointed remote tags don't fail the update

In-app updater was failing on installs whose local clone had stale
  tags (e.g. v0.2.1 re-pointed upstream after a post-release re-tag).
  git fetch --prune --tags returns a non-zero exit when even one tag
  would be clobbered, even though origin/main and the target release
  tag itself fetched cleanly:

    ! [rejected] v0.2.1 -> v0.2.1 (would clobber existing tag)
    ...
    ERROR Git fetch failed: From https://github.com/maziggy/bambuddy

  The updater surfaced this as "Failed to fetch updates" and aborted,
  leaving the user stuck on the previous release.

  Adding --force lets the moved tag overwrite the local stale copy.
  That matches the in-app updater's contract ("sync me to the remote")
  and is what the native update.sh sidesteps entirely by not using
  --tags at all. The in-app path can't drop --tags because release-tag
  refs (v0.2.4b1, v0.2.4.1, etc.) need to be resolvable locally for the
  subsequent git reset --hard.

  Regression test asserts --force is in the fetch args alongside the
  existing --tags assertion.
This commit is contained in:
maziggy
2026-05-16 15:04:44 +02:00
parent 6569d5d1a7
commit 9c7e6a7b2c
3 changed files with 229 additions and 0 deletions
+9
View File
@@ -523,12 +523,21 @@ async def _perform_update(target_ref: str):
# locally resolvable for the reset below. `--tags` is required —
# plain `git fetch origin` doesn't bring tags by default, so a
# release tag would not be resolvable.
#
# `--force` lets a moved tag on the remote overwrite the local copy.
# Without it, any tag that was re-tagged upstream (e.g. v0.2.1 being
# re-pointed after a hotfix re-tag) makes `git fetch --tags` return
# a non-zero exit even though every other ref fetched cleanly —
# which we'd then surface as "Failed to fetch updates" to the user.
# The in-app updater's contract is "sync me to the remote"; force-
# overwriting a stale local tag matches that intent.
process = await asyncio.create_subprocess_exec(
git_path,
*git_config,
"fetch",
"--prune",
"--tags",
"--force",
"origin",
cwd=str(base_dir),
stdout=asyncio.subprocess.PIPE,