From first commit to advanced rebasing, hooks and GitHub Actions - everything in one place.
Git is a distributed version control system (DVCS) created by Linus Torvalds in 2005. Every developer has a complete copy of the repo (including full history) so it works entirely offline.
Repository
Dir tracked by Git containing your files + the hidden .git folder.
Commit
Snapshot of your project. Each has a unique SHA-1 hash.
Branch
Lightweight pointer to a commit. Free to create, instant.
Working Tree
The actual files you see and edit on disk.
Staging Area
Preparation zone - you choose exactly what enters each commit.
HEAD
Pointer to the current branch's latest commit.
GitHub is a cloud platform built on Git that adds collaboration: pull requests, code review, issues and CI/CD via Actions. Git is the tool; GitHub is the social layer.
| Feature | Git | GitHub |
|---|---|---|
| Type | CLI Tool | Web Platform |
| Works Offline | ✅ Yes | ❌ No |
| PRs / Reviews | ❌ | ✅ Built-in |
| CI/CD | ❌ | ✅ GitHub Actions |
| Issue Tracking | ❌ | ✅ Built-in |
# Windows Package Manager $ winget install Git.Git # Scoop $ scoop install git
$ brew install git
# Debian/Ubuntu $ sudo apt install git # Fedora $ sudo dnf install git # Arch $ sudo pacman -S git
$ git --version git version 2.45.0
Three scopes: --system (all users) ·
--global (your account) ·
--local (this repo). Local wins.
$ git config --global user.name "Your Name" $ git config --global user.email "git@github.com" $ git config --global core.editor "code --wait" $ git config --global init.defaultBranch main $ git config --global color.ui auto
$ ssh-keygen -t ed25519 -C "git@github.com" $ eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519 # Copy public key → GitHub → Settings → SSH Keys $ ssh -T git@github.com Hi atharvbyadav! You've successfully authenticated.
Every file passes through three states. Understanding this unlocks Git's mental model.
| Type | Description | Contains |
|---|---|---|
blob |
File snapshot | Raw file content |
tree |
Directory | Refs to blobs + other trees |
commit |
Snapshot | Root tree + parent + author + message |
tag |
Named pointer | Commit hash + metadata |
git diff are
computed on-the-fly by comparing snapshots.
$ git init # new repo $ git clone https://github.com/u/r.git # clone HTTPS $ git clone git@github.com:u/r.git # clone SSH $ git clone --depth 1 https://… # shallow (fast)
# full status $ git status # compact status $ git status -s M README.md ← modified, not staged ?? new.js ← untracked # show unstaged / staged / branch diff $ git diff $ git diff --staged $ git diff main..feature
# specific file $ git add README.md # stage everything in current dir $ git add . # hunk-by-hunk interactive staging $ git add -p src/app.js # unstage a file $ git restore --staged README.md
$ git commit -m "feat: add login" # stage all tracked files and commit $ git commit -am "fix: typo" # fix last commit without changing message $ git commit --amend --no-edit
# pretty tree view $ git log --oneline --graph --all # last 5 commits $ git log -5 # history of a specific file $ git log --follow src/app.js # filter by author $ git log --author="Atharv"
feat:
fix: docs:
chore: prefixes. Enables automated changelogs
and semantic versioning.
Branches are just pointers to commits - creating one is instant and free.
# list local / all branches $ git branch $ git branch -a # create + switch / just switch $ git switch -c feature/login $ git switch main # rename / delete merged / force delete $ git branch -m new-name $ git branch -d feature/login $ git branch -D feature/login
$ git switch main && git merge feature/login # fast-forward $ git merge --no-ff feature/login # force merge commit $ git merge --squash feature/login # squash to one $ git merge --abort # bail out
| Strategy | History | Best for |
|---|---|---|
| Fast-Forward | Linear | Simple features, no divergence |
--no-ff |
Preserves branch shape | Keeping feature context |
--squash |
Single clean commit | Messy WIP → clean main |
| Rebase + merge | Linear, clean | Teams preferring linear log |
$ git remote -v # list $ git remote add origin git@github.com:u/r # add $ git remote add upstream git@github.com:o/r # fork upstream $ git remote set-url origin git@… # change URL $ git remote remove origin # remove
# download without merging $ git fetch origin # fetch + merge / fetch + rebase (cleaner history) $ git pull origin main $ git pull --rebase origin main # push and set upstream tracking $ git push -u origin feature/x # delete remote branch / safe force push $ git push origin --delete old $ git push --force-with-lease
--force on shared branches. Use
--force-with-lease - it fails if someone pushed
since your last fetch.
git switch -c feature/my-feature -
never work directly on main.
git add →
git commit with meaningful messages.
git push -u origin feature/my-feature
$ gh auth login $ gh pr create --title "feat: auth" --body "…" $ gh pr list $ gh pr review 42 --approve $ gh pr merge 42 --squash $ gh pr checkout 42
## What does this PR do? Brief description of what changed and why. ## Type of Change - [ ] Bug fix - [ ] New feature - [ ] Breaking change ## How to Test 1. Step one 2. Step two ## Checklist - [ ] Tests added - [ ] Docs updated - [ ] No console.log
Fork = your own GitHub copy of someone else's repo, so you can contribute without write access.
# 1. Fork on GitHub (click Fork) # 2. Clone YOUR fork $ git clone git@github.com:YOU/repo.git # 3. Add original as upstream $ git remote add upstream git@github.com:OWNER/repo.git # 4. Sync fork with upstream $ git fetch upstream && git switch main $ git merge upstream/main && git push origin main # 5. Work on a branch, then open PR to upstream $ git switch -c fix/my-fix && git push -u origin fix/my-fix
Conflicts occur when two branches change the same lines differently. Git marks them - you decide what to keep.
<<<<<<< HEAD const greeting = "Hello, World!"; // your version ======= const greeting = "Hi there!"; // incoming version >>>>>>> feature/new-greeting
git status shows "both modified" files.
git add src/app.js
git commit (message pre-filled)
$ git checkout --theirs file # take incoming entirely $ git checkout --ours file # keep current entirely $ git merge --abort # abandon merge
Stash saves uncommitted changes to a temporary stack, so you can switch branches cleanly.
# stash tracked / include untracked / named $ git stash $ git stash -u $ git stash push -m "WIP: auth form" # list / apply + remove / apply specific $ git stash list $ git stash pop $ git stash apply stash@{2} # delete one / turn stash into branch $ git stash drop stash@{0} $ git stash branch feature/x
Tags are permanent named pointers to commits, used for version releases. Unlike branches, they don't move.
# list tags $ git tag # create annotated tag / tag a past commit $ git tag -a v1.0.0 -m "stable release" $ git tag -a v0.9.0 9fceb02 # push one tag / push all tags $ git push origin v1.0.0 $ git push origin --tags # delete local / delete remote $ git tag -d v1.0.0 $ git push origin --delete v1.0.0
vMAJOR.MINOR.PATCH. MAJOR = breaking, MINOR =
new feature, PATCH = bug fix.
$ git show a3f2b1c # commit details $ git blame src/app.js # who changed what $ git blame -L 10,25 src/app.js # limit to lines $ git log -S "loginUser" # when was this added? $ git log -G "login.*User" # regex search in diffs $ git log -L :loginUser:src/auth.js # function history
$ git bisect start $ git bisect bad # current = broken $ git bisect good v1.0 # v1.0 = fine # Test each checkout, then: $ git bisect good # or git bisect bad # Git finds exact breaking commit $ git bisect reset
| Scenario | Command | Safe? |
|---|---|---|
| Discard file changes | git restore <file> |
✅ |
| Unstage a file | git restore --staged <f> |
✅ |
| Undo commit (keep staged) | git reset --soft HEAD~1 |
✅ |
| Undo commit (keep unstaged) | git reset HEAD~1 |
✅ |
| Undo commit (discard all) | git reset --hard HEAD~1 |
⚠️ |
| Revert (creates undo commit) | git revert <hash> |
✅✅ |
| Delete untracked files | git clean -fd |
⚠️ |
git revert - it creates a new
commit. reset rewrites history and breaks
teammates' clones.
# Exact file secrets.env # Directory node_modules/ dist/ # All .log files *.log # Ignore .env but not .env.example .env !.env.example # Common patterns .DS_Store Thumbs.db .vscode/ __pycache__/ *.pyc coverage/
$ git rm --cached secrets.env $ echo "secrets.env" >> .gitignore $ git commit -m "chore: untrack secrets"
Rebasing re-applies your commits on top of another branch's tip, creating linear history.
# standard rebase $ git switch feature && git rebase main # interactive: edit/squash/reorder commits $ git rebase -i HEAD~4 # after resolving a conflict / bail out $ git rebase --continue $ git rebase --abort
| Action | Key | Effect |
|---|---|---|
| pick | p |
Keep as-is |
| reword | r |
Edit commit message |
| squash | s |
Merge into previous |
| fixup | f |
Like squash, discard message |
| drop | d |
Delete commit entirely |
Apply specific commits from one branch to another - great for porting a bug fix to a release branch.
$ git cherry-pick a3f2b1c # single commit $ git cherry-pick a3f2b1c^..d4g5h6i # range $ git cherry-pick -n a3f2b1c # stage only, don't commit $ git cherry-pick --continue # after conflict $ git cherry-pick --abort # bail out
Reflog records every position HEAD has ever been at. It's your safety net after bad resets or deleted branches.
$ git reflog a3f2b1c HEAD@{0}: commit: feat: add login d4g5h6i HEAD@{1}: reset: moving to HEAD~1 9fceb02 HEAD@{2}: commit: fix: typo # Recover after bad reset $ git reset --hard HEAD@{1} # Restore deleted branch $ git branch feature/deleted a3f2b1c
--hard reset and deleted branches are
recoverable.
Embed one Git repo inside another, tracking a specific commit of the dependency.
$ git submodule add https://github.com/u/lib libs/lib $ git clone --recurse-submodules https://… # clone with subs $ git submodule update --init --recursive # init existing $ git submodule update --remote # update to latest # Remove a submodule: $ git submodule deinit libs/lib && git rm libs/lib $ rm -rf .git/modules/libs/lib
Scripts in .git/hooks/ that run automatically at
Git lifecycle events.
| Hook | When | Common Use |
|---|---|---|
pre-commit |
Before commit | Lint, format, test |
commit-msg |
After message written | Validate message format |
pre-push |
Before push | Run full test suite |
post-merge |
After merge | Install dependencies |
#!/bin/sh npx eslint . --ext .js,.ts if [ $? -ne 0 ]; then echo "❌ ESLint failed."; exit 1 fi
$ npm i --save-dev husky lint-staged $ npx husky init $ echo "npx lint-staged" > .husky/pre-commit
[alias] st = status -s lg = log --oneline --graph --all --decorate ca = commit -am sw = switch co = checkout br = branch undo = reset HEAD~1 --mixed pushup = !git push -u origin $(git branch --show-current) aliases= config --get-regexp '^alias\\.'
$ git config --global alias.lg "log --oneline --graph --all" $ git config --global alias.pushup "!git push -u origin $(git branch --show-current)"
$ git cat-file -t HEAD # object type (commit) $ git cat-file -p HEAD # object contents $ git ls-tree -r HEAD # all files in HEAD $ git hash-object README.md # compute hash $ git count-objects -v # repo size stats $ git gc --aggressive # compact repo $ git fsck # check integrity
main is always deployable. Feature branches merge
directly via PR. Simple and fast.
Structured: main for releases,
develop for integration, feature/release/hotfix
branches.
| Strategy | Team | Release | Complexity |
|---|---|---|---|
| GitHub Flow | Small–Med | Continuous | Low |
| Git Flow | Med–Large | Scheduled | High |
| Trunk-Based | Any | Continuous | Medium |
Automate workflows triggered by GitHub events: push, PR, schedule and more.
name: CI on: push: { branches: [main] } pull_request: { branches: [main] } jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20, cache: npm } - run: npm ci - run: npm run lint - run: npm test
name: Deploy on: push: { branches: [main] } permissions: { pages: write, id-token: write } jobs: deploy: environment: { name: github-pages } runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/upload-pages-artifact@v3 with: { path: dist/ } - uses: actions/deploy-pages@v4
## SETUP git init git clone <url> git config --global user.name "Name" ## SNAPSHOT git status git add . git add -p git diff --staged git commit -m "msg" git commit --amend --no-edit ## BRANCH git branch git switch -c feature/x git merge feature/x git branch -d feature/x ## REMOTE git remote -v git fetch origin git pull origin main git push -u origin feature/x ## UNDO git restore <file> git restore --staged <f> git revert HEAD git reset --soft HEAD~1 ## ADVANCED git stash / git stash pop git rebase -i HEAD~3 git cherry-pick <hash> git reflog git bisect start / good / bad git log --oneline --graph --all
feat:
fix: docs: chore:
feature/user-auth,
fix/login-crash
main must always be releasable. Protect it with
branch rules.
main regularly to
minimize conflicts.
git commit -S -m "..."
git filter-repo to purge it from all history.
git filter-repo --path secrets.env --invert-paths
- faster and safer than the deprecated
git filter-branch.