Post

The Complete Git Tutorial: Everything You Need to Know

The Complete Git Tutorial: Everything You Need to Know

A comprehensive guide to Git — from first commit to advanced workflows.

Coding

Table of Contents

  1. What is Git?
  2. Installing Git
  3. Initial Configuration
  4. Core Concepts
  5. Starting a Repository
  6. The Three Stages of Git
  7. Basic Commands
  8. Branching
  9. Merging
  10. Rebasing
  11. Remote Repositories
  12. Undoing Things
  13. Stashing
  14. Tagging
  15. Git Log & History
  16. Git Diff
  17. .gitignore
  18. Advanced Topics
  19. Git Workflows
  20. Useful Tips & Tricks
  21. Quick Reference Cheat Sheet

What is Git?

Git is a distributed version control system (DVCS) created by Linus Torvalds in 2005. It tracks changes in source code during software development, enabling multiple developers to work on the same project simultaneously without conflicts.

Why Use Git?

  • History tracking — Every change is recorded. You can go back to any version at any time.
  • Collaboration — Multiple people can work on the same codebase concurrently.
  • Branching — Experiment freely without affecting the main codebase.
  • Backup — With remote repositories, your code is safe even if your machine fails.
  • Speed — Almost all operations are local and extremely fast.

Git vs. Other Version Control Systems

FeatureGitSVNCVS
ArchitectureDistributedCentralizedCentralized
Offline workYesLimitedNo
BranchingLightweightExpensiveDifficult
SpeedFastSlowerSlow

Installing Git

macOS

1
2
3
4
# Using Homebrew (recommended)
brew install git

# Or download from https://git-scm.com

Windows

Download the installer from https://git-scm.com/download/win.

Or use winget:

1
winget install --id Git.Git -e --source winget

Linux (Ubuntu/Debian)

1
2
sudo apt update
sudo apt install git

Linux (Fedora/RHEL)

1
sudo dnf install git

Verify Installation

1
2
git --version
# git version 2.43.0

Initial Configuration

Before using Git, tell it who you are. This information is attached to every commit you make.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Set your name
git config --global user.name "Your Name"

# Set your email
git config --global user.email "you@example.com"

# Set your default editor (e.g., VS Code)
git config --global core.editor "code --wait"

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

# View all config settings
git config --list

The --global flag applies the setting to all repositories on your machine. Omit it to set it only for the current repository.

Config is stored in ~/.gitconfig (global) or .git/config (local).


Core Concepts

Understanding these four concepts will unlock everything else in Git.

Repository (Repo)

A repository is a directory that Git is tracking. It contains:

  • Your project files
  • A hidden .git/ folder where Git stores all history and metadata

Commit

A commit is a snapshot of your project at a specific point in time. Each commit has:

  • A unique SHA-1 hash (e.g., a3f9c2d)
  • Author and timestamp
  • A commit message
  • A reference to its parent commit(s)

Branch

A branch is a lightweight, movable pointer to a commit. The default branch is usually called main (or master in older projects). Branches let you develop features in isolation.

HEAD is a special pointer that tells Git where you currently are in the repository. It usually points to the tip (latest commit) of the currently checked-out branch.


Starting a Repository

Initialize a New Repository

1
2
3
4
# Create a new directory and initialize Git
mkdir my-project
cd my-project
git init

This creates a .git/ folder inside my-project/.

Clone an Existing Repository

1
2
3
4
5
6
7
8
9
10
11
# Clone via HTTPS
git clone https://github.com/user/repo.git

# Clone via SSH
git clone git@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 commit (shallow clone, faster)
git clone --depth 1 https://github.com/user/repo.git

The Three Stages of Git

This is the most important mental model in Git.

1
2
Working Directory  →  Staging Area (Index)  →  Repository (.git/)
     (Modify)             (git add)               (git commit)
StageDescription
Working DirectoryWhere you edit files. Changes here are “untracked” or “modified”.
Staging AreaA holding area where you prepare changes before committing.
RepositoryThe permanent history of committed snapshots.

Think of it like this: the staging area is your draft before you publish (commit).


Basic Commands

Checking Status

1
git status

This is your best friend. It tells you:

  • Which files are untracked
  • Which files are modified
  • Which files are staged

Adding Files to Staging

1
2
3
4
5
6
7
8
9
10
11
# Stage a specific file
git add filename.txt

# Stage multiple files
git add file1.txt file2.txt

# Stage all changes in the current directory
git add .

# Stage changes interactively (choose hunks)
git add -p

Committing

1
2
3
4
5
6
7
8
# Commit with a message
git commit -m "Add login feature"

# Stage all tracked files AND commit (skips staging step)
git commit -am "Fix typo in README"

# Commit and open editor for a detailed message
git commit

Writing Good Commit Messages:

A good commit message follows this pattern:

1
2
3
4
5
6
7
Short summary (50 chars max)

Optional body explaining the WHY, not the what.
Wrap lines at 72 characters.

- Use bullet points if needed
- Reference issues: Closes #42

Removing Files

1
2
3
4
5
# Remove file from Git and filesystem
git rm filename.txt

# Remove from Git tracking only (keep the file locally)
git rm --cached filename.txt

Moving / Renaming Files

1
git mv old-name.txt new-name.txt

Branching

Branches are one of Git’s most powerful features. They are cheap, fast, and encourage experimentation.

Creating Branches

1
2
3
4
5
6
7
8
# Create a new branch
git branch feature/login

# Create and switch to a new branch (modern syntax)
git switch -c feature/login

# Create and switch (older syntax)
git checkout -b feature/login

Switching Branches

1
2
3
4
5
# Modern syntax
git switch main

# Older syntax
git checkout main

Listing Branches

1
2
3
4
5
6
7
8
# List local branches
git branch

# List remote branches
git branch -r

# List all branches (local + remote)
git branch -a

Renaming a Branch

1
2
3
4
5
# Rename current branch
git branch -m new-name

# Rename a specific branch
git branch -m old-name new-name

Deleting a Branch

1
2
3
4
5
6
7
8
# Delete a merged branch (safe)
git branch -d feature/login

# Force delete (even if not merged)
git branch -D feature/login

# Delete a remote branch
git push origin --delete feature/login

Merging

Merging integrates changes from one branch into another.

Fast-Forward Merge

When the target branch has no new commits since the feature branch diverged, Git simply moves the pointer forward. No new merge commit is created.

1
2
3
git switch main
git merge feature/login
# Fast-forward: main pointer moves to feature/login tip

Three-Way Merge

When both branches have diverged, Git creates a merge commit that ties both histories together.

1
2
3
git switch main
git merge feature/login
# Creates a merge commit automatically

Merge with No Fast-Forward

Force a merge commit even if fast-forward is possible. Useful to preserve feature branch history.

1
git merge --no-ff feature/login

Merge Conflicts

A conflict happens when two branches modify the same part of a file differently.

1
2
git merge feature/login
# CONFLICT (content): Merge conflict in app.js

Git marks the conflict in the file:

1
2
3
4
5
<<<<<<< HEAD
const greeting = "Hello";
=======
const greeting = "Hi there";
>>>>>>> feature/login

To resolve:

  1. Edit the file to keep the correct version
  2. Remove the conflict markers (<<<<<<<, =======, >>>>>>>)
  3. Stage the resolved file: git add app.js
  4. Complete the merge: git commit

To abort a merge: git merge --abort


Rebasing

Rebasing is an alternative to merging that rewrites history to create a cleaner, linear timeline.

1
2
3
# Rebase feature branch onto main
git switch feature/login
git rebase main

Instead of creating a merge commit, rebasing replays your feature commits on top of the latest main.

Interactive Rebase

Interactive rebase lets you rewrite, squash, reorder, or edit commits before sharing them.

1
2
# Rebase the last 3 commits interactively
git rebase -i HEAD~3

Commands available in the editor:

CommandDescription
pickKeep the commit as-is
rewordKeep the commit, but edit the message
editPause and amend the commit
squashCombine commit into the previous one
fixupLike squash, but discard the commit message
dropRemove the commit entirely

⚠️ Golden Rule of Rebasing: Never rebase commits that have been pushed to a shared remote branch. Rewriting shared history causes problems for other contributors.


Remote Repositories

Remote repositories are versions of your project hosted on the internet or a network (e.g., GitHub, GitLab, Bitbucket).

Managing Remotes

1
2
3
4
5
6
7
8
9
10
11
# View remotes
git remote -v

# Add a remote
git remote add origin https://github.com/user/repo.git

# Rename a remote
git remote rename origin upstream

# Remove a remote
git remote remove origin

Fetching, Pulling, and Pushing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Fetch: Download changes but don't merge them into your working branch
git fetch origin

# Pull: Fetch + Merge (updates your current branch)
git pull origin main

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

# Push: Upload your commits to remote
git push origin main

# Push and set upstream tracking (first time)
git push -u origin main

# Push all branches
git push --all origin

Tracking Remote Branches

After setting upstream with -u, you can just run git pull or git push without specifying the remote and branch.

1
2
3
4
git push -u origin feature/login
# Later:
git push   # knows to push to origin/feature/login
git pull   # knows to pull from origin/feature/login

Undoing Things

Amend the Last Commit

Fix the last commit message or add forgotten files.

1
2
3
4
5
6
# Change just the commit message
git commit --amend -m "New, correct message"

# Add a forgotten file to the last commit
git add forgotten-file.txt
git commit --amend --no-edit

⚠️ Avoid amending pushed commits. It rewrites history.

Unstage a File

1
2
3
4
5
# Remove from staging area, keep changes in working directory
git restore --staged filename.txt

# Older syntax
git reset HEAD filename.txt

Discard Changes in Working Directory

1
2
3
4
5
# Discard unstaged changes in a file (PERMANENT)
git restore filename.txt

# Discard all unstaged changes (PERMANENT)
git restore .

Reverting a Commit (Safe)

git revert creates a new commit that undoes a previous one. This is safe for shared branches because it doesn’t rewrite history.

1
git revert a3f9c2d

Resetting (Careful!)

git reset moves the branch pointer and optionally affects the staging area and working directory.

1
2
3
4
5
6
7
8
# Soft reset: Move HEAD, keep changes staged
git reset --soft HEAD~1

# Mixed reset (default): Move HEAD, unstage changes
git reset HEAD~1

# Hard reset: Move HEAD, DISCARD all changes (PERMANENT)
git reset --hard HEAD~1

When to use which:

  • --soft: “I want to recommit these changes differently”
  • --mixed: “I want to unstage and review before recommitting”
  • --hard: “I want to completely throw away these commits”

Recovering Lost Commits with Reflog

Even after a hard reset, commits aren’t immediately gone. git reflog records every HEAD movement.

1
2
3
4
5
6
git reflog
# a3f9c2d HEAD@{0}: reset: moving to HEAD~1
# b7e1a3f HEAD@{1}: commit: Add feature X

# Restore the lost commit
git reset --hard b7e1a3f

Stashing

Stashing temporarily saves your uncommitted changes so you can switch context without committing unfinished work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Stash current changes
git stash

# Stash with a descriptive name
git stash push -m "WIP: half-done login form"

# Include untracked files in stash
git stash -u

# List all stashes
git stash list
# stash@{0}: WIP: half-done login form
# stash@{1}: On main: quick fix attempt

# Apply the most recent stash (keeps it in the stash list)
git stash apply

# Apply a specific stash
git stash apply stash@{1}

# Apply and remove from stash list
git stash pop

# Delete a specific stash
git stash drop stash@{0}

# Delete all stashes
git stash clear

Tagging

Tags mark specific points in history, typically used for release versions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Create a lightweight tag
git tag v1.0.0

# Create an annotated tag (recommended for releases)
git tag -a v1.0.0 -m "Release version 1.0.0"

# Tag a specific commit
git tag -a v0.9.0 a3f9c2d -m "Beta release"

# List all tags
git tag

# List tags matching a pattern
git tag -l "v1.*"

# Show tag details
git show v1.0.0

# Push a tag to remote
git push origin v1.0.0

# Push all tags
git push origin --tags

# Delete a local tag
git tag -d v1.0.0

# Delete a remote tag
git push origin --delete v1.0.0

Git Log & History

Viewing Commit History

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Full log
git log

# Compact one-line log
git log --oneline

# With branch graph
git log --oneline --graph --all

# Show last N commits
git log -5

# Commits by a specific author
git log --author="Jane Doe"

# Commits containing a keyword in the message
git log --grep="fix"

# Commits since a date
git log --since="2024-01-01"

# Commits that changed a specific file
git log -- path/to/file.txt

# Show what changed in each commit
git log -p

# Summarize changes (files changed, insertions, deletions)
git log --stat

Pretty Formatting

1
2
git log --pretty=format:"%h - %an, %ar : %s"
# a3f9c2d - Jane Doe, 2 days ago : Add login feature

Common format placeholders:

PlaceholderMeaning
%hAbbreviated commit hash
%HFull commit hash
%anAuthor name
%aeAuthor email
%arAuthor date, relative
%sSubject (commit message)

Git Diff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Diff between working directory and staging area (unstaged changes)
git diff

# Diff between staging area and last commit (staged changes)
git diff --staged

# Diff between two commits
git diff a3f9c2d b7e1a3f

# Diff between two branches
git diff main feature/login

# Diff a specific file
git diff HEAD -- filename.txt

# Summarize diff (just show which files changed)
git diff --stat

.gitignore

The .gitignore file tells Git which files and directories to ignore.

Creating a .gitignore

Create a file named .gitignore in your repository root:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Ignore node_modules
node_modules/

# Ignore build output
dist/
build/

# Ignore environment files
.env
.env.local

# Ignore OS files
.DS_Store
Thumbs.db

# Ignore log files
*.log

# Ignore all .txt files in a specific folder
docs/*.txt

# Ignore everything in a directory except one file
secrets/*
!secrets/README.md

Global .gitignore

Set up a global ignore file for OS/editor files across all repos:

1
git config --global core.excludesfile ~/.gitignore_global

Ignoring Already-Tracked Files

If a file is already tracked, adding it to .gitignore won’t stop Git from tracking it. You need to untrack it first:

1
2
git rm --cached filename.txt
git commit -m "Stop tracking filename.txt"

Advanced Topics

Cherry-Picking

Apply a specific commit from one branch to another.

1
git cherry-pick a3f9c2d

Useful when you want to bring a single bug fix from a feature branch into main without merging the whole branch.

Git Bisect

Binary search through your history to find which commit introduced a bug.

1
2
3
4
5
6
7
8
9
10
11
git bisect start
git bisect bad           # Current commit is broken
git bisect good v1.0.0   # This version was fine

# Git checks out a commit in the middle
# Test your code, then tell Git:
git bisect good   # or: git bisect bad

# Git narrows down until it finds the culprit
# When done:
git bisect reset

Git Blame

Find out who wrote each line of a file and in which commit.

1
2
3
4
git blame filename.txt

# Blame a specific range of lines
git blame -L 10,25 filename.txt

Submodules

Include one Git repository inside another.

1
2
3
4
5
6
7
8
# Add a submodule
git submodule add https://github.com/user/library.git libs/library

# Clone a repo with submodules
git clone --recurse-submodules https://github.com/user/repo.git

# Update all submodules
git submodule update --init --recursive

Sparse Checkout

Check out only specific directories from a large repository.

1
2
3
4
git clone --no-checkout https://github.com/user/big-repo.git
cd big-repo
git sparse-checkout set src/module-a docs
git checkout main

Git Workflows

GitHub Flow (Simple)

Best for continuous deployment:

  1. main is always deployable
  2. Create a branch for every new feature or fix
  3. Open a Pull Request when ready
  4. Review, discuss, and merge into main
  5. Deploy immediately
1
2
3
main ────●────────────────────●──── (deploy)
          \                  /
           ● feature/login  ●

Git Flow (Feature-Based)

Best for versioned software with scheduled releases:

  • main — production-ready code
  • develop — integration branch
  • feature/* — new features (branch from develop)
  • release/* — release preparation (branch from develop)
  • hotfix/* — urgent production fixes (branch from main)
1
2
3
4
5
6
main     ────●───────────────────────●────
              \                     /
hotfix         ● fix-critical-bug  ●
develop  ──●──────────────────●────────●──
            \               /
feature      ● feature/x   ●

Trunk-Based Development

Developers commit directly to main (or short-lived branches merged within a day). Used at large scale with feature flags.


Useful Tips & Tricks

Aliases

Create shortcuts for frequently used commands:

1
2
3
4
5
6
7
8
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.lg "log --oneline --graph --all"

# Now you can use:
git st
git lg

Search Through Code History

1
2
3
4
5
# Find which commit added or removed a specific string
git log -S "function login" --source --all

# Search commit messages with regex
git log --all --grep="bug fix" --oneline

Save Credentials

1
2
3
4
5
# Cache credentials for 1 hour (3600 seconds)
git config --global credential.helper "cache --timeout=3600"

# Store credentials permanently (less secure)
git config --global credential.helper store

Clean Up Untracked Files

1
2
3
4
5
6
7
8
9
10
11
# Preview what will be deleted
git clean -n

# Delete untracked files
git clean -f

# Delete untracked files AND directories
git clean -fd

# Delete ignored files too
git clean -fdx

Show Who Changed What in a Commit

1
2
git show a3f9c2d
git show a3f9c2d --stat

Find the Merge Base of Two Branches

1
2
git merge-base main feature/login
# Returns the common ancestor commit hash

Quick Reference Cheat Sheet

Setup

1
2
3
4
git config --global user.name "Name"
git config --global user.email "email"
git init
git clone <url>

Stage & Commit

1
2
3
4
5
git status
git add <file>
git add .
git commit -m "message"
git commit --amend

Branching

1
2
3
4
5
6
git branch                    # List branches
git switch -c <branch>        # Create & switch
git switch <branch>           # Switch
git branch -d <branch>        # Delete
git merge <branch>            # Merge
git rebase <branch>           # Rebase

Remote

1
2
3
4
5
6
git remote -v
git remote add origin <url>
git fetch
git pull
git push origin <branch>
git push -u origin <branch>

Undo

1
2
3
4
5
git restore <file>            # Discard changes
git restore --staged <file>   # Unstage
git revert <commit>           # Safe undo
git reset --soft HEAD~1       # Undo commit, keep staged
git reset --hard HEAD~1       # Undo commit, discard changes

Inspect

1
2
3
4
5
6
7
git log --oneline --graph
git diff
git diff --staged
git blame <file>
git show <commit>
git stash list
git reflog

Conclusion

Git is an essential tool for any developer, and mastering it will make you significantly more productive and confident. Start with the basics — add, commit, push, pull, and branch — and gradually explore more advanced features like rebasing, cherry-picking, and bisecting as you need them.

The best way to learn Git is to use it daily. Don’t be afraid to experiment — as long as you haven’t force-pushed to a shared branch, almost anything can be undone.


This post is licensed under CC BY 4.0 by the author.