Git stores commits as immutable objects. Branches and tags are just names pointing at commits. Most "destructive" commands move names: reset, commit --amend, rebase, and a force push. The old commits usually still exist. They become unreachable, and garbage collection eventually removes them after the reflog entries expire.
That gives three recovery tools:
git reflogrecords whereHEADand each branch pointed over time in your clone. After an accidental reset, the lost commit is a line in the reflog. Create a branch at it (git branch rescue <sha>) before doing anything else.git fsck --lost-foundfinds dangling commits when the reflog has no entry.- Remote copies. Someone else's clone, or the CI system's checkout, may still have the ref.
Know the three trees that reset manipulates, as Pro Git's "Reset Demystified" section explains: HEAD, the index, and the working directory. --soft moves only HEAD, --mixed also resets the index, and --hard also overwrites the working tree. --hard is the one that can destroy uncommitted work, which Git cannot recover for you.
Shared history is a contract. On a branch others have pulled, prefer git revert, which records a new commit undoing an old one. Rewriting published history forces every collaborator to reconcile. When you must rewrite an unmerged PR branch, for example to purge a leaked credential, publish with --force-with-lease so you do not overwrite someone else's push.
Removing a secret from history does not make it secret again. Anyone may have fetched it, and hosting platforms may cache it. Rotate first, then clean the history.