DEV Community

Cover image for 🌿 Git Mastery: The Complete Developer Guide
Dishon Oketch
Dishon Oketch

Posted on

🌿 Git Mastery: The Complete Developer Guide

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
# git version 2.x.x
Enter fullscreen mode Exit fullscreen mode

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"

# Set your default editor (VS Code shown here)
git config --global core.editor "code --wait"

# Set default branch name to 'main'
git config --global init.defaultBranch main

# Verify your config
git config --list
Enter fullscreen mode Exit fullscreen mode

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
# Initialized empty Git repository in .git/
Enter fullscreen mode Exit fullscreen mode

From an existing remote

git clone https://github.com/user/repo.git

# Clone into a specific folder name
git clone https://github.com/user/repo.git my-folder

# Clone only the latest snapshot (faster for large repos)
git clone --depth 1 https://github.com/user/repo.git
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Checking status

git status              # What's changed? What's staged?
git status -s           # Short format: M = modified, A = added, ? = untracked
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"

# Stage all tracked files and commit in one step
git commit -am "fix: correct typo in error message"

# Open your editor for a detailed commit message
git commit
Enter fullscreen mode Exit fullscreen mode

Writing good commit messages

Follow the Conventional Commits format:

<type>(<scope>): <short summary>

<optional body>

<optional footer>
Enter fullscreen mode Exit fullscreen mode

Common types: feat, fix, docs, refactor, test, chore

# Good
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"

# Bad
git commit -m "stuff"
git commit -m "fix"
git commit -m "asdfgh"
Enter fullscreen mode Exit fullscreen mode

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

# Filter by author
git log --author="Jane"

# Filter by date
git log --since="2 weeks ago"
git log --after="2024-01-01" --before="2024-06-01"

# Search commit messages
git log --grep="authentication"

# See what changed in each commit
git log -p

# Show stats (files changed, insertions, deletions)
git log --stat
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

# Old syntax (still works everywhere)
git checkout -b feature/login
Enter fullscreen mode Exit fullscreen mode

Merging branches

# Switch to the branch you want to merge INTO
git switch main

# Merge feature branch
git merge feature/login

# Merge with a commit even if fast-forward is possible (preserves history)
git merge --no-ff feature/login
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Before rebase:

main:    A → B → C
feature:     D → E
Enter fullscreen mode Exit fullscreen mode

After git rebase main:

main:    A → B → C
feature:         D' → E'   (commits replayed on top of C)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 Pause 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
Enter fullscreen mode Exit fullscreen mode

Pushing and pulling

# Push local branch to remote
git push origin main

# Push and set upstream tracking (then you can just use `git push`)
git push -u origin main

# Pull = fetch + merge
git pull origin main

# Pull with rebase instead of merge (cleaner)
git pull --rebase origin main

# Fetch remote changes without merging
git fetch origin
git fetch --all    # Fetch all remotes
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Part 8: Undoing Things

This is where most developers get nervous. Don't be — Git almost never truly deletes anything.

Amending the last commit

# Fix the last commit message
git commit --amend -m "correct message"

# Add a forgotten file to the last commit
git add forgotten-file.js
git commit --amend --no-edit   # Keeps the original message
Enter fullscreen mode Exit fullscreen mode

Unstaging files

git restore --staged file.js    # Unstage (keep changes in working tree)
git restore file.js             # Discard working tree changes (DESTRUCTIVE)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

# Stash including untracked files
git stash -u

# Create a branch from a stash
git stash branch feature/wip stash@{0}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

# Dependencies
node_modules/
vendor/

# Build output
dist/
build/
*.min.js

# Environment & secrets
.env
.env.local
*.pem
*.key

# OS files
.DS_Store
Thumbs.db

# Editor files
.vscode/
.idea/
*.swp
Enter fullscreen mode Exit fullscreen mode

Apply a gitignore to already-tracked files:

# If you added .env to .gitignore but already committed it:
git rm --cached .env
git commit -m "chore: remove .env from tracking"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

GitHub Flow

Simpler — best for continuous deployment:

main          ←── always deployable
feature/*     ←── branch from main, PR back to main, deploy
Enter fullscreen mode Exit fullscreen mode

Trunk-Based Development

Best for mature teams with strong CI/CD:

main          ←── everyone commits here (short-lived branches only)
Enter fullscreen mode Exit fullscreen mode

Choose the strategy that matches your team size and release cadence. GitHub Flow is the right default for most teams.


Git Commands Cheat Sheet

# Setup
git config --global user.name "Name"
git config --global user.email "email"

# Start
git init
git clone <url>

# Daily workflow
git status
git add .
git commit -m "message"
git push
git pull

# Branches
git switch -c feature/name
git merge feature/name
git rebase main
git branch -d feature/name

# Inspection
git log --oneline --graph --all
git diff
git show <commit>

# Undo
git restore --staged <file>
git revert <commit>
git reset --soft HEAD~1
git reflog

# Remote
git remote add origin <url>
git fetch --all
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls & How to Avoid Them

❌ Committed secrets or credentials

# Remove a file from ALL history (nuclear option)
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch path/to/secret.env" \
  --prune-empty --tag-name-filter cat -- --all

# Then force push and rotate your credentials immediately
git push origin --force --all
Enter fullscreen mode Exit fullscreen mode

Better: use git-secrets or gitleaks to prevent it happening in the first place.

❌ Merge conflicts

# When a merge conflict occurs, Git marks the file:
<<<<<<< HEAD
  your changes
=======
  incoming changes
>>>>>>> feature/login

# After resolving manually:
git add resolved-file.js
git commit   # or git rebase --continue if rebasing
Enter fullscreen mode Exit fullscreen mode

❌ 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
Enter fullscreen mode Exit fullscreen mode

git pull creates ugly merge commits

# Use rebase by default
git config --global pull.rebase true
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

Here's what you've learned:

  1. Setup — configuring Git globally for clean commit attribution
  2. Core workflow — stage, commit, push — the heartbeat of Git
  3. Branching — creating, merging, and deleting branches with confidence
  4. Rebasing — rewriting history for a cleaner log
  5. Remotes — pushing, pulling, fetching, and tracking
  6. Undoingrevert, reset, restore, and the reflog safety net
  7. Stashing — saving work-in-progress without a commit
  8. Tags — marking releases with annotated tags
  9. Branching strategies — Git Flow vs GitHub Flow vs Trunk-based
  10. 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?

  • GitHub Actions — automate CI/CD triggered by Git events
  • Signed commits — verify your identity with GPG keys
  • git bisect — binary search your history to find which commit introduced a bug
  • Worktrees — check out multiple branches at once in different directories

Found this useful? Drop a ❤️ and follow for more. Got a Git horror story or a tip I missed? Share it in the comments.

Top comments (0)