Note HTTPS asks for credentials each time unless you configure a credential helper. SSH uses your key pair and is generally preferred for frequent pushes.
Note Without --global, the setting only applies to the current repository. Your commits will be attributed to whatever name/email is configured, so double-check with git config user.email before committing to a work repo.
set usernameset emailconfigure identitywho am i in gitchange git author
Set Default Editor
syntax
git config --global core.editor "<editor>"
example
git config --global core.editor "code --wait"# Or for vim:git config --global core.editor "vim"
Note The --wait flag for VS Code is essential. Without it, Git thinks the editor closed immediately and aborts the commit message.
# macOS — use the system keychain:git config --global credential.helper osxkeychain# Linux — cache for 1 hour:git config --global credential.helper 'cache --timeout=3600'
Note The 'store' helper saves passwords in plain text on disk. Prefer 'osxkeychain' on Mac or a credential manager on Windows. For SSH repos, credentials are handled by your SSH key instead.
save passwordstop asking passwordcredential helperremember loginauthentication
Global .gitignore
syntax
git config --global core.excludesFile <path>
example
git config --global core.excludesFile ~/.gitignore_global# Then in ~/.gitignore_global:
.DS_Store
Thumbs.db
*.swp
.idea/
.vscode/
Note Use this for OS and editor-specific files. Project-specific ignores still belong in the repo's own .gitignore so all contributors benefit.
global ignoreignore everywheresystem gitignoreignore IDE files
On branch main
Changes not staged for commit:
modified: app.js
Untracked files:
utils.js
# Short format:
M app.js
?? utils.js
Note The short flag (-s) gives a compact two-column output: left column = staging area, right column = working tree. M = modified, A = added, ? = untracked, D = deleted.
Note git add . stages new and modified files in the current directory and below. git add -A stages everything including deletions from anywhere in the repo. Prefer staging specific files to avoid accidentally committing secrets or large binaries.
stage filesadd to stagingprepare committrack filegit add
Stage Part of a File
syntax
git add -p [file]
example
git add -p utils.js
# Git shows each hunk and asks:# Stage this hunk [y,n,q,a,d,s,e,?]?# y = stage, n = skip, s = split into smaller hunks
Note Extremely useful when a file has multiple unrelated changes. You can commit them separately for a cleaner history. Use 's' to split a hunk if Git grouped too many changes together.
partial stagestage part of fileinteractive addstage hunkselective staging
Commit Staged Changes
syntax
git commit [-m "message"]
example
git commit -m "Add user authentication endpoint"# Multi-line message:git commit -m "Fix cart total calculation
The discount was applied after tax instead of before.
Updated the order of operations in calculateTotal()."
Note Without -m, Git opens your configured editor for the message. A good commit message has a short summary line (under 72 chars), a blank line, then optional details.
[main f4e5d6c] Update API response format
1 file changed, 12 insertions(+), 8 deletions(-)
Note The -a flag only stages already-tracked files that have been modified or deleted. It will NOT add new untracked files. You still need git add for brand new files.
quick commitcommit all modifiedstage and commitshortcut commit
View Unstaged Changes
syntax
git diff [file]
git diff --staged [file]
example
# Changes in working tree (not yet staged):git diff
# Changes that are staged for the next commit:git diff --staged# Diff for a specific file:git diff src/auth.js
Note Plain git diff shows only unstaged changes. Use --staged (or --cached, they're identical) to see what will go into the next commit. If git diff shows nothing, your changes are probably already staged.
see diffview changeswhat did i changecompare changesshow modifications
Note -s shows only counts (no commit messages), -n sorts by number of commits (most first), -e includes email addresses. Handy for seeing who contributed most.
commits by authorwho committedcontributor summarycommit count per person
Note This only creates the branch but does NOT switch to it. Use git switch or git checkout to move to the new branch. Branch names cannot contain spaces.
create branchnew branchmake branch
Switch Branches
syntax
gitswitch <branch>
gitswitch -c <new-branch>
example
gitswitch feature/user-profile
# Create and switch in one step:gitswitch -c feature/payments
output
Switched to branch 'feature/user-profile'
Note git switch is the modern replacement for git checkout (for branch switching). The -c flag creates the branch if it doesn't exist. You'll get an error if you have uncommitted changes that conflict with the target branch.
switch branchchange branchgo to branchcreate and switch branch
git checkout main
git checkout -b bugfix/login-error
output
Switched to branch 'main'
Note git checkout does double duty: switching branches AND restoring files. The newer git switch (branches) and git restore (files) commands split this up for clarity. Both approaches still work.
checkout branchswitch branch classiccreate branch and switch
Merge a Branch
syntax
git merge <branch>
example
# Switch to the target branch first:gitswitch main
git merge feature/user-profile
Note If there are no diverging commits, Git does a 'fast-forward' merge (no merge commit). Use --no-ff to force a merge commit even when fast-forward is possible, which keeps branch history visible in the log.
merge branchcombine branchesintegrate branchmerge into main
Resolve Merge Conflicts
syntax
# After a merge with conflicts:git status
# Edit conflicted files, then:git add <resolved-file>
git commit
example
git merge feature/payments
# CONFLICT in src/cart.js# Open the file — look for conflict markers:# <<<<<<< HEAD# ... your changes ...# =======# ... their changes ...# >>>>>>> feature/payments# Manually resolve, then:git add src/cart.js
git commit -m "Merge feature/payments, resolve cart conflict"
Note Never leave conflict markers (<<<, ===, >>>) in your code. Use git diff --check before committing to verify no markers remain. You can abort a conflicted merge with git merge --abort.
Note WARNING: -D (uppercase) force-deletes without checking if the branch is merged. You could lose unmerged work. Always use lowercase -d first, which will warn you if the branch has unmerged changes.
# Rename the current branch:git branch -m feature/user-settings
# Rename a different branch:git branch -m old-name new-name
Note If the branch has been pushed, you'll also need to delete the old remote branch and push the new one: git push origin --delete old-name && git push -u origin new-name
# Branches already merged into main:git branch --merged main# Branches NOT yet merged into main:git branch --no-merged main
output
feature/user-profile
bugfix/login-error
Note Great for cleanup. Branches listed by --merged are safe to delete. Combine with xargs to bulk-delete: git branch --merged main | grep -v main | xargs git branch -d
Note The -v flag shows the full URLs. Without it, you only see remote names. Fetch and push URLs can differ if configured separately.
show remoteslist remotesremote urlcheck originview remote url
Fetch from Remote
syntax
git fetch [remote] [branch]
example
git fetch origin
git fetch origin main
git fetch --all
output
remote: Counting objects: 15, done.
From https://github.com/user/project
a1b2c3d..f4e5d6c main -> origin/main
Note Fetch downloads new data but does NOT modify your working directory or merge anything. It's always safe to run. Use --all to fetch from every configured remote.
fetch changesdownload updatessync remoteget latest from remote
Pull (Fetch + Merge)
syntax
git pull [remote] [branch]
example
git pull origin main
# Pull with rebase instead of merge:git pull --rebase origin main
Note git pull = git fetch + git merge. Use --rebase to replay your local commits on top of the fetched changes (cleaner linear history). Some teams mandate pull --rebase to avoid unnecessary merge commits.
pull changesget latestupdate localsync with remotedownload and merge
Push to Remote
syntax
git push [remote] [branch]
example
git push origin main
git push origin feature/payments
output
Counting objects: 5, done.
To https://github.com/user/project.git
a1b2c3d..f4e5d6c main -> main
Note Push will be rejected if the remote has commits you don't have locally. Pull first, resolve any conflicts, then push again. Avoid --force unless you truly understand the consequences (see Common Mistakes).
push changesupload commitspush to githubsend to remote
# First push of a new branch — set tracking:git push -u origin feature/payments
# After this, just use:git push
git pull
output
Branch 'feature/payments' set up to track remote branch 'feature/payments' from 'origin'.
Note The -u flag (or --set-upstream-to) links your local branch to a remote branch. After that, plain git push and git pull know where to go without specifying the remote and branch every time.
set upstreamtrack remote branchlink branch to remotepush -udefault push target
Prune Stale Remote Branches
syntax
git remote prune <remote>
git fetch --prune
example
git remote prune origin
# Or prune during fetch:git fetch --prune
Note When someone deletes a remote branch, your local reference to it lingers. Pruning removes these stale references. Does not delete your local branches — only the remote-tracking references.
prune branchesremove stale remotescleanup remote branchesdeleted remote branch still showing
Note Renaming a remote also updates all remote-tracking branches. Removing a remote deletes all its tracking branches and configuration entries.
rename remoteremove remotedelete remotechange remote name
Push All Branches
syntax
git push --all <remote>git push --tags <remote>
example
git push --all origingit push --tags origin
Note Pushes every local branch to the remote. Be cautious with this in shared repos — you might push experimental branches others don't want. --tags pushes all tags separately.
push all branchespush everythingpush tagsmirror push
git stash
git stash push -m "WIP: payment form validation"
output
Saved working directory and index state WIP on main: a1b2c3d Add auth
Note Stash saves your uncommitted changes (staged and unstaged) and reverts the working directory to HEAD. New untracked files are NOT stashed by default — use -u to include them.
stash changessave work temporarilyshelve changesput aside changes
Saved working directory and index state On main: WIP: new config files
Note Without -u, brand new files that Git hasn't started tracking will be left behind when you stash. This catches most people off guard.
stash new filesstash untrackedstash everythinginclude new files in stash
Restore Stashed Changes
syntax
git stash pop [stash@{n}]
example
# Restore most recent stash and remove it from the stash list:git stash pop
# Restore a specific stash:git stash pop stash@{2}
output
On branch main
Changes not staged for commit:
modified: src/cart.js
Note pop = apply + drop in one step. If there's a conflict during pop, the stash is NOT dropped — you'll need to resolve the conflict and manually run git stash drop afterwards.
restore stashpop stashget stash backunstashapply and remove stash
Apply Stash Without Removing
syntax
git stash apply [stash@{n}]
example
git stash apply
git stash apply stash@{1}
Note Unlike pop, apply keeps the stash in the list. Useful when you want to apply the same stash to multiple branches.
apply stashuse stash keep itapply without removing
List All Stashes
syntax
git stash list
example
git stash list
output
stash@{0}: On main: WIP: payment form validation
stash@{1}: WIP on feature/auth: a1b2c3d Add login
stash@{2}: On main: WIP: new config files
Note Stashes are stored as a stack — stash@{0} is always the most recent. The list shows which branch and commit each stash was created from.
list stashesshow stashsee all stasheshow many stashes
Delete a Stash
syntax
git stash drop [stash@{n}]
git stash clear
example
# Drop a specific stash:git stash drop stash@{1}
# Delete ALL stashes:git stash clear
output
Dropped stash@{1} (a1b2c3d4e5f6...)
Note WARNING: git stash clear deletes every stash with no confirmation and no way to recover them. Drop individual stashes if you're not sure.
delete stashremove stashdrop stashclear all stashes
View Stash Contents
syntax
git stash show [-p] [stash@{n}]
example
# Summary of changes:git stash show stash@{0}
# Full diff:git stash show -p stash@{0}
Note Without -p, you see a summary like git diff --stat. With -p, you see the full diff. Useful for checking what's in a stash before applying it.
view stash diffwhat is in stashstash contentsinspect stashpreview stash
Create Branch from Stash
syntax
git stash branch <branch-name> [stash@{n}]
example
git stash branch feature/rescue-work stash@{0}
output
Switched to a new branch 'feature/rescue-work'
On branch feature/rescue-work
Changes not staged for commit:
modified: src/cart.js
Dropped stash@{0} (a1b2c3d...)
Note Creates a new branch starting from the commit where the stash was originally created, applies the stash, then drops it. Perfect when you stashed on the wrong branch.
branch from stashstash to branchmove stash to branchwrong branch stash
Stash Specific Files
syntax
git stash push [-m "message"] -- <file1> <file2>
example
git stash push -m "just the config changes"-- config.yaml .env.example
output
Saved working directory and index state On main: just the config changes
Note The -- separator tells Git that everything after it is a file path. You can also use git stash push -p to interactively choose which hunks to stash.
stash one filestash specific filespartial stashselective stash
# Discard changes to one file:git restore src/app.js
# Discard all unstaged changes:git restore .
Note WARNING: This permanently discards your uncommitted changes — there is no undo. The file reverts to whatever is in the staging area (or HEAD if nothing is staged). Double-check with git diff first.
Note This moves the file from staged back to unstaged. Your actual changes in the working directory are preserved — nothing is lost. This is the modern replacement for git reset HEAD <file>.
unstage fileremove from stagingundo git adduntrack staged file
Undo Commit, Keep Changes Staged
syntax
git reset --soft HEAD~<n>
example
# Undo the last commit, keep everything staged:git reset --soft HEAD~1
Note Moves HEAD back but leaves your changes in the staging area, ready to be committed again. Perfect for rewriting a commit message or combining the last few commits into one.
# Undo last commit, unstage changes but keep them:git reset HEAD~1
output
Unstaged changes after reset:
M src/auth.js
M src/routes.js
Note --mixed is the default mode. Changes end up in your working directory as unstaged modifications. You can re-add specific files and commit differently.
undo commit keep changesmixed resetunstage commitundo last commit
# Throw away the last commit entirely:git reset --hard HEAD~1# Reset to a specific commit:git reset --hard a1b2c3d
output
HEAD is now at f4e5d6c Update API response format
Note DANGER: This permanently destroys uncommitted changes AND the commits you reset past. There is no undo for uncommitted work. Commits can be recovered via git reflog within ~30 days, but unstaged/uncommitted edits are gone forever.
hard resetdiscard commitsthrow away commitsnuke changesreset to commit
Note Revert creates a NEW commit that undoes the changes from the specified commit. Unlike reset, it doesn't rewrite history, making it safe for shared/public branches. Always prefer revert over reset for pushed commits.
revert commitundo commit safelyreverse a commitundo pushed commit
Amend the Last Commit
syntax
git commit --amend [-m "new message"]
example
# Fix the commit message:git commit --amend -m "Add user auth endpoint with JWT support"# Add forgotten files to the last commit:git add forgotten-file.js
git commit --amend --no-edit
output
[main b2c3d4e] Add user auth endpoint with JWT support
Note WARNING: Amending rewrites the commit hash. Never amend a commit that has already been pushed to a shared branch — other developers who pulled the original commit will have conflicts. Use git revert instead for pushed commits.
amend commitfix last commitchange commit messageadd file to last commitedit commit
# Restore app.js to how it was 3 commits ago:git restore --source=HEAD~3 src/app.js# Classic syntax:git checkout a1b2c3d -- src/app.js
Note This replaces the file in your working directory with the version from that commit. The change is staged automatically. Useful for recovering a deleted or broken file without resetting the whole branch.
restore file from commitget old version of filerecover deleted filecheckout specific file
Remove Untracked Files
syntax
git clean -f [-d] [-n]
example
# Preview what would be deleted:git clean -n -d
# Actually delete untracked files and directories:git clean -f -d
output
Would remove build/
Would remove temp.log
Removing build/
Removing temp.log
Note DANGER: This permanently deletes files that are not tracked by Git. Always run with -n (dry run) first to preview what will be removed. Use -x to also remove files that match .gitignore patterns (like node_modules).
remove untracked filesclean working directorydelete generated filesgit clean
Reset a Single File to HEAD
syntax
git checkout HEAD -- <file>git restore --source=HEAD --staged --worktree <file>
example
# Fully reset one file (both staging and working directory):git restore --source=HEAD --staged --worktree src/config.js
Note This restores the file to exactly how it is in the last commit, discarding both staged and unstaged changes to that specific file only.
reset one fileundo changes to filerestore single file
# While on feature/payments:gitswitch feature/payments
git rebase main
output
Successfully rebased and updated refs/heads/feature/payments.
Note Rebase replays your branch's commits on top of the target branch, creating a linear history. NEVER rebase commits that have been pushed to a shared branch — it rewrites commit hashes and will cause chaos for collaborators.
rebase branchrebase onto mainlinear historyreplay commits
Interactive Rebase
syntax
git rebase -i HEAD~<n>
git rebase -i <commit>
example
# Rewrite the last 4 commits:git rebase -i HEAD~4# Editor opens with:# pick a1b2c3d Add login page# pick d4e5f6g Add signup page# pick h7i8j9k Fix typo in login# pick l0m1n2o Update styles## Change 'pick' to: squash, reword, edit, drop, etc.
Note Commands: pick (keep as-is), reword (change message), squash (merge into previous commit), fixup (squash but discard message), edit (pause to modify), drop (delete commit). Save and close the editor to execute.
git rebase -i HEAD~<n>
# Change 'pick' to 'squash' or 'fixup' for commits to merge
example
git rebase -i HEAD~3# In the editor, change to:# pick a1b2c3d Add user profile feature# squash d4e5f6g Fix profile image upload# squash h7i8j9k Add profile validation# Save — a new editor opens to combine commit messages
output
[detached HEAD x1y2z3w] Add user profile feature
Date: Mon Mar 30 14:22:01 2026 +0000
3 files changed, 120 insertions(+), 15 deletions(-)
Note Use 'squash' to combine commit messages. Use 'fixup' (or just 'f') to discard the squashed commit's message entirely. Great for cleaning up WIP commits before merging to main.
squash commitscombine commitsmerge commits into oneclean up commits
Rebase onto a Different Base
syntax
git rebase --onto <new-base> <old-base> <branch>
example
# Move feature/settings from feature/auth onto main:git rebase --onto main feature/auth feature/settings
Note This is for when you branched off the wrong branch. It takes the commits unique to <branch> (after <old-base>) and replays them onto <new-base>. Visualize it as transplanting a range of commits.
rebase ontomove branch basechange branch parenttransplant commitsbranched off wrong branch
Abort a Rebase in Progress
syntax
git rebase --abort
example
# Things went wrong during rebase — bail out:git rebase --abort
Note Restores your branch to exactly how it was before the rebase started. Safe to run at any point during a rebase. No work is lost.
abort rebasecancel rebasestop rebaseundo rebase
Continue Rebase After Resolving Conflict
syntax
git rebase --continue
example
# After resolving conflicts in a file:git add src/auth.js
git rebase --continue
Note When a conflict occurs during rebase, resolve it, stage the file with git add, then continue. Do NOT commit — rebase --continue handles the commit for you.
continue rebaserebase after conflictresume rebasefinish rebase
Skip a Conflicting Commit During Rebase
syntax
git rebase --skip
example
git rebase --skip
Note Drops the current conflicting commit entirely and moves to the next one. Use this when the conflict is caused by a commit that's no longer needed (e.g., it was already applied upstream).
skip commit rebaseskip conflictdrop commit during rebase
Autosquash with Fixup Commits
syntax
git commit --fixup=<commit>git rebase -i --autosquash <base>
example
# Make a commit that will auto-squash into a1b2c3d:git commit --fixup=a1b2c3d# Later, rebase with autosquash:git rebase -i --autosquash main
Note Git automatically reorders and marks fixup commits to be squashed into their target. The commit message starts with 'fixup!' followed by the target's message. Set rebase.autoSquash=true in config to always enable this.
Note This is the CLI equivalent of a graphical Git viewer. --all shows all branches, --decorate labels commits with branch/tag names. A great alias candidate: git config --global alias.lg 'log --oneline --graph --all --decorate'
Note Shows the commit, author, date, and line content for each line. Use -L to limit to a range. Blame doesn't mean assigning fault — it's just finding who last changed a line and why.
who changed lineblameline historywho wrote thisfind author of code
Binary Search for Bug Introduction
syntax
git bisect start
git bisect bad [commit]
git bisect good <commit>
example
git bisect start
git bisect bad # Current commit is brokengit bisect good v1.0.0# This tag was working# Git checks out a middle commit, you test it:# If it's good:git bisect good
# If it's bad:git bisect bad
# Repeat until Git finds the first bad commitgit bisect reset # Return to original branch
output
Bisecting: 8 revisions left to test after this (roughly 3 steps)
a1b2c3d is the first bad commit
Note Bisect uses binary search to find the exact commit that introduced a bug. For N commits, it takes at most log2(N) steps. You can automate it with: git bisect run <test-script>
find bug commitbisectwhen did bug appearbinary search commitstrack down regression
View Reference Log (Recovery Tool)
syntax
git reflog [branch]
example
git reflog
git reflog show feature/payments
output
a1b2c3d HEAD@{0}: commit: Add auth endpoint
f4e5d6c HEAD@{1}: reset: moving to HEAD~1
9g8h7i6 HEAD@{2}: commit: Broken attempt
b3c4d5e HEAD@{3}: checkout: moving from feature to main
Note The reflog records every time HEAD moves. It's your safety net — even after a hard reset or accidental branch deletion, you can find the commit hash here and recover with: git reset --hard HEAD@{n}. Entries expire after ~90 days.
reflogrecover lost commitundo resetfind deleted branchsafety netrecovery
# All differences between the two branches:git diff main..feature/payments
# Only changes introduced in feature/payments since it diverged from main:git diff main...feature/payments
Note Two dots (..) shows the full diff between branch tips. Three dots (...) shows only what changed in the right branch since the common ancestor. Three dots is usually what you want for reviewing a feature branch.
diff branchescompare branchesbranch differenceswhat changed on branch
Cherry-Pick a Commit
syntax
git cherry-pick <commit> [<commit2> ...]
example
gitswitch main
git cherry-pick a1b2c3d
# Cherry-pick a range:git cherry-pick d4e5f6g..h7i8j9k
Note Creates a NEW commit with the same changes (but different hash). Useful for applying a bugfix from one branch to another without merging the whole branch. If there's a conflict, resolve it and run git cherry-pick --continue.
cherry pickcopy commit to branchapply specific commitpick one commit
# Find commits mentioning 'authentication':git log --grep="authentication" --oneline# Find commits where 'validateToken' was added or removed:git log -S "validateToken"--oneline# Regex search in diffs:git log -G "api.*endpoint"--oneline
output
a1b2c3d Add user authentication endpoint
f4e5d6c Refactor authentication middleware
Note -S (pickaxe) finds commits where the number of occurrences of a string changed. -G finds commits where the diff matches a regex. --grep searches commit messages only.
search commitsfind commit by messagesearch code changespickaxegrep commits
Note Lightweight tags are just pointers to a commit — no extra metadata. For releases, prefer annotated tags which include author, date, and a message.
create tagsimple taglightweight taglabel commit
Create an Annotated Tag
syntax
git tag -a <name> -m "message" [commit]
example
git tag -a v2.0.0 -m "Major release: new payment system"git tag -a v1.5.0 -m "Performance improvements" a1b2c3d
Note Annotated tags store the tagger's name, email, date, and a message. They're full Git objects. Use these for releases so you have a record of who tagged and why.
annotated tagrelease tagtag with messageversion tag
List Tags
syntax
git tag [-l "pattern"]
example
git tag
git tag -l "v2.*"
output
v1.0.0
v1.5.0
v2.0.0
v2.1.0
Note Use -l with a glob pattern to filter. Combine with --sort=-version:refname to sort by semantic version descending.
list tagsshow tagssee all tagsfilter tags
Push Tags to Remote
syntax
git push origin <tag>
git push origin --tags
example
# Push a single tag:git push origin v2.0.0# Push all tags:git push origin --tags
output
To https://github.com/user/project.git
* [new tag] v2.0.0 -> v2.0.0
Note Tags are NOT pushed automatically with git push. You must explicitly push them. --tags pushes all local tags that don't exist on the remote.
push tagupload tagshare tagpush version tag
Delete a Tag
syntax
git tag -d <tag>
git push origin --delete <tag>
example
# Delete locally:git tag -d v1.0.0-beta
# Delete from remote:git push origin --delete v1.0.0-beta
output
Deleted tag 'v1.0.0-beta' (was a1b2c3d)
Note You must delete a tag both locally and on the remote — they're independent. Other collaborators who already fetched the tag will still have it until they prune.
delete tagremove taguntagdelete remote tag
Checkout a Tag
syntax
git checkout <tag>
example
git checkout v2.0.0
output
Note: switching to 'v2.0.0'.
You are in 'detached HEAD' state.
Note Checking out a tag puts you in detached HEAD state — you're not on any branch. To make changes, create a branch from the tag: git switch -c hotfix/v2.0.1
checkout taggo to tagview tagged versionswitch to release
View Tag Details
syntax
git show <tag>
example
git show v2.0.0
output
tag v2.0.0
Tagger: Samira Khan <[email protected]>
Date: Mon Mar 30 14:22:01 2026 +0000
Major release: new payment system
commit a1b2c3d...
...
Note For annotated tags, shows the tagger info and message plus the commit it points to. For lightweight tags, just shows the commit.
tag infotag detailsshow tagwho tagged
Find the Latest Tag
syntax
git describe --tags --abbrev=0git describe --tags
example
git describe --tags --abbrev=0git describe --tags
output
v2.0.0
v2.0.0-3-ga1b2c3d
Note --abbrev=0 gives just the tag name. Without it, Git also shows how many commits since that tag and the current abbreviated hash. Useful in CI/CD for version stamping.
latest tagcurrent versionmost recent tagdescribe version
# Check out main in a separate directory:git worktree add ../project-main main
# Now you can work on both branches simultaneously# List all worktrees:git worktree list
Note Worktrees let you have multiple branches checked out at once in different directories, sharing the same .git database. Huge time-saver when you need to quickly fix a bug on main without stashing your feature work.
worktreemultiple checkoutswork on two branchesparallel branches
# Add a submodule:git submodule add https://github.com/lib/shared-utils.git libs/shared# After cloning a repo with submodules:git submodule update--init --recursive# Update submodule to latest:cd libs/shared && git pull origin main
cd ../.. && git add libs/shared && git commit -m "Update shared-utils submodule"
Note Submodules are notoriously tricky. Each submodule is pinned to a specific commit. Collaborators must run git submodule update --init after cloning or they'll have empty directories. Consider alternatives like package managers when possible.
Note Only checks out specified directories from a large repo. Extremely useful for monorepos where you only need a small portion. --filter=blob:none avoids downloading file content you don't need.
# Remove a file from entire history (e.g., accidentally committed secret):git filter-repo --invert-paths --path secrets.env# Extract a subdirectory into its own repo:git filter-repo --path src/auth/
Note WARNING: This rewrites ALL commit hashes. Every collaborator must re-clone. git-filter-repo is a separate tool (pip install git-filter-repo) that replaces the deprecated git filter-branch. Back up your repo before using this.
remove file from historyrewrite historyfilter-reporemove secret from gitextract subdirectory
Git Hooks
syntax
# Hooks live in .git/hooks/# Common hooks: pre-commit, pre-push, commit-msg, post-merge
example
# Create a pre-commit hook that runs linting:# File: .git/hooks/pre-commit#!/bin/sh
npm run lint
if [ $? -ne 0 ]; thenecho"Linting failed. Commit aborted."
exit 1fi# Make it executable:
chmod +x .git/hooks/pre-commit
Note Hooks in .git/hooks/ are local and not committed to the repo. Use tools like Husky (Node) or pre-commit (Python) to share hooks via the repo. The hook must be executable and must exit 0 to allow the action to proceed.
git hookspre-commit hookpre-push hookautomate checksrun tests before commit
# Find your GPG key:
gpg --list-secret-keys --keyid-format=long# Configure Git to sign:git config --global user.signingkey ABC123DEF456git config --global commit.gpgsign true# Verify a signed commit:git log --show-signature -1
output
gpg: Signature made Mon 30 Mar 14:22:01 2026
gpg: Good signature from "Samira Khan <[email protected]>"
Note Signing proves you actually authored a commit. GitHub shows a 'Verified' badge for signed commits. You can also use SSH keys for signing (Git 2.34+): git config --global gpg.format ssh
git config --global rerere.enabled true# Now when you resolve a merge conflict, Git remembers.# Next time the same conflict appears, Git resolves it automatically.
output
Recorded resolution for 'src/config.js'.
...
Resolved 'src/config.js' using previous resolution.
Note rerere = 'reuse recorded resolution'. Incredibly useful when you repeatedly rebase a long-lived branch and keep hitting the same conflict. Enable it globally — there's no downside.
# Create .git-blame-ignore-revs in the repo root:# Content: one commit hash per line# a1b2c3d4e5f6... (bulk prettier formatting)git config blame.ignoreRevsFile .git-blame-ignore-revs
git blame src/app.js
Note After running a bulk formatting tool (Prettier, Black, etc.), every line shows you as the author in blame. This file tells Git to skip those commits, preserving meaningful blame history. GitHub also respects this file automatically.
ignore formatting commitblame ignore revsskip bulk commit in blamemeaningful blame
# Bundle the entire repo:git bundle create repo.bundle --all# Bundle just the last 10 commits on main:git bundle create recent.bundle -10 main
# Clone from a bundle (offline transfer):git clone repo.bundle my-project
Note Bundles are single-file snapshots of a repo that can be transferred via USB, email, etc. Useful for air-gapped environments or when network access is limited.
bundle repooffline gittransfer repoexport repogit without network
# DANGEROUS — overwrites remote without checking:git push --force origin main # DON'T DO THIS# SAFER — fails if someone else pushed since your last fetch:git push --force-with-lease origin feature/my-work
Note NEVER force push to main/master or any shared branch. It rewrites remote history and other developers' work will be lost or corrupted. If you must force push (e.g., after rebase on a personal branch), always use --force-with-lease which checks that the remote hasn't changed since you last fetched.
force pushsafe force pushpush rejectedforce push dangerforce with lease
Detached HEAD State
syntax
# You're in detached HEAD when:git checkout <commit-hash>
git checkout <tag>
# To fix — create a branch:gitswitch -c <new-branch>
example
git checkout a1b2c3d
# Warning: you are in 'detached HEAD' state.# If you made commits here and want to keep them:gitswitch -c rescue-branch
# If you just want to go back to a branch:gitswitch main
Note Detached HEAD means you're not on any branch. Commits made here will be garbage-collected if you switch away without creating a branch. If you accidentally left commits behind, find them with git reflog.
detached headnot on any branchlost commits detachedfix detached head
Merge vs Rebase: When to Use Which
syntax
# Merge: preserves history as it happenedgit merge feature/branch
# Rebase: creates linear historygit rebase main
example
# Team rule of thumb:# - Rebase your local/personal feature branch onto main regularlygitswitch feature/my-work
git rebase main
# - Merge the feature branch into main when donegitswitch main
git merge --no-ff feature/my-work
Note The golden rule: NEVER rebase commits that exist on a remote branch others are using. Rebase rewrites history (new hashes), while merge preserves it. Use rebase to keep your feature branch up-to-date privately; use merge to integrate completed work publicly.
merge vs rebasewhen to rebasewhen to mergerebase or merge
# Find the lost commit:git reflog
# Look for the entry before the mistake:# a1b2c3d HEAD@{5}: commit: Important work# Option 1: Reset to that point:git reset --hard a1b2c3d# Option 2: Cherry-pick just that commit:git cherry-pick a1b2c3d
# Option 3: Create a branch from it:git branch recovery a1b2c3d
Note Reflog entries expire after ~90 days by default. As long as the commit is in the reflog, it hasn't been garbage-collected and you can recover it. If you ran git gc manually, some commits may be permanently lost.
recover commitlost commitundo hard resetfind deleted commitaccidentally deleted branch
# File was tracked before adding to .gitignore:git rm --cached .envgit rm -r --cached node_modules/git commit -m "Stop tracking ignored files"
output
rm '.env'
rm 'node_modules/package-lock.json'
...
Note .gitignore only prevents UNTRACKED files from being added. If a file is already tracked, .gitignore has no effect on it. You must remove it from tracking with git rm --cached (the file itself stays on disk). This is one of the most common Git gotchas.
gitignore not workingfile still trackedignore already tracked filestop tracking file
Undo a Commit That's Already Pushed
syntax
git revert <commit>
git push
example
# The safe way — create a new commit that reverses the changes:git revert a1b2c3d
git push origin main
# If you need to undo multiple commits:git revert a1b2c3d..f4e5d6g
git push origin main
Note NEVER use git reset + force push on a shared branch. Use git revert instead, which creates a new commit that undoes the changes while preserving history. Everyone who pulled the original commit won't have conflicts.
undo pushed commitrevert published commitfix mistake on mainundo after push
Accidentally Committed a Secret
syntax
git filter-repo --invert-paths --path <secret-file># Then: rotate the credential immediately
example
# Step 1: ROTATE THE SECRET IMMEDIATELY# (It's in the remote history even if you delete it)# Step 2: Remove from all history:
pip install git-filter-repo
git filter-repo --invert-paths --path .env# Step 3: Force push the rewritten history:git push --force --all# Step 4: Tell all collaborators to re-clone
Note CRITICAL: Even if you delete the file in a new commit, the secret is still in the Git history and anyone can find it. The FIRST thing to do is rotate/revoke the compromised credential. Rewriting history with filter-repo is step two. GitHub also has a 'secret scanning' feature that alerts you.
committed secretremove password from gitleaked credentialsecret in historyremove sensitive file
Committed to the Wrong Branch
syntax
# Move last commit to a different branch:gitswitch <correct-branch>
git cherry-pick <commit>
gitswitch <wrong-branch>
git reset --hard HEAD~1
example
# Oops, committed to main instead of feature branch:git log --oneline -1 # Note the hash: a1b2c3dgitswitch feature/payments
git cherry-pick a1b2c3d
gitswitch main
git reset --hard HEAD~1
Note If the wrong branch hasn't been pushed yet, reset is safe. If it's already pushed, you'll need git revert on the wrong branch instead of reset, to avoid force-pushing shared history.
wrong branchcommitted to wrong branchmove commit to another branchmisplaced commit
# If not yet pushed — remove and recommit:git rm --cached data/huge-dataset.csvecho'data/*.csv' >> .gitignore
git commit -m "Remove large file, update gitignore"# If already pushed — rewrite history:git filter-repo --strip-blobs-bigger-than 50M
Note Git stores every version of every file forever. A 500MB file committed once bloats the repo permanently (even after deletion) unless you rewrite history. Use Git LFS for large files, and add size limits to your .gitignore proactively.
large filebig file in gitreduce repo sizegit lfsbloated repo