Learn the fundamentals of version control and understand how Git differs from GitHub.
Git and GitHub are different
Git is a version control system used to track changes to your files. It is free, open-source, and available for Windows, macOS, and Linux. Git is installed as software directly on your computer.
GitHub is a web-based hosting platform for Git repositories. It allows you to store and share code with others online. While GitHub is the most popular provider, alternatives include Gitlab, Bitbucket and Azure Repos.
🖥️
Git
Local software. Tracks file changes. Works offline. Installed on your machine.
☁️
GitHub
Remote platform. Hosts repos online. Enables team collaboration. Accessed via browser or CLI.
Version Control Systems
Version control systems manage the history of your code. Think of them as checkpoints in a video game — you can move to any point in history and always go back to a previous checkpoint.
Before Git became mainstream, systems like SCCS (Source Code Control System) were used, but they were proprietary and expensive. Git was created to replace them. Other version control systems include Subversion (SVN), CVS, and Perforce.
Git was created by Linus Torvalds in 2005 to manage Linux kernel development. It was designed to be fast, distributed, and handle large projects efficiently.
Install Git
Download Git from the official website or use your system's package manager:
Create a free account at github.com. You'll need it later when we set up SSH keys to push code to remote repositories. Password authentication is no longer recommended — SSH key authentication is the standard.
The typical workflow from creating a file to pushing it to GitHub:
Working Directory → Staging Area → Local Repository → Remote (GitHub) (edit files) git add git commit git push
Stage
Staging tells Git which changes to include in the next commit. Files in the staging area are ready to be committed but not yet saved permanently:
bash
git add index.html # Stage a single file
git add . # Stage all changes
git status # See staged vs unstaged files
Commit
A commit is a permanent snapshot of your staged changes. Each commit gets a unique hash ID.
bash
git commit -m "Add homepage layout"
git log # Full commit history
git log --oneline # Compact one-line view
Omitting the -m flag opens your default editor (usually Vim). You can change this to VS Code: git config --global core.editor "code --wait"
Atomic Commits
Each commit should be a single, self-contained unit of work. This makes it easy to revert specific changes without breaking other things. A good commit message is: feat: add user authentication — not changes.
.gitignore
The .gitignore file lists files and folders that Git should never track:
How Git internally stores every change — snapshots, objects and hash codes.
Git Snapshots
Every time you commit, Git doesn't just record diffs — it stores a full snapshot of your project as it exists at that moment. Each snapshot is identified by a SHA-1 hash — a 40-character string uniquely identifying the contents.
This snapshot system is backed by a key-value object database inside the .git/ folder. There are three core object types.
The 3 Musketeers of Git
Commit Object
Points to a Tree Object
Points to parent commit
Author + Committer
Commit message
Timestamp
Tree Object
Container for files/dirs
File mode (permissions)
File name → hash mapping
Points to Blob Objects
Can point to sub-Trees
Blob Object
Raw file content
No filename stored here
Same content = same hash
Stored in .git/objects/
If two files have identical content, Git stores only one Blob Object. The filename lives in the Tree Object, not the Blob — this is why Git is so storage-efficient.
Exploring Internals
You can inspect Git's objects directly with these plumbing commands:
bash
# View raw commit data
git show -s --pretty=raw <commit-hash>
# List files in a tree object
git ls-tree <tree-id>
# View a blob's content
git show <blob-id>
# Pretty-print any object
git cat-file -p <object-id>
Work on multiple features simultaneously without affecting the main codebase.
What is a Branch?
A branch is an independent line of development. Multiple developers can work on different features in parallel — one on a header, another on a footer, another on auth — without stepping on each other's code.
HEAD is a special pointer that always points to your current branch's latest commit. When you switch branches, HEAD moves with you. The default branch is now called main (previously master) — it's just a convention, nothing special.
Branch Commands
git branch
List all local branches
git branch <name>
Create a new branch
git switch <name>
Switch to a branch (modern way)
git switch -c <name>
Create and switch in one step
git checkout <name>
Switch to a branch (classic way)
git branch -m <old> <new>
Rename a branch
git branch -d <name>
Delete a branch (safe)
Always commit your work before switching branches. Uncommitted changes can cause conflicts or get overwritten when you switch.
Merging Branches
Merging brings changes from one branch into another. Git has two merge strategies:
Fast-Forward Merge
Used when the target branch has no new commits since the feature branch was created. Git simply moves the pointer forward — no merge commit needed.
bash
git checkout main
git merge bug-fix # Fast-forward: no conflicts, clean history
3-Way Merge
Used when both branches have diverged (both have new commits). Git looks at 3 commits: the common ancestor + the tip of each branch, then creates a new merge commit.
Conflicts arise when both branches modified the same lines. You must manually resolve them — there are no shortcuts.
VS Code has an excellent built-in merge conflict editor. Look for "Accept Current Change", "Accept Incoming Change", or "Accept Both" options highlighted inline.
Managing Conflicts
Conflict resolution isn't scary — it's about communication with your team. When Git marks a conflict, it looks like this:
function greet() {
<<<<<<< HEAD
return "Hello World";
=======
return "Hi there!";
>>>>>>> feature-branch
}
Choose which version to keep (or combine them), then git add the resolved file and commit.
Powerful but often overlooked commands for debugging, context-switching and release management.
Git Diff
git diff compares different versions of files. Git treats the before and after as two separate files and highlights exactly what changed:
Symbol
Meaning
a/
The original file (before changes)
b/
The updated file (after changes)
---
Marks the start of the original file section
+++
Marks the start of the updated file section
@@
Shows line numbers where changes occur
- (red)
Removed line
+ (green)
Added line
git diff
Unstaged changes (working dir vs staging)
git diff --staged
Staged changes (staging vs last commit)
git diff <branch1> <branch2>
Compare two branches
git diff <hash1> <hash2>
Compare two specific commits
Git Stash
Stash is a temporary clipboard for uncommitted work. When you need to switch branches but aren't ready to commit, stash saves your changes to a stack:
bash
git stash # Save changes to stash
git stash save "WIP: login feature" # Save with a name
git stash list # View all stashes
git stash apply # Apply most recent stash
git stash apply stash@{0} # Apply specific stash
git stash pop # Apply + remove from list
git stash drop # Remove most recent stash
git stash clear # Remove all stashes
Git Tags
Tags are permanent bookmarks on specific commits — ideal for marking release versions like v1.0.0. Unlike branches, tags don't move when new commits are added.
Lightweight vs Annotated Tags
bash
# Lightweight tag (just a pointer)
git tag v1.0.0
# Annotated tag (stores metadata — preferred for releases)
git tag -a v1.0.0 -m "Release version 1.0.0"
# Tag a specific commit
git tag v0.9.0 <commit-hash>
# List all tags
git tag
# Push tags to remote
git push origin v1.0.0
git push origin --tags # Push all tags
# Delete a tag
git tag -d v1.0.0
git push origin :v1.0.0 # Delete from remote
Always use annotated tags for releases — they store the tagger name, date, and a message, making them much more useful for changelogs and auditing.
Keep your project history clean with rebase, and recover anything with reflog.
Merge Commits
A merge commit is automatically created during a 3-way merge. It has two parents and preserves the full history of both branches. This results in a non-linear history that explicitly shows when and where branches were merged.
Rebase in Git
Rebase rewrites commit history by replaying your branch's commits on top of another branch. The result is a clean, linear history — as if you had started your feature branch from the latest commit on main.
Before rebase: main: A──B──C
╲ feature: D──E
After rebase (feature onto main): main: A──B──C
╲ feature: D'──E'
bash
# Switch to your feature branch
git checkout feature-branch
# Rebase onto main (replay your commits on top of main)
git rebase main
# If conflicts arise, resolve them, then:
git add <resolved-files>
git rebase --continue
# To abort the rebase entirely:
git rebase --abort
Never rebase shared public branches. Rebasing rewrites commit hashes, which breaks history for anyone else who has those commits. Only rebase local, unshared branches. Avoid --force unless you know exactly what you're doing.
Git Reflog
Reflog is your safety net. It records every movement of HEAD — even actions that aren't in regular git log output, like resets and rebases. This means you can recover almost anything.
bash
# View the full reflog
git reflog
# Example output:
# a3f8d21 HEAD@{0}: commit: add dark mode
# b9c1e45 HEAD@{1}: rebase (finish): refs/heads/feature
# c2d7f90 HEAD@{2}: checkout: moving from main to feature
# Recover a lost commit or branch
git reset --hard <commit-hash>
# or using HEAD reference:
git reset --hard HEAD@{2}
Reflog is local and expires after ~90 days by default. It cannot recover work that was never committed. Always commit (or stash) before risky operations!
Push your code to the cloud, set up SSH authentication, and collaborate with your team.
What is GitHub?
GitHub is a web-based Git repository hosting service — the most popular platform for open-source and team development. Alternatives include GitLabBitbucketAzure ReposGitea, but GitHub dominates the ecosystem.
Password authentication is deprecated. SSH keys are the secure, recommended way to authenticate with GitHub:
1
Generate an SSH key
bash
ssh-keygen -t ed25519 -C "you@example.com"
2
Save the key
Press Enter to accept the default location (~/.ssh/id_ed25519). Optionally add a passphrase for extra security.
3
Add to ssh-agent
bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
4
Add public key to GitHub
Copy the output of cat ~/.ssh/id_ed25519.pub, then go to GitHub → Settings → SSH and GPG Keys → New SSH Key and paste it.
Publish Code to Remote
bash
# 1. Initialize and commit locally
git init
git add .
git commit -m "Initial commit"
# 2. Add remote repository
git remote add origin git@github.com:username/repo.git
# 3. Check your remote
git remote -v
# 4. Push and set upstream (first time)
git push -u origin main
# After that, just:
git push
Getting Code from Remote
git fetch origin
Download changes but don't merge
git pull origin main
Download + merge into current branch
git clone <url>
Copy a full remote repo to local
git remote add upstream <url>
Track the original repo (for forks)
🎉 Congratulations! You've completed the Chai aur Git series. You now understand version control from core concepts to real-world collaboration on GitHub. Keep practicing and happy coding!
git rebase <branch> # Rebase current onto branch
git rebase --continue # Continue after conflict
git rebase --abort # Abort rebase
git reflog # View HEAD history
git reset --hard <hash> # Reset to commit
Tags
bash
git tag # List tags
git tag v1.0.0 # Lightweight tag
git tag -a v1.0.0 -m "msg" # Annotated tag
git push origin v1.0.0 # Push tag
git tag -d v1.0.0 # Delete local tag