Git for Developers: The Commands, Workflows, and Mental Models You Actually Need
Git is the one tool every developer uses daily but most students learn only superficially. This guide teaches Git properly β from how commits actually work to branch strategies, resolving merge conflicts, and the commands that save you in real situations.
The Git knowledge gap is real: most CS students know git add, git commit, git push β and nothing else. Then they join a team, encounter a merge conflict or a broken branch, and freeze.
This guide closes that gap. By the end, you'll understand Git's mental model (not just the commands), handle merge conflicts confidently, and use the commands that actually matter at work.
The Mental Model That Makes Everything Click
Git doesn't store differences between files. It stores snapshots.
Every commit is a complete snapshot of your project at that moment, plus a pointer to the parent commit(s). This makes Git operations fast and branching cheap β a branch is just a pointer to a commit.
A β B β C β D (main)
β
E β F (feature-branch)
main points to D. feature-branch points to F. Both branches share commits A, B, C, D β no duplication, just pointers.
The three areas:
Working Directory β [git add] β Staging Area β [git commit] β Repository
- Working directory: Your actual files (what you see in the folder)
- Staging area (index): Files you've
added, ready to commit - Repository: Committed history, stored in
.git/
The Commands You'll Use Every Day
# Configure once
git config --global user.name "Your Name"
git config --global user.email "you@email.com"
# Start a project
git init # new repo
git clone <url> # copy existing repo
# Daily workflow
git status # what's changed?
git diff # what exactly changed? (unstaged)
git diff --staged # what's staged?
git add <file> # stage a file
git add . # stage everything
git commit -m "message" # commit staged changes
git push origin main # push to remote
git pull # fetch + merge remote changes
# Branching
git branch # list branches
git branch feature-login # create branch
git checkout feature-login # switch to branch
git checkout -b feature-login # create + switch in one step
git switch feature-login # modern alternative to checkout
git merge feature-login # merge into current branch
git branch -d feature-login # delete merged branch
git branch -D feature-login # force delete (not merged)
Commits That Actually Help Your Team
A good commit message makes code review easier, helps you understand changes 6 months later, and makes git log useful.
Bad:
fix bug
update stuff
wip
changes
Good:
Fix null pointer in user authentication when email is not set
When a user signs up via Google OAuth, the email field can be null
if the user has hidden it from Google. This caused a NPE in the
profile creation flow.
Fixes #247
Format: Imperative present tense for the first line ("Fix null pointer" not "Fixed null pointer"). Keep first line under 72 characters. Leave a blank line before the body. Explain why, not what (the diff shows what).
Conventional commits (used at many companies):
feat: add user profile picture upload
fix: resolve null email crash on Google OAuth
docs: update API authentication guide
refactor: extract email validation into utility function
test: add coverage for empty cart checkout
chore: upgrade Next.js to 14.2.3
Branching Strategies
Git Flow
For teams with scheduled releases:
main β production code only
develop β integration branch
feature/* β one branch per feature
release/* β release preparation
hotfix/* β emergency production fixes
git checkout -b feature/user-login develop # branch from develop
# ... work ...
git checkout develop
git merge feature/user-login # merge back to develop
Trunk-Based Development
For fast-moving teams with CI/CD:
main β everyone integrates here frequently (multiple times per day)
Feature flags hide incomplete features in production. Short-lived branches (1-2 days max) merge directly to main. Requires strong CI/CD and test coverage.
Most modern product companies use trunk-based development.
Resolving Merge Conflicts
Conflicts happen when two branches modify the same lines. Don't fear them β understand them.
git merge feature-login
# Auto-merging auth.py
# CONFLICT (content): Merge conflict in auth.py
# Automatic merge failed; fix conflicts and then commit the result.
The conflict markers:
<<<<<<< HEAD
def authenticate(email, password):
if not email:
raise ValueError("Email required")
=======
def authenticate(email, password):
if not email or not password:
raise ValueError("Email and password required")
>>>>>>> feature-login
<<<<<<< HEADto=======β your current branch's version=======to>>>>>>>β the incoming branch's version
Resolution: Edit the file to keep what you want. Remove ALL conflict markers. Then:
git add auth.py
git commit -m "Merge feature-login: combine email+password validation"
Recommended tool: Use git mergetool or your editor's built-in merge tool (VS Code shows both versions side by side with Accept/Reject buttons).
The Commands That Save You
Undo a staged file (before commit)
git restore --staged auth.py # unstage, keep changes
# Old syntax:
git reset HEAD auth.py
Undo local changes (before staging)
git restore auth.py # discard changes, restore from last commit
# Old syntax:
git checkout -- auth.py
Undo last commit (keep changes staged)
git reset --soft HEAD~1 # un-commit, keep files staged
Undo last commit (keep changes unstaged)
git reset --mixed HEAD~1 # default: un-commit, keep files changed but unstaged
Undo last commit and discard changes
git reset --hard HEAD~1 # DANGEROUS: permanently deletes changes
Fix last commit message
git commit --amend -m "Correct message"
# Only if not pushed yet!
Temporarily save work without committing
git stash # save current changes to a stash
git stash list # see all stashes
git stash pop # restore most recent stash
git stash pop stash@{1} # restore specific stash
git stash drop # delete most recent stash
Use stash when: You need to switch branches but aren't ready to commit.
Rebase vs Merge
Both integrate changes from one branch into another. Different results:
Merge: Creates a new "merge commit" that combines both histories.
A - B - C - D (main)
\ /
E - F (feature, after merge)
Rebase: Moves the feature branch commits on top of main, rewriting history.
A - B - C - D - E' - F' (feature, after rebase)
E' and F' are new commits with the same changes as E and F but different hashes.
When to use which:
mergefor integrating complete features into main (preserves history)rebasefor incorporating upstream changes into your feature branch (clean linear history)- Never rebase commits that are on shared branches β rewriting shared history causes chaos
# Update your feature branch with latest main (clean approach)
git checkout feature-login
git rebase main
# If conflicts: fix them, git add, git rebase --continue
# Interactive rebase: clean up commits before merging
git rebase -i HEAD~3 # opens editor to squash/reword/drop last 3 commits
Tags and Releases
git tag v1.0.0 # lightweight tag
git tag -a v1.0.0 -m "Version 1.0.0" # annotated tag (recommended)
git push origin v1.0.0 # push tag to remote
git push origin --tags # push all tags
git tag # list all tags
The .gitignore File
Every project needs one. Never commit:
# Python
__pycache__/
*.pyc
.env
venv/
# Node.js
node_modules/
.next/
dist/
# Editors
.vscode/
.idea/
*.swp
# OS
.DS_Store
Thumbs.db
# Secrets (most important!)
.env
.env.local
*.pem
*secret*
Use gitignore.io to generate language-specific .gitignore files.
What Interviewers Ask About Git
"How do you handle merge conflicts?"
"I run
git statusto see conflicting files, open them in VS Code which highlights the conflict regions, decide which version to keep (or combine both), remove the markers,git addthe resolved files, andgit commit. For complex conflicts I usegit mergetoolor look at both sides' history withgit logto understand the intent."
"What's the difference between rebase and merge?"
"Both integrate changes. Merge creates a merge commit preserving branch history. Rebase moves my commits on top of the target branch, creating a linear history. I use merge for integrating features into main, and rebase to update my feature branch with upstream changes before making a PR. I avoid rebasing shared branches."
"How do you undo a commit that's already been pushed?"
"I use
git revert, which creates a new commit that undoes the changes β this is safe on shared branches because it doesn't rewrite history.git reset --hardwould work but requires force-pushing, which can cause problems for teammates who've already pulled."
Build the habit: From your next project, use a proper branching workflow. Create a feature branch for each feature, write meaningful commit messages, and practice resolving a deliberate merge conflict. This is more impressive on a resume than yet another certification.
Ready to practice what you just learned?
Apply these concepts with AI-powered tools built for CS students.