1. Introduction to Git and Version Control {#introduction}
Version control systems are the backbone of modern software development, and Git stands as the most widely adopted distributed version control system in the world. Created by Linus Torvalds in 2005 to manage the Linux kernel’s source code, Git has evolved into an indispensable tool that transcends programming—writers, designers, data scientists, and countless other professionals rely on it daily.
What Makes Git Special?
Unlike centralized version control systems like SVN or CVS, Git is fundamentally distributed. Every developer’s working copy is a complete repository with full history and version-tracking capabilities, independent of network access or a central server. This architecture provides several profou…
1. Introduction to Git and Version Control {#introduction}
Version control systems are the backbone of modern software development, and Git stands as the most widely adopted distributed version control system in the world. Created by Linus Torvalds in 2005 to manage the Linux kernel’s source code, Git has evolved into an indispensable tool that transcends programming—writers, designers, data scientists, and countless other professionals rely on it daily.
What Makes Git Special?
Unlike centralized version control systems like SVN or CVS, Git is fundamentally distributed. Every developer’s working copy is a complete repository with full history and version-tracking capabilities, independent of network access or a central server. This architecture provides several profound advantages:
Speed: Nearly all operations are local, making Git blazingly fast compared to systems that need constant server communication.
Redundancy: Every clone is a full backup of the entire project history.
Branching: Git’s branching model is exceptionally lightweight and fast, encouraging experimental workflows.
Data Integrity: Git uses SHA-1 hashing to ensure data integrity—every file and commit is checksummed, making silent corruption nearly impossible.
Non-linear Development: Multiple parallel branches of development can proceed simultaneously without interfering with each other.
The Philosophy Behind Git
Git was designed with specific goals that shape how it works:
Content-addressable filesystem: Git is fundamentally a simple key-value data store where the content itself determines the key (via SHA-1 hashing). 1.
Snapshots, not differences: Unlike systems that store file changes, Git takes snapshots of your entire project at each commit. 1.
Branch-centric workflow: Branches are cheap and merging is encouraged. 1.
Distributed is better: No single point of failure; everyone has the full history.
Understanding Git’s Learning Curve
Git has a reputation for complexity, and this reputation isn’t entirely undeserved. However, the complexity stems not from arbitrary design choices but from Git’s powerful, flexible model. Once you understand Git’s internal model—how it stores data, what commits really are, how branches work—the commands begin to make intuitive sense.
This guide will take you from absolute beginner to Git expert, explaining not just what commands do, but why they work the way they do, and when you should use them. We’ll cover every significant Git command with unprecedented depth, including real-world examples, internal mechanics, and expert-level variations.
2. Git’s Architecture and Internal Model {#architecture}
Before diving into commands, understanding Git’s internal architecture is crucial. This knowledge transforms Git from a collection of mysterious commands into a logical, predictable system.
The Object Database
At its core, Git is a content-addressable filesystem with a version control interface built on top. Everything in Git—files, directories, commits—is stored as an object in the .git/objects directory.
There are four types of objects:
1. Blob (Binary Large Object)
- Stores file contents
- Contains no metadata—not even the filename
- Identified by SHA-1 hash of contents
- Immutable once created
2. Tree
- Represents a directory
- Contains pointers to blobs (files) and other trees (subdirectories)
- Stores filenames and permissions
- Like a snapshot of directory structure
3. Commit
- Points to a tree object (the project snapshot)
- Contains metadata: author, committer, timestamp, message
- Points to parent commit(s)
- Creates the history chain
4. Tag
- Points to a commit (usually)
- Contains annotation metadata
- Used for marking releases
How Git Stores Data
When you commit changes, Git:
- Creates blob objects for each modified file
- Creates tree objects representing directory structure
- Creates a commit object pointing to the root tree
- Updates the branch reference to point to the new commit
This creates an immutable, content-addressed history. Because objects are identified by content hash, identical files are stored only once (deduplication), and any corruption is immediately detectable.
References (Refs)
While objects are immutable and content-addressed, Git needs mutable pointers to track branches, tags, and other important commits. These are called references or refs, stored in .git/refs/.
Types of references:
refs/heads/*: Local branchesrefs/remotes/*: Remote-tracking branchesrefs/tags/*: TagsHEAD: Special reference pointing to current branch
The Index (Staging Area)
Git uniquely features a staging area (also called the index), sitting between your working directory and the repository. This intermediate step allows you to carefully craft commits, including only the changes you want.
The index is stored in .git/index as a binary file containing:
- List of files to be committed
- Their blob object hashes
- File metadata (permissions, timestamps)
The Three States
Every file in Git exists in one of three states:
- Modified: Changed in working directory but not staged
- Staged: Marked for inclusion in next commit
- Committed: Safely stored in repository
Understanding these states is fundamental to mastering Git’s workflow.
3. Installation and Initial Configuration {#installation}
Installing Git
Git is available for all major platforms:
Linux:
# Debian/Ubuntu
sudo apt-get update
sudo apt-get install git
# Fedora/RHEL
sudo dnf install git
# Arch Linux
sudo pacman -S git
macOS:
# Using Homebrew
brew install git
# Or download from git-scm.com
# Xcode Command Line Tools also includes Git
xcode-select --install
Windows:
- Download Git for Windows from git-scm.com
- Includes Git BASH, a terminal emulator
- Integrates with Windows Terminal
Verify Installation:
git --version
This displays the installed Git version, confirming successful installation.
4. git config - Configuration Management
Purpose
git config manages Git configuration at three hierarchical levels, controlling everything from user identity to default behaviors, aliases, and external tool integration.
Configuration Levels
Git configuration operates at three scopes, each overriding the previous:
1. System-level (--system)
- Location:
/etc/gitconfig(Linux/macOS) orC:\Program Files\Git\etc\gitconfig(Windows) - Applies to all users and repositories on the system
- Requires administrator privileges to modify
- Rarely used except in corporate environments
2. Global/User-level (--global)
- Location:
~/.gitconfigor~/.config/git/config - Applies to all repositories for the current user
- Most common configuration level
- Your identity and preferences go here
3. Repository-level (--local)
- Location:
.git/configin the repository - Applies only to the specific repository
- Overrides global and system settings
- Default when no scope specified
Essential Initial Configuration
After installing Git, you must configure your identity:
git config --global user.name "Your Full Name"
git config --global user.email "your.email@example.com"
Why this matters: Every commit includes author information. Without configuration, Git will guess (poorly) or refuse to commit. This identity is embedded in every commit you create and cannot be easily changed later without rewriting history.
Advanced Identity Configuration:
# Set different identity for a specific repository
cd /path/to/work-project
git config user.name "Your Name"
git config user.email "work.email@company.com"
# Use conditional includes for automatic identity switching
# In ~/.gitconfig:
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
[includeIf "gitdir:~/personal/"]
path = ~/.gitconfig-personal
This powerful feature automatically applies different configurations based on directory location.
Default Branch Name
Modern Git allows customizing the default branch name:
git config --global init.defaultBranch main
This sets “main” as the default branch for new repositories instead of “master”. Many projects and platforms have adopted this convention.
Editor Configuration
Git opens an editor for commit messages, interactive rebases, and other tasks:
# Use your preferred editor
git config --global core.editor "vim"
git config --global core.editor "nano"
git config --global core.editor "code --wait" # VS Code
git config --global core.editor "subl -n -w" # Sublime Text
git config --global core.editor "emacs"
The --wait flag (VS Code) tells Git to wait for you to close the file before continuing.
Viewing Configuration
# Show all configuration and their sources
git config --list --show-origin
# Show specific configuration value
git config user.name
# List all global configuration
git config --global --list
# List all local (repository) configuration
git config --local --list
Removing Configuration
# Remove specific setting
git config --global --unset user.name
# Remove entire section
git config --global --remove-section user
Advanced Configuration Options
Line Ending Configuration:
Different operating systems use different line ending conventions (CRLF on Windows, LF on Unix). Git can normalize these:
# Windows: Convert LF to CRLF on checkout, CRLF to LF on commit
git config --global core.autocrlf true
# macOS/Linux: Convert CRLF to LF on commit, no conversion on checkout
git config --global core.autocrlf input
# Disable line ending conversion entirely
git config --global core.autocrlf false
Whitespace Handling:
# Warn about whitespace errors (trailing whitespace, etc.)
git config --global core.whitespace trailing-space,space-before-tab
# Make Git diff highlight whitespace errors
git config --global color.diff.whitespace "red reverse"
Color Configuration:
# Enable colored output
git config --global color.ui auto
# Customize specific colors
git config --global color.branch.current "yellow reverse"
git config --global color.branch.local yellow
git config --global color.branch.remote green
git config --global color.status.added green
git config --global color.status.changed yellow
git config --global color.status.untracked red
Diff and Merge Tools:
# Configure external diff tool
git config --global diff.tool meld
git config --global difftool.meld.path "/usr/bin/meld"
git config --global difftool.prompt false
# Configure external merge tool
git config --global merge.tool kdiff3
git config --global mergetool.kdiff3.path "/usr/bin/kdiff3"
git config --global mergetool.prompt false
git config --global mergetool.keepBackup false
Aliases for Efficiency:
Aliases are custom shortcuts for Git commands:
# Basic aliases
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
# Advanced aliases
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual 'log --oneline --graph --decorate --all'
git config --global alias.amend 'commit --amend --no-edit'
# Aliases with arguments
git config --global alias.show-branches '!git for-each-ref --sort=-committerdate --format="%(committerdate:short) %(refname:short)" refs/heads/'
The ! prefix executes the command in the shell, allowing complex operations.
Credential Storage:
Avoid repeatedly entering passwords:
# Cache credentials in memory for 15 minutes (900 seconds)
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600'
# Store credentials permanently (less secure)
git config --global credential.helper store
# Use OS-specific credential managers
# macOS Keychain
git config --global credential.helper osxkeychain
# Windows Credential Manager
git config --global credential.helper manager-core
# Linux Secret Service
git config --global credential.helper libsecret
Push Behavior:
# Only push current branch to its upstream
git config --global push.default simple
# Push all matching branches
git config --global push.default matching
# Push current branch to branch of same name
git config --global push.default current
# Refuse to push if upstream differs
git config --global push.default nothing
Pull Behavior:
# Use rebase instead of merge when pulling
git config --global pull.rebase true
# Only allow fast-forward merges when pulling
git config --global pull.ff only
Performance Settings:
# Enable parallel index preload for faster operations
git config --global core.preloadindex true
# Enable filesystem monitor for large repositories
git config --global core.fsmonitor true
# Increase HTTP buffer size for large repositories
git config --global http.postBuffer 524288000
Configuration File Format
Git configuration files use INI-style format:
[user]
name = Your Name
email = your.email@example.com
[core]
editor = vim
autocrlf = input
[alias]
st = status
co = checkout
[color]
ui = auto
[push]
default = simple
You can edit these files directly with a text editor, though using git config is safer.
Real-World Configuration Example
Here’s a comprehensive .gitconfig for professional development:
[user]
name = Jane Developer
email = jane@example.com
signingkey = ABC123DEF456
[core]
editor = code --wait
autocrlf = input
whitespace = trailing-space,space-before-tab
pager = delta
[init]
defaultBranch = main
[pull]
rebase = true
[push]
default = simple
autoSetupRemote = true
[fetch]
prune = true
[rebase]
autoStash = true
[merge]
conflictstyle = diff3
tool = vscode
[diff]
colorMoved = zebra
tool = vscode
[alias]
st = status -sb
co = checkout
br = branch
ci = commit
unstage = reset HEAD --
last = log -1 HEAD
lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
amend = commit --amend --no-edit
undo = reset --soft HEAD^
stash-all = stash save --include-untracked
[color]
ui = auto
[credential]
helper = osxkeychain
5. Repository Initialization and Cloning {#repository-basics}
git init - Initialize a New Repository
Purpose: Creates a new Git repository, transforming an ordinary directory into a version-controlled project.
Basic Usage:
# Initialize in current directory
git init
# Initialize in a new directory
git init my-project
# Initialize with specific initial branch name
git init --initial-branch=main my-project
# Or shorter:
git init -b main my-project
What Actually Happens:
When you run git init, Git creates a .git subdirectory containing the entire repository structure:
.git/
├── HEAD # Points to current branch
├── config # Repository configuration
├── description # Repository description (for GitWeb)
├── hooks/ # Hook scripts
├── info/ # Additional repository information
│ └── exclude # Local .gitignore equivalent
├── objects/ # Object database
│ ├── info/
│ └── pack/
└── refs/ # References (branches, tags)
├── heads/ # Local branches
└── tags/ # Tags
Bare Repositories:
A bare repository contains only Git data (no working directory) and is used as a central sharing point:
# Create bare repository
git init --bare project.git
# Convention: bare repositories end with .git
Bare repositories are used for:
- Central servers (like GitHub’s storage)
- Shared network repositories
- Backup repositories
When to Use git init:
- Starting a new project from scratch
- Converting existing project to Git
- Creating a local repository for experimentation
- Setting up a central bare repository
Real-World Example:
# Start a new web project
mkdir awesome-website
cd awesome-website
git init -b main
# Create initial structure
mkdir src css js
touch README.md src/index.html css/style.css js/app.js
# Make first commit
git add .
git commit -m "Initial project structure"
git clone - Clone a Repository
Purpose: Creates a complete local copy of a remote repository, including all history, branches, and tags.
Basic Usage:
# Clone a repository
git clone https://github.com/user/repository.git
# Clone into specific directory
git clone https://github.com/user/repository.git my-local-name
# Clone specific branch
git clone -b develop https://github.com/user/repository.git
# Shallow clone (limited history)
git clone --depth 1 https://github.com/user/repository.git
Protocol Options:
Git supports several protocols for cloning:
1. HTTPS:
git clone https://github.com/user/repo.git
- Most common and universally supported
- Works through firewalls
- Requires authentication for private repos
- Can cache credentials
2. SSH:
git clone git@github.com:user/repo.git
- Requires SSH key setup
- More secure than HTTPS with passwords
- No credential prompting once keys configured
- Preferred for frequent access
3. Git Protocol:
git clone git://github.com/user/repo.git
- Fastest protocol
- No authentication (read-only for public repos)
- Often blocked by firewalls
- Rarely used today
4. Local Filesystem:
git clone /path/to/repository
git clone file:///path/to/repository
- Clone from local directories
- Useful for network shares
- Can hardlink objects for efficiency
What git clone Does:
- Creates a new directory with the repository name
- Initializes a
.gitdirectory inside it - Fetches all repository data from remote
- Checks out the default branch into working directory
- Sets up
originremote pointing to source - Creates remote-tracking branches for all remote branches
Advanced Cloning Options:
Shallow Clones:
For large repositories where you only need recent history:
# Clone only latest commit
git clone --depth 1 https://github.com/user/large-repo.git
# Clone with last 50 commits
git clone --depth 50 https://github.com/user/repo.git
# Shallow clone single branch
git clone --depth 1 --single-branch --branch main https://github.com/user/repo.git
Benefits:
- Faster cloning
- Less disk space
- Faster operations on huge repositories
Limitations:
- Cannot push to shallow clones
- Limited history access
- Cannot clone from shallow clones
Deepen Shallow Clones:
# Fetch more history
git fetch --depth=100
# Convert to full clone
git fetch --unshallow
Partial Clones (Sparse Checkout):
Clone without immediately downloading all files:
# Clone with blob filtering (no file contents initially)
git clone --filter=blob:none https://github.com/user/repo.git
# Clone with tree filtering (fetch trees on-demand)
git clone --filter=tree:0 https://github.com/user/repo.git
Files are downloaded on-demand when checked out. Perfect for monorepos where you only work on specific areas.
Mirror Clones:
Create an exact mirror of the repository:
# Mirror clone (includes all refs and history)
git clone --mirror https://github.com/user/repo.git
# Useful for backups or creating exact replicas
Submodule Handling:
# Clone repository and initialize submodules
git clone --recursive https://github.com/user/repo.git
# Or equivalently:
git clone --recurse-submodules https://github.com/user/repo.git
Configuration During Clone:
# Set configuration during clone
git clone -c http.proxy=http://proxy.example.com:8080 https://github.com/user/repo.git
# Set branch name
git clone -c init.defaultBranch=main https://github.com/user/repo.git
Clone and Checkout Specific Commit:
# Clone then checkout specific commit
git clone https://github.com/user/repo.git
cd repo
git checkout abc123def456
Real-World Scenarios:
Scenario 1: Contributing to Open Source
# Clone the project
git clone https://github.com/original/project.git
cd project
# Add your fork as remote
git remote add myfork git@github.com:yourname/project.git
# Create feature branch
git checkout -b feature/awesome-addition
# Work, commit, push to your fork
git push -u myfork feature/awesome-addition
Scenario 2: Large Repository (e.g., Linux Kernel)
# Shallow clone for quick start
git clone --depth 1 --single-branch --branch master https://github.com/torvalds/linux.git
# Later, if you need more history
cd linux
git fetch --unshallow
Scenario 3: Backup Creation
# Create exact mirror for backup
git clone --mirror https://github.com/company/critical-project.git backup.git
# Update backup regularly
cd backup.git
git remote update
6. The Three Trees: Working Directory, Staging Area, and Repository {#three-trees}
Understanding Git’s three-tree architecture is essential for mastering its workflow. These “trees” represent three different states of your project:
The Three Trees Explained
1. Working Directory (Working Tree)
- Your actual project files
- What you see in your file explorer
- Where you make changes
- Can contain untracked, modified, or clean files
2. Staging Area (Index)
- Proposed next commit
- Intermediate storage between working directory and repository
- Allows selective committing
- Stored in
.git/index
3. Repository (HEAD)
- Committed history
- Permanent storage of snapshots
- Immutable object database
- Organized as commits, trees, and blobs
The Git Workflow Cycle
Working Directory → (git add) → Staging Area → (git commit) → Repository
← (git checkout) ← ← (git reset) ←
Visual Representation
┌─────────────────────┐
│ Working Directory │ ← Your files as you edit them
│ - file1.txt │
│ - file2.txt │
└─────────────────────┘
│
│ git add
▼
┌─────────────────────┐
│ Staging Area │ ← Files staged for commit
│ - file1.txt │
└─────────────────────┘
│
│ git commit
▼
┌─────────────────────┐
│ Repository (HEAD) │ ← Committed history
│ - commit abc123 │
│ - commit def456 │
└─────────────────────┘
7. Basic Git Workflow Commands {#basic-workflow}
git status - Check Repository Status
Purpose: Shows the state of the working directory and staging area. This is your primary diagnostic command.
Basic Usage:
# Full status
git status
# Short format
git status -s
git status --short
# Show branch and tracking info
git status -sb
git status --short --branch
Understanding Output:
Full Status Output:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt
new file: file2.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file3.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file4.txt
Section Breakdown:
- Current Branch: Which branch you’re on
- Branch Status: Relationship to remote tracking branch
- Changes to be committed: Staged changes (green in color terminals)
- Changes not staged: Modified but not staged (red in color terminals)
- Untracked files: New files Git doesn’t know about
Short Status Format:
$ git status -s
M file1.txt # Modified and staged
M file3.txt # Modified but not staged
A file2.txt # Added (new file staged)
?? file4.txt # Untracked
MM file5.txt # Modified, staged, then modified again
Status Codes:
??- UntrackedA- Added (staged)M- ModifiedD- DeletedR- RenamedC- CopiedU- Updated but unmerged (conflict)
Advanced Usage:
# Show ignored files
git status --ignored
# Show untracked files in detail
git status -u
git status --untracked-files=all
# Machine-readable format (for scripts)
git status --porcelain
# Show status with file stats
git status -v
git status --verbose
Why Use git status Frequently:
Running git status before and after operations helps you:
- Understand current repository state
- Prevent accidental commits
- Verify operations completed successfully
- Catch uncommitted changes before switching branches
- Identify merge conflicts
Best Practice: Run git status constantly. It’s fast, safe, and informative.
git add - Stage Changes
Purpose: Adds file contents to the staging area, preparing them for commit. This is how you tell Git which changes to include in the next commit.
Basic Usage:
# Stage specific file
git add filename.txt
# Stage multiple files
git add file1.txt file2.txt file3.txt
# Stage all changes in current directory and subdirectories
git add .
# Stage all changes in repository
git add -A
git add --all
# Stage all modified and deleted files (not new files)
git add -u
git add --update
Understanding the Differences:
| Command | New Files | Modified Files | Deleted Files | Scope |
|---|---|---|---|---|
git add . | ✓ | ✓ | ✓ | Current directory and below |
git add -A | ✓ | ✓ | ✓ | Entire repository |
git add -u | ✗ | ✓ | ✓ | Entire repository |
Interactive Staging:
The most powerful git add feature is interactive mode, allowing surgical precision in staging changes:
# Enter interactive mode
git add -i
git add --interactive
Interactive Mode Menu:
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
Patch Mode (Most Useful):
# Stage parts of files interactively
git add -p filename.txt
git add --patch filename.txt
# Patch mode for all files
git add -p
Patch Mode Commands:
When in patch mode, Git shows each change hunk and asks what to do:
y- Stage this hunkn- Don’t stage this hunkq- Quit; don’t stage this or remaining hunksa- Stage this and all remaining hunks in filed- Don’t stage this or any remaining hunks in files- Split the hunk into smaller hunkse- Manually edit the hunk?- Print help
Example Patch Mode Session:
$ git add -p example.py
diff --git a/example.py b/example.py
index 1234567..abcdefg 100644
--- a/example.py
+++ b/example.py
@@ -10,6 +10,7 @@ def process_data(data):
result = []
for item in data:
processed = transform(item)
+ validated = validate(processed)
result.append(processed)
return result
Stage this hunk [y,n,q,a,d,s,e,?]? y
Staging by File Type:
# Stage all Python files
git add *.py
# Stage all files in directory
git add src/
# Stage all .txt files recursively
git add '*.txt'
Note: Quote wildcards to prevent shell expansion.
Force Adding Ignored Files:
# Add file even if in .gitignore
git add -f ignored-file.txt
git add --force ignored-file.txt
Intent to Add:
# Register untracked file without staging content
git add -N newfile.txt
git add --intent-to-add newfile.txt
This makes the file visible to git diff without staging it.
Dry Run:
# Show what would be added
git add --dry-run .
git add -n .
Verbose Mode:
# Show added files
git add -v file.txt
git add --verbose file.txt
Real-World Scenarios:
Scenario 1: Commit Related Changes Separately
You’ve fixed a bug and also refactored unrelated code. Commit them separately:
# Stage only bug fix
git add src/buggy_module.py
git commit -m "Fix: Resolve null pointer exception"
# Stage refactoring
git add src/refactored_module.py
git commit -m "Refactor: Improve code readability"
Scenario 2: Stage Part of a File
You’ve made multiple independent changes in one file. Stage them separately:
git add -p complex_file.py
# Stage first logical change: y
# Skip unrelated change: n
git commit -m "Add validation function"
# Now stage the second change
git add -p complex_file.py
# Stage second logical change: y
git commit -m "Add logging"
Scenario 3: Stage Everything Except One File
# Stage all changes
git add -A
# Unstage one file
git restore --staged unwanted.txt
Common Mistakes:
Mistake 1: Using git add . in wrong directory
# You're in src/ but meant to stage root files
pwd # /home/user/project/src
git add . # Only stages src/ contents!
# Solution: Navigate to repository root or use -A
cd ..
git add -A
Mistake 2: Forgetting to stage after modifications
git add file.txt
# Edit file.txt more
git commit -m "Update file" # Doesn't include latest edits!
# Solution: Always check status before committing
git status
git add file.txt #
Stage latest changes
git commit -m "Update file"
Mistake 3: Accidentally staging sensitive data
# Accidentally added secrets file
git add config/secrets.yml
# Solution: Unstage immediately
git restore --staged config/secrets.yml
# Add to .gitignore
echo "config/secrets.yml" >> .gitignore
git commit - Record Changes to Repository
Purpose: Creates a snapshot of staged changes in the repository, permanently recording them in project history with metadata (author, timestamp, message).
Basic Usage:
# Commit with inline message
git commit -m "Add user authentication feature"
# Open editor for commit message
git commit
# Commit with detailed message
git commit -m "Add user authentication" -m "- Implement JWT tokens" -m "- Add login endpoint" -m "- Add tests"
Commit Message Best Practices:
A good commit message has structure:
Short summary (50 characters or less)
More detailed explanatory text, if necessary. Wrap it to about 72
characters. The blank line separating the summary from the body is
critical; tools like git log --oneline will only show the summary.
Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequences of this
change? Here's the place to explain them.
- Bullet points are okay
- Use a hyphen or asterisk for bullets
If you use an issue tracker, put references to them at the bottom:
Resolves: #123
See also: #456, #789
Conventional Commits Format:
Many teams use structured commit messages:
git commit -m "feat: add user authentication with JWT"
git commit -m "fix: resolve null pointer in payment processing"
git commit -m "docs: update API documentation"
git commit -m "refactor: simplify database query logic"
git commit -m "test: add unit tests for auth module"
git commit -m "chore: update dependencies"
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, semicolons, etc.)refactor: Code refactoringperf: Performance improvementstest: Adding or updating testschore: Maintenance tasksci: CI/CD changesbuild: Build system changes
Advanced Commit Options:
Skip Staging Area:
# Stage and commit modified tracked files in one step
git commit -a -m "Update all tracked files"
git commit -am "Update all tracked files"
⚠️ Warning: This doesn’t include untracked files, only modified tracked files.
Amend Previous Commit:
# Modify the last commit
git commit --amend
# Amend without changing message
git commit --amend --no-edit
# Amend and change message
git commit --amend -m "Corrected commit message"
What --amend does:
- Replaces the last commit entirely
- Includes currently staged changes
- Allows message modification
- Changes commit SHA (rewrites history)
⚠️ Critical Warning: Never amend commits that have been pushed to shared branches. This rewrites history and causes problems for collaborators.
Use case for amend:
# Made a commit
git commit -m "Add feature X"
# Oops, forgot to include a file
git add forgotten-file.txt
git commit --amend --no-edit
# Or fix typo in commit message
git commit --amend -m "Add feature X (corrected)"
Empty Commits:
# Create commit without changes (useful for triggering CI/CD)
git commit --allow-empty -m "Trigger CI pipeline"
Commit with Specific Date:
# Set custom commit date
git commit --date="2024-01-15 10:30:00" -m "Backdated commit"
# Use author date instead of commit date
GIT_AUTHOR_DATE="2024-01-15 10:30:00" git commit -m "Message"
Commit as Different Author:
# Commit with different author
git commit --author="Jane Doe <jane@example.com>" -m "Fix bug"
Useful when:
- Applying patches from others
- Committing on behalf of someone
- Maintaining proper attribution
Sign Commits (GPG):
# Sign commit with GPG key
git commit -S -m "Signed commit"
git commit --gpg-sign -m "Signed commit"
# Configure automatic signing
git config --global commit.gpgsign true
Signed commits prove authenticity and are required by many organizations.
Verbose Mode:
# Show diff in commit message editor
git commit -v
git commit --verbose
This displays the diff below the commit message template, helping you write accurate messages.
Template Messages:
# Use commit message template
git config --global commit.template ~/.gitmessage.txt
Example template (~/.gitmessage.txt):
# [Type] Short description (max 50 chars)
# Detailed description (wrap at 72 chars)
# Explain WHY this change is being made
# Related issues
# Resolves: #
# See also: #
Partial Commits with Interactive Staging:
# Stage specific changes, then commit
git add -p file.txt
git commit -m "Add first feature"
git add -p file.txt
git commit -m "Add second feature"
Commit Only Specific Files:
# Commit specific files, bypassing staging area
git commit file1.txt file2.txt -m "Update specific files"
Reuse Previous Message:
# Reuse message from previous commit
git commit -C HEAD -m "Same message as before"
# Edit the reused message
git commit -c HEAD
Real-World Commit Strategies:
Atomic Commits:
Each commit should be a single logical unit:
# Bad: Everything in one commit
git add .
git commit -m "Various updates"
# Good: Separate logical commits
git add src/auth.js
git commit -m "feat: add JWT authentication"
git add src/validation.js
git commit -m "feat: add input validation"
git add tests/auth.test.js
git commit -m "test: add authentication tests"
git add README.md
git commit -m "docs: update authentication section"
Benefits:
- Easier code review
- Simpler to revert specific changes
- Clearer history
- Better bisecting for bugs
Commit Frequency:
# Too infrequent (bad)
# Work all day, commit once with "Day's work"
# Too frequent (bad)
# Commit after every line change
# Just right (good)
# Commit after each logical, working change
git commit -m "feat: add login form"
git commit -m "feat: add form validation"
git commit -m "feat: connect form to API"
git commit -m "test: add login form tests"
Work-in-Progress Commits:
For checkpoint commits you’ll squash later:
# Quick checkpoint
git commit -m "WIP: working on feature X"
# More checkpoints
git commit -m "WIP: partial implementation"
# Before pushing, squash WIP commits
git rebase -i HEAD~3
# Mark WIP commits with 'squash' or 'fixup'
Commit Verification:
Before committing, verify:
# Check what's staged
git diff --staged
git diff --cached
# Check status
git status
# Run tests
npm test # or your test command
# Then commit
git commit -m "feat: add feature X"
Commit Message Examples by Type:
Feature Addition:
git commit -m "feat: add password reset functionality
Implement password reset workflow:
- Add /forgot-password endpoint
- Send email with reset token
- Add token validation
- Allow password update with valid token
Closes #234"
Bug Fix:
git commit -m "fix: resolve race condition in payment processing
The payment confirmation was sometimes sent before database commit,
causing confusion when users refreshed and saw 'pending' status.
Now we ensure database commit completes before sending confirmation.
Fixes #567"
Refactoring:
git commit -m "refactor: extract user validation into separate module
No functional changes. Improves code organization and makes
validation logic reusable across auth and profile modules."
Documentation:
git commit -m "docs: add API authentication guide
Add comprehensive guide covering:
- JWT token generation
- Token refresh flow
- Error handling
- Code examples in multiple languages"
git diff - Show Changes
Purpose: Shows differences between various Git states: working directory, staging area, commits, branches, and more. Essential for understanding what changed.
Basic Usage:
# Show unstaged changes (working directory vs staging area)
git diff
# Show staged changes (staging area vs last commit)
git diff --staged
git diff --cached
# Show all changes (working directory vs last commit)
git diff HEAD
Understanding Diff Output:
$ git diff example.txt
diff --git a/example.txt b/example.txt
index 1234567..abcdefg 100644
--- a/example.txt
+++ b/example.txt
@@ -1,5 +1,6 @@
Line 1
Line 2
-Line 3
+Line 3 modified
+Line 4 added
Line 5
Output breakdown:
diff --git a/example.txt b/example.txt: Compared filesindex 1234567..abcdefg: Object hashes--- a/example.txt: Original file+++ b/example.txt: Modified file@@ -1,5 +1,6 @@: Hunk header (old range, new range)- Lines with
-: Removed - Lines with
+: Added - Lines with no prefix: Context (unchanged)
Comparing Commits:
# Compare two commits
git diff commit1 commit2
# Compare with specific commit
git diff abc123
# Compare with HEAD
git diff HEAD
git diff HEAD~1 # One commit before HEAD
git diff HEAD~5 # Five commits before HEAD
# Compare with branch
git diff main
git diff origin/main
Comparing Branches:
# Show differences between branches
git diff main..feature-branch
# Show what's in feature-branch but not in main
git diff main...feature-branch
# Compare current branch with main
git diff main
Difference between .. and ...:
git diff main..feature: Changes between the tips of both branchesgit diff main...feature: Changes in feature since it diverged from main (more useful)
Specific File Diff:
# Diff specific file
git diff filename.txt
# Diff specific file between commits
git diff commit1 commit2 filename.txt
# Diff specific file on different branch
git diff main:file.txt feature:file.txt
Diff Statistics:
# Show summary statistics
git diff --stat
# Show statistics and brief diff
git diff --shortstat
# Show file name and statistics
git diff --numstat
# Show summary of changes
git diff --summary
Example output:
$ git diff --stat
file1.txt | 10 +++++-----
file2.txt | 5 +++++
file3.txt | 15 ---------------
3 files changed, 15 insertions(+), 20 deletions(-)
Word-Level Diff:
# Show word-by-word differences (better for prose)
git diff --word-diff
# Color word diff
git diff --word-diff=color
# Show only changed words
git diff --word-diff=plain
Ignoring Whitespace:
# Ignore whitespace changes
git diff -w
git diff --ignore-all-space
# Ignore whitespace at line end
git diff --ignore-space-at-eol
# Ignore whitespace amount changes
git diff -b
git diff --ignore-space-change
Context Lines:
# Show 10 lines of context instead of default 3
git diff -U10
git diff --unified=10
# Show entire file
git diff --unified=999999
# Show only changes, no context
git diff -U0
Color and Highlighting:
# Force color output (useful for piping)
git diff --color
# Highlight moved code
git diff --color-moved
# Show whitespace errors
git diff --check
Diff Tools:
# Use external diff tool
git difftool
# Use specific tool
git difftool --tool=meld
# Compare directory
git difftool --dir-diff
Advanced Diff Options:
Function Context:
# Show function name in hunk headers
git diff -p
git diff --show-function
# For Python/Java/C, shows which function changed
Rename Detection:
# Detect renames (default)
git diff -M
git diff --find-renames
# Detect renames with threshold (50% similarity)
git diff -M50%
# Detect copies as well
git diff -C
git diff --find-copies
Binary Files:
# Show binary files changed
git diff --binary
# Don't show binary file differences
git diff --no-binary
Diff Algorithms:
# Use different diff algorithm
git diff --minimal # Spend extra time to find smaller diff
git diff --patience # Patience algorithm (better for complex changes)
git diff --histogram # Histogram algorithm (fast and good)
# Configure default algorithm
git config --global diff.algorithm histogram
Diff Filters:
# Show only added files
git diff --diff-filter=A
# Show only deleted files
git diff --diff-filter=D
# Show only modified files
git diff --diff-filter=M
# Show renamed files
git diff --diff-filter=R
# Combine filters (added or modified)
git diff --diff-filter=AM
Real-World Diff Scenarios:
Scenario 1: Pre-Commit Review
# Review what you're about to commit
git diff --staged
# If using multiple stages, review each step
git diff # Unstaged changes
git diff --staged # Staged changes
git diff HEAD # All changes since last commit
Scenario 2: Code Review
# Review all changes in feature branch
git diff main...feature-branch
# Review specific file changes
git diff main...feature-branch -- src/
# Get statistics for pull request
git diff main...feature-branch --stat
Scenario 3: Finding When Bug Was Introduced
# Compare current broken state with last known good commit
git diff known-good-commit current-broken-commit
# Focus on specific problematic file
git diff known-good-commit current-broken-commit -- src/buggy-file.js
Scenario 4: Reviewing Others’ Changes
# See what changed in latest pull
git fetch
git diff HEAD origin/main
# Review changes before merging
git diff main origin/feature-branch
Scenario 5: Checking Whitespace Issues
# Find whitespace errors before committing
git diff --check
# See exactly what whitespace changed
git diff --ws-error-highlight=all
Creating Patches:
# Create patch file
git diff > changes.patch
# Create patch for specific commits
git diff commit1 commit2 > feature.patch
# Apply patch
git apply changes.patch
# Apply patch and stage changes
git apply --index changes.patch
Diff Output Formats:
# Default unified format
git diff
# Context format (old-style)
git diff --context
# Raw format (for scripts)
git diff --raw
# Name only
git diff --name-only
# Name and status
git diff --name-status
Performance Optimization:
# For large repositories, limit diff
git diff --patience # More accurate but slower
git diff --minimal # Spend extra time finding minimal diff
git diff -U0 # No context (faster)
# Skip binary files
git diff --no-binary
git restore - Restore Working Tree Files
Purpose: Modern command (Git 2.23+) for discarding changes in working directory or unstaging files. Replaces old git checkout and git reset usage for these purposes.
Basic Usage:
# Discard changes in working directory (restore from staging area or HEAD)
git restore filename.txt
# Discard all changes
git restore .
# Unstage file (restore staging area from HEAD)
git restore --staged filename.txt
# Unstage all files
git restore --staged .
Understanding git restore:
git restore operates on two main areas:
- Working directory: Discard local modifications
- Staging area: Unstage changes
Restore from Different Sources:
# Restore from HEAD (last commit)
git restore --source=HEAD filename.txt
# Restore from specific commit
git restore --source=abc123 filename.txt
# Restore from different branch
git restore --source=main filename.txt
# Restore from previous commit
git restore --source=HEAD~1 filename.txt
Restore Both Working Directory and Staging Area:
# Restore file in both working directory and staging area
git restore --staged --worktree filename.txt
# Short version
git restore -SW filename.txt
Interactive Restore:
# Interactively choose changes to discard
git restore -p filename.txt
git restore --patch filename.txt
Similar to git add -p, this lets you selectively discard hunks of changes.
Restore Specific Paths:
# Restore entire directory
git restore src/
# Restore by pattern
git restore '*.txt'
# Restore from specific source and path
git restore --source=main -- src/
Advanced Options:
# Show what would be restored (dry run)
git restore --dry-run filename.txt
# Merge restored content (when conflicts)
git restore --merge filename.txt
# Keep unmerged entries (during conflict resolution)
git restore --ours filename.txt
git restore --theirs filename.txt
Overlay vs No-Overlay Mode:
# Overlay mode (default): only restore specified files
git restore --overlay filename.txt
# No-overlay: remove files in target that aren't in source
git restore --no-overlay --source=main src/
Real-World Scenarios:
Scenario 1: Undo Accidental Changes
# Oops, made unwanted changes to file
git status
# modified: important-file.txt
# Discard changes
git restore important-file.txt
# Verify
git status
# nothing to commit, working tree clean
Scenario 2: Unstage Accidentally Added File
# Added wrong file
git add secrets.txt
git status
# Changes to be committed: secrets.txt
# Unstage it
git restore --staged secrets.txt
git status
# Untracked files: secrets.txt
# Add to .gitignore
echo "secrets.txt" >> .gitignore
Scenario 3: Partially Discard Changes
# File has multiple changes, keep some, discard others
git restore -p complex-file.js
# Interactively choose:
# y - discard this hunk
# n - keep this hunk
# q - quit
# s - split into smaller hunks
Scenario 4: Restore File from Different Branch
# Need version of file from main branch
git restore --source=main -- config/settings.json
# This brings main's version to working directory without switching branches
Scenario 5: Completely Reset File
# Staged changes AND working directory changes
git restore -SW buggy-file.py
# File now matches HEAD exactly
Comparison with Old Commands:
Before Git 2.23, you’d use:
# OLD WAY (still works but discouraged)
git checkout -- filename.txt # Discard changes
git reset HEAD filename.txt # Unstage
# NEW WAY (clearer)
git restore filename.txt # Discard changes
git restore --staged filename.txt # Unstage
The new git restore is clearer in intent and safer.
Safety Warnings:
⚠️ Critical: git restore permanently discards changes in working directory. There’s no undo (unless you have editor backups or IDE history).
# DANGEROUS: Discards ALL local changes
git restore .
# SAFER: Check what you're about to discard
git diff # See unstaged changes
git status # See overall state
git stash # Consider stashing instead
# Then decide
git restore filename.txt # Discard specific file
git rm - Remove Files
Purpose: Remove files from working directory AND stage the removal for commit. Combines file system deletion with Git staging.
Basic Usage:
# Remove file from working directory and stage deletion
git rm filename.txt
# Remove multiple files
git rm file1.txt file2.txt file3.txt
# Remove all .log files
git rm *.log
# Remove directory recursively
git rm -r directory/
What git rm Does:
- Deletes file from working directory
- Stages the deletion (ready for commit)
- After commit, file is removed from repository history going forward
Remove from Git Only (Keep File Locally):
# Stop tracking file but keep in working directory
git rm --cached filename.txt
# Remove from Git, keep locally (common for .env files)
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Stop tracking .env file"
This is crucial when you accidentally committed sensitive files.
Force Removal:
# Force remove (even if file has uncommitted changes)
git rm -f filename.txt
git rm --force filename.txt
⚠️ Warning: This discards uncommitted changes. Use cautiously.
Dry Run:
# See what would be removed
git rm --dry-run -r d