Git & Version Control
From basics to branching strategies, master the most essential tool in modern software development
Why Git Matters
Imagine working on a project where you can't undo mistakes, can't collaborate without overwriting each other's work, and have no history of what changed or why. Git solves these problems. It's the foundation of modern software development, enabling safe experimentation, seamless collaboration, and complete project history. Whether you're building solo projects or working in teams of hundreds, Git is essential.
What is Version Control?
Version control is a system that records changes to files over time so you can recall specific versions later.
The Problem Git Solves
❌ Without Version Control
- Files named: final.txt, final_v2.txt, final_FINAL.txt
- No idea what changed or why
- Can't experiment safely (fear of breaking things)
- Collaboration = emailing files back and forth
- Lost work when files get overwritten
- Can't go back to previous versions easily
✅ With Git
- Complete history of all changes
- Know who changed what and when
- Experiment in branches without breaking main code
- Multiple people work simultaneously
- Easy rollback to any previous version
- Changes explained with commit messages
Git vs GitHub, What's the Difference?
| Tool | What It Is | Example |
|---|---|---|
| Git | Version control software that runs on your computer. Tracks changes locally. | Commands: git commit, git branch |
| GitHub | Website/service that hosts Git repositories online. Adds collaboration features. | Features: Pull requests, issues, code review |
| GitLab | Alternative to GitHub. Also hosts repositories + CI/CD built-in. | Similar to GitHub, with DevOps tools |
| Bitbucket | Another alternative. Popular in enterprise (Atlassian ecosystem). | Integrates with Jira, Confluence |
Getting Started, First-Time Setup
Before using Git, you need to configure your identity. This information gets attached to every commit you make.
Installing Git
# macOS (using Homebrew) $ brew install git # Ubuntu/Debian $ sudo apt update $ sudo apt install git # Windows # Download from https://git-scm.com/download/win # Verify installation $ git --version git version 2.39.0
Essential Configuration
# Set your name (shown in commits) $ git config --global user.name "Your Name" # Set your email (shown in commits) $ git config --global user.email "your.email@example.com" # Set default branch name to 'main' $ git config --global init.defaultBranch main # Set default text editor $ git config --global core.editor "code --wait" # VS Code $ git config --global core.editor "vim" # Vim # View your configuration $ git config --list # View specific setting $ git config user.name
--global to set options per-repository.Core Concepts, Understanding Git's Mental Model
Git tracks your project in three main areas. Understanding this is crucial.
The Three Areas
Local vs Remote Repositories
Local Repository
Lives on your computer (.git folder)
- Where you commit changes
- Full project history stored locally
- Can work offline
- Fast operations
Remote Repository
Lives on a server (GitHub, GitLab, etc.)
- Shared with team
- Backup of your work
- Collaboration hub
- Usually called "origin"
Your First Repository, Hands-On
Let's create a repository and make your first commit. Two ways to start:
Method 1: Create a New Repository (git init)
# Create a new project directory
$ mkdir my-project
$ cd my-project
# Initialize Git repository
$ git init
Initialized empty Git repository in /Users/you/my-project/.git/
# Create a file
$ echo "# My Project" > README.md
# Check status
$ git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
# Stage the file
$ git add README.md
# Check status again
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README.md
# Commit the change
$ git commit -m "Initial commit: Add README"
[main (root-commit) a1b2c3d] Initial commit: Add README
1 file changed, 1 insertion(+)
create mode 100644 README.md1. Created .git directory (hidden), this IS your repository
2. Created a file
3. Staged it (git add)
4. Committed it (git commit), now it's in history forever
Method 2: Copy an Existing Repository (git clone)
# Clone a repository from GitHub $ git clone https://github.com/username/repository.git Cloning into 'repository'... remote: Counting objects: 100, done. remote: Compressing objects: 100% (80/80), done. Receiving objects: 100% (100/100), done. $ cd repository # You now have a complete copy with full history $ git log --oneline a1b2c3d Fix bug in login e4f5g6h Add user authentication i7j8k9l Initial commit
Basic Workflow, Daily Git Commands
These are the commands you'll use every day. Master these and you're 80% there.
The Essential Commands
| Command | What It Does | When to Use |
|---|---|---|
git status | Shows what's changed, what's staged | Always! Check before committing |
git add [file] | Stage specific file | Add files one by one |
git add . | Stage all changes | When all changes are related |
git commit -m "msg" | Save staged changes | After staging what you want |
git log | View commit history | See what's been done |
git diff | See unstaged changes | Review before staging |
git pull | Download + merge remote changes | Start of day, before pushing |
git push | Upload your commits | Share your work with team |
Complete Daily Workflow Example
# Start your day - get latest changes $ git pull Already up to date. # Make some changes to your code $ vim app.js $ vim styles.css # See what changed $ git status On branch main Changes not staged for commit: modified: app.js modified: styles.css # Review your changes $ git diff app.js # Stage specific files $ git add app.js styles.css # Or stage everything $ git add . # Commit with a descriptive message $ git commit -m "Add user authentication feature" [main f8e7d6c] Add user authentication feature 2 files changed, 45 insertions(+), 3 deletions(-) # Push to remote $ git push To github.com:username/project.git a1b2c3d..f8e7d6c main -> main
pull → make changes → status → add → commit → push → repeat
Understanding Branches, Parallel Development
Branches let you work on features, fixes, or experiments without affecting the main codebase.
Why Use Branches?
Without branches:
main: A → B → C → D (broken!) → E (fixing D)
All changes go directly to main. If something breaks, everyone is blocked.
With branches:
main: A → B → C ──────────── → G (merge feature)
\ /
feature: D → E → F ─────
Work in isolation. Main stays stable. Merge when ready.Working with Branches
# List all branches (* = current branch) $ git branch * main feature/authentication # Create new branch $ git branch feature/new-ui # Switch to branch $ git checkout feature/new-ui Switched to branch 'feature/new-ui' # Create AND switch in one command (recommended) $ git checkout -b feature/payment Switched to a new branch 'feature/payment' # Modern syntax (Git 2.23+) $ git switch -c feature/dashboard Switched to a new branch 'feature/dashboard' # Make changes and commit $ echo "New feature code" > feature.js $ git add feature.js $ git commit -m "Add payment processing" # Switch back to main $ git checkout main # Merge your feature branch $ git merge feature/payment Updating a1b2c3d..f8e7d6c Fast-forward feature.js | 1 + 1 file changed, 1 insertion(+) # Delete branch after merging $ git branch -d feature/payment Deleted branch feature/payment (was f8e7d6c).
feature/user-auth, New featuresbugfix/login-error, Bug fixeshotfix/security-patch, Urgent fixesUse descriptive names, lowercase, with hyphens
Working with Remotes, Collaborating with Others
Remotes are versions of your repository hosted on the internet or network. Usually on GitHub, GitLab, or Bitbucket.
Understanding Remotes
# View remotes $ git remote -v origin https://github.com/username/project.git (fetch) origin https://github.com/username/project.git (push) # Add a remote $ git remote add origin https://github.com/username/project.git # Remove a remote $ git remote remove origin # Rename a remote $ git remote rename origin upstream
Push and Pull Explained
| Command | What It Does | Example |
|---|---|---|
git push | Upload your commits to remote | git push origin main |
git pull | Download + merge remote changes | git pull origin main |
git fetch | Download changes but don't merge | git fetch origin |
# Push to remote (first time) $ git push -u origin main # -u sets upstream, so next time just: git push # Pull before you push (avoid conflicts) $ git pull $ git push # Push a new branch $ git push -u origin feature/new-ui # Delete remote branch $ git push origin --delete feature/old-feature
git fetch, Downloads changes, doesn't merge (safe)git pull, Downloads AND merges (fetch + merge in one step)Viewing History, git log & git diff
Understanding your project's history is crucial for debugging and collaboration.
Viewing Commit History
# Basic log
$ git log
commit f8e7d6c2a1b3e4f5g6h7i8j9k0l1m2n3o4p5q6r7
Author: Your Name <your.email@example.com>
Date: Mon Jan 19 14:30:00 2026 -0500
Add user authentication feature
# Compact one-line format (most useful)
$ git log --oneline
f8e7d6c Add user authentication feature
a1b2c3d Fix navigation bug
e4f5g6h Update dependencies
i7j8k9l Initial commit
# Show last N commits
$ git log --oneline -5
# Show commits with file changes
$ git log --stat
# Visual graph of branches
$ git log --oneline --graph --all
* f8e7d6c (HEAD -> main) Merge feature
|\
| * d5c4b3a Add feature
|/
* a1b2c3d Fix bug
* e4f5g6h Initial commit
# Search commits by message
$ git log --oneline --grep="authentication"
# Show commits by author
$ git log --author="Your Name"
# Show commits in date range
$ git log --since="2 weeks ago" --until="yesterday"Viewing Changes (git diff)
# Show unstaged changes $ git diff # Show staged changes $ git diff --staged # Compare branches $ git diff main..feature/new-ui # Show changes in specific file $ git diff README.md # Show changes between commits $ git diff a1b2c3d..f8e7d6c
git log --oneline --graph --all to visualize your branch structure. Add as an alias: git config --global alias.lg "log --oneline --graph --all"Undoing Changes, Fixing Mistakes
Everyone makes mistakes. Git provides multiple ways to undo changes safely.
| Scenario | Command | What It Does |
|---|---|---|
| Discard changes in working directory | git restore file.txt | Revert file to last commit |
| Unstage a file | git restore --staged file.txt | Remove from staging area |
| Amend last commit | git commit --amend | Fix commit message or add files |
| Undo last commit (keep changes) | git reset --soft HEAD~1 | Removes commit, keeps files staged |
| Undo last commit (discard changes) | git reset --hard HEAD~1 | Removes commit AND changes |
| Create reverse commit | git revert a1b2c3d | Safe undo for public commits |
# Made a typo in commit message? $ git commit --amend -m "Correct message" # Forgot to add a file? $ git add forgotten_file.txt $ git commit --amend --no-edit # Want to temporarily save changes? $ git stash Saved working directory and index state # Bring stashed changes back $ git stash pop # View stashed changes $ git stash list
Warning: reset --hard is Destructive
git reset --hard permanently deletes uncommitted changes. There's no undo. Use git stash if you might want them back later.10. Merge Conflicts, Resolving Disagreements
When two branches modify the same lines, Git can't automatically merge them. You must resolve manually.
What a Conflict Looks Like
$ git merge feature/new-ui
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch main
You have unmerged paths.
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: app.jsThe Conflict Markers
// app.js content with conflict:
function greet() {
<<<<<<< HEAD
console.log("Hello World");
=======
console.log("Hi there!");
>>>>>>> feature/new-ui
}Resolving the Conflict (Step-by-Step)
# 1. Open the conflicted file
$ vim app.js
# 2. Choose which version to keep (or combine them)
// Remove conflict markers and edit to:
function greet() {
console.log("Hello World"); // Kept this version
}
# 3. Stage the resolved file
$ git add app.js
# 4. Complete the merge
$ git commit -m "Resolve merge conflict in app.js"
# Conflict resolved!git mergetool, Opens visual diff toolVS Code, Sublime, and most IDEs have built-in conflict resolution
11. .gitignore, What NOT to Track
Not everything should be in version control. .gitignore tells Git what to ignore.
What to Ignore
❌ NEVER Commit
- Secrets (.env, API keys)
- Passwords, tokens
- node_modules/ (dependencies)
- Build outputs (dist/, build/)
- OS files (.DS_Store)
- IDE configs (.vscode/, .idea/)
- Log files (*.log)
✅ DO Commit
- Source code (.js, .py, .java)
- Configuration templates
- Documentation (README.md)
- Tests
- package.json, requirements.txt
- .gitignore itself
Example .gitignore File
# .gitignore
# Dependencies node_modules/ vendor/ __pycache__/ # Environment variables .env .env.local .env.*.local # Build outputs dist/ build/ *.min.js *.min.css # Logs *.log npm-debug.log* # OS files .DS_Store Thumbs.db # IDE .vscode/ .idea/ *.swp *.swo # Test coverage coverage/ .nyc_output/
https://gitignore.io, Select your language/framework12. Collaboration Workflow, Working with Teams
How teams collaborate using Git, pull requests, and code review.
The Fork & Pull Request Workflow
1. Fork the repository on GitHub (Creates your own copy) 2. Clone your fork locally $ git clone https://github.com/YOUR-USERNAME/project.git 3. Create a branch for your work $ git checkout -b feature/my-feature 4. Make changes and commit $ git add . $ git commit -m "Add awesome feature" 5. Push to your fork $ git push origin feature/my-feature 6. Open Pull Request on GitHub (Request to merge your changes into original repo) 7. Code review & discussion 8. Merge (by maintainer) 9. Update your fork $ git checkout main $ git pull upstream main
Pull Request Best Practices
Good PR
- Clear title and description
- Small, focused changes
- One feature per PR
- Includes tests
- Updated documentation
- Linked to issue/ticket
Bad PR
- Vague title: "Fixed stuff"
- Changes 50+ files
- Multiple unrelated changes
- No tests or documentation
- Commits like "wip", "fix"
- No context provided
13. Branching Strategies, Team Workflows
Different teams use different branching strategies. Choose based on your deployment frequency and team size.
Git Flow
Traditional model with main, develop, feature, release, hotfix branches. Ideal for versioned releases.
git checkout -b develop main git checkout -b feature/new-microservice develop # Work and commit git checkout develop git merge --no-ff feature/new-microservice
Main ●───────────────────────────────────────────● (v1.0)
\ /
Develop ●───────●───────●───────●─────────────────●
\ / \ / /
Feature ●─────● ●───● (Release) ●Best for: Large enterprise applications with scheduled release cycles.
GitHub Flow
Simple: Branch from main, create PR, merge after review. Suits continuous deployment.
git checkout -b feature/event-handler main # Work and commit git push origin feature/event-handler # Create Pull Request, review, merge
Main ●──────●──────────────────────────────●──────►
\ /
Feature ●────────●───────●────────● (PR Merge)Best for: SaaS products, microservices, continuous deployment.
Trunk-Based Development
Short-lived branches merged frequently to trunk. Best for high-velocity teams.
git checkout -b short-feature main # Small changes, commit git push origin short-feature # PR, quick merge to main
Trunk (Main) ●──●──●──●──●──●──●──●──●──●──●──●──●──►
\ / \ / \ / \ / \ /
Short-lived ● ● ● ● ● (Hours, not days)Best for: High-performing DevOps teams and CI/CD excellence.
14. Writing Good Commit Messages
Commit messages are communication. They help future you and your team understand what changed and why.
Good vs Bad Messages
❌ Bad Messages
"fixed stuff" "update" "wip" "asdfasdf" "commit"
No context, no value, annoying in git log
✅ Good Messages
"Add user authentication" "Fix login redirect bug" "Update README with setup steps" "Refactor payment processing" "Remove deprecated API endpoint"
Clear, descriptive, action-oriented
Conventional Commits Format
# Format: <type>(<scope>): <subject> feat(auth): add OAuth2 login fix(api): resolve null pointer in user endpoint docs(readme): update installation instructions style(css): fix button alignment refactor(db): optimize query performance test(auth): add login unit tests chore(deps): update dependencies # Types: feat - New feature fix - Bug fix docs - Documentation style - Formatting, no code change refactor - Code restructuring test - Adding tests chore - Maintenance, dependencies
15. Common Mistakes & How to Fix Them
Everyone makes these mistakes. Here's how to fix them.
❌ Committed to Wrong Branch
Problem: Made commits on main instead of feature branch
# Create branch from current position $ git branch feature/fix # Move back to previous commit $ git reset --hard HEAD~1 # Switch to feature branch $ git checkout feature/fix
❌ Committed Secrets/Passwords
Problem: Accidentally committed .env file with API keys
# Remove from Git but keep local file $ git rm --cached .env $ echo ".env" >> .gitignore $ git commit -m "Remove .env from tracking" # If already pushed, ROTATE YOUR KEYS! # Consider using git-filter-repo or BFG Repo-Cleaner
❌ Need to Undo Last Push
Problem: Pushed bad commit to remote
# Safe way: Create reverse commit $ git revert HEAD $ git push # Dangerous way (if working alone): $ git reset --hard HEAD~1 $ git push --force # ONLY if nobody else pulled!
❌ Detached HEAD State
Problem: Checked out a specific commit, now commits disappear
# Create a branch from this state $ git branch temp-branch $ git checkout temp-branch # Or just return to main $ git checkout main
❌ Long-Lived Feature Branches
Problem: Branch hasn't been merged for weeks, huge merge conflicts
Prevention: Merge main into your branch frequently, keep branches short-lived, break features into smaller PRs
Best Practices Summary
- Commit often with meaningful messages
- Pull before you push (avoid conflicts)
- Use branches for features/fixes (never commit directly to main)
- Keep branches short-lived (merge within days, not weeks)
- Review your changes before committing (
git diff) - Write descriptive commit messages
- Use .gitignore from day one
- Never commit secrets, passwords, or API keys
- Test before you commit
- Use pull requests for code review
Key Takeaways
- Version Control: Git solves collaboration, history, and experimentation problems
- Three Areas: Working Directory → Staging → Repository → Remote
- Basic Workflow: pull → make changes → add → commit → push
- Branches: Isolate work, merge when ready, delete after merging
- Remotes: origin is your GitHub/GitLab repository
- Conflicts: Normal and fixable, edit, add, commit
- Undo: git stash, reset, revert, multiple ways to fix mistakes
- Collaboration: Fork, branch, PR, code review, merge
- .gitignore: Exclude secrets, dependencies, build outputs
- Commit Messages: Clear, descriptive, action-oriented
Quick Reference Cheat Sheet
# Setup git config --global user.name "Your Name" git config --global user.email "you@example.com" # Start git init # New repo git clone <url> # Copy existing repo # Daily Workflow git status # Check status git add <file> # Stage file git add . # Stage all git commit -m "message" # Commit git pull # Get updates git push # Send commits # Branching git branch # List branches git branch <name> # Create branch git checkout <name> # Switch branch git checkout -b <name> # Create + switch git merge <branch> # Merge branch git branch -d <name> # Delete branch # Viewing git log --oneline # View history git diff # See changes # Undoing git restore <file> # Discard changes git restore --staged <file> # Unstage git commit --amend # Fix last commit git revert <commit> # Safe undo # Remote git remote -v # View remotes git push origin <branch> # Push branch git pull origin <branch> # Pull branch