How to use Git properly

6 min read

Most people learn Git as three commands: add, commit, push. That works until you need to find when a bug was introduced, undo a mistake, or review someone else’s work. Then you discover that Git is not a save button. It is a history, and the main reader of that history is you, six months from now.

This post covers the habits that make that history useful.

The mental model

Git has three places your changes can be:

flowchart LR
  A[Working tree<br/>files you edit] -- git add --> B[Staging area<br/>what goes in the next commit]
  B -- git commit --> C[History<br/>commits]
  C -- git push --> D[Remote<br/>GitHub, GitLab]
  • Working tree: the files on your disk.
  • Staging area (also called the index): the changes you chose for the next commit.
  • History: commits. Each commit is a snapshot of the whole project plus a pointer to its parent.

A branch is just a name that points to a commit. Creating a branch is cheap, so make one for every task.

Once this clicks, most commands make sense. git add moves changes into staging. git restore --staged moves them back out. git commit turns staging into a commit.

Look before you commit

Two commands, every time:

git status          # what changed, what is staged
git diff --staged   # exactly what will go into the commit

This catches debug console.logs, files you did not mean to change, and the occasional .env. It takes ten seconds.

Make small commits

One commit should be one logical change: “fix the date parsing bug”, not “fix bug, rename variables, update dependencies, format files”.

Small commits are easier to review, easier to revert, and easier to understand when you find them in git log a year later.

If you already changed several unrelated things, you do not have to commit them together. Stage part of a file:

git add -p

Git shows each change (“hunk”) and asks y (stage), n (skip), or s (split into smaller pieces). This is the single most useful Git command most beginners do not know.

Tip

Formatting changes and logic changes should never be in the same commit. A diff where 300 lines moved because of a formatter hides the 2 lines that actually matter.

Write messages for the future reader

The code already shows what changed. The message should say why.

Bad:

fix
update
changes
wip

Good:

Fix pubDate parsing for posts without timezone

Dates like '2026-10-08' were parsed as UTC midnight, so posts
showed the previous day in UTC-negative timezones. Parse them
as local dates instead.

The usual format:

  • Subject line: about 50 characters, imperative mood (“Fix”, “Add”, “Remove”, not “Fixed” or “Fixes”). Read it as “If applied, this commit will fix pubDate parsing”.
  • Blank line.
  • Body (optional): why the change was needed, what the trade-off was, what you tried that did not work.

Your team may use a convention like fix: ... / feat: ... (Conventional Commits). Follow it. The rule about explaining why still applies.

Keep branches short

One branch per task, created from an up-to-date main:

git switch main
git pull
git switch -c fix-date-parsing

The longer a branch lives, the more main moves away from it, and the more painful the merge. Aim for days, not weeks. If a feature is big, split it into several smaller branches that each make sense on their own.

To bring new changes from main into your branch, you can merge or rebase:

git fetch
git rebase origin/main   # replay your commits on top of the latest main
# or
git merge origin/main    # add a merge commit

Rebase gives a cleaner, straight history. Merge is safer because it never rewrites commits. Use whatever your team uses, but understand the rule in the next section first.

Never rewrite shared history

Commands like rebase, commit --amend, and reset create new commits and throw away the old ones. On your own local branch, that is fine. On a branch someone else has already pulled, it breaks their copy.

The rule: rewrite only what nobody else has.

If you rebased your own pushed branch and need to push again, use:

git push --force-with-lease

not --force. --force-with-lease refuses to push if someone else pushed to the branch since you last fetched, so you do not silently delete their work.

Caution

Never force push to main or any shared branch.

Undo things safely

Most Git panic comes from not knowing how to undo. Here is the short version:

SituationCommand
Unstage a file (keep the changes)git restore --staged <file>
Throw away changes in a filegit restore <file>
Fix the last commit message, or add a forgotten filegit commit --amend
Undo the last commit, keep the changesgit reset --soft HEAD~1
Undo a commit that is already pushedgit revert <commit>

git revert is the safe one for shared branches: it creates a new commit that undoes the old one, so history is not rewritten.

git restore <file> and git reset --hard delete uncommitted work for real. Git cannot bring it back, because it was never committed.

The safety net: reflog

Committed work is very hard to lose. Even after a bad reset or rebase, Git remembers where HEAD has been:

git reflog

You will see a list like HEAD@{3}: commit: Add tag pages. Find the state you want and go back to it:

git switch -c rescue HEAD@{3}

Knowing reflog exists makes you much less afraid of Git.

Read the history

Good history is only useful if you read it. A few commands worth memorizing:

git log --oneline --graph           # compact view of commits and branches
git log -p -- src/i18n.ts           # every change to one file, with diffs
git log -S 'readingMinutes'         # commits that added or removed this text
git blame src/i18n.ts               # who last changed each line, and in which commit
git show <commit>                   # one commit in full

git log -S is great for questions like “when did this function appear?” or “who removed this config?”. git blame gives you a commit; the commit message (hopefully) tells you why. This is where your good commit messages pay off.

Keep secrets out

Add a .gitignore on day one, with node_modules/, build output, and .env.

If you ever commit a secret (API key, password, token):

  1. Rotate it immediately. Create a new key and disable the old one.
  2. Then clean it from history if you want.

Deleting the file in a new commit does not help: the secret is still in the old commit. And once it was pushed to a public repo, assume someone already copied it. Rotating is the only real fix.

Warning

git rm removes a file from the next commit, not from history.

Summary

  • Run git status and git diff --staged before every commit.
  • One commit, one logical change. Use git add -p to split.
  • Commit messages: short imperative subject, body explains why.
  • One short-lived branch per task.
  • Rewrite only history nobody else has. Use --force-with-lease, never plain --force.
  • git revert for shared branches, git reflog when you think you lost something.
  • Never commit secrets. If you did, rotate first.