skip to content

git β€” Version Control

Complete Git reference. Setup, staging, committing, branching, merging, rebasing, stashing, remotes, tags, log inspection, undoing mistakes, submodules, and power-user aliases.

8 min read 21 snippets 2d ago deep dive

git β€” Version Control#

Setup#

git config --global user.name  "Jay Jagatheesan"
git config --global user.email "jay@example.com"
git config --global core.editor "vim"
git config --global init.defaultBranch main
git config --global pull.rebase false          # merge on pull (safer default)
git config --global push.default current       # push current branch
git config --global core.autocrlf input        # Linux/macOS: convert CRLF β†’ LF
git config --global diff.colorMoved zebra      # highlight moved code differently

git config --list --show-origin               # show all config + source file
git config --global --edit                    # open global config in editor

Init and clone#

git init                             # new repo in cwd
git init --bare /srv/repo.git        # bare repo (server)
git clone https://github.com/u/r     # clone
git clone --depth 1 URL              # shallow clone (latest commit only)
git clone --branch dev URL           # clone specific branch
git clone --single-branch URL        # only the cloned branch
git clone --recurse-submodules URL   # clone + init submodules

Staging and committing#

git status                           # working tree state
git status -s                        # short format
git add file.txt                     # stage a file
git add src/                         # stage a directory
git add -p                           # interactively stage hunks
git add -u                           # stage all tracked changes (no new files)
git add .                            # stage everything in cwd (use with care)

git commit -m "message"
git commit -m "subject" -m "body"   # multi-paragraph
git commit --amend                   # amend last commit (staged changes + msg)
git commit --amend --no-edit         # amend without changing message
git commit -C HEAD --amend           # amend keeping exact same message
git commit --allow-empty -m "ci: trigger"  # empty commit

Diff#

git diff                            # unstaged changes
git diff --staged                   # staged changes (what will be committed)
git diff HEAD                       # all changes vs last commit
git diff main..feature              # between two branches
git diff HEAD~3                     # vs 3 commits ago
git diff --stat                     # summary (files changed, insertions, deletions)
git diff --word-diff                # word-level diff
git diff -w                         # ignore whitespace

Branching#

git branch                          # list local branches
git branch -a                       # list all (local + remote)
git branch -v                       # with last commit message
git branch feature/login            # create branch (don't switch)
git switch feature/login            # switch to branch (git 2.23+)
git switch -c feature/login         # create + switch
git checkout -b feature/login       # legacy equivalent

git branch -d feature/login         # delete (safe: checks merge)
git branch -D feature/login         # delete (force)
git branch -m old-name new-name     # rename local branch
git branch --merged main            # branches merged into main
git branch --no-merged              # branches NOT yet merged

Merging#

git merge feature/login             # merge into current branch
git merge --no-ff feature/login     # always create merge commit
git merge --squash feature/login    # squash into one staged change
git merge --abort                   # abort in-progress merge

# Conflict resolution
git mergetool                       # open visual merge tool
git checkout --ours   file.txt      # take our version
git checkout --theirs file.txt      # take their version
git add file.txt && git commit      # mark resolved

Rebasing#

git rebase main                     # rebase current branch onto main
git rebase -i HEAD~4                # interactive: rewrite last 4 commits
git rebase --continue               # after resolving conflict
git rebase --abort                  # abandon rebase
git rebase --skip                   # skip current conflicting commit

# Interactive rebase commands (used in editor)
# pick   = keep commit as-is
# reword = keep, edit message
# edit   = keep, pause to amend
# squash = meld into previous, combine messages
# fixup  = meld into previous, discard message
# drop   = remove commit entirely
# exec   = run shell command after this line

Stashing#

git stash                           # stash tracked changes
git stash push -m "WIP login form"  # named stash
git stash push -u                   # include untracked files
git stash push -p                   # interactively pick hunks to stash

git stash list                      # list stashes
git stash show stash@{0}            # inspect latest stash
git stash show -p stash@{1}         # full diff of stash

git stash pop                       # apply + drop latest stash
git stash apply stash@{2}           # apply without dropping
git stash drop stash@{0}            # delete a stash
git stash clear                     # delete all stashes
git stash branch feature/fix stash@{0}  # create branch from stash

Remotes#

git remote -v                       # list remotes
git remote add origin URL           # add remote
git remote rename origin upstream   # rename
git remote remove upstream          # remove
git remote set-url origin NEW_URL   # change URL

git fetch                           # download remote changes (no merge)
git fetch --prune                   # + remove deleted remote branches
git fetch --all                     # fetch all remotes

git pull                            # fetch + merge (or rebase if configured)
git pull --rebase                   # fetch + rebase
git pull origin main --rebase       # explicit

git push                            # push current branch
git push -u origin feature/login    # push + set upstream
git push --force-with-lease         # force push (safe: checks remote state)
git push origin --delete feature    # delete remote branch
git push --tags                     # push all tags

Tags#

git tag                             # list tags
git tag v1.0.0                      # lightweight tag on HEAD
git tag -a v1.0.0 -m "Release 1.0" # annotated tag (recommended)
git tag -a v1.0.0 abc1234           # tag a specific commit
git tag -d v1.0.0                   # delete local tag
git push origin v1.0.0             # push one tag
git push origin --tags              # push all tags
git push origin --delete v1.0.0     # delete remote tag
git describe --tags                 # current tag + distance

Log and history#

git log                             # full log
git log --oneline                   # one line per commit
git log --oneline --graph --all     # branch graph (text)
git log --stat                      # with file change summary
git log -p                          # with full diff
git log -10                         # last 10 commits
git log --author="Alice"
git log --since="2 weeks ago"
git log --after="2025-01-01" --before="2025-03-31"
git log --grep="fix:"               # commits where message matches
git log -S "function_name"          # commits that added/removed string (pickaxe)
git log -G "regex"                  # commits where diff matches regex
git log -- path/to/file             # history of a file
git log main..feature               # commits in feature not in main
git log origin/main..HEAD           # commits not yet pushed

# Pretty format
git log --pretty=format:"%h %as %an %s"
# %h = short hash, %as = author date (YYYY-MM-DD), %an = author name, %s = subject

Searching commits#

git log -S "myFunction" --oneline   # when was myFunction added?
git bisect start                    # begin binary search for bad commit
git bisect bad                      # mark current as bad
git bisect good v2.0.0              # mark known-good point
git bisect run ./tests.sh           # automated bisect
git bisect reset                    # done

blame#

git blame file.txt                  # who last changed each line
git blame -L 20,40 file.txt         # lines 20–40 only
git blame -w file.txt               # ignore whitespace changes
git blame -C file.txt               # detect moved code from other files

Undoing changes#

# Discard unstaged changes
git restore file.txt                # restore to HEAD (git 2.23+)
git checkout -- file.txt            # legacy equivalent

# Unstage (keep changes in working tree)
git restore --staged file.txt
git reset HEAD file.txt             # legacy

# Undo last commit (keep changes staged)
git reset --soft HEAD~1

# Undo last commit (keep changes unstaged)
git reset HEAD~1

# Undo last commit (discard changes completely)
git reset --hard HEAD~1

# Revert a commit (creates a new "undo" commit β€” safe for shared branches)
git revert HEAD                     # revert last commit
git revert abc1234                  # revert a specific commit
git revert -n abc1234               # revert without auto-committing
git revert main..HEAD               # revert a range

# Restore a deleted file
git checkout HEAD -- deleted-file.txt

# Recover from a bad reset (reflog saves you)
git reflog                          # list every HEAD movement
git reset --hard HEAD@{3}           # go back to 3 moves ago

Submodules#

git submodule add https://github.com/u/lib libs/lib   # add submodule
git submodule init                                      # init after clone
git submodule update                                    # fetch submodule commits
git submodule update --init --recursive                 # init + update all
git submodule update --remote --merge                   # update to latest upstream

git submodule foreach 'git pull'   # run command in every submodule
git submodule status               # show current commit per submodule
git submodule deinit libs/lib      # remove submodule
git rm libs/lib && rm -rf .git/modules/libs/lib

Worktrees (multiple working trees)#

git worktree add ../hotfix hotfix/urgent   # new worktree for branch
git worktree list                           # list all worktrees
git worktree remove ../hotfix              # clean up

Reflog β€” the safety net#

git reflog                          # all HEAD movements (local only)
git reflog show feature/login       # reflog for a branch
git reflog expire --expire=90.days.ago --all
git gc --prune=30.days.ago          # prune unreachable objects

Useful aliases (~/.gitconfig)#

[alias]
  st  = status -s
  co  = checkout
  sw  = switch
  br  = branch -vv
  lg  = log --oneline --graph --all --decorate
  lp  = log --oneline -15
  df  = diff --word-diff
  dfs = diff --staged --word-diff
  undo = reset HEAD~1
  unstage = restore --staged
  save = stash push -u -m
  wip  = commit -am "wip: save progress"
  oops = commit --amend --no-edit
  pushf = push --force-with-lease
  aliases = config --get-regexp alias

.gitignore patterns#

# Directories
node_modules/
dist/
.cache/

# Files by extension
*.log
*.pyc
*.class

# Specific files
.env
*.env.local
secrets.json

# Negate (don't ignore)
!important.log

# Anywhere in tree (no leading slash)
*.DS_Store

# Only at root
/config.local.yaml
git check-ignore -v file.txt        # explain why a file is ignored
git ls-files --ignored --exclude-standard  # list all ignored files

Cherry-pick#

git cherry-pick abc1234             # apply a specific commit to HEAD
git cherry-pick abc1234..def5678    # apply a range of commits
git cherry-pick -n abc1234          # stage without committing
git cherry-pick --abort
git cherry-pick --continue

[!TIP] git push --force-with-lease is always safer than --force. It verifies that the remote ref hasn’t changed since your last fetch, preventing accidentally overwriting someone else’s pushed commits.

[!WARN] git reset --hard, git clean -fd, and git checkout -- . permanently discard uncommitted changes. They are not recoverable once your working tree is clean. Use git stash if there’s any chance you want the changes back.