Teams argue about workflow names when the actual decisions are narrower than that. There are five of them, they are mostly independent, and getting them written down prevents most of the friction.
This is the one that determines everything else. A branch that lives hours rarely conflicts; a branch that lives three weeks conflicts with everything and its review is unmanageable. If you want short branches, you need feature flags, because incomplete work must be able to ship dark. If you cannot do feature flags, you will have long branches and you should plan for the merge cost rather than pretend otherwise. Decide this first and the rest follows.
Squash merge gives one commit per pull request. History is clean and readable, git bisect lands on a whole feature rather than a single line, and intermediate commits disappear. Merge commits preserve every commit and show the branch structure, at the cost of a graph that needs --first-parent to read. Rebase-and-merge gives linear history with individual commits preserved, but rewrites SHAs, which breaks anyone who pulled the branch. Pick one, configure the forge to allow only that one, and stop revisiting it.
If merging to main triggers production, main must be protected and green at all times, and you need a fast rollback. If you cut releases from a branch or a tag instead, you can merge riskier things sooner but you now maintain a release process. Both are fine. What is not fine is main deploying automatically while people treat it as a scratch area.
Review quality falls off sharply with size. A 200-line diff gets real comments; a 2,000-line diff gets approved. If a change must be large, split it: refactor in one pull request that changes no behaviour, then the behaviour change in a second. Reviewers can verify "this only moved code" quickly, and then give real attention to the twenty lines that matter.
A .gitignore that covers build output, environment files and editor directories — adding it after secrets are committed is much harder, since removing a file from history requires rewriting it. A .gitattributes with * text=auto to stop line-ending churn between Windows and Unix machines. A commit message convention, even a loose one; if you want automated changelogs, Conventional Commits is the format tooling expects. And a .git-blame-ignore-revs file, empty at first, ready for the day you run a formatter across everything.
git reflog shows every position HEAD has held, including after a bad rebase or a hard reset, and entries persist for about 90 days. git reset --hard HEAD@{5} gets you back. For a bad commit already pushed, git revert creates a new commit that undoes it and does not rewrite shared history — always prefer it over force-pushing a shared branch. If you must force-push your own branch, use --force-with-lease, which refuses when someone else has pushed in the meantime.