DevOps · 35 Days · Week 2 Day 06 — Git Fundamentals
1 / 22
Week 2 · Day 6

Git Fundamentals

init, clone, add, commit, push, pull — every DevOps pipeline, IaC repo, and GitOps workflow starts with a git push. This is the most important tool in the course.

⏱ Duration 60 min
📖 Theory 25 min
🔧 Lab 30 min
❓ Quiz 5 min
Why Git is #1
CI/CD pipelines trigger on git push. Infrastructure-as-Code lives in Git repos. ArgoCD syncs from Git. Every tool in this course depends on Git.
Session Overview

What we cover today

01
Git Mental Model
Snapshots, not diffs. The 4 areas: Working Dir → Staging → Local Repo → Remote.
02
Essential Commands
The 20% of commands you'll use 80% of the time — status, add, commit, push, pull, log.
03
Conventional Commits
feat:, fix:, chore:, docs: — writing commit messages your team (and CI/CD) can parse.
04
.gitignore
Keep secrets, node_modules, and build artifacts out of your repo forever.
05
git log, diff & stash
Visualise history, compare changes, and temporarily shelve work.
06
Remote Workflows
origin, fetch vs pull, tracking branches, and authentication.
07
🔧 Lab — Full Git Workflow
Init → configure → commit → .gitignore → connect remote → push to GitHub.
Part 1 of 6

How Git works — the mental model

📝
Working Directory
Files you're actively editing. Git sees them as "modified" but not yet saved to history.
git status
git add
🎭
Staging Area
Files you've explicitly selected for the next commit. The "draft" of your snapshot.
git diff --staged
git commit
🏛
Local Repository
The permanent history on your machine. Each commit is an immutable snapshot.
git log
git push
🌐
Remote Repository
GitHub/GitLab — the shared copy your team pulls from. Source of truth.
git pull
Snapshots, Not Diffs
Git doesn't store what changed — it stores the complete state of all tracked files at every commit. This makes switching branches, reverting changes, and comparing history instant and reliable. Each commit is identified by a SHA-1 hash (e.g. a3f7c2d).
💡 The Staging Area is a Superpower
You edited 5 files but only want to commit 2. Use git add file1 file2 to stage just those. The other 3 stay in your working directory. This gives you atomic, focused commits instead of "changed stuff" mega-commits.
Part 1 of 6 — continued

Commit history — the DAG

init
Initial commit
a1b2c3d
feat
add login endpoint
e4f5a6b
fix
handle empty password
c7d8e9f
chore
add .gitignore
a0b1c2d
HEAD
docs: update README
f3e2d1c
← you are here
What HEAD means

HEAD points to your current position in the commit graph — usually the tip of the branch you're on. When you run git log, commits above HEAD don't exist yet. When you make a new commit, HEAD advances forward.

  • HEAD — current commit
  • HEAD~1 — one commit back
  • HEAD~3 — three commits back
  • HEAD^ — same as HEAD~1
Useful log commands
  • git log --oneline — compact one-line per commit
  • git log --oneline --graph --all — ASCII branch graph
  • git log --author="name" — commits by person
  • git log --since="2 days ago" — recent commits
  • git show a1b2c3d — see what a commit changed
  • git diff HEAD~1 HEAD — diff last two commits
Part 2 of 6

Essential commands — your daily Git

🐧🪟 All Platforms
# === Setup (do once per machine) ===
git config --global user.name  "Your Name"
git config --global user.email "you@example.com"
git config --global core.editor "code --wait"
git config --global init.defaultBranch main
git config --list              # Verify config

# === Start a project ===
git init                       # New repo in current dir
git clone https://github.com/user/repo.git
git clone repo.git my-dir      # Clone into custom dir

# === See what's happening ===
git status                     # Untracked / modified / staged
git diff                       # Unstaged changes (vs last commit)
git diff --staged              # Staged changes (what will be committed)
git diff HEAD~1                # Changes since last commit

# === Stage + commit ===
git add file.txt               # Stage specific file
git add .                      # Stage ALL changes (use carefully)
git add -p                     # Interactive: choose hunks to stage
git commit -m "feat: add login"
git commit --amend             # Fix last commit message (before push)

# === Undo ===
git restore file.txt           # Discard working dir changes
git restore --staged file.txt  # Unstage (keep changes in working dir)
git reset HEAD~1               # Undo last commit (keep changes staged)
git revert abc123              # Safe undo — creates a new "undo commit"
🐧🪟 Remote operations
# === Remote management ===
git remote -v                  # List remotes
git remote add origin URL      # Add GitHub remote
git remote set-url origin URL  # Change remote URL

# === Sync with remote ===
git fetch origin               # Download (don't merge)
git pull                       # fetch + merge
git pull --rebase              # fetch + rebase (cleaner history)
git push origin main           # Push to remote
git push -u origin main        # Push + set upstream tracking
git push --force-with-lease    # Safe force push (after rebase)

# === Stash (temporarily shelve work) ===
git stash                      # Save dirty state, clean WD
git stash push -m "WIP: login" # Named stash
git stash list                 # Show all stashes
git stash pop                  # Apply last stash + drop it
git stash apply stash@{1}      # Apply specific stash (keep it)
git stash drop stash@{0}       # Delete a stash
💡 git add -p — the pro move
git add -p (patch mode) lets you choose exactly which lines to stage within a file. Edited 3 unrelated things in one file? Stage them as 3 separate logical commits. Reviewers love this.
Part 3 of 6

Conventional Commits — writing messages that matter

feat:
A new feature for users (not a new test or build script).
feat: add JWT authentication to API
fix:
A bug fix — something was broken and now it's not.
fix: handle null pointer in login handler
chore:
Build tools, dependencies, config — nothing the user sees.
chore: upgrade npm dependencies to latest
docs:
Documentation only — README, JSDoc, API docs.
docs: add setup guide to README.md
refactor:
Code restructured — no new features, no bug fixes.
refactor: extract auth logic to service layer
test:
Adding or correcting tests — no production code change.
test: add unit tests for password validation
style:
Formatting only — whitespace, semicolons, no logic change.
style: apply prettier formatting to src/
BREAKING CHANGE:
Incompatible API change. Use ! after type or in footer.
feat!: rename userId to user_id in API
Full Format
type(scope): description
blank line
optional body
feat(auth): add OAuth2 Google login

Implements Google OAuth flow using passport.js. Closes #42.
Why It Matters for DevOps
CI/CD tools parse commit messages: semantic-release auto-bumps versions based on feat: / fix:. Changelogs are auto-generated. Commitlint enforces the format in git hooks (Day 9).
⚠ Bad commit messages cost real time
"fixed stuff", "WIP", "asdfgh", "changes" — these make git log useless for debugging. When you're chasing a regression at 2 AM, a good commit history is your best friend.
Part 4 of 6

.gitignore — what never goes in a repo

.gitignore — complete template
# === SECRETS (NEVER commit these) ===
.env
.env.local
.env.*.local
*.pem
*.key
*_rsa
*_dsa
secrets.yml
credentials.json

# === Node.js ===
node_modules/
dist/
build/
.npm
npm-debug.log*
yarn-error.log

# === Python ===
__pycache__/
*.pyc
*.pyo
.venv/
venv/
*.egg-info/

# === OS / IDE ===
.DS_Store         # macOS
Thumbs.db         # Windows
.idea/            # IntelliJ
.vscode/          # VS Code (optional)
*.swp             # Vim swap files

# === Docker ===
*.dockerignore    # Already handled by Docker

# === Terraform ===
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars          # May contain secrets

# === Logs + Temp ===
*.log
*.tmp
coverage/
.nyc_output/
.gitignore Rules
  • *.log — ignore all .log files (glob)
  • logs/ — ignore the logs directory
  • !important.log — un-ignore a specific file (! = exception)
  • **/temp — ignore "temp" anywhere in tree
  • doc/**/*.txt — all .txt inside doc/
⚠ Already committed a secret?
.gitignore only prevents future commits. If .env is already in history:
1. Rotate the secret immediately — treat it as compromised.
2. Remove from history: git filter-repo --path .env --invert-paths
3. Force push to remote.
💡 gitignore.io
Visit gitignore.io and type "node, python, macos, windows, visualstudiocode" — it generates a comprehensive .gitignore in seconds.
Part 5 of 6

Remotes, fetch vs pull & authentication

fetch vs pull — the difference

git fetch — Downloads remote changes into origin/main but does NOT touch your local main. Safe — nothing breaks.

git pull = git fetch + git merge. Downloads AND merges into your current branch. Can cause merge conflicts.

git pull --rebase = git fetch + git rebase. Replays your commits on top of the remote — cleaner linear history. Recommended.

GitHub Authentication Options
  • HTTPS + PAT — Personal Access Token as password. Easy setup. Best for beginners.
  • SSH Key — Generate key pair, add public key to GitHub. No password prompts. Best for daily work.
  • GitHub CLIgh auth login. Handles auth automatically.
  • Credential Manager — Windows Git Credential Manager stores tokens securely.
🐧🪟 SSH Key Setup
# === Generate SSH key pair ===
ssh-keygen -t ed25519 -C "you@example.com"
# Press Enter for default location (~/.ssh/id_ed25519)
# Set a passphrase (optional but recommended)

# === Start SSH agent + add key ===
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# === Copy public key to clipboard ===
cat ~/.ssh/id_ed25519.pub
# Copy the output
# GitHub → Settings → SSH keys → New SSH key → Paste

# === Test the connection ===
ssh -T git@github.com
# Hi your-username! You've successfully authenticated.

# === Use SSH remote URL ===
git remote set-url origin git@github.com:user/repo.git
git push  # No password prompts!

# === PAT (simpler option) ===
# GitHub → Settings → Developer settings → Tokens (classic)
# Scopes: repo, workflow
# Use token as password when git push prompts

# === GitHub CLI (easiest) ===
gh auth login  # Interactive, handles everything
Part 5 of 6 — continued

git diff, stash & useful shortcuts

🐧🪟 diff, stash, shortcuts
# === diff ===
git diff                    # Working dir vs last commit
git diff --staged           # Staged vs last commit
git diff main feature-x     # Between two branches
git diff HEAD~3 HEAD -- app.js  # One file, 3 commits back

# === stash — save incomplete work ===
# Scenario: mid-feature, urgent hotfix needed
git stash push -m "WIP: user dashboard"
git checkout main           # Switch to main for hotfix
# ... fix the bug, commit, push ...
git checkout feature/dashboard
git stash pop               # Restore your work

# === Useful aliases — add to ~/.gitconfig ===
git config --global alias.st  "status -sb"
git config --global alias.lg  "log --oneline --graph --all --decorate"
git config --global alias.cm  "commit -m"
git config --global alias.aa  "add -A"
git config --global alias.undo "reset HEAD~1"

# Now you can use:
git st        # Short status
git lg        # Pretty graph log
git undo      # Undo last commit

# === Show file history ===
git log --follow -p src/auth.js  # All commits that touched this file
git blame src/auth.js            # Who wrote each line
git log --all --full-history -- "*.secret"  # Find deleted file history
The Stash Use Case
You're 40 minutes into a feature when your PM says "critical bug in production." git stash saves your messy uncommitted work, cleans your working directory, lets you switch branches, fix and push the hotfix, then git stash pop to resume exactly where you left off.
git blame — the debugging tool

git blame filename shows who last changed each line and in which commit. When you find a bug in the code, blame tells you:

  • Who wrote it (to ask for context)
  • When it was written (helps narrow the regression)
  • Which commit introduced it (to read the PR)

In VS Code: GitLens extension shows blame inline automatically.

Hands-On Lab

🔧 Full Git Workflow

init → configure → commit → .gitignore → connect GitHub → push — the complete first-repo workflow

⏱ 30 minutes
GitHub account ✓
Git installed ✓
🔧 Lab — Steps

Build your first Git workflow

1
Configure Git identity (one-time setup)
Set your name, email, default editor, and default branch name. These appear on every commit you ever make.
2
Create and initialise a project
Create my-devops-app directory, run git init, and create a README.md.
3
Make your first 3 commits
Stage and commit: README → .gitignore → package.json. Use Conventional Commits for all messages. Verify with git log --oneline.
4
Practise staging area
Edit 2 files simultaneously. Use git add -p to stage only part of one file. Commit them separately — experience atomic commits.
5
Connect to GitHub and push
Create a new repo on github.com → add remote → push. Verify the commits appear on GitHub. Set up SSH keys or PAT authentication.
6
Practise stash and log
Make changes without committing → git stash → run git lg alias → git stash pop to restore.
🔧 Lab — Commands

Complete lab script

🐧🪟 bash / WSL2 / Git Bash
# === 1. Configure Git ===
git config --global user.name  "Your Full Name"
git config --global user.email "you@example.com"
git config --global core.editor "code --wait"
git config --global init.defaultBranch main
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.st "status -sb"

# === 2. Create project ===
mkdir my-devops-app && cd my-devops-app
git init
echo "# My DevOps App" > README.md
echo "A sample app built during the DevOps 35-day course." >> README.md

# === 3. First commit ===
git status
git add README.md
git status             # See it go from red to green
git commit -m "feat: initial project setup with README"
git log --oneline

# === 4. Add .gitignore ===
cat > .gitignore << 'EOF'
node_modules/
.env
*.log
dist/
.DS_Store
.vscode/
EOF
git add .gitignore
git commit -m "chore: add gitignore for Node.js project"

# === 5. Create package.json ===
cat > package.json << 'EOF'
{
  "name": "my-devops-app",
  "version": "1.0.0",
  "description": "DevOps 35-day course app"
}
EOF
git add package.json
git commit -m "chore: add package.json"
git log --oneline      # Should show 3 commits
🐧🪟 Connect to GitHub
# === 6. Connect to GitHub ===
# Create repo at github.com first (no init, no README)
git remote add origin https://github.com/YOUR_USERNAME/my-devops-app.git
# OR use SSH:
# git remote add origin git@github.com:YOUR_USERNAME/my-devops-app.git
git remote -v          # Verify remote added
git push -u origin main
# Open github.com/YOUR_USERNAME/my-devops-app — see your commits!

# === 7. Practise stash ===
echo "incomplete feature" > feature.txt
git status             # See untracked file
git stash push -m "WIP: incomplete feature"
git status             # Working dir is clean
git stash list         # See the stash
git stash pop          # Restore it
ls                     # feature.txt is back
rm feature.txt         # Clean up

# === 8. Explore log ===
git lg                 # Pretty graph (alias we set)
git log --stat         # Files changed per commit
git log -p README.md   # All changes to README
git show HEAD          # See last commit details
git diff HEAD~1 HEAD   # What changed in last commit
💡 After the lab
Add the notes/day6-git.md to this repo with: 3 commands you found most useful, 1 thing that confused you, and your SSH or PAT setup method. Commit: docs(day6): git fundamentals notes
Knowledge Check

Quiz Time

3 questions · 5 minutes · Git areas, stash, and commit messages

Test your Git fundamentals →
QUESTION 1 OF 3
What does git stash do to your working directory?
A
Permanently deletes uncommitted changes
B
Temporarily saves uncommitted changes to a stack and cleans the working directory
C
Commits all changes with the message "stash"
D
Pushes uncommitted changes to a remote branch
QUESTION 2 OF 3
In Conventional Commits, which prefix should you use for a bug fix?
A
feat: — new feature
B
chore: — maintenance task
C
fix: — bug fix
D
refactor: — code restructure
QUESTION 3 OF 3
What is the Git Staging Area (Index) used for?
A
A backup copy of your files stored on GitHub
B
A buffer where you select exactly which changes will go into the next commit
C
A temporary branch for testing changes before merging
D
The cloud storage where your commits are permanently stored
Day 6 — Complete

What you learned today

🧠
Mental Model
4 areas: Working Dir → Staging → Local Repo → Remote. Snapshots not diffs.
⌨️
Core Commands
init, add, commit, push, pull, fetch, stash, log, diff, blame — your daily toolkit.
📝
Commit Messages
feat:, fix:, chore:, docs:, refactor: — Conventional Commits make history readable and CI parseable.
🛡
.gitignore
.env, node_modules/, *.key — never commit secrets or build artifacts. Add gitignore first.
Day 6 Action Items
  1. Complete all 6 lab steps — repo live on GitHub ✓
  2. Set up SSH keys or PAT authentication ✓
  3. Add 3 git aliases to ~/.gitconfig (st, lg, undo)
  4. Commit notes/day6-git.md with your takeaways
Tomorrow — Day 7
Git Branching Strategies

GitFlow vs GitHub Flow vs Trunk-Based Development. When to use which. Creating, merging, and deleting branches. The foundation for all CI/CD pipelines.

git branch git checkout git merge GitHub Flow
📌 Reference

Git commands — complete cheatsheet

CategoryCommandWhat it does
Setupgit config --global user.name "Name"Set identity for all commits
Startgit initInitialise new repo in current dir
Startgit clone URLClone remote repo locally
Statusgit statusShow modified/staged/untracked files
Stagegit add fileStage specific file
Stagegit add -pInteractively stage hunks
Commitgit commit -m "msg"Save staged snapshot with message
Commitgit commit --amendFix last commit message (before push)
Historygit log --oneline --graphVisual commit history
Historygit blame fileWho wrote each line
Diffgit diffUnstaged changes vs last commit
Diffgit diff --stagedStaged changes vs last commit
Undogit restore fileDiscard working dir changes
Undogit reset HEAD~1Undo last commit (keep changes)
Undogit revert SHASafe undo — new "undo" commit
Remotegit remote add origin URLConnect to GitHub remote
Remotegit push -u origin mainPush + set tracking
Remotegit pull --rebaseFetch + rebase (clean history)
Stashgit stash push -m "msg"Save dirty state to stack
Stashgit stash popRestore last stash + remove it
📌 Reference

Recommended ~/.gitconfig

~/.gitconfig — full template
[user]
	name  = Your Full Name
	email = you@example.com

[core]
	editor      = code --wait
	autocrlf    = input      # Linux/Mac: input | Windows: true
	whitespace  = trailing-space,space-before-tab

[init]
	defaultBranch = main

[pull]
	rebase = true             # Always rebase on pull

[push]
	autoSetupRemote = true    # Auto -u on first push

[fetch]
	prune = true              # Remove deleted remote branches

[merge]
	conflictstyle = diff3     # Show base + both sides in conflicts

[diff]
	colorMoved = zebra

[alias]
	st   = status -sb
	lg   = log --oneline --graph --all --decorate
	cm   = commit -m
	aa   = add -A
	undo = reset HEAD~1
	hide = stash push -m

[color]
	ui = auto
Key Config Choices
pull.rebase = true — every git pull rebases instead of merging. Keeps history linear. No "Merge branch 'main' into main" noise.

push.autoSetupRemote — no more "fatal: no upstream branch". First push sets tracking automatically.

fetch.prune = true — after teammates delete remote branches, your local tracking refs are automatically cleaned up.
💡 Windows: core.autocrlf
On Windows, set autocrlf = true. This converts LF↔CRLF automatically. In WSL2, use input (same as Linux). This prevents "all lines changed" noise in diffs.
⚠ Do NOT use core.autocrlf = false on Windows
If you're committing Windows CRLF line endings and teammates are on Mac/Linux, every file appears as "all lines changed" in diffs. Always configure autocrlf on Windows.
📌 Deeper Understanding

Inside .git/ — what Git actually stores

🐧🪟 Explore .git directory
# Inside any git repo, run:
ls -la .git/
# .git/
# ├── HEAD          ← which branch/commit you're on
# ├── config        ← repo-level git config
# ├── index         ← the staging area (binary)
# ├── objects/      ← all commits, trees, blobs (compressed)
# └── refs/
#     ├── heads/    ← local branches (just text files with SHA)
#     └── remotes/  ← remote tracking branches

# See what HEAD points to:
cat .git/HEAD
# ref: refs/heads/main

# See what main branch points to:
cat .git/refs/heads/main
# a3f7c2d1e4b5... (the SHA of the latest commit)

# Read a commit object:
git cat-file -p HEAD
# tree abc123...    ← snapshot of all files
# parent def456...  ← previous commit
# author ...
# committer ...

# Git objects are content-addressed storage:
# SHA = hash of content. Same content = same SHA.
# This is why Git is reliable — nothing can silently change.
Git's 3 Object Types
blob — the content of a single file at a point in time (compressed)

tree — a directory listing: maps filenames to blob/tree SHAs

commit — points to a tree (snapshot) + parent commit + author + message

Every git operation just creates/reads these objects. That's the entire Git internals.
Why this matters

Understanding that branches are just text files containing a SHA explains everything:

  • Creating a branch is instant (just write a file)
  • Deleting a branch doesn't delete commits (just deletes the pointer)
  • Merging is just moving a pointer forward (fast-forward)
  • git reflog can recover "deleted" commits
📌 Anti-patterns

Common Git mistakes — and how to fix them

MistakeWhy it's badFix
Committed .env or API keys Secret exposed to anyone with repo access — even after deletion, it's in history git filter-repo --path .env --invert-paths then rotate the secret immediately
Committing node_modules/ (500MB+) Massively bloats repo, slows clone, makes diffs useless Add to .gitignore, then: git rm -r --cached node_modules/
Vague commit messages ("fix", "wip", "stuff") Impossible to understand history, makes git bisect and blame useless Use Conventional Commits. Enforce with commitlint (Day 9).
git add . on everything Accidentally commits unrelated changes, debug files, temp files Use git add -p or git add specific-file
git push --force on shared branch Rewrites history that others have already pulled — breaks their repos Use git push --force-with-lease (safe) or never force-push main
Never using git stash Committing "WIP" or losing work when switching branches git stash push -m "WIP: description" before switching
No .gitignore in new projects OS files, IDE config, build artefacts accidentally committed Create .gitignore as the first commit in every new project
Week 2 Roadmap

Week 2 — Git mastery path

DayTopicKey SkillLab Output
Day 6 ✅Git Fundamentalsinit, add, commit, push, stash, logmy-devops-app on GitHub
Day 7Branching StrategiesGitFlow / GitHub Flow / Trunk-BasedFeature branch → merged PR
Day 8Pull Requests & ReviewPR template, code review, merge strategiesPR with review comments
Day 9Git Hooks & AutomationHusky, lint-staged, commitlintPre-commit hook enforcing Conventional Commits
Day 10Git Advanced Labrebase -i, cherry-pick, bisect, reflogConflict resolution + power commands
Why 5 Days on Git?
Git is the foundation of every other Week. CI/CD (Week 3) triggers on git events. IaC (Week 5) is stored in Git. GitOps (Week 8) uses Git as source of truth. A DevOps engineer who struggles with Git struggles with everything downstream.
This week's goal
By Day 10, you should be able to:

  • Work confidently on feature branches
  • Submit and review pull requests
  • Write consistent, parseable commit messages
  • Use pre-commit hooks to enforce quality
  • Use advanced commands (rebase, cherry-pick) without fear