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?

ToolWhat It IsExample
GitVersion control software that runs on your computer. Tracks changes locally.Commands: git commit, git branch
GitHubWebsite/service that hosts Git repositories online. Adds collaboration features.Features: Pull requests, issues, code review
GitLabAlternative to GitHub. Also hosts repositories + CI/CD built-in.Similar to GitHub, with DevOps tools
BitbucketAnother 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
What --global means: Settings apply to all repositories on your computer. Omit --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

Working DirectoryYour files — what you see and editStaging Area(Index) — changes ready to commitRepository(.git directory) — permanent historyRemote Repository(GitHub / GitLab) — shared with teamgit addgit commitgit push
Why the staging area? It lets you control exactly what goes into each commit. You can modify 10 files but only commit 3 related changes, keeping commits focused and logical.

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.md
What just happened:
1. 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
What clone does: Downloads the entire repository including all history, automatically sets up "origin" as the remote, and checks out the main branch.

Basic Workflow, Daily Git Commands

These are the commands you'll use every day. Master these and you're 80% there.

The Essential Commands

CommandWhat It DoesWhen to Use
git statusShows what's changed, what's stagedAlways! Check before committing
git add [file]Stage specific fileAdd files one by one
git add .Stage all changesWhen all changes are related
git commit -m "msg"Save staged changesAfter staging what you want
git logView commit historySee what's been done
git diffSee unstaged changesReview before staging
git pullDownload + merge remote changesStart of day, before pushing
git pushUpload your commitsShare 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
This workflow is 90% of what you'll do with Git!
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).
Branch naming conventions:
feature/user-auth, New features
bugfix/login-error, Bug fixes
hotfix/security-patch, Urgent fixes
Use 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

CommandWhat It DoesExample
git pushUpload your commits to remotegit push origin main
git pullDownload + merge remote changesgit pull origin main
git fetchDownload changes but don't mergegit 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
Pull vs Fetch:
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
Pro-Tip: Use 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.

ScenarioCommandWhat It Does
Discard changes in working directorygit restore file.txtRevert file to last commit
Unstage a filegit restore --staged file.txtRemove from staging area
Amend last commitgit commit --amendFix commit message or add files
Undo last commit (keep changes)git reset --soft HEAD~1Removes commit, keeps files staged
Undo last commit (discard changes)git reset --hard HEAD~1Removes commit AND changes
Create reverse commitgit revert a1b2c3dSafe 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.js

The 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!
Pro-Tip: Use a merge tool for complex conflicts:
git mergetool, Opens visual diff tool
VS 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/
Pro-Tip: Use gitignore.io to generate .gitignore templates:
https://gitignore.io, Select your language/framework

12. 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
Pro-Tip: Start with a verb in imperative mood: "Add" not "Added", "Fix" not "Fixed". Think: "This commit will [your message]"

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
Infrastructure for EngineersLesson 1