CS

Git Guide — Branching, Merging & Undo

Practical git reference: staging and committing, branching and merging, remotes, inspecting history, stashing, and safely undoing changes.

What this covers

The everyday git operations that come up in normal development: configuring the repo, staging and committing, branching and merging, syncing with a remote, inspecting history, stashing work in progress, and — the part worth getting right — undoing changes safely. For running parallel branches in separate working directories, see Git Worktrees ; for the Gitflow branching model, see Git Flow .

When to use it

  • Looking up the right command for a common git operation
  • Deciding how to undo something (uncommitted changes, a bad commit, a pushed commit) without losing work
  • Choosing between merge and rebase for integrating a branch
  • Recovering from a mistake (detached HEAD, wrong branch, accidental reset) — see Troubleshooting

Setup & Configuration

git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main

git init                       # new repo in current directory
git clone <url>                # copy an existing repo
git clone <url> <dir>          # into a named directory

Staging & Committing

git status                     # what's changed / staged
git add <file>                 # stage a specific file
git add -p                     # stage selectively, hunk by hunk
git add .                      # stage everything under the current directory

git commit -m "message"        # commit staged changes
git commit -am "message"       # stage tracked files + commit (skips new files)
git commit --amend             # rewrite the last commit (only if not pushed)

git add -p is worth the habit — it lets you review and split changes into focused commits instead of committing unrelated work together.

Branching & Merging

git branch                     # list local branches
git switch <branch>            # change branch (modern; replaces checkout)
git switch -c <branch>         # create and switch
git branch -d <branch>         # delete a merged branch
git branch -D <branch>         # force-delete an unmerged branch

git merge <branch>             # merge <branch> into the current one
git merge --no-ff <branch>     # always create a merge commit

Merge vs rebase:

  • git merge preserves history exactly and creates a merge commit — safe default, especially for shared branches.
  • git rebase <base> replays your commits on top of <base> for a linear history — cleaner, but rewrites commit hashes.
  • Never rebase commits that others have already pulled. Rewriting shared history forces everyone else into conflicts.
git rebase main                # replay current branch on top of main
git rebase -i HEAD~3           # interactively squash/reword/reorder last 3 commits

Rebase step by step

The most common use: bring new commits from main into your feature branch without a merge commit.

git switch feature/login
git fetch origin
git rebase origin/main         # replay the branch's commits on top of current main

# on a conflict: edit the files, then
git add <file>
git rebase --continue          # move on to the next commit
git rebase --abort             # or go back to the state before the rebase

git push --force-with-lease    # the branch was already pushed → hashes changed

A rebase resolves conflicts commit by commit, so the same file can come up more than once. When that gets tedious, git merge origin/main is an equally valid and cheaper way.

Remotes & Syncing

git remote -v                  # list remotes
git remote add origin <url>    # add a remote

git fetch                      # download remote changes, don't merge
git pull                       # fetch + merge into current branch
git pull --rebase              # fetch + rebase (avoids a merge commit)

git push                       # push current branch
git push -u origin <branch>    # push and set upstream (first time)
git push --force-with-lease    # force-push safely — refuses if remote moved unexpectedly

Prefer --force-with-lease over --force: it still refuses to overwrite work someone else pushed after your last fetch, so you don’t clobber a teammate’s commits.

Inspecting History

git log --oneline --graph --all   # compact visual history of all branches
git log -p <file>                 # history with diffs for one file
git show <commit>                 # a commit's message and diff
git diff                          # unstaged changes
git diff --staged                 # staged changes
git blame <file>                  # who last changed each line

Stashing

git stash                      # shelve uncommitted changes, clean the tree
git stash -u                   # include untracked files
git stash list                 # list stashes
git stash pop                  # reapply the most recent stash and drop it
git stash apply                # reapply but keep it in the stash list
git stash drop                 # discard a stash

Stashing is for a quick context switch. For anything longer-lived, a branch (or a worktree ) is safer — stashes are easy to forget and have no branch name to remind you what they held.

Undoing Changes

The right command depends on how far the change has gone. From least to most drastic:

# Discard unstaged changes to a file (irreversible — the edits are gone)
git restore <file>

# Unstage a file but keep the edits
git restore --staged <file>

# Undo the last commit, KEEP the changes staged
git reset --soft HEAD~1

# Undo the last commit, keep changes in the working tree (unstaged)
git reset HEAD~1

# Undo the last commit AND discard its changes (destructive)
git reset --hard HEAD~1

# Revert a commit by making a new commit that undoes it (safe for shared history)
git revert <commit>

Rules of thumb:

  • Not pushed yet → reset is fine.
  • Already pushed / shared → use revert, not reset --hard + force-push.
  • git reset --hard and git restore discard uncommitted work permanently — there’s no undo. Commit or stash first if unsure.

Common mistakes

  • git add . without looking. Logs, .env or build output ride along into the commit. Check git status and git diff --staged before committing; what does not belong in the repository goes into .gitignore.
  • git push --force to a shared branch. It overwrites commits someone else pushed in the meantime. Use --force-with-lease, and only on your own branch.
  • Rebasing a shared branch. Teammates’ history diverges and they merge the old commits back in. Rebase only what is still yours alone.
  • git reset --hard with work in progress. Uncommitted changes are gone and the reflog cannot bring them back. Commit or git stash first.
  • One big “WIP” commit. It cannot be reverted in parts or reviewed sensibly. Split with git add -p into commits that each make sense on their own.
  • git pull into a dirty tree. The merge gets mixed up with your edits. Commit or stash before pulling.
  • Forgotten stashes. A month later git stash list tells nobody anything. For more than a few minutes, create a branch.

Troubleshooting

SymptomFix
Committed to the wrong branchgit switch <right-branch> then git cherry-pick <commit>; remove it from the wrong branch with git reset --hard HEAD~1 (if unpushed)
Detached HEAD (“not on a branch”)git switch -c <new-branch> to keep the commits, or git switch main to discard them
Need a “deleted” commit backgit reflog lists recent HEAD positions; git reset --hard <sha> or git switch -c rescue <sha>
Accidental reset --hardThe commits usually still exist — find them with git reflog and recover
Merge conflictEdit the marked files, git add them, then git commit (or git merge --continue); git merge --abort to back out
Pushed a secretRotate the secret immediately — history rewriting doesn’t undo the exposure

Frequently asked questions

What is the difference between git merge and git rebase?

git merge joins two branches with a merge commit and leaves history untouched. git rebase replays your commits on top of another branch, which keeps history linear but gives the commits new hashes. Rebase is fine on your own unshared branch; integrate shared branches with merge.

How do I undo the last commit in git?

If the commit is not pushed yet, git reset --soft HEAD~1 removes it and keeps the changes staged. If it is already on the remote, use git revert <commit> — it adds a new commit that undoes the change and does not rewrite shared history.

What is git stash for?

git stash shelves work in progress and gives you a clean working tree, for example when you need to switch branches quickly. git stash pop brings it back. For anything longer-lived, a branch or a worktree is safer.

How do I delete a branch locally and on the remote?

Locally git branch -d <branch> (-D for an unmerged branch), on the remote git push origin --delete <branch>. git fetch --prune cleans up stale references to deleted remote branches.

What should I do when git pull ends in a conflict?

Open the files marked with <<<<<<<, pick the right version, save, git add them and finish with git commit (git rebase --continue during a rebase). You can always back out: git merge --abort or git rebase --abort restores the state before the pull.