Moving rewritten history into a blobless (filter=blob:none) clone: use a self-contained pack, not a thin bundle
Situation: history is rewritten elsewhere (for example git-filter-repo on a server), and the new commits have to land in a laptop clone made with --filter=blob:none, without fetching from the remote.
What failed: a thin bundle (git bundle create ... --not <old tips>). On the blobless clone, git bundle unbundle / fetch from it died with "could not fetch <sha> from promisor remote". The pack inside has deltas against old blobs the clone never downloaded, so resolving them needs a lazy fetch. With GIT_NO_LAZY_FETCH=1 (git >= 2.44) it fails immediately instead of reaching the network.
What worked (git 2.52): 1. On the server: { list new tips; list ^<old tip> for each old tip; } | git pack-objects --revs --stdout --delta-base-offset > new.pack. Do not pass --thin: deltas then only refer to objects inside the pack. 2. Check it on the server: git init --bare /tmp/x && git --git-dir /tmp/x index-pack --stdin < new.pack. 3. On the clone: ssh server cat new.pack | git index-pack --stdin. This writes objects only, no refs. 4. Before moving any ref, run a pre-pass for every target: git cat-file -e <new>^{commit} && git rev-list --objects --missing=print <new> --not <old> | grep -c ^?, which should be 0. If any target fails, abort with nothing moved. 5. Move refs with a compare-and-swap (git update-ref <ref> <new> <old>), and use git reset --keep inside worktrees.
Other traps: - git fetch <bundle> auto-follows tags, so use --no-tags when you are working out which refs are local-only. - for wt in $(git worktree list ...) breaks on paths with spaces. A failed git -C <wt> status --porcelain prints nothing and looks clean, so check its exit status. - Test the procedure end to end against a real blobless clone (a clone of a local bare repo with --filter=blob:none and uploadpack.allowFilter=true). A full clone hides all of this.