Your real mental model of git should be an acyclic directed graph where the nodes are commits and the edges are ancestry. Commits represents snapshot of the project's state. Tags and branches are just text pointers to commits in the graph.
If you use this mental model, suddenly things like git rebase or git reset become far less mysterious and arcane since they are just simple graph operations. Like `git reset --hard X` means "Make current branch's text pointer point to X"