I assume you are speaking of the same project as @CraigSiemens?
With Package.resolved registered in .gitignore, the expected behaviour would be that any new clone would resolve afresh, because no pins are travelling with the clone. When switching branches, Git would not touch the pins file, and so SwiftPM should use the pins as long as they still satisfy the constraints in the manifest. If the manifest changed with the branch switch then the pins may no longer satisfy it, in which case SwiftPM will discard them, resolve anew and write out those pins. Anything besides this behaviour would indicate a bug.
Xcode is a whole different can of worms because it seems to reimplement parts of SwiftPM separately and it has a lot of bugs that SwiftPM itself does not.
I noticed in at least Xcode 12.4 that Xcode seemed to erroneously load its checkouts before looking at the pins, and since the last loading step is registering the pins, it would consistently revert the pins to match any existing checkout. That meant that any change to the pins by SwiftPM, by Git or by hand would be erased upon opening Xcode. The only remedy was to delete the derived data directory each time you make such a change. I do not know if 12.5 still suffers from that problem.
Other relevant Xcode bugs include differences in the way it handles percent encoding of dependency URLs, and other URL normalizations such as capitalization, .git, etc. That can in turn lead to Xcode and SwiftPM rejecting each other’s pins.
So if Xcode is breaking your pins, I am entirely unsurprised, but you are asking in the wrong place to get a fix. You will have to try your luck with the Feedback Assistant.
I have made a habit of ensuring that I choose either SwiftPM or Xcode as “standard” for each particular project and watch to make sure I erase any changes the opposite tool makes to the pins before I check them in.
This is not the case. The pins file is supposed to be stable. Unless one of the corner case bugs above is triggered, it should come out identical every time.
So if it is changing, then it is tripping something unusual. The same URL with differing capitalization is an example of the sort of thing to look for, because that used to be able to throw off the string sort position or hash depending on which declaration the resolver encountered first and decided was canonical. This destroyed the deterministic order, but it had nothing to do with an unordered dictionary. I do not know if that particular bug still persists, but it gives you an idea of the sort of thing that might be going on.
I will try to take a closer look and get back to you.