Except for single developer environments with super simple branch structures, it is always an error to label the source code with a SemVer version string.
See my related answer.
SemVer should be applied at the tail end of the publish/release phase, not when you push to the repo. That publish/release phase cannot happen in parallel with other branches, without some sort of inter-branch coordination. This can be automated, but most of the canned DevOps tooling I've seen, doesn't handle this properly. After a version is applied to a particular output, you can then associate it as a tag to the git hash that produced that output, or you can use some other media to record a record of git-hash/semver-string.
I prefer to tag the git repo AND maintain a separate log. In fact, I have developed tooling in the past that added the git hash to the SemVer build meta tag (best practice!).
While not strictly required, it's also nice to have the branch name embedded in that build meta tag.
The git hash is the only "tag" that can't be modified. Best practice is to ensure that you can never produce two packages with different content. If you have automation producing packages from a repo and it faithfully applies the latest version tag from that repo, or use a version string from a checked-in file, then you can produce potentially many packages with different signatures, simply by rerunning the build.
There are very few build suites capable of creating exactly the same bits from the same source code, twice in a row. Time stamps change between builds. Builds on different machine by different users will have machine and user specific meta data embedded in them. Tool chains can be updated between builds, etc.
Hence my strong wording with regard to the common malpractice of using repo tags for versioning. Besides that, even if you are very careful to prevent tags from being moved or deleted and reapplied, they simply redundant. There's a reason that git does not use a sequence number to track repo versions. The hash of the content is always unique for a given repo.
All of that said, I wish I could say I never put version strings in a repo. I generally follow my customer's current work-flows, and try to propose improvements on them, when they fall short of the ideal (as virtually all do).
Even if you could produce exactly the same bits, from the same code, as many times as you like, it's still a bad practice. It's not easy to manage when you have multiple release branches and it is error prone. It also increases friction when scaling across multiple build systems.