# 🌿 Git Mastery: The Complete Developer Guide

> Source: <https://dev.to/oketch/git-mastery-the-complete-developer-guide-2n7j>
> Published: 2026-05-23 17:42:25+00:00

*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](https://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
```

## 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
```

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/
```

### 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
```

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

# 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
```

### Writing good commit messages

Follow the [Conventional Commits](https://www.conventionalcommits.org/) format:

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

<optional body>

<optional footer>
```

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"
```

## 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
```

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

# Old syntax (still works everywhere)
git checkout -b feature/login
```

### 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
```

**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` |
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
```

### 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
```

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

```
# 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
```

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

# Stash including untracked files
git stash -u

# Create a branch from a stash
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:

```
# 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
```

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"
```

Find pre-made `.gitignore`

templates for your stack at [gitignore.io](https://www.toptal.com/developers/gitignore).

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

```
# 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
```

## 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
```

Better: use [git-secrets](https://github.com/awslabs/git-secrets) or [gitleaks](https://github.com/gitleaks/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
```

### ❌ 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

```
# Use rebase by default
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 events[GitHub Actions](https://docs.github.com/en/actions) -
— verify your identity with GPG keys[Signed commits](https://docs.github.com/en/authentication/managing-commit-signature-verification) -
— binary search your history to find which commit introduced a bug[git bisect](https://git-scm.com/docs/git-bisect) -
— check out multiple branches at once in different directories[Worktrees](https://git-scm.com/docs/git-worktree)

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