From your first commit to advanced branching strategies β everything you need to version control like a pro
Why Git? #
Every file you've ever accidentally deleted, every "final_v3_REAL_final.js" you've created β Git is the solution to all of that. It's a distributed version control system that tracks every change to your codebase, lets you experiment without fear, and enables teams of hundreds to collaborate without stepping on each other.
Git isn't just a tool β it's the backbone of modern software development. GitHub, GitLab, Bitbucket β they're all built on top of it.
Here's the mental model before we dive in:
**Repository (repo)**β A project tracked by Git (the.git
folder) - Commitβ A snapshot of your files at a point in time - Branchβ An independent line of development - Remoteβ A copy of the repo hosted elsewhere (GitHub, GitLab, etc.) - Working treeβ The files you're currently editing - **Staging area (index)**β Where you prepare changes before committing
Prerequisites #
- Git installed: git-scm.com - A terminal you're comfortable with
- (Optional) A GitHub account for remote repos
Verify your install:
git --version
Part 1: First-Time Setup #
Before your first commit, tell Git who you are. This info is embedded in every commit you make.
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
These settings live in ~/.gitconfig
and apply to every repo on your machine. You can override them per-repo by dropping the --global
flag.
Part 2: Starting a Repository #
From scratch
mkdir my-project && cd my-project
git init
From an existing remote
git clone https://github.com/user/repo.git
git clone https://github.com/user/repo.git my-folder
git clone --depth 1 https://github.com/user/repo.git
Part 3: The Core Workflow β Stage, Commit, Repeat #
This is the heartbeat of Git. Everything else builds on it.
Working Tree β Staging Area β Repository
(edit) (git add) (git commit)
Checking status
git status # What's changed? What's staged?
git status -s # Short format: M = modified, A = added, ? = untracked
Staging changes
git add file.js # Stage a specific file
git add src/ # Stage an entire directory
git add . # Stage everything in the current directory
git add -p # Interactive: stage changes chunk by chunk
Pro tip:git add -p
is one of Git's most underused features. It lets you review and selectively stage individual hunks of changes β perfect for keeping commits focused and atomic.
Committing
git commit -m "feat: add user authentication"
git commit -am "fix: correct typo in error message"
git commit
Writing good commit messages
Follow the Conventional Commits format:
<type>(<scope>): <short summary>
<optional body>
<optional footer>
Common types: feat
, fix
, docs
, refactor
, test
, chore
git commit -m "feat(auth): add JWT refresh token rotation"
git commit -m "fix(api): handle null response from payment gateway"
git commit -m "docs: update README with Docker setup instructions"
git commit -m "stuff"
git commit -m "fix"
git commit -m "asdfgh"
Part 4: Viewing History #
git log # Full log
git log --oneline # Compact: one commit per line
git log --oneline --graph # ASCII branch graph
git log --oneline --graph --all # Include all branches
git log --author="Jane"
git log --since="2 weeks ago"
git log --after="2024-01-01" --before="2024-06-01"
git log --grep="authentication"
git log -p
git log --stat
Inspecting a specific commit
git show abc1234 # Show commit details + diff
git show abc1234:src/app.js # Show a file as it was at that commit
Comparing changes
git diff # Unstaged changes vs last commit
git diff --staged # Staged changes vs last commit
git diff main feature-branch # Diff between two branches
git diff abc1234 def5678 # Diff between two commits
Part 5: Branching β Git's Superpower #
Branches are cheap and fast in Git (just a pointer to a commit). Use them liberally.
git branch # List local branches
git branch -a # List local + remote branches
git branch feature/login # Create a new branch
git switch feature/login # Switch to it
git switch -c feature/login # Create AND switch in one command
git checkout -b feature/login
Merging branches
git switch main
git merge feature/login
git merge --no-ff feature/login
Fast-forward vs merge commit:
Fast-forward (linear history):
main: A β B β C β D β E
β feature merged cleanly
Merge commit (preserves branch context):
main: A β B β C β M
β β merge commit
feature: D β E
Use --no-ff
when you want a clear record that a feature branch was merged.
Deleting branches
git branch -d feature/login # Delete (safe β won't delete unmerged branches)
git branch -D feature/login # Force delete
git push origin --delete feature/login # Delete remote branch
Part 6: Rebasing β A Cleaner History #
Rebase rewrites commit history by replaying your commits on top of another branch. The result is a clean, linear history.
git switch feature/login
git rebase main
Before rebase:
main: A β B β C
feature: D β E
After git rebase main
:
main: A β B β C
feature: D' β E' (commits replayed on top of C)
Interactive rebase β rewrite history
This is the power tool. Use it to clean up commits before merging:
git rebase -i HEAD~3 # Interactively edit the last 3 commits
In the editor that opens, you can:
| Command | What it does |
|---|---|
pick |
|
| Keep the commit as-is | |
reword |
|
| Keep but edit the commit message | |
squash |
|
| Combine with the previous commit | |
fixup |
|
| Like squash but discard this commit's message | |
drop |
|
| Delete the commit entirely | |
edit |
|
| to amend the commit |
β οΈ
Golden Rule of Rebasing:Never rebase commits that have been pushed to a shared remote branch. Rebase rewrites history β doing it on shared branches causes pain for everyone.
Part 7: Working with Remotes #
git remote -v # List remotes
git remote add origin https://github.com/user/repo.git # Add a remote
git remote rename origin upstream # Rename a remote
git remote remove origin # Remove a remote
Pushing and pulling
git push origin main
git push -u origin main
git pull origin main
git pull --rebase origin main
git fetch origin
git fetch --all # Fetch all remotes
Tracking branches
Once you've set an upstream with -u
, Git knows which remote branch your local branch corresponds to:
git push # Pushes to tracked remote branch
git pull # Pulls from tracked remote branch
git branch -vv # Shows tracking info for all branches
Part 8: Undoing Things #
This is where most developers get nervous. Don't be β Git almost never truly deletes anything.
Amending the last commit
git commit --amend -m "correct message"
git add forgotten-file.js
git commit --amend --no-edit # Keeps the original message
Unstaging files
git restore --staged file.js # Unstage (keep changes in working tree)
git restore file.js # Discard working tree changes (DESTRUCTIVE)
Reverting commits (safe β creates a new commit)
git revert abc1234 # Creates a new commit that undoes abc1234
git revert HEAD # Revert the last commit
git revert HEAD~3..HEAD # Revert the last 3 commits
Use revert
on shared branches β it's non-destructive.
Resetting (rewrites history β be careful)
git reset --soft HEAD~1 # Undo last commit, keep changes STAGED
git reset --mixed HEAD~1 # Undo last commit, keep changes UNSTAGED (default)
git reset --hard HEAD~1 # Undo last commit, DISCARD all changes
The escape hatch: reflog
Even after a hard reset, Git keeps a log of where HEAD has been. You can recover "lost" commits:
git reflog # See the full history of HEAD movements
git reset --hard abc1234 # Jump back to any previous state
Part 9: Stashing β Save Work Without Committing #
Need to switch branches but you're mid-feature? Stash it.
git stash # Stash all uncommitted changes
git stash push -m "wip: login form validation" # With a label
git stash list # See all stashes
git stash pop # Apply most recent stash and remove it
git stash apply stash@{2} # Apply a specific stash (keep it in the list)
git stash drop stash@{0} # Delete a specific stash
git stash clear # Delete all stashes
git stash -u
git stash branch feature/wip stash@{0}
Part 10: Tags β Marking Releases #
git tag # List all tags
git tag v1.0.0 # Lightweight tag (just a pointer)
git tag -a v1.0.0 -m "Release 1.0.0" # Annotated tag (recommended)
git push origin v1.0.0 # Push a specific tag
git push origin --tags # Push all tags
git tag -d v1.0.0 # Delete local tag
git push origin --delete v1.0.0 # Delete remote tag
Annotated tags store extra metadata (tagger name, date, message) and can be signed. Use them for releases.
Part 11: The .gitignore File #
Tell Git which files to never track:
node_modules/
vendor/
dist/
build/
*.min.js
.env
.env.local
*.pem
*.key
.DS_Store
Thumbs.db
.vscode/
.idea/
*.swp
Apply a gitignore to already-tracked files:
git rm --cached .env
git commit -m "chore: remove .env from tracking"
Find pre-made .gitignore
templates for your stack at gitignore.io.
Part 12: Branching Strategies #
Git Flow
Best for projects with scheduled releases:
main βββ stable production code
develop βββ integration branch
feature/* βββ new features (branch from develop)
release/* βββ release prep (branch from develop)
hotfix/* βββ urgent fixes (branch from main)
GitHub Flow
Simpler β best for continuous deployment:
main βββ always deployable
feature/* βββ branch from main, PR back to main, deploy
Trunk-Based Development
Best for mature teams with strong CI/CD:
main βββ everyone commits here (short-lived branches only)
Choose the strategy that matches your team size and release cadence. GitHub Flow is the right default for most teams.
Git Commands Cheat Sheet #
git config --global user.name "Name"
git config --global user.email "email"
git init
git clone <url>
git status
git add .
git commit -m "message"
git push
git pull
git switch -c feature/name
git merge feature/name
git rebase main
git branch -d feature/name
git log --oneline --graph --all
git diff
git show <commit>
git restore --staged <file>
git revert <commit>
git reset --soft HEAD~1
git reflog
git remote add origin <url>
git fetch --all
git push -u origin main
Common Pitfalls & How to Avoid Them #
β Committed secrets or credentials
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch path/to/secret.env" \
--prune-empty --tag-name-filter cat -- --all
git push origin --force --all
Better: use git-secrets or gitleaks to prevent it happening in the first place.
β Merge conflicts
<<<<<<< HEAD
incoming changes
>>>>>>> feature/login
git add resolved-file.js
git commit # or git rebase --continue if rebasing
β Pushed to the wrong branch
git revert HEAD # If others may have pulled it
git push origin --force # Only if no one else has the commits
β git pull
creates ugly merge commits
git config --global pull.rebase true
Wrapping Up #
Here's what you've learned:
Setupβ configuring Git globally for clean commit attribution -
Core workflowβ stage, commit, push β the heartbeat of Git -
Branchingβ creating, merging, and deleting branches with confidence -
Rebasingβ rewriting history for a cleaner log -
Remotesβ pushing, pulling, fetching, and tracking -
Undoingβrevert
,reset
,restore
, and the reflog safety net - Stashingβ saving work-in-progress without a commit - Tagsβ marking releases with annotated tags - Branching strategiesβ Git Flow vs GitHub Flow vs Trunk-based - Common pitfallsβ handling conflicts, secrets, and wrong-branch pushes
Git rewards practice. The commands that seem scary now (rebase -i
, reflog
, reset --hard
) become second nature once you've used them a few times in a safe environment.
What's Next? #
β automate CI/CD triggered by Git eventsGitHub Actions - β verify your identity with GPG keysSigned commits - β binary search your history to find which commit introduced a buggit bisect - β check out multiple branches at once in different directoriesWorktrees
Found this useful? Drop a β€οΈ and follow for more. Got a Git horror story or a tip I missed? Share it in the comments.