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-leaseis 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, andgit checkout -- .permanently discard uncommitted changes. They are not recoverable once your working tree is clean. Usegit stashif thereβs any chance you want the changes back.