Interesting. What's wrong with branching in git?
OTOH, git branches are pointers to one single commit (with the git UI tentatively converting this information sometimes into "that commit, specifically" or sometimes as "all ancestors commits leading to that commit", with more or less success and consistency).
Where it matters (besides fostering good/consistent UX) is when you merge several (topological) branches together: git won't be able to tell if you just merged A into B or B into A. Although the content is identical at code-level, the semantic/intent of the merge is lost. Similarly, once the head has progressed so much ahead and your history is riddled with merges, you can't tell from the DAG where the individual features/PR/series start and end. This makes bisecting very hard: while hunting down a regression, you would rather avoid checking-out mid-series commits that might break the build, and instead stick to the series boundaries. You can't do that natively with git. That also makes maintaining concurrent versions unnecessarily difficult, and many projects are struggling with that: have you seen for instance Django¹ prefixing each and every commit with the (long-lived) branch name? That's what you get with git while most other VCSes (like Mercurial, my preference) got right from the start.
That's why git also offer tags. Tags are immutable.
That's an important distinction.
tl;dr you lose some semantic (and practical capabilities, UX and effectiveness) when you don't capture branch information at commit-level
There is when you stop thinking in terms of dev, staging and prod, and you realize that you might have thousands of different environments, all named differently.
Do you create a branch for each one of them?
Using the environment name as branch name is coupling your repository with the external infrastructure that's running your code. If that infrastructure changes, you need to change your repository. That in itself is a cue it's a bad idea to use branches this way.
Another issue with this pattern is that you can't know what's deployed at any given time in prod. Deploying the "production" branch might yield a different result 10 minutes from now, than it did 25 minutes ago. (add to the mix caching issues, and you have a great recipe for confusing and hard to debug issues)
If you use tags, which literally are meant for that, combined with semver (though not necessarily a requirement, but a strong recommendation), you decouple your code and the external environment.
You can now point your "dev" environment to "main", point staging to ">= v1.25.0" and "prod" to "v1.25.0", "dev-alice" to "v2.0.0", "dev-john" to "deadb33f".
When you deploy "v1.25.0" in prod, you _know_ it will deploy v1.25.0 and not commit deadb33f that so happened to have been merged to the "production" branch 30 seconds ago.
Seems you have bigger process issues to tackle. There's nothing inherently wrong with having per-env branches (if one thing, it's made harder by git being so terrible at branching in the general/long lived case, but the VCS cannot alone be blamed for developers consistently pushing to inadequate branches).