Let's say we know that SHA-256 is weakened in every possible way
You can test that case. Just replace 64 rounds of SHA-256 with 16 rounds of SHA-256. Then you can test all attacks you want, you will have full control over this hash function.
we know the attacks will be possible but will come gradually, not overnight
Just create some honest nodes that will check hashes incrementally. Then, create one node that will produce stronger and stronger hashes, raising difficulty to the insane levels. You will quickly see, how that scenario looks like.
Let's say we have a safe replacement in the form of, for example, SHA-3
Try fixing your 16 rounds SHA-256 with real SHA-3 and see, why it is not so trivial.
Not particularly concerned about PoW at the moment.
It is probably the first problem you will see, because if SHA-256 is fully broken, then you can create a block with zero hash. That would also mean, it would be a previous block from the Genesis Block perspective, so you will have a ring, instead of a chain. By breaking SHA-256, you can quickly realize that your attack will shut down nodes, will freeze the chain, will cause huge blockchain reorganization after passing difficulty adjustment, and will cause huge damage, unless carefully planned and gradually introduced.
What would have to be upgraded in Bitcoin
Bitcoin Message signing (outside of on-chain signatures of regular transactions). Also all hashed public keys (because if you can execute a preimage attack, then you can replace public keys). All hashed scripts (faster than hashed public keys, because you can just use "
OP_DROP OP_CHECKSIG" everywhere and spend that). The same with commitments (if you commit anything to the chain and it uses SHA-256, then that commitment is no longer valid). All timestamps outside Bitcoin that used block hashes will be invalid. In short, it would be just a public database that anyone can modify, so it will be useless, unless fixed.
How hard would be to migrate these parts of Bitcoin with a minimum damage?
Porting txids: every txid should be re-hashed, just like in Segwit you have txid and hash, here you will have one more field for a new hash.
Mining: you will have two difficulties, one to preserve the old, SHA-256 chain, and the new, to keep it unaltered.
Merkle trees: each merkle tree should be re-hashed, it could be a commitment, like in Segwit, but could be also better compressed.
Block headers: you will have 80-byte new header that would contain both old and new data, in a combined and compressed form.
Signature hashes: you have to use new hashes, and re-hash old signatures, because it is the same (or even worse) case than txids.
Bitcoin Message: the same solution as for signature hashes; also encourage upgrading to signet-way-of-signing-things.
Hashed public keys: old coins should be moved to the new addresses; it could be spendable or not, whatever option will win.
Hashed scripts: the same solution as for hashed public keys.
Commitments: the new chain will be started with one, huge commitment, containing the whole old chain.
Timestamps: they are invalid, new timestamps should be year-2038-resistant and year-2106-resistant, it could be modulo 2^32, but should be clearly defined, whatever it will be.
could they still be moved after the hash function change (which would be a hardfork probably)?
It could be a soft-fork if done correctly. It is just a matter of data format. We can stick to the old format, but just put more rules on top of that, making it as soft as Segwit or Taproot.
but wouldn't it be much more likely to have 160-bit collisions (from RIPEMD-160) first?
It would be. We have ASIC's for SHA-256d, we could have ASIC's for RIPEMD-160(SHA-256) faster than SHA-256 will be fully broken (they now would require 2^80 operations, but I think it could be simplified to something like 2^64, that would make it as real as SHA-1 collision).