What is Git?
Git is a distributed version control system that tracks changes in files over time. It lets you save snapshots of your project at any point, compare versions, revert mistakes and collaborate with other people without overwriting each other’s work.
Created by Linus Torvalds in 2005 to manage the Linux kernel, Git has become the most widely used version control system in the world. Whether you are building a solo side project or contributing to a thousand-developer codebase, Git is the tool that makes it possible.
The core idea is simple: instead of saving a single copy of your project, Git saves a series of commits — snapshots of every tracked file at a specific moment. You can travel back in time, compare versions, branch off to try new ideas and merge your work back when it is ready. That model scales from one person to thousands.
Why version control matters
Without version control, collaboration is chaos. Two people editing the same file means someone’s work gets overwritten. A bug introduced last week means manually undoing hours of changes. A feature experiment gone wrong means starting from scratch.
Git solves all of these problems:
- Track every change — who changed what, when and why. The full history is always available.
- Revert safely — go back to any previous state without losing current work.
- Work in parallel — branches let multiple people work on different features simultaneously.
- Collaborate without fear — Git merges changes intelligently and flags conflicts for you to resolve.
- Work offline — everything is local until you choose to push to a remote server.
Version control is not optional for professional development. It is a foundational skill that every developer needs, regardless of language, framework or platform.
How Git works
Git operates on three main areas: the working directory, the staging area and the repository.
Your working directory is where you edit files. When you run git add, changes move to the staging area — a preparation zone for the next commit. When you run git commit, the staged changes are saved as a new snapshot in the repository.
# Edit a file in your working directory
echo "Hello, Git!" > hello.txt
# Stage the change
git add hello.txt
# Commit the staged change
git commit -m "Add hello.txt"
This three-step flow — edit, stage, commit — gives you precise control over what goes into each snapshot. You can stage parts of a file, stage multiple files together and create commits that each represent a single logical change.
Under the hood, Git stores data as a directed acyclic graph (DAG) of commit objects. Each commit points to its parent, forming a chain of history. Branches are just lightweight pointers to specific commits, and HEAD is a pointer to the commit you are currently on.
Installing Git
Git is available for every major operating system:
macOS:
# Using Homebrew
brew install git
# Or install Xcode Command Line Tools
xcode-select --install
Windows:
# Download from git-scm.com or use winget
winget install Git.Git
Linux:
# Debian/Ubuntu
sudo apt install git
# Fedora
sudo dnf install git
# Arch
sudo pacman -S git
Verify the installation:
git --version
# git version 2.45.0
After installing, set your identity — this information is included in every commit you make:
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
Core Git commands
Creating a repository
# Start a new repository in the current directory
git init
# Clone an existing repository
git clone https://github.com/user/repo.git
git init creates a hidden .git/ folder that contains all of Git’s tracking data. git clone copies an existing repository — including its full history — to your machine.
Tracking changes
# See which files are modified, staged or untracked
git status
# Stage a specific file
git add filename.js
# Stage all changes
git add .
# Commit with a message
git commit -m "Add user authentication"
git status is the command you will run most often. It tells you what has changed, what is staged and what Git is not tracking. Always check status before committing.
Viewing history
# Show commit history
git log
# Compact one-line format
git log --oneline
# Show changes in each commit
git log -p
# Show a visual branch graph
git log --oneline --graph --all
The commit history is your project’s timeline. Use it to understand how code evolved, who made changes and why decisions were made.
Comparing changes
# Diff between working directory and staging area
git diff
# Diff between staging area and last commit
git diff --staged
# Diff between two commits
git diff abc1234 def5678
git diff shows you exactly what changed, line by line. It is essential for reviewing your own work before committing and for understanding what someone else changed in a pull request.
Undoing changes
# Discard changes in working directory
git checkout -- filename.js
# Unstage a file
git reset HEAD filename.js
# Amend the last commit
git commit --amend -m "Updated commit message"
# Revert a commit (creates a new commit that undoes it)
git revert abc1234
Git makes it safe to experiment because you can always go back. The key is understanding the difference between discarding changes, unstaging them and creating new commits that reverse previous ones.
Working with branches
# List branches
git branch
# Create a new branch
git branch feature-login
# Switch to a branch
git checkout feature-login
# Create and switch in one command
git checkout -b feature-signup
# Delete a branch
git branch -d feature-login
Branches are Git’s superpower. They let you work on new features, fix bugs or experiment without affecting the main codebase. When your work is ready, you merge the branch back.
Merging and rebasing
When a branch is ready, you integrate it back into the main line. git merge creates a merge commit that joins the two histories, while git rebase replays your commits on top of another branch for a linear history:
# Merge a feature branch into main
git checkout main
git merge feature-login
# Rebase a feature branch onto the latest main
git checkout feature-login
git rebase main
If the same lines changed in both branches, Git stops and marks the files as conflicted. Edit the files to keep the correct content, remove the conflict markers (<<<<<<<, =======, >>>>>>>), then stage and continue:
git add .
git merge --continue # or: git rebase --continue
To keep history tidy, a squash merge collapses every commit from a feature branch into a single commit on the target branch:
git checkout main
git merge --squash feature-dark-mode
git commit -m "Add dark mode feature"
Merge when you want to preserve the full branch history; rebase or squash when you want a clean, linear main branch. Never rebase or squash commits that have already been pushed to a shared branch.
Syncing with remotes
# Add a remote server
git remote add origin https://github.com/user/repo.git
# Push commits to remote
git push origin main
# Pull latest changes
git pull origin main
# Fetch without merging
git fetch origin
Remotes are copies of your repository hosted on servers. The default remote is usually called origin. Push sends your commits to the remote, and pull downloads and merges remote changes into your branch.
The .gitignore file
Not everything should be tracked. Dependencies, build outputs, environment files and IDE settings are typically ignored:
# Dependencies
node_modules/
vendor/
# Build output
dist/
build/
# Environment variables
.env
.env.local
# IDE settings
.vscode/
.idea/
# OS files
.DS_Store
Thumbs.db
Place a .gitignore file in your repository root. Git will skip any file or directory matching these patterns. If you have already committed files you want to ignore, remove them from tracking first:
git rm -r --cached node_modules/
git commit -m "Remove node_modules from tracking"
Understanding commits
A commit is the fundamental unit of Git. Each commit contains:
- A unique hash — a 40-character SHA-1 identifier like
a1b2c3d4e5f6... - A message — a description of what changed and why
- An author — who made the change
- A timestamp — when the change was made
- A parent — the commit it builds on (except the first commit)
- A snapshot — the state of all tracked files at that moment
Good commit messages follow a convention. The first line is a short summary in imperative mood (under 72 characters). An optional body explains the context:
git commit -m "Fix null pointer in user authentication
The login endpoint was throwing a 500 error when the email
field was missing from the request body. Added a null check
before accessing user properties."
Each commit should represent one logical change. This makes it easy to understand, review and revert individual changes without affecting unrelated work.
Common Git workflows
Centralized workflow
Everyone works on the main branch. Simple but risky for teams:
git pull origin main
# make changes
git add .
git commit -m "Update feature"
git push origin main
Feature branch workflow
Each feature or fix gets its own branch. Merges happen through pull requests:
git checkout -b feature-user-profile
# make changes
git push origin feature-user-profile
# open a pull request on GitHub
Gitflow workflow
A structured model with main, develop, feature, release and hotfix branches. Best for projects with scheduled releases:
git checkout -b develop
git checkout -b feature-new-login
# work on feature
git checkout develop
git merge feature-new-login
git checkout -b release-v1.0
# finalize release
git checkout main
git merge release-v1.0
git tag v1.0.0
Trunk-based development
Everyone commits to main (the trunk) frequently, using short-lived feature flags. Popular at companies like Google and Meta:
git checkout main
# make small, frequent changes
git add .
git commit -m "Add feature flag for new dashboard"
git push origin main
Best practices
- Commit early, commit often — small commits are easier to understand and revert.
- Write clear commit messages — use imperative mood and explain why, not just what.
- Branch for every feature — keep main clean and deployable.
- Pull before you push — always integrate remote changes before sharing your work.
- Review your own diff — run
git diff --stagedbefore committing to catch mistakes. - Never commit secrets — use environment variables and .gitignore.
- Tag releases — use semantic version tags like
v1.0.0for easy rollback. - Keep history clean — rebase feature branches before merging to avoid messy merge commits.
Common mistakes
- Committing too many unrelated changes in one commit.
- Using vague messages like “fix” or “update” that explain nothing.
- Force-pushing to shared branches and overwriting others’ work.
- Not setting up .gitignore, leading to committed node_modules or .env files.
- Working on main instead of creating a feature branch.
- Forgetting to pull before pushing, causing merge conflicts.
- Not backing up remote repositories — local is not enough.
What to learn next
You now have the Git fundamentals: repositories, commits, branches, merging and remotes. From here the natural next step is GitHub for cloud collaboration, issues, pull requests and Actions. Pick a project, build something real and let the practice compound.