Git & GitHub
Mastery
A comprehensive, hands-on guide that takes you from absolute beginner to professional-grade version control. Built the way real engineering teams learn.
Introduction to Version Control
BeginnerVersion control is a system that records changes to files over time so you can recall specific versions later. In software engineering, it is non-negotiable — it is the foundation of collaborative work.
You're working on a critical payment feature. After three days and 15 files changed, you realize a change on day two broke checkout. Without version control, you'd spend hours manually undoing changes. With Git, you revert to any previous state in seconds.
Git vs GitHub: Know the Difference
| Git | GitHub |
|---|---|
| Distributed version control system, installed locally | Cloud hosting platform for Git repositories |
| Command-line tool that runs on your machine | Web GUI with collaboration features |
| Manages versions, branches, merges | Adds pull requests, issues, CI/CD, project management |
| Works completely offline | Requires internet to sync |
Git is like a camera that takes snapshots of your project. GitHub is like a cloud service where you share and collaborate on those snapshots.
Core Concepts
Repository (Repo) — A container for your project that stores all files and their complete revision history. Can exist locally or remotely on GitHub.
Commit — A snapshot of your project at a specific point in time, each with a unique SHA hash and a message explaining the change.
Branch — A parallel version of your repository. The default branch is usually called main. Branches let you work on features without affecting stable code.
Merge — The process of combining changes from one branch into another.
Remote vs Local — Local is your copy on your computer. Remote is the version hosted on a server that your team shares.
[Your Computer] <--git push/pull--> [GitHub Remote] (local repo) (remote repo)
Installation & Initial Setup
BeginnerInstalling Git
# Download from git-scm.com and run installer
# Verify installation:
git --version
# git version 2.44.0
# Using Homebrew (recommended)
brew install git
git --version
sudo apt update && sudo apt install git
git --version
Configure Your Identity
Before making any commits, configure your identity. This information becomes part of every commit you create — it's your professional signature.
# Set your name (use your professional name)
git config --global user.name "Alex Chen"
# Set your email (must match your GitHub email)
git config --global user.email "alex.chen@company.com"
# Set default branch name to 'main' (modern standard)
git config --global init.defaultBranch main
# Optional: set preferred editor (VS Code)
git config --global core.editor "code --wait"
# Verify your configuration
git config --list
The --global flag applies settings to all repositories on your system. For project-specific settings, omit the flag and run inside a repo directory.
Creating Your First Repository
mkdir my-awesome-project
cd my-awesome-project
git init
# Initialized empty Git repository in /path/to/my-awesome-project/.git/
git status
# On branch main — nothing to commit
# Clone a repository from GitHub
git clone https://github.com/username/repo-name.git
# Clone into a specific folder name
git clone https://github.com/username/repo-name.git my-folder
# Clone a specific branch
git clone -b develop https://github.com/username/repo-name.git
Core Workflow: The Three-Stage Model
BeginnerUnderstanding Git's staging area is the most important concept for beginners. Files exist in one of three states:
Working Directory --(git add)--> Staging Area --(git commit)--> Repository (modified) (staged) (committed)
Practical Step-by-Step Walkthrough
# 1. Create new files
echo "# My Project" > README.md
echo "console.log('Hello World');" > app.js
# 2. Check status — files are untracked
git status
# Untracked files:
# README.md
# app.js
# 3. Add specific files to staging area
git add README.md
git add app.js
# Or add everything at once
git add .
# 4. Commit the staged changes with a meaningful message
git commit -m "feat: initial commit with README and main app file"
# 5. View commit history
git log --oneline --graph --all
# * a3f2b1c (HEAD -> main) feat: initial commit with README and main app file
Essential Commands Reference
# See changes not yet staged
git diff
# See staged changes about to be committed
git diff --staged
# Unstage a file (undo git add)
git restore --staged <file>
# Discard changes in working directory
git restore <file>
# Show details of a specific commit
git show <commit-hash>
# Quick status overview
git status -s
Fixing Common Mistakes
# MISTAKE: Committed with wrong author info
# FIX: Amend the most recent commit
git commit --amend --author="Alex Chen <alex@company.com>"
# MISTAKE: Forgot to include a file in last commit
git add forgotten-file.js
git commit --amend --no-edit # adds file without changing message
# MISTAKE: Terrible commit message
git commit --amend -m "fix(auth): handle token expiry edge case"
GitHub Essentials
BeginnerSetting Up Authentication
GitHub no longer accepts passwords for Git operations. Use SSH keys (recommended for professionals) or Personal Access Tokens.
# 1. Generate SSH key pair
ssh-keygen -t ed25519 -C "your_email@example.com"
# 2. Start SSH agent and add your key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# 3. Copy your public key
cat ~/.ssh/id_ed25519.pub # copy this output
# 4. Go to GitHub → Settings → SSH Keys → New SSH Key
# Paste the key and save
# 5. Test your connection
ssh -T git@github.com
# Hi username! You've successfully authenticated.
Connect Local Repo to GitHub
# 1. Create repo on GitHub (empty, no README)
# 2. Link your local repo to remote
git remote add origin git@github.com:username/my-project.git
# 3. Verify remote is set
git remote -v
# origin git@github.com:username/my-project.git (fetch)
# origin git@github.com:username/my-project.git (push)
# 4. Push to GitHub (-u sets upstream for future git push)
git push -u origin main
# 5. Pull changes from teammates
git pull origin main
The .gitignore File
Never commit secrets, build artifacts, or OS-specific files. The .gitignore file tells Git what to ignore.
# Dependencies
node_modules/
npm-debug.log*
# Environment variables — NEVER commit these
.env
.env.local
.env.*.local
# Build output
dist/
build/
out/
# OS metadata
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
If you accidentally committed a secret (API key, password), assume it is compromised. Rotate it immediately — git history is permanent even after deletion.
Repository Structure Best Practices
project/ ├── .github/ │ ├── workflows/ # GitHub Actions CI/CD │ └── PULL_REQUEST_TEMPLATE.md ├── src/ # Source code ├── tests/ # Test files ├── docs/ # Documentation ├── .gitignore ├── README.md ├── LICENSE ├── CONTRIBUTING.md └── package.json
Branching & Pull Requests
IntermediateBranches are the heart of parallel development. The main branch should always contain production-ready code. All work happens in feature branches.
# Create and switch to a new branch in one command
git switch -c feature/user-profile
# List all branches (* = current)
git branch
# * feature/user-profile
# main
# Push branch to remote
git push -u origin feature/user-profile
# Delete a branch after merging
git branch -d feature/user-profile
git push origin --delete feature/user-profile
Real-World Feature Branch Workflow
# 1. Always start from an updated main
git checkout main
git pull origin main
# 2. Create feature branch
git switch -c feature/user-profile
# 3. Work and commit frequently (small, atomic commits)
git add profile.html
git commit -m "feat(profile): add profile page structure"
git add profile.css
git commit -m "feat(profile): add responsive grid layout"
git add profile.js
git commit -m "feat(profile): implement data fetching with fetch API"
# 4. Push branch to GitHub regularly
git push origin feature/user-profile
# 5. If main has new changes, rebase to stay current
git fetch origin
git rebase origin/main
Resolving Merge Conflicts
Conflicts happen when Git can't automatically reconcile differences. This is completely normal — here's how to handle it.
<<<<<<< HEAD
const API_URL = "https://api.production.com/v1";
=======
const API_URL = "https://api.staging.com/v1";
>>>>>>> feature/update-endpoint
- 1Open the conflicted file in your editor. You'll see conflict markers.
- 2Choose which version to keep (or combine them). Delete all conflict markers.
- 3Stage the resolved file:
git add config.js - 4Complete the merge:
git commit -m "merge: keep production API URL" - 5Push the resolved merge to remote.
VS Code has a built-in merge editor with "Accept Current", "Accept Incoming", and "Accept Both" buttons — much easier than editing conflict markers manually.
Writing a Quality Pull Request
## Description
Implements user profile page with avatar upload and editable fields.
## Changes Made
- Created profile.html with responsive grid layout
- Implemented form validation for email field
- Added fetch API integration for data loading
## Testing Instructions
1. Navigate to `/profile` route
2. Click "Edit Profile"
3. Verify email validation shows error for invalid format
## Related Issues
Closes #42
## Checklist
- [x] Tested locally
- [x] Added unit tests
- [x] Updated documentation
Advanced Git Concepts
AdvancedRebasing vs Merging
Rebasing rewrites commit history by replaying commits on top of another branch, creating a clean linear history.
# Merge approach (creates a merge commit)
git checkout feature
git merge main
# Rebase approach (linear history, replays feature commits on top)
git checkout feature
git rebase main
# Interactive rebase — squash, reword, reorder last 3 commits
git rebase -i HEAD~3
| Scenario | Recommendation |
|---|---|
| Updating feature branch with main changes | git rebase — keeps history clean |
| Merging completed feature into main | git merge --no-ff — preserves branch context |
| Public or shared branches | NEVER rebase — only merge |
Never rebase a branch that other people are working on. Rewriting shared history causes severe confusion and data loss for your teammates.
Stashing Changes
Stashing temporarily shelves uncommitted work so you can switch context quickly.
# Stash current work with a description
git stash save "WIP: debugging payment flow"
# List all stashes
git stash list
# stash@{0}: On feature/payment: WIP: debugging payment flow
# Apply most recent stash and remove it
git stash pop
# Apply a specific stash (keeps it in list)
git stash apply stash@{1}
# Create a branch from a stash
git stash branch new-branch-name stash@{0}
You're deep in a feature when an urgent P0 bug lands. git stash save "WIP: feature X", switch to main, create a hotfix branch, fix and deploy — then git stash pop to resume exactly where you left off.
Reset vs Revert
# Soft: undo last commit, keep changes staged
git reset --soft HEAD~1
# Mixed (default): undo commit, keep changes unstaged
git reset HEAD~1
# Hard: DESTRUCTIVE — permanently delete commits and changes
git reset --hard HEAD~2
# Creates a NEW commit that undoes the specified commit
git revert a3f2b1c
# Revert a merge commit
git revert -m 1 <merge-commit-hash>
| Situation | Command |
|---|---|
| Unstage a file | git restore --staged file.js |
| Discard uncommitted changes | git restore file.js |
| Undo local commits (not pushed) | git reset HEAD~1 |
| Undo pushed commits on shared branch | git revert <hash> |
| Completely wipe local changes | git reset --hard HEAD |
Tagging Releases
# Annotated tag (recommended for releases)
git tag -a v1.0.0 -m "Release v1.0.0 — Production ready"
# Push tag to remote
git push origin v1.0.0
# Push all tags at once
git push --tags
# List tags matching a pattern
git tag -l "v1.*"
vMAJOR.MINOR.PATCH — Major = breaking changes, Minor = new backward-compatible features, Patch = bug fixes.
Team Workflows & Git Flow
AdvancedGit Flow: A Robust Branching Strategy
main (production-ready, tagged) │ ├── develop (integration branch, always deployable) │ │ │ ├── feature/user-auth │ ├── feature/payment │ └── feature/notifications │ ├── release/1.2.0 (preparing a release, bug fixes only) │ └── hotfix/1.1.1 (urgent production fixes from main)
# Feature development
git checkout develop && git pull origin develop
git switch -c feature/new-dashboard
# ... work, commit, push, open PR to develop ...
# Release preparation
git checkout develop
git switch -c release/1.2.0
git commit -am "chore: bump version to 1.2.0"
# After testing, merge to main AND develop
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release 1.2.0"
git push origin main --tags
git checkout develop
git merge --no-ff release/1.2.0
git push origin develop
Commit Message Standards (Conventional Commits)
# Format: <type>[scope]: <description>
git commit -m "feat(auth): add JWT token refresh mechanism"
git commit -m "fix(payment): handle timeout errors gracefully"
git commit -m "docs: update API endpoints in README"
git commit -m "refactor(utils): extract date formatting logic"
git commit -m "perf(db): add index to reduce query time by 80%"
git commit -m "test(profile): add unit tests for email validation"
git commit -m "chore: upgrade dependencies to latest"
# BAD commit messages — never do this:
# "fix"
# "updated code"
# "WIP"
# "asdf"
# "stuff"
Code Review Best Practices
| Role | Best Practice |
|---|---|
| Author | Keep PRs small (<400 lines). Write clear descriptions. Don't take feedback personally. |
| Reviewer | Be respectful and constructive. Distinguish "must fix" from "nice to have". Review within 24 hours. |
**Nitpick (optional):** Consider using `const` instead of `let` here.
**Suggestion:** This could be extracted to a utility for reusability.
**Important:** This API call lacks error handling. Please add try/catch.
**Question:** Why this approach over using the existing helper?
CI/CD with GitHub Actions
AdvancedCI (Continuous Integration) — Automatically build and test code when changes are pushed. CD (Continuous Delivery) — Automatically deploy to staging/production after tests pass.
Catch bugs early, ensure consistent builds, get faster feedback loops, and eliminate manual deployment errors. Modern engineering teams ship multiple times a day because of this.
Basic GitHub Actions Workflow
name: CI Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
# Check out the repository code
- uses: actions/checkout@v4
# Set up Node.js runtime
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
# Install dependencies (ci = clean, reproducible install)
- run: npm ci
# Run quality checks
- run: npm run lint
- run: npm test
# Upload coverage report
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
Full Production Pipeline with Deployment
name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci && npm run build && npm test
- uses: actions/upload-artifact@v4
with: { name: dist, path: dist/ }
deploy-staging:
needs: build-and-test
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v4
with: { name: dist, path: dist/ }
- name: Deploy to Staging
run: npx vercel deploy --token ${{ secrets.VERCEL_TOKEN }}
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with: { name: dist, path: dist/ }
- name: Deploy to Production
run: npx vercel deploy --token ${{ secrets.VERCEL_TOKEN }} --prod
Go to Repository → Settings → Secrets and variables → Actions and add VERCEL_TOKEN, CODECOV_TOKEN, SLACK_WEBHOOK, etc. Never hardcode credentials in workflow files.
Security Best Practices
AdvancedManaging Secrets Safely
# .env — ADD TO .gitignore — NEVER commit this
API_KEY=abc123_real_secret_key
DATABASE_URL=postgresql://localhost/dev
# .env.example — safe to commit, shows what vars are needed
API_KEY=your_api_key_here
DATABASE_URL=your_database_url_here
- name: Deploy
env:
API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run deploy
Branch Protection Rules
Go to Repository → Settings → Branches → Add rule for main. Enable these settings:
- Require pull request reviews before merging (at least 1 reviewer)
- Require status checks to pass (your CI pipeline)
- Require conversation resolution before merging
- Require linear history (no merge commits directly)
- Do not allow force pushes
- Restrict who can push to matching branches
Common Security Mistakes to Avoid
| Mistake | Consequence | Prevention |
|---|---|---|
| Committing large binary files | Bloated repo, slow clones | Use .gitignore and Git LFS |
git push --force on shared branches | Overwrites teammates' work | Use --force-with-lease instead |
| Committing secrets or API keys | Security breach — rotate immediately | Pre-commit hooks + .gitignore |
| Not pulling before pushing | Avoidable merge conflicts | git pull --rebase first |
| Merging without review | Bugs shipped to production | Branch protection rules (enforced) |
Multiple GitHub Accounts via SSH Config
# Work account
Host github.com-work
HostName github.com
User git
IdentityFile ~/.ssh/github_work
# Personal account
Host github.com-personal
HostName github.com
User git
IdentityFile ~/.ssh/github_personal
# Clone using alias:
git clone git@github.com-work:company/project.git
Production-Ready Checklist
Use this checklist to verify you're following professional engineering standards. Check every box before considering yourself production-ready.
- Configured Git with proper
user.nameanduser.email - Using SSH keys for GitHub authentication
- Have a meaningful
.gitignorein every repository - Commit messages follow Conventional Commits format
- Using feature branches for all development work
- Opening descriptive Pull Requests with testing instructions
- Resolving merge conflicts locally before pushing
- Understand
git rebaseand know when NOT to use it - Using
git stashfor context switching during interruptions - Know the difference between
resetandrevert - Using annotated tags for releases following SemVer
- Branch protection rules enabled on
main - CI pipeline runs tests automatically on all PRs
- Secrets stored in GitHub Secrets — never hardcoded in code
- Can recover from common Git mistakes without panic