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).
Snapshots, not differences: Unlike systems that store file changes, Git takes snapshots of your entire project at each commit.
Branch-centric workflow: Branches are cheap and merging is encouraged.
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 branches -
refs/remotes/*: Remote-tracking branches -
refs/tags/*: Tags -
HEAD: 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:
-
??- Untracked -
A- Added (staged) -
M- Modified -
D- Deleted -
R- Renamed -
C- Copied -
U- 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 hunk -
n- Don't stage this hunk -
q- Quit; don't stage this or remaining hunks -
a- Stage this and all remaining hunks in file -
d- Don't stage this or any remaining hunks in file -
s- Split the hunk into smaller hunks -
e- 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 feature -
fix: Bug fix -
docs: Documentation changes -
style: Code style changes (formatting, semicolons, etc.) -
refactor: Code refactoring -
perf: Performance improvements -
test: Adding or updating tests -
chore: Maintenance tasks -
ci: CI/CD changes -
build: 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 files -
index 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 branches -
git 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 directory/
git rm -n *.txt
Remove Files Matching Pattern:
# Remove all .txt files in directory
git rm directory/*.txt
# Remove all .log files recursively
git rm -r '*.log'
Note: Quote wildcards to prevent shell expansion.
Handling Modified Files:
If file has uncommitted changes:
$ git rm modified-file.txt
error: the following file has changes staged in the index:
modified-file.txt
# Options:
# 1. Commit changes first
git commit -am "Save changes before removing"
git rm modified-file.txt
# 2. Force remove (loses changes)
git rm -f modified-file.txt
# 3. Unstage and remove
git restore --staged modified-file.txt
git rm modified-file.txt
Real-World Scenarios:
Scenario 1: Remove Accidentally Committed Secrets
# OH NO! Committed API keys
git rm --cached config/secrets.yml
echo "config/secrets.yml" >> .gitignore
git commit -m "Remove secrets file from tracking"
# Note: This doesn't remove from history!
# File still exists in previous commits
# For complete removal, need git filter-branch or BFG Repo-Cleaner
Scenario 2: Cleanup Obsolete Files
# Remove entire deprecated module
git rm -r src/old-module/
git commit -m "Remove deprecated authentication module"
Scenario 3: Convert to Ignored Files
# Stop tracking node_modules but keep locally
git rm -r --cached node_modules/
echo "node_modules/" >> .gitignore
git commit -m "Stop tracking node_modules"
Scenario 4: Mass File Removal
# Remove all temporary files
git rm '*.tmp' '*.cache' '*.swp'
git commit -m "Remove temporary files"
Comparison with Regular File Deletion:
Method 1: Regular deletion (BAD for Git)
rm filename.txt # Delete file
git add filename.txt # Stage deletion
git commit -m "Remove file"
Method 2: Git rm (GOOD)
git rm filename.txt # Delete AND stage in one step
git commit -m "Remove file"
git rm is cleaner and prevents forgotten staging.
Method 3: Delete then commit -a
rm filename.txt
git commit -am "Remove file" # Works but less explicit
Recovery from git rm:
If you haven't committed yet:
# Undo git rm before commit
git restore --staged filename.txt # Unstage
git restore filename.txt # Restore file
# Or in one step (old command)
git checkout HEAD filename.txt
If you've committed:
# Restore file from previous commit
git restore --source=HEAD~1 filename.txt
# Or checkout from history
git checkout HEAD~1 -- filename.txt
git mv - Move or Rename Files
Purpose: Move or rename files while maintaining Git history tracking. Git automatically detects renames, but git mv makes it explicit and efficient.
Basic Usage:
# Rename file
git mv oldname.txt newname.txt
# Move file to directory
git mv file.txt directory/
# Move and rename
git mv old-name.txt new-directory/new-name.txt
# Move directory
git mv old-directory/ new-directory/
What git mv Does:
- Renames/moves file in working directory
- Stages the change (ready for commit)
- Git tracks it as a rename (not delete + add)
Internally, git mv is equivalent to:
mv oldname.txt newname.txt
git rm oldname.txt
git add newname.txt
But git mv is cleaner and more explicit.
Git's Rename Detection:
Git doesn't actually store renames—it detects them by content similarity:
# These are equivalent:
git mv file.txt renamed.txt
# Same as:
mv file.txt renamed.txt
git add renamed.txt
git rm file.txt
Git sees the content is similar and marks it as a rename.
Checking Rename Detection:
# Status shows rename
$ git mv readme.txt README.md
$ git status
Changes to be committed:
renamed: readme.txt -> README.md
# In log
git log --follow README.md # Shows history through renames
Force Move/Rename:
# Overwrite existing file (dangerous!)
git mv -f source.txt destination.txt
git mv --force source.txt destination.txt
Dry Run:
# See what would happen
git mv -n oldname.txt newname.txt
git mv --dry-run oldname.txt newname.txt
Handling Case-Sensitive Renames:
On case-insensitive filesystems (Windows, macOS), renaming file.txt to File.txt is tricky:
# This might not work on case-insensitive systems:
git mv file.txt File.txt
# Solution: use intermediate name
git mv file.txt temp.txt
git mv temp.txt File.txt
git commit -m "Fix file name casing"
Real-World Scenarios:
Scenario 1: Reorganize Project Structure
# Move related files into subdirectory
mkdir utils
git mv helper1.py utils/
git mv helper2.py utils/
git mv validator.py utils/
git commit -m "Organize utility functions into utils/ directory"
Scenario 2: Rename for Conventions
# Rename to follow naming convention
git mv myComponent.js MyComponent.js # React component
git mv database_helper.py db_helper.py # Python convention
git commit -m "Rename files to follow project conventions"
Scenario 3: Mass Renaming
# Rename multiple files (using shell script)
for file in *.html; do
git mv "$file" "${file%.html}.htm"
done
git commit -m "Change file extension from .html to .htm"
Scenario 4: Rename with History Preservation
# Rename file
git mv old-implementation.js new-implementation.js
git commit -m "Rename old-implementation to new-implementation"
# View history following renames
git log --follow new-implementation.js
# Shows full history including when it was "old-implementation.js"
Rename Detection Threshold:
Git detects renames based on content similarity:
# Configure similarity threshold (default 50%)
git config diff.renameLimit 1000
git config diff.renames copies # Also detect copies
# View renames in log with similarity percentage
git log --stat -M # Show renames
git log --stat -C # Show renames and copies
Advanced Rename Options:
# Detect renames even if only 30% similar
git log --find-renames=30%
# Show exact rename percentage
git log --stat -M --summary
When Manual Rename Detection Fails:
If Git doesn't detect a rename automatically:
# After manual move
mv oldname.txt newname.txt
# Git shows as delete + add
$ git status
deleted: oldname.txt
untracked: newname.txt
# Fix it
git rm oldname.txt
git add newname.txt
# Commit with both staged
git commit -m "Rename oldname to newname"
# Git will detect rename if content is similar enough
8. Branching and Branch Management {#branching}
Branches are one of Git's most powerful features, allowing parallel lines of development without affecting the main codebase. Understanding branching is essential for effective Git use.
What Is a Branch?
A branch is simply a movable pointer to a commit. Git's default branch is typically main or master. When you create a branch, you're creating a new pointer—nothing more. This makes branching incredibly lightweight and fast.
Branch Representation:
main → commit C
feature → commit E
A ← B ← C (main)
↖
D ← E (feature)
git branch - List, Create, and Delete Branches
Purpose: Manage branches—list existing branches, create new ones, delete old ones, rename branches.
Basic Usage:
# List local branches
git branch
# List all branches (including remote)
git branch -a
git branch --all
# List remote branches only
git branch -r
git branch --remotes
# Create new branch (doesn't switch to it)
git branch feature-x
# Create branch from specific commit
git branch feature-x abc123
# Create branch from another branch
git branch feature-x develop
Branch Listing with Details:
# Show last commit on each branch
git branch -v
git branch --verbose
# Show tracking information
git branch -vv
# Show merged branches
git branch --merged
# Show unmerged branches
git branch --no-merged
Example output:
$ git branch -vv
* main abc123 [origin/main] Latest commit message
develop def456 [origin/develop: ahead 2] Work in progress
feature ghi789 Feature implementation
-
*indicates current branch -
[origin/main]shows tracking branch -
ahead 2means 2 commits not pushed yet
Deleting Branches:
# Delete branch (safe: prevents deleting unmerged branches)
git branch -d feature-x
git branch --delete feature-x
# Force delete (even if unmerged)
git branch -D feature-x
git branch --delete --force feature-x
# Delete remote branch
git push origin --delete feature-x
git push origin :feature-x # Old syntax
Renaming Branches:
# Rename current branch
git branch -m new-name
git branch --move new-name
# Rename different branch
git branch -m old-name new-name
# Force rename (overwrite existing)
git branch -M old-name new-name
Renaming and Updating Remote:
# Rename local branch
git branch -m old-name new-name
# Delete old remote branch
git push origin --delete old-name
# Push new branch and set upstream
git push origin -u new-name
Creating Branch and Switching:
While git branch creates branches, it doesn't switch to them:
# Create and switch (two commands)
git branch feature-x
git checkout feature-x
# Better: create and switch in one command
git checkout -b feature-x
# Modern way (Git 2.23+)
git switch -c feature-x
Advanced Branch Options:
Copy Branches:
# Copy branch to new name
git branch -c existing-branch new-branch
git branch --copy existing-branch new-branch
# Force copy (overwrite if exists)
git branch -C existing-branch new-branch
Set Upstream Tracking:
# Set upstream for existing branch
git branch -u origin/feature-x
git branch --set-upstream-to=origin/feature-x
# Remove upstream tracking
git branch --unset-upstream
Branch with Specific Start Point:
# Create branch from tag
git branch hotfix v1.2.3
# Create branch from remote branch
git branch local-feature origin/remote-feature
# Create branch from specific commit
git branch bugfix abc123def
List Branches by Pattern:
# List branches matching pattern
git branch --list 'feature/*'
git branch --list '*fix*'
# Case-insensitive matching
git branch -i --list 'FEATURE/*'
Branch Sorting:
# Sort by commit date
git branch --sort=-committerdate
# Sort by author date
git branch --sort=-authordate
# Sort alphabetically (default)
git branch --sort=refname
Branch Coloring:
# Always use colors
git branch --color=always
# Never use colors
git branch --color=never
# Auto (default)
git branch --color=auto
Real-World Branch Strategies:
Feature Branch Workflow:
# Create feature branch from main
git checkout main
git pull
git checkout -b feature/user-authentication
# Work on feature
git add .
git commit -m "Implement login endpoint"
# Push to remote
git push -u origin feature/user-authentication
# After review and merge, delete
git checkout main
git branch -d feature/user-authentication
git push origin --delete feature/user-authentication
Release Branch Workflow:
# Create release branch from develop
git checkout develop
git checkout -b release/v2.0
# Finalize release (bug fixes only)
git commit -m "Bump version to 2.0"
# Merge to main
git checkout main
git merge release/v2.0
git tag v2.0
# Merge back to develop
git checkout develop
git merge release/v2.0
# Delete release branch
git branch -d release/v2.0
Hotfix Branch Workflow:
# Critical bug in production!
git checkout main
git checkout -b hotfix/critical-security-fix
# Fix and test
git commit -m "Fix SQL injection vulnerability"
# Merge to main
git checkout main
git merge hotfix/critical-security-fix
git tag v1.2.1
# Merge to develop
git checkout develop
git merge hotfix/critical-security-fix
# Delete hotfix branch
git branch -d hotfix/critical-security-fix
git checkout - Switch Branches and Restore Files
Purpose: Historically did two things: switch branches and restore files. In Git 2.23+, split into git switch (branches) and git restore (files) for clarity, but git checkout still works.
Branch Switching:
# Switch to existing branch
git checkout existing-branch
# Create and switch to new branch
git checkout -b new-branch
# Create branch from specific commit and switch
git checkout -b hotfix abc123
# Switch to previous branch
git checkout -
# Switch to remote branch (creates tracking branch)
git checkout remote-branch
What Happens During Checkout:
- Updates HEAD to point to the branch
- Updates staging area to match branch's commit
- Updates working directory to match branch's commit
- Fails if you have uncommitted changes that conflict
Handling Uncommitted Changes:
# If you have uncommitted changes
$ git checkout other-branch
error: Your local changes would be overwritten by checkout
# Options:
# 1. Commit changes
git commit -am "WIP"
# 2. Stash changes
git stash
git checkout other-branch
git stash pop
# 3. Force checkout (DANGEROUS - loses changes)
git checkout -f other-branch
Detached HEAD State:
Checking out a commit (not a branch) creates "detached HEAD":
# Checkout specific commit
git checkout abc123
# You're now in detached HEAD state
$ git status
HEAD detached at abc123
# Any commits made here are orphaned unless you create a branch
git checkout -b save-my-work
Use Cases for Detached HEAD:
- Inspecting historical code
- Testing specific versions
- Building release artifacts
- Bisecting to find bugs
Checkout with Path:
# Restore file from HEAD
git checkout -- filename.txt
# Restore file from specific commit
git checkout abc123 -- filename.txt
# Restore file from different branch
git checkout main -- config.yml
# Restore entire directory
git checkout main -- src/
Advanced Checkout Options:
Merge During Checkout:
# Attempt three-way merge when switching branches
git checkout -m branch-name
# If conflicts, resolves what it can and marks conflicts
Orphan Branches:
# Create branch with no history (useful for gh-pages)
git checkout --orphan gh-pages
git rm -rf .
# Add new content
git add .
git commit -m "Initial GitHub Pages commit"
Track Remote Branches:
# Create tracking branch
git checkout -t origin/feature-x
git checkout --track origin/feature-x
# Create with different name
git checkout -b local-name origin/remote-name
Checkout and Reset:
# Checkout and discard all local changes
git checkout -f branch-name
git checkout --force branch-name
git switch - Switch Branches (Modern)
Purpose: Modern command (Git 2.23+) specifically for switching branches, clearer than git checkout.
Basic Usage:
# Switch to existing branch
git switch branch-name
# Create and switch to new branch
git switch -c new-branch
git switch --create new-branch
# Switch to previous branch
git switch -
# Force switch (discard local changes)
git switch -f branch-name
git switch --force branch-name
# Discard local changes with confirmation
git switch --discard-changes branch-name
Create Branch from Commit:
# Create branch from specific commit and switch
git switch -c bugfix abc123
# Create from remote branch
git switch -c local-feature origin/remote-feature
Guess and Create Tracking Branch:
# If "feature" doesn't exist locally but "origin/feature" does
git switch feature
# Automatically creates local tracking branch
Detached HEAD:
# Switch to specific commit (detached HEAD)
git switch --detach abc123
# Create branch from detached HEAD
git switch -c save-detached-work
Advantages of git switch over git checkout:
- Clarity: Only switches branches, doesn't restore files
- Safety: Won't accidentally overwrite files
- Simplicity: Fewer options, easier to understand
- Modern: Part of Git's effort to improve user experience
Comparison:
# OLD WAY (still works)
git checkout branch-name
git checkout -b new-branch
# NEW WAY (clearer)
git switch branch-name
git switch -c new-branch
Branch Management Best Practices
Branch Naming Conventions:
# Feature branches
feature/user-authentication
feature/payment-integration
feat/shopping-cart
# Bug fix branches
bugfix/login-error
fix/memory-leak
hotfix/security-vulnerability
# Release branches
release/v1.2.0
release/2024-q1
# Experimental branches
experiment/new-architecture
poc/graphql-migration
Benefits of conventions:
- Easy to identify branch purpose
- Helps with filtering and organization
- Supports automated workflows
- Improves team communication
Branch Lifecycle:
# 1. Create from up-to-date main
git checkout main
git pull
git checkout -b feature/new-feature
# 2. Regular commits
git commit -m "Progress on feature"
# 3. Keep updated with main
git checkout main
git pull
git checkout feature/new-feature
git merge main # or rebase
# 4. Push to remote
git push -u origin feature/new-feature
# 5. Create pull request (on GitHub/GitLab/etc.)
# 6. After merge, clean up
git checkout main
git pull
git branch -d feature/new-feature
git push origin --delete feature/new-feature
Cleaning Up Old Branches:
# List merged branches
git branch --merged main
# Delete all merged branches (except main)
git branch --merged main | grep -v "\* main" | xargs -n 1 git branch -d
# Delete remote-tracking branches that no longer exist
git fetch --prune
git fetch -p
# Remove stale remote-tracking references
git remote prune origin
Branch Protection:
On remote repositories (GitHub, GitLab), protect important branches:
- Require pull request reviews
- Require status checks to pass
- Require signed commits
- Restrict who can push
- Prevent force pushes
- Prevent deletion
Long-Lived vs Short-Lived Branches:
Long-lived branches:
-
main/master: Production code -
develop: Integration branch -
staging: Pre-production testing
Short-lived branches:
- Feature branches: Deleted after merge
- Bug fix branches: Deleted after merge
- Hotfix branches: Deleted after merge
Recommended: Keep short-lived branches actually short (days to weeks, not months).
9. Merging Strategies and Conflict Resolution {#merging}
Merging combines independent lines of development. Understanding merge strategies is crucial for maintaining clean history and resolving conflicts.
git merge - Join Development Histories
Purpose: Integrate changes from one branch into another, creating a merge commit if necessary.
Basic Usage:
# Merge branch into current branch
git merge feature-branch
# Merge with commit message
git merge feature-branch -m "Merge feature X implementation"
# Merge without committing (stage changes)
git merge --no-commit feature-branch
# Abort merge
git merge --abort
How Merging Works:
Git finds the common ancestor (merge base) of the two branches and performs a three-way merge:
C---D (feature)
/
A---B---E---F (main)
After merge:
C---D
/ \
A---B---E---F---G (main, merge commit G)
Types of Merges:
1. Fast-Forward Merge:
When the target branch hasn't diverged, Git simply moves the pointer forward:
# Before:
A---B---C (main)
\
D---E (feature)
# After fast-forward merge:
A---B---C---D---E (main, feature)
# Fast-forward merge happens automatically
git checkout main
git merge feature # Fast-forward
# Force merge commit even for fast-forward
git merge --no-ff feature
2. Three-Way Merge:
When branches have diverged, Git creates a merge commit:
# Before:
C---D (feature)
/
A---B---E (main)
# After three-way merge:
C---D
/ \
A---B---E---F (main, merge commit)
3. Squash Merge:
Combines all commits from feature branch into one commit on target:
git merge --squash feature-branch
git commit -m "Implement feature X (squashed)"
This creates clean history but loses individual commit information.
Merge Strategies:
Git supports different merge strategies for complex scenarios:
Recursive (Default):
# Explicit recursive strategy
git merge -s recursive feature-branch
git merge --strategy=recursive feature-branch
# With strategy options
git merge -s recursive -X ours feature-branch # Prefer our changes
git merge -s recursive -X theirs feature-branch # Prefer their changes
Ours (Take Our Version):
# Use our version entirely, discard theirs
git merge -s ours obsolete-feature
Use case: Record that a branch was merged, but don't actually include its changes.
Octopus (Multiple Branches):
# Merge multiple branches at once
git merge feature1 feature2 feature3
Used for merging multiple branches simultaneously (rarely needed manually).
Subtree:
# Merge as subdirectory
git merge -s subtree sub-project-branch
Useful when one project is part of another.
Merge Strategy Options:
# Ignore whitespace changes
git merge -Xignore-space-change feature
# Ignore all whitespace
git merge -Xignore-all-space feature
# Be more patient (better conflict resolution)
git merge -Xpatience feature
# Use diff3 conflict style (shows common ancestor)
git merge -Xdiff3 feature
# Rename threshold (for rename detection)
git merge -Xrename-threshold=50% feature
Merge Verification:
# Verify merge would succeed before doing it
git merge --no-commit --no-ff feature
git diff --staged # Review changes
git merge --abort # Back out if not happy
# Or just check if branches can merge
git merge-base main feature # Shows common ancestor
Fast-Forward Control:
# Only allow fast-forward merges
git merge --ff-only feature
# Fails if fast-forward not possible
# Always create merge commit (no fast-forward)
git merge --no-ff feature
# Allow fast-forward (default)
git merge --ff feature
When to use --no-ff:
- Preserve feature branch history
- Make it clear when feature was integrated
- Easier to revert entire feature later
Merge with Specific File Handling:
# Use theirs version for specific file
git checkout --theirs conflicted-file.txt
git add conflicted-file.txt
# Use ours version
git checkout --ours conflicted-file.txt
git add conflicted-file.txt
Conflict Resolution
What Is a Merge Conflict?
Occurs when Git can't automatically reconcile differences between commits. Requires manual intervention.
Conflict Markers:
def calculate_total(items):
<<<<<<< HEAD (Current Change)
total = sum(item.price for item in items)
return total * 1.1 # Add 10% tax
=======
total = sum(item.cost for item in items)
return total * 1.15 # Add 15% tax
>>>>>>> feature-branch (Incoming Change)
Marker Explanation:
-
<<<<<<< HEAD: Start of your current branch's version -
=======: Separator -
>>>>>>> feature-branch: End of incoming branch's version
Step-by-Step Conflict Resolution:
1. Identify conflicts:
$ git merge feature-branch
Auto-merging src/calculator.py
CONFLICT (content): Merge conflict in src/calculator.py
Automatic merge failed; fix conflicts and then commit the result.
# Check status
$ git status
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/calculator.py
2. Open conflicted file:
# Use your editor
vim src/calculator.py
code src/calculator.py
nano src/calculator.py
3. Resolve conflict:
Choose which version to keep, or combine both:
# Option 1: Keep ours
def calculate_total(items):
total = sum(item.price for item in items)
return total * 1.1
# Option 2: Keep theirs
def calculate_total(items):
total = sum(item.cost for item in items)
return total * 1.15
# Option 3: Combine (often best)
def calculate_total(items):
# Use 'price' attribute (from HEAD)
total = sum(item.price for item in items)
# Use 15% tax rate (from feature-branch)
return total * 1.15
Remove conflict markers completely.
4. Mark as resolved:
# Stage resolved file
git add src/calculator.py
# Check status
git status
# All conflicts fixed: run "git commit"
5. Complete merge:
# Commit merge
git commit
# Opens editor with merge commit message
# Or provide message directly
git commit -m "Merge feature-branch, resolve calculation conflicts"
Tools for Conflict Resolution:
Using Diff Tools:
# Launch configured merge tool
git mergetool
# Use specific tool
git mergetool --tool=meld
git mergetool --tool=kdiff3
git mergetool --tool=vimdiff
Popular merge tools:
- Meld: Visual diff and merge (Linux, macOS, Windows)
- KDiff3: Three-way merge tool
- P4Merge: Perforce's merge tool (free)
- Beyond Compare: Commercial but powerful
- VS Code: Built-in merge conflict UI
Configure Merge Tool:
# Set default merge tool
git config --global merge.tool meld
# Don't save backup files (.orig)
git config --global mergetool.keepBackup false
# Don't prompt before launching
git config --global mergetool.prompt false
Using Built-in Merge Conflict Resolution:
# Accept all "ours" changes
git checkout --ours .
# Accept all "theirs" changes
git checkout --theirs .
# For specific files
git checkout --ours path/to/file
git checkout --theirs path/to/file
⚠️ Warning: These commands automatically resolve ALL conflicts in favor of one side. Use cautiously.
Conflict Resolution Strategies:
Strategy 1: Diff3 Style (Recommended)
Shows common ancestor for better context:
# Configure diff3 style
git config --global merge.conflictstyle diff3
# Conflict now shows:
<<<<<<< HEAD
current version
||||||| merged common ancestor
original version
=======
incoming version
>>>>>>> branch
The middle section shows the original (common ancestor), helping understand what each side changed.
Strategy 2: Always Use Specific Strategy:
# Configure default merge strategy
git config --global pull.rebase false # merge (default)
git config --global pull.rebase true # rebase
# Always use ours for specific file types
git config --global merge.ours.driver true
# Then in .gitattributes:
# *.config merge=ours
Advanced Conflict Handling:
Restart Merge:
# Made a mistake? Abort and restart
git merge --abort
# Or reset to before merge
git reset --hard HEAD
Continue After Resolving:
# If merge was stopped (e.g., by pre-commit hook)
git merge --continue
Skip Specific Commits:
# During rebase, skip commit causing conflicts
git rebase --skip
# Or cherry-pick
git cherry-pick --skip
View Conflicts:
# List conflicted files
git diff --name-only --diff-filter=U
# Show conflicts for specific file
git diff file.txt
# Show changes from both sides
git diff HEAD...MERGE_HEAD
Real-World Conflict Scenarios:
Scenario 1: Simple Conflict Resolution
# Merge causes conflict
$ git merge feature
CONFLICT (content): Merge conflict in app.js
# Edit app.js, resolve conflicts, remove markers
# Stage resolved file
git add app.js
# Complete merge
git commit
Scenario 2: Multiple File Conflicts
# Multiple conflicts
$ git merge feature
CONFLICT in file1.js
CONFLICT in file2.js
CONFLICT in file3.js
# Resolve each file
vim file1.js # resolve
git add file1.js
vim file2.js # resolve
git add file2.js
vim file3.js # resolve
git add file3.js
# Commit when all resolved
git commit
Scenario 3: Using Merge Tool
# Launch merge tool for all conflicts
git mergetool
# Resolve in GUI
# Tool stages files automatically
# Clean up backup files
rm *.orig
# Commit
git commit
Scenario 4: Accept One Side Entirely
# Feature branch is definitely correct
git checkout --theirs .
git add .
git commit -m "Merge feature, accepting all their changes"
# Or main is definitely correct
git checkout --ours .
git add .
git commit -m "Merge feature, keeping our changes"
Scenario 5: Complex Three-Way Conflict
# Use diff3 to see ancestor
git config merge.conflictstyle diff3
git merge feature
# In file:
<<<<<<< HEAD
new implementation
||||||| merged common ancestor
old implementation
=======
alternative new implementation
>>>>>>> feature
# Now you understand:
# - Old had X
# - We changed X to Y
# - They changed X to Z
# - Need to combine Y and Z approaches
Preventing Conflicts:
1. Frequent Integration:
# Regularly merge main into feature branch
git checkout feature
git merge main
2. Small, Focused Changes:
- Keep feature branches small
- Merge frequently
- Avoid long-lived branches
3. Communication:
- Coordinate with team on overlapping work
- Use feature flags for incomplete features
- Split work to minimize conflicts
4. Code Reviews:
- Review before merging
- Discuss potential conflicts
- Plan integration strategy
10. Rebasing: Rewriting History {#rebasing}
Rebasing is a powerful but potentially dangerous operation that rewrites commit history. It's an alternative to merging that creates a linear history.
git rebase - Reapply Commits on Top of Another Base
Purpose: Move or combine a sequence of commits to a new base commit, creating a linear history.
Basic Usage:
# Rebase current branch onto main
git rebase main
# Rebase feature branch onto main (while on feature)
git checkout feature
git rebase main
# Rebase specific branch onto another
git rebase main feature-branch
# Interactive rebase
git rebase -i HEAD~5
git rebase --interactive HEAD~5
How Rebase Works:
Before rebase:
C---D (feature)
/
A---B---E---F (main)
After git checkout feature; git rebase main:
C'---D' (feature)
/
A---B---E---F (main)
Git:
- Finds common ancestor (B)
- Saves commits C and D as patches
- Resets feature to main
- Applies patches C and D on top
Note: C' and D' are new commits with different SHAs (history rewritten).
Rebase vs Merge:
Merge:
- Preserves complete history
- Shows when branches were integrated
- Creates merge commits
- Safe for public branches
Rebase:
- Creates linear history
- Cleaner, easier to follow
- No merge commits
- Dangerous for public branches
The Golden Rule of Rebasing:
🚨 NEVER rebase commits that have been pushed to a shared/public branch 🚨
Why? Rebasing rewrites history. If others have based work on your original commits, rebasing causes massive conflicts and confusion.
Safe rebasing:
# Safe: Rebasing local, unpushed commits
git checkout feature
git rebase main
Dangerous rebasing:
# DANGEROUS: Rebasing shared branch
git checkout main # Public branch!
git rebase some-branch # DON'T DO THIS
Interactive Rebase
Interactive rebase (-i) is incredibly powerful for cleaning up commit history before sharing.
Basic Interactive Rebase:
# Rebase last 5 commits interactively
git rebase -i HEAD~5
# Rebase all commits since main
git rebase -i main
# Rebase from specific commit
git rebase -i abc123
Interactive Rebase Editor:
pick abc123 Add user authentication
pick def456 Fix typo in authentication
pick ghi789 Add password validation
pick jkl012 Update documentation
pick mno345 Fix bug in validation
# Rebase abc123..mno345 onto previous commit
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, meld into previous commit
# f, fixup <commit> = like squash, discard commit message
# x, exec <command> = run command
# b, break = stop here (continue with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD
# t, reset <label> = reset HEAD to label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
Interactive Rebase Commands:
1. pick (p): Use commit as-is
pick abc123 Add feature
2. reword (r): Change commit message
reword def456 Fix typo # Will prompt for new message
3. edit (e): Stop at commit to make changes
edit ghi789 Add validation
# Git stops here, you can:
# - Amend commit: git commit --amend
# - Split commit: git reset HEAD^, then make multiple commits
# - Continue: git rebase --continue
4. squash (s): Combine with previous commit, edit message
pick abc123 Add feature
squash def456 Add tests for feature
# Git combines both, lets you edit combined message
5. fixup (f): Combine with previous, discard this message
pick abc123 Add feature
fixup def456 Fix typo
# Git combines, keeps only first message
6. drop (d): Remove commit entirely
drop ghi789 Experimental code
7. exec (x): Run shell command
pick abc123 Add feature
exec npm test
pick def456 Add another feature
exec npm test
# Runs tests after each commit
Real-World Interactive Rebase Examples:
Example 1: Clean Up Messy History
# You have:
abc123 WIP: start feature
def456 WIP: continue
ghi789 WIP: almost done
jkl012 Feature complete
mno345 Fix typo
pqr678 Actually fix typo
# Clean it up:
git rebase -i HEAD~6
# Change to:
pick abc123 WIP: start feature
fixup def456 WIP: continue
fixup ghi789 WIP: almost done
reword jkl012 Feature complete
fixup mno345 Fix typo
fixup pqr678 Actually fix typo
# Result: Two clean commits
Example 2: Reorder Commits
# You have:
abc123 Add feature A
def456 Add feature B
ghi789 Fix bug in feature A
jkl012 Add tests for feature B
# Reorder logically:
git rebase -i HEAD~4
# Change to:
pick abc123 Add feature A
pick ghi789 Fix bug in feature A
pick def456 Add feature B
pick jkl012 Add tests for feature B
Example 3: Split a Commit
# One commit does too much
git rebase -i HEAD~3
# Mark commit with 'edit':
edit abc123 Add many features
pick def456 Other commit
# When Git stops:
git reset HEAD^
# Now make separate commits:
git add feature1.js
git commit -m "Add feature 1"
git add feature2.js
git commit -m "Add feature 2"
# Continue rebase:
git rebase --continue
Example 4: Remove Sensitive Data
# Oops, committed secrets
git rebase -i HEAD~10
# Find commit with secrets:
drop ghi789 Add configuration (contains secrets!)
# Or edit to remove just the sensitive file:
edit ghi789 Add configuration
git rm --cached secrets.yml
git commit --amend
git rebase --continue
⚠️ Note: This only removes from future history. Old commits still contain secrets. For complete removal, use git filter-branch or BFG Repo-Cleaner.
Handling Rebase Conflicts
Conflicts during rebase are common:
$ git rebase main
...
CONFLICT (content): Merge conflict in file.txt
error: could not apply abc123... Commit message
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
Resolution Steps:
# 1. Fix conflicts in files
vim file.txt # Resolve conflicts
# 2. Stage resolved files
git add file.txt
# 3. Continue rebase
git rebase --continue
# If you want to skip this commit:
git rebase --skip
# If you want to abort entirely:
git rebase --abort
Common Rebase Scenarios:
Scenario 1: Update Feature Branch
# Keep feature branch up-to-date with main
git checkout feature
git rebase main
# If conflicts, resolve and continue
git add resolved-file.txt
git rebase --continue
# Force push (since history changed)
git push --force-with-lease origin feature
Scenario 2: Squash All Commits Before Merging
# Squash all feature commits into one
git checkout feature
git rebase -i main
# Mark all except first as 'squash' or 'fixup'
pick abc123 First commit
squash def456 Second commit
squash ghi789 Third commit
# Edit combined message, then:
git push --force-with-lease
Scenario 3: Clean History Before Pull Request
# Fix commit messages, combine WIP commits
git rebase -i HEAD~10
# Reword unclear messages
# Fixup WIP commits
# Reorder for logical flow
# Force push
git push --force-with-lease
Advanced Rebase Options
Preserve Merges:
# Preserve merge commits during rebase
git rebase --preserve-merges main
git rebase -p main
# Note: --preserve-merges is deprecated, use --rebase-merges
git rebase --rebase-merges main
Rebase Onto:
# Rebase onto different branch than fork point
git rebase --onto new-base old-base branch
# Example: Move feature from main to develop
# Before:
# main: A---B---C
# feature: D---E
# develop: A---B---F---G
#
# After git rebase --onto develop main feature:
# main: A---B---C
# develop: A---B---F---G
# feature: D'---E'
Autosquash:
# Create fixup commits
git commit --fixup=abc123
git commit --squash=def456
# Rebase with autosquash
git rebase -i --autosquash main
# Configure as default
git config --global rebase.autosquash true
This automatically organizes fixup/squash commits in interactive rebase.
Autostash:
# Automatically stash and reapply during rebase
git rebase --autostash main
# Configure as default
git config --global rebase.autostash true
Force Push Safely:
# After rebase, force push required (history changed)
git push --force-with-lease
# This fails if someone else pushed to branch
# Safer than --force which overwrites unconditionally
When to Use Rebase
Good Use Cases:
- Update feature branch with latest main:
git checkout feature
git rebase main
- Clean up local commits before pushing:
git rebase -i HEAD~5
- Maintain linear history:
# Instead of merging main into feature repeatedly
git rebase main # Creates linear history
- Fix recent commits:
# Fix last commit
git commit --amend
# Fix earlier commits
git rebase -i HEAD~3
Bad Use Cases:
- Public/shared branches:
# NEVER do this:
git checkout main
git rebase feature # Rewrites public history!
- Commits already pushed and used by others:
# DANGEROUS:
git rebase main # If others branched from your commits
git push --force
- When merge commits provide valuable context:
# Sometimes merge commits are informative
git merge feature # Shows when feature was integrated
Rebase Best Practices
1. Only Rebase Local Commits:
# Safe: Rebase commits not yet pushed
git rebase main
# Check if commits are pushed:
git log origin/feature..feature # Shows unpushed commits
2. Use --force-with-lease Instead of --force:
# Safer force push
git push --force-with-lease origin feature
# This fails if remote has commits you don't have locally
# Prevents accidentally overwriting others' work
3. Communicate with Team:
If you must rebase shared branches:
- Warn team members
- Coordinate timing
- Provide instructions for updating their local copies
4. Test After Rebase:
# After rebase, verify everything works
git rebase main
npm test # or your test command
git push --force-with-lease
5. Keep Rebase Sessions Short:
# Don't rebase too many commits at once
git rebase -i HEAD~5 # Manageable
# Instead of:
git rebase -i HEAD~50 # Too many, error-prone
6. Use Descriptive Messages During Interactive Rebase:
When rewording commits, make messages clear:
# Bad:
"Fix stuff"
# Good:
"Fix null pointer exception in payment processing"
Recovery from Rebase Disasters
If Rebase Goes Wrong:
1. Abort Ongoing Rebase:
# During rebase, if things are messy
git rebase --abort
# Returns to state before rebase started
2. Use Reflog to Recover:
The reflog tracks all HEAD movements, even rebase operations:
# View reflog
git reflog
# Output:
abc123 HEAD@{0}: rebase: Add feature
def456 HEAD@{1}: rebase: Fix bug
ghi789 HEAD@{2}: rebase: Start
jkl012 HEAD@{3}: commit: Original state (before rebase)
# Reset to before rebase
git reset --hard HEAD@{3}
# or
git reset --hard jkl012
3. Recover Specific Commits:
# Find lost commit in reflog
git reflog | grep "lost commit message"
# Cherry-pick it back
git cherry-pick abc123
4. If Force Push Caused Problems:
# If you force pushed and broke things for others:
# Option 1: Revert the force push (if possible)
git reset --hard origin/feature@{1} # Previous state
git push --force-with-lease
# Option 2: Create revert commits
git revert abc123..def456
git push
11. Remote Repositories and Collaboration {#remotes}
Remote repositories enable collaboration. Understanding remote operations is essential for team workflows.
git remote - Manage Remote Repositories
Purpose: Add, view, rename, and remove remote repository connections.
Basic Usage:
# List remotes
git remote
# List with URLs
git remote -v
git remote --verbose
# Add remote
git remote add origin https://github.com/user/repo.git
# Add remote with different name
git remote add upstream https://github.com/original/repo.git
# Remove remote
git remote remove origin
git remote rm origin
# Rename remote
git remote rename old-name new-name
# Change remote URL
git remote set-url origin https://github.com/user/new-repo.git
Viewing Remote Details:
# Show detailed information about remote
git remote show origin
# Output includes:
# - Fetch/push URLs
# - Remote branches
# - Local branches configured for push/pull
# - Stale branches
Example output:
$ git remote show origin
* remote origin
Fetch URL: https://github.com/user/repo.git
Push URL: https://github.com/user/repo.git
HEAD branch: main
Remote branches:
main tracked
develop tracked
feature/new-feature tracked
refs/remotes/origin/old-branch stale (use 'git remote prune' to remove)
Local branch configured for 'git pull':
main merges with remote main
Local ref configured for 'git push':
main pushes to main (up to date)
Managing Multiple Remotes:
Common pattern: fork workflow
# Add original repository as 'upstream'
git remote add upstream https://github.com/original/repo.git
# Add your fork as 'origin'
git remote add origin https://github.com/yourusername/repo.git
# List remotes
$ git remote -v
origin https://github.com/yourusername/repo.git (fetch)
origin https://github.com/yourusername/repo.git (push)
upstream https://github.com/original/repo.git (fetch)
upstream https://github.com/original/repo.git (push)
# Fetch from upstream
git fetch upstream
# Merge upstream changes
git merge upstream/main
Pruning Stale Remote Branches:
# Remove stale remote-tracking branches
git remote prune origin
# Show what would be pruned
git remote prune origin --dry-run
# Prune during fetch
git fetch --prune
git fetch -p
Changing Remote URLs:
# Switch from HTTPS to SSH
git remote set-url origin git@github.com:user/repo.git
# Add push URL different from fetch URL
git remote set-url --add --push origin git@github.com:user/repo.git
# View remote URLs
git remote get-url origin
git remote get-url --all origin
git fetch - Download Objects and Refs from Remote
Purpose: Download commits, files, and refs from remote repository without merging. Updates remote-tracking branches.
Basic Usage:
# Fetch from default remote (origin)
git fetch
# Fetch from specific remote
git fetch upstream
# Fetch specific branch
git fetch origin main
# Fetch all remotes
git fetch --all
# Fetch and prune stale branches
git fetch --prune
git fetch -p
What git fetch Does:
Remote repository:
origin/main: A---B---C---D---E
Local repository before fetch:
origin/main: A---B---C
main: A---B---C
After git fetch:
origin/main: A---B---C---D---E (updated)
main: A---B---C (unchanged)
Key Points:
- Downloads new commits from remote
- Updates remote-tracking branches (
origin/main, etc.) - Does NOT modify your working directory
- Does NOT merge anything
- Safe to run anytime
Fetch Specific Refs:
# Fetch specific branch
git fetch origin feature-branch
# Fetch specific tag
git fetch origin tag v1.0.0
# Fetch all tags
git fetch --tags
# Fetch and create local branch
git fetch origin feature-branch:feature-branch
Fetch Depth:
# Shallow fetch (last N commits only)
git fetch --depth=10
# Deepen existing shallow clone
git fetch --deepen=50
# Convert shallow to complete
git fetch --unshallow
# Shallow fetch single branch
git fetch --depth=1 origin main
Fetch with Pruning:
# Remove remote-tracking branches that no longer exist on remote
git fetch --prune
# Aggressive pruning (also remove tags)
git fetch --prune --prune-tags
# Configure automatic pruning
git config --global fetch.prune true
Dry Run:
# See what would be fetched without actually fetching
git fetch --dry-run
git fetch -n
Fetch and Display:
# Fetch and show what was fetched
git fetch --verbose
git fetch -v
# Show what changed after fetch
git fetch
git log HEAD..origin/main # Commits on remote not on local
git diff HEAD origin/main # Changes between local and remote
Real-World Fetch Scenarios:
Scenario 1: Check for Updates Before Starting Work
# Start of day routine
git fetch --all --prune
# See what's new
git log HEAD..origin/main --oneline
# Update your branch
git merge origin/main
# or
git rebase origin/main
Scenario 2: Review Changes Before Merging
# Fetch updates
git fetch origin
# Review what's new without merging
git log origin/main --not main --oneline
git diff main origin/main
# If satisfied, merge
git merge origin/main
Scenario 3: Update Multiple Remotes
# Fetch from all configured remotes
git fetch --all
# See status across remotes
git branch -vv
# Merge from upstream
git merge upstream/main
git pull - Fetch and Merge
Purpose: Fetch from remote and immediately merge into current branch. Combination of git fetch + git merge.
Basic Usage:
# Pull from tracking branch
git pull
# Pull from specific remote and branch
git pull origin main
# Pull with rebase instead of merge
git pull --rebase
git pull -r
# Pull from specific remote
git pull upstream
How git pull Works:
# git pull is equivalent to:
git fetch origin
git merge origin/main
# git pull --rebase is equivalent to:
git fetch origin
git rebase origin/main
Pull Strategies:
1. Merge (Default):
git pull
# Creates merge commit if branches diverged
2. Rebase:
git pull --rebase
# Rebases your commits on top of remote commits
# Creates linear history
3. Fast-forward Only:
git pull --ff-only
# Only succeeds if fast-forward possible
# Fails if branches diverged (safety measure)
Configure Default Pull Behavior:
# Use merge (default)
git config --global pull.rebase false
# Use rebase
git config --global pull.rebase true
# Use fast-forward only
git config --global pull.ff only
Pull Specific Branch:
# Pull specific branch into current branch
git pull origin feature-branch
# Pull and create/update local branch
git pull origin feature-branch:feature-branch
Pull with Options:
# Pull without commit (stage changes)
git pull --no-commit
# Pull with verbose output
git pull --verbose
git pull -v
# Pull all remotes
git pull --all
# Pull and prune
git pull --prune
Handling Pull Conflicts:
$ git pull
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.
# Resolve conflicts
vim file.txt
# Stage resolved files
git add file.txt
# Complete merge
git commit
# Or abort pull
git merge --abort
Pull vs Fetch:
Use git fetch when:
- Checking for updates without applying them
- Reviewing changes before integrating
- Need to inspect remote branches
- Working on multiple features
Use git pull when:
- Ready to integrate remote changes immediately
- Branch is up-to-date and simple fast-forward expected
- Quick synchronization needed
Best Practice: Many developers prefer git fetch + manual git merge/git rebase for more control:
# Explicit workflow (recommended)
git fetch origin
git log HEAD..origin/main # Review changes
git merge origin/main # Merge when ready
# Quick workflow (convenient but less control)
git pull
git push - Update Remote Refs
Purpose: Upload local commits to remote repository, updating remote branches.
Basic Usage:
# Push to tracking branch
git push
# Push to specific remote and branch
git push origin main
# Push and set upstream tracking
git push -u origin feature-branch
git push --set-upstream origin feature-branch
# Push all branches
git push --all
# Push tags
git push --tags
git push --follow-tags # Push tags reachable from pushed commits
How git push Works:
Local repository:
main: A---B---C---D---E
Remote repository before push:
origin/main: A---B---C
After git push origin main:
origin/main: A---B---C---D---E (updated)
Setting Upstream:
# First push of new branch, set tracking
git push -u origin feature-branch
# Future pushes just need:
git push
# Configure automatic upstream setup
git config --global push.autoSetupRemote true
Force Push:
# Force push (DANGEROUS - overwrites remote)
git push --force
# Safer force push (fails if remote has changes you don't have)
git push --force-with-lease
# Force push specific branch
git push --force-with-lease origin feature-branch
⚠️ Critical Warning: Never force push to shared branches like main or develop. Only force push to your personal feature branches after rebasing.
Push Options:
Delete Remote Branch:
# Delete remote branch
git push origin --delete feature-branch
# Old syntax (still works)
git push origin :feature-branch
Push Specific Refs:
# Push local branch to different remote branch
git push origin local-branch:remote-branch
# Push tag
git push origin v1.0.0
# Push all tags
git push origin --tags
# Delete remote tag
git push origin --delete tag v1.0.0
Push with Options:
# Dry run (see what would be pushed)
git push --dry-run
git push -n
# Verbose output
git push --verbose
git push -v
# Verify before push (run pre-push hook)
git push --verify
# Skip pre-push hook
git push --no-verify
Push Behavior Configuration:
# Configure default push behavior
# Simple: Push current branch to its upstream (default)
git config --global push.default simple
# Current: Push current branch to branch of same name
git config --global push.default current
# Upstream: Push current branch to its upstream branch
git config --global push.default upstream
# Matching: Push all matching branches
git config --global push.default matching
# Nothing: Don't push anything unless explicitly specified
git config --global push.default nothing
Recommended: Use simple (default) or current.
Handling Push Rejection:
$ git push
To https://github.com/user/repo.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/user/repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
# Solution 1: Pull first (merge)
git pull
git push
# Solution 2: Pull with rebase
git pull --rebase
git push
# Solution 3: Force push (DANGEROUS - only for personal branches)
git push --force-with-lease
Real-World Push Scenarios:
Scenario 1: First Push of New Branch
# Create and switch to new branch
git checkout -b feature/new-feature
# Make commits
git commit -am "Implement feature"
# Push and set upstream
git push -u origin feature/new-feature
# Future pushes
git push # Just works
Scenario 2: Update Feature Branch After Rebase
# Rebase feature on latest main
git fetch origin
git rebase origin/main
# Force push (history changed)
git push --force-with-lease
Scenario 3: Push Multiple Branches
# Push all branches
git push --all origin
# Push specific branches
git push origin main develop feature-x
Scenario 4: Cleaning Up Remote Branches
# Delete merged feature branch
git branch -d feature-old
git push origin --delete feature-old
# Or in one command
git push origin --delete feature-old
Scenario 5: Push with CI/CD Trigger
# Push specific commit message to trigger deployment
git commit -m "Deploy to production [deploy]"
git push
# Or use tags for releases
git tag -a v1.2.0 -m "Release version 1.2.0"
git push origin v1.2.0
Remote Branch Tracking
Understanding Remote-Tracking Branches:
Remote-tracking branches are references to the state of branches on remote repositories. They're named <remote>/<branch> (e.g., origin/main).
Viewing Tracking Information:
# Show all branches with tracking info
git branch -vv
# Output:
* main abc123 [origin/main] Latest commit
feature def456 [origin/feature: ahead 2, behind 1] Work in progress
local ghi789 No tracking information
# Ahead 2: You have 2 commits not pushed
# Behind 1: Remote has 1 commit you don't have
Setting Up Tracking:
# Set upstream for current branch
git branch -u origin/main
git branch --set-upstream-to=origin/main
# Set upstream when pushing
git push -u origin feature-branch
# Create branch tracking remote branch
git checkout -b local-branch origin/remote-branch
# Or with git switch
git switch -c local-branch origin/remote-branch
# Automatic tracking (Git 2.37+)
git config --global push.autoSetupRemote true
Removing Tracking:
# Remove upstream tracking
git branch --unset-upstream
# Branch no longer tracks remote
Synchronizing with Remote:
# Fetch updates
git fetch origin
# See differences
git log HEAD..origin/main # What's on remote you don't have
git log origin/main..HEAD # What you have that's not on remote
# Pull updates
git pull
# Push updates
git push
Collaboration Workflows
Centralized Workflow:
Single central repository, everyone pushes to main:
# Clone repository
git clone https://github.com/team/project.git
cd project
# Make changes
git add .
git commit -m "Add feature"
# Pull latest changes
git pull
# Push changes
git push
Feature Branch Workflow:
Each feature developed in dedicated branch:
# Create feature branch
git checkout -b feature/awesome-feature
# Make commits
git commit -am "Implement feature"
# Push feature branch
git push -u origin feature/awesome-feature
# Create pull request on GitHub/GitLab
# After review and approval, merge
# Delete branch
git branch -d feature/awesome-feature
git push origin --delete feature/awesome-feature
Gitflow Workflow:
Structured branching model with dedicated branches:
# Main branches:
# - main: Production-ready code
# - develop: Integration branch
# Create feature branch from develop
git checkout develop
git checkout -b feature/new-feature
# Develop feature
git commit -am "Add feature"
# Merge back to develop
git checkout develop
git merge feature/new-feature
git branch -d feature/new-feature
# Create release branch
git checkout -b release/1.0 develop
# Final testing and bug fixes
# Merge to main and develop
git checkout main
git merge release/1.0
git tag v1.0
git checkout develop
git merge release/1.0
git branch -d release/1.0
# Hotfix for production
git checkout -b hotfix/critical-bug main
# Fix bug
git checkout main
git merge hotfix/critical-bug
git tag v1.0.1
git checkout develop
git merge hotfix/critical-bug
git branch -d hotfix/critical-bug
Fork and Pull Request Workflow:
Common in open source:
# Fork repository on GitHub/GitLab
# Clone your fork
git clone https://github.com/yourname/project.git
cd project
# Add upstream remote
git remote add upstream https://github.com/original/project.git
# Create feature branch
git checkout -b feature/contribution
# Make changes and commit
git commit -am "Add contribution"
# Keep updated with upstream
git fetch upstream
git rebase upstream/main
# Push to your fork
git push -u origin feature/contribution
# Create pull request from your fork to upstream
# After merge, sync your fork
git checkout main
git fetch upstream
git merge upstream/main
git push origin main
12. Inspection, Logging, and History Navigation {#inspection}
Understanding your repository's history is crucial. Git provides powerful tools for inspecting commits, changes, and project evolution.
git log - Show Commit Logs
Purpose: Display commit history with various formatting and filtering options.
Basic Usage:
# Show commit history
git log
# One commit per line
git log --oneline
# Show last N commits
git log -n 5
git log -5
# Show commits with diffs
git log -p
git log --patch
# Show commit stats
git log --stat
# Show graph of branches
git log --graph --oneline --all
Understanding Log Output:
$ git log
commit abc123def456... (HEAD -> main, origin/main)
Author: John Doe <john@example.com>
Date: Mon Dec 2 10:30:00 2024 -0800
Add user authentication feature
Implemented JWT-based authentication with login and logout endpoints.
Added middleware for protected routes.
commit 789ghi012jkl...
Author: Jane Smith <jane@example.com>
Date: Sun Dec 1 15:20:00 2024 -0800
Fix bug in payment processing
Formatting Options:
# Oneline format
git log --oneline
# Output: abc123 Commit message
# Short format
git log --pretty=short
# Full format (includes committer)
git log --pretty=full
# Fuller format (includes dates)
git log --pretty=fuller
# Custom format
git log --pretty=format:"%h - %an, %ar : %s"
# Output: abc123 - John Doe, 2 days ago : Commit message
Custom Format Placeholders:
| Placeholder | Description |
|---|---|
%H |
Commit hash (full) |
%h |
Commit hash (abbreviated) |
%T |
Tree hash |
%t |
Tree hash (abbreviated) |
%P |
Parent hashes |
%p |
Parent hashes (abbreviated) |
%an |
Author name |
%ae |
Author email |
%ad |
Author date |
%ar |
Author date, relative |
%cn |
Committer name |
%ce |
Committer email |
%cd |
Committer date |
%cr |
Committer date, relative |
%s |
Subject (commit message) |
%b |
Body (commit message) |
%Cred |
Switch color to red |
%Cgreen |
Switch color to green |
%Cblue |
Switch color to blue |
%Creset |
Reset color |
Beautiful Custom Format:
git log --pretty=format:"%C(yellow)%h%Creset %C(blue)%ad%Creset %C(green)%an%Creset %s" --date=short
# Or create alias:
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative"
# Use:
git lg
Graph Visualization:
# Show branch structure
git log --graph
# Compact graph
git log --graph --oneline
# All branches
git log --graph --all --oneline
# Decorated (show branch/tag names)
git log --graph --decorate --oneline
Example output:
* abc123 (HEAD -> main) Latest commit
* def456 Previous commit
| * ghi789 (feature) Feature commit
| * jkl012 Another feature commit
|/
* mno345 Common ancestor
Filtering Commits:
By Author:
# Commits by specific author
git log --author="John Doe"
# Multiple authors (regex)
git log --author="John\|Jane"
# Case-insensitive
git log --author="john" -i
By Date:
# Commits after date
git log --since="2024-01-01"
git log --after="2 weeks ago"
# Commits before date
git log --until="2024-12-31"
git log --before="1 month ago"
# Date range
git log --since="2024-01-01" --until="2024-12-31"
# Relative dates
git log --since="2 weeks ago"
git log --since="yesterday"
git log --until="3 days ago"
By Message:
# Commits with message containing text
git log --grep="bug fix"
# Case-insensitive
git log --grep="BUG" -i
# Multiple patterns (OR)
git log --grep="fix" --grep="bug"
# Multiple patterns (AND)
git log --grep="fix" --grep="bug" --all-match
# Invert match (exclude)
git log --grep="WIP" --invert-grep
By File:
# Commits affecting specific file
git log -- path/to/file.txt
# Multiple files
git log -- file1.txt file2.txt
# Files in directory
git log -- src/
# Follow file renames
git log --follow -- file.txt
By Content Changes:
# Commits adding or removing specific string
git log -S "function_name"
# With regex
git log -G "regex_pattern"
# Show patches with changes
git log -S "function_name" -p
This is incredibly powerful for finding when specific code was introduced or removed.
By Commit Range:
# Commits between two commits
git log commit1..commit2
# Commits reachable from commit2 but not commit1
git log commit1...commit2
# Commits on branch not on main
git log main..feature-branch
# Commits on main not on feature
git log feature-branch..main
# Not yet merged to main
git log --no-merges main..feature
Limiting Output:
# First N commits
git log -n 10
git log -10
# Skip first N commits
git log --skip=5
# Maximum count
git log --max-count=20
Merge and Non-Merge Commits:
# Only merge commits
git log --merges
# Exclude merge commits
git log --no-merges
# First parent only (simplifies merge history)
git log --first-parent
Statistics and Patches:
# Show files changed and stats
git log --stat
# Show shorter stat
git log --shortstat
# Show name and status only
git log --name-status
# Show filenames only
git log --name-only
# Show patches (diffs)
git log -p
# Show word diff in patches
git log -p --word-diff
Advanced Log Options:
# Show commits that changed number of lines
git log --diff-filter=M --stat
# Show only added files
git log --diff-filter=A --name-only
# Show only deleted files
git log --diff-filter=D --name-only
# Show renamed files
git log --diff-filter=R --name-status
# Complex filter (Added or Modified)
git log --diff-filter=AM
# Show commits touching specific function
git log -L :function_name:path/to/file.c
# Show commits changing lines 10-20
git log -L 10,20:path/to/file.txt
Real-World Log Examples:
Example 1: Daily Standup Report
# What did I do yesterday?
git log --author="Your Name" --since="yesterday" --oneline
# What did the team do this week?
git log --since="1 week ago" --pretty=format:"%an: %s" --no-merges
Example 2: Release Notes
# Changes since last release
git log v1.0.0..HEAD --oneline --no-merges
# With categories
git log v1.0.0..HEAD --pretty=format:"%s" --no-merges | grep "^feat:"
git log v1.0.0..HEAD --pretty=format:"%s" --no-merges | grep "^fix:"
Example 3: Find Bug Introduction
# When was this function added?
git log -S "problematic_function" --oneline
# Show the actual changes
git log -S "problematic_function" -p
# Who introduced this bug?
git log -S "buggy_code" --pretty=format:"%an - %s"
Example 4: Code Review Preparation
# All commits in feature branch
git log main..feature-branch --oneline
# With file changes
git log main..feature-branch --name-status
# With full diffs
git log main..feature-branch -p
git show - Show Various Types of Objects
Purpose: Display information about Git objects (commits, tags, trees, blobs).
Basic Usage:
# Show latest commit
git show
# Show specific commit
git show abc123
# Show commit with specific formatting
git show --oneline abc123
# Show specific file at specific commit
git show abc123:path/to/file.txt
# Show tag
git show v1.0.0
Showing Commits:
# Show commit with diff
git show HEAD
# Show previous commit
git show HEAD^
git show HEAD~1
# Show grandparent commit
git show HEAD~2
# Show specific commit
git show abc123def
# Show abbreviated commit
git show --abbrev-commit abc123
Showing Specific Files:
# Show file at specific commit
git show HEAD:README.md
# Show file from different branch
git show main:src/app.js
# Show file as it was 5 commits ago
git show HEAD~5:config.yml
Showing Tags:
# Show tag (displays tag object and commit)
git show v1.0.0
# Show lightweight tag (just the commit)
git show v1.0.1
Formatting Options:
# Show with stats
git show --stat abc123
# Show with patch
git show -p abc123
# Show abbreviated
git show --oneline abc123
# Show with specific format
git show --pretty=format:"%h - %s" abc123
Showing Trees:
# Show tree object (directory listing)
git show abc123^{tree}
# Show specific directory
git show abc123:src/
Real-World Show Examples:
Example 1: Inspect Merge Commit
# Show what was merged
git show merge-commit-hash
# Show files changed in merge
git show --stat merge-commit-hash
Example 2: Compare File Versions
# Current version
cat README.md
# Version from 5 commits ago
git show HEAD~5:README.md
# Side-by-side comparison
diff <(git show HEAD~5:README.md) README.md
Example 3: Extract File from History
# Get old version of file
git show abc123:old-file.txt > recovered-file.txt
git diff - Show Differences (Covered Earlier)
We covered git diff extensively in the Basic Workflow section. Quick refresher on history-related diff usage:
# Diff between commits
git diff commit1 commit2
# Diff with ancestor
git diff HEAD~3 HEAD
# Diff between branches
git diff main feature-branch
# Diff specific file between commits
git diff abc123 def456 -- file.txt
# Diff with three dots (since branches diverged)
git diff main...feature-branch
git reflog - Reference Logs
Purpose: Shows history of HEAD and branch references, even after commits are "lost" from normal history. Essential for recovery.
Basic Usage:
# Show reflog for HEAD
git reflog
# Show reflog for specific branch
git reflog show main
# Show with dates
git reflog --date=iso
# Show relative dates
git reflog --date=relative
Understanding Reflog Output:
$ git reflog
abc123 HEAD@{0}: commit: Add feature X
def456 HEAD@{1}: commit: Fix bug Y
ghi789 HEAD@{2}: checkout: moving from main to feature
jkl012 HEAD@{3}: commit: Update documentation
mno345 HEAD@{4}: merge feature: Fast-forward
pqr678 HEAD@{5}: commit: Initial commit
Each entry shows:
- Commit hash
- HEAD position reference (HEAD@{N})
- Action performed
- Message
Using Reflog References:
# Checkout previous HEAD position
git checkout HEAD@{1}
# Reset to where HEAD was 3 moves ago
git reset --hard HEAD@{3}
# Show commit from reflog
git show HEAD@{5}
# Diff with previous position
git diff HEAD HEAD@{1}
Reflog with Time:
# HEAD position 1 hour ago
git show HEAD@{1.hour.ago}
# HEAD position yesterday
git show HEAD@{yesterday}
# HEAD position last week
git show HEAD@{1.week.ago}
# Branch position 2 days ago
git show main@{2.days.ago}
Filtering Reflog:
# Show only specific action
git reflog --grep="commit"
git reflog --grep="rebase"
git reflog --grep="checkout"
# Show reflog for specific file
git reflog -- path/to/file.txt
# Limit output
git reflog -n 20
git reflog -10
Reflog for All References:
# Show reflog for all refs (branches, tags, etc.)
git reflog show --all
# Specific branch reflog
git reflog show feature-branch
# Remote branch reflog
git reflog show origin/main
Reflog Expiration:
Reflog entries expire after a certain time:
# Show reflog expiration configuration
git config --get gc.reflogExpire # Default: 90 days
git config --get gc.reflogExpireUnreachable # Default: 30 days
# Change expiration
git config gc.reflogExpire 180 # Keep for 180 days
# Expire reflog manually
git reflog expire --expire=30.days --all
# Never expire (dangerous, can bloat repository)
git config gc.reflogExpire never
Real-World Reflog Usage:
Scenario 1: Recover from Bad Reset
# Oops, accidentally reset too far
git reset --hard HEAD~10
# Check reflog
git reflog
# abc123 HEAD@{0}: reset: moving to HEAD~10
# def456 HEAD@{1}: commit: Important work
# Recover
git reset --hard HEAD@{1}
# or
git reset --hard def456
Scenario 2: Recover Deleted Branch
# Accidentally deleted branch
git branch -D important-feature
# Deleted branch important-feature (was abc123).
# Find it in reflog
git reflog show important-feature
# or search all reflog
git reflog | grep "important-feature"
# Recreate branch
git branch important-feature abc123
Scenario 3: Undo Rebase Disaster
# Rebase went wrong
git rebase main
# ... conflicts, mess ...
git rebase --abort # If still in progress
# Or if rebase completed but broke things
git reflog
# Find commit before rebase
git reset --hard HEAD@{5}
Scenario 4: Find Lost Commit
# Made commit, then somehow lost it
git reflog --all | grep "commit message fragment"
# Found it: abc123
git cherry-pick abc123
# or create branch
git branch recovered-work abc123
git blame - Show What Revision Modified Each Line
Purpose: Show who last modified each line of a file and when. Essential for understanding code history and accountability.
Basic Usage:
# Blame entire file
git blame filename.txt
# Blame specific line range
git blame -L 10,20 filename.txt
# Blame with commit details
git blame -c filename.txt
# Show email instead of name
git blame -e filename.txt
Understanding Blame Output:
$ git blame example.py
abc123de (John Doe 2024-11-15 10:30:00 -0800 1) def calculate_total(items):
abc123de (John Doe 2024-11-15 10:30:00 -0800 2) total = 0
def456gh (Jane Smith 2024-11-20 14:15:00 -0800 3) for item in items:
def456gh (Jane Smith 2024-11-20 14:15:00 -0800 4) total += item.price
ghi789jk (Bob Lee 2024-12-01 09:00:00 -0800 5) return total * 1.1 # Add tax
Columns:
- Commit hash
- Author name
- Date and time
- Line number
- Line content
Formatting Options:
# Show long format (full commit hash)
git blame -l filename.txt
# Show short format (abbreviated hash)
git blame -s filename.txt
# Suppress author name
git blame -n filename.txt
# Show line number
git blame -n filename.txt
# Show email addresses
git blame -e filename.txt
# Show relative dates
git blame --date=relative filename.txt
# Show custom date format
git blame --date=short filename.txt
git blame --date=iso filename.txt
Line Range:
# Blame lines 10 to 20
git blame -L 10,20 filename.txt
# Blame from line 10 to end
git blame -L 10, filename.txt
# Blame first 50 lines
git blame -L 1,50 filename.txt
# Blame function (if Git can detect)
git blame -L :function_name: filename.c
Following File Renames:
# Follow file renames
git blame -C filename.txt
# Follow with copy detection
git blame -C -C filename.txt
# Aggressive copy detection
git blame -C -C -C filename.txt
Ignoring Whitespace:
# Ignore whitespace changes
git blame -w filename.txt
# Ignore whitespace changes at line ends
git blame --ignore-space-at-eol filename.txt
# Ignore all space changes
git blame --ignore-space-change filename.txt
Blame Specific Revision:
# Blame as of specific commit
git blame abc123 filename.txt
# Blame as of tag
git blame v1.0.0 filename.txt
# Blame as of date
git blame HEAD@{2.weeks.ago} filename.txt
Ignoring Revisions:
Useful for ignoring bulk formatting commits:
# Create .git-blame-ignore-revs
echo "abc123def456" >> .git-blame-ignore-revs # Formatting commit
# Configure Git to use it
git config blame.ignoreRevsFile .git-blame-ignore-revs
# Now blame ignores that commit
git blame filename.txt
Interactive Blame:
# Use tig (if installed) for interactive blame
tig blame filename.txt
# Use Git GUI
git gui blame filename.txt
Real-World Blame Examples:
Example 1: Find Who Introduced Bug
# Find problematic line
grep -n "buggy_code" src/app.js
# Line 42 matches
# Blame that line
git blame -L 42,42 src/app.js
# abc123 (John Doe 2024-11-15) buggy_code
# Show full commit
git show abc123
# Contact John about the bug
Example 2: Understand Code History
# Why does this code exist?
git blame -L 100,150 complex_file.py
# See commits that modified these lines
git log -p abc123 def456 ghi789
# Read commit messages for context
Example 3: Code Review Context
# Reviewing someone's changes
git blame new_file.py
# See who wrote each part
# Check if recent changes are from PR author
# Review their overall contribution patterns
Example 4: Track Feature Development
# When was this feature added?
git blame -L :feature_function: src/features.js
# See evolution
git log -p abc123 # Initial commit
git log -p def456 # Later modification
git shortlog - Summarize Git Log
Purpose: Summarize commit history by author, useful for generating release notes and contributor lists.
Basic Usage:
# Summarize by author
git shortlog
# Count commits per author
git shortlog -sn
git shortlog --summary --numbered
# Show email addresses
git shortlog -sne
git shortlog --summary --numbered --email
Understanding Output:
$ git shortlog
Jane Doe (10):
Add user authentication
Fix login bug
Update documentation
...
John Smith (5):
Implement payment processing
Add tests
...
With -sn (summary, numbered):
$ git shortlog -sn
10 Jane Doe
5 John Smith
3 Bob Lee
1 Alice Johnson
Filtering:
# Specific date range
git shortlog --since="2024-01-01" --until="2024-12-31"
# Specific author
git shortlog --author="John"
# Specific branch
git shortlog main
# Between commits/branches
git shortlog v1.0..v2.0
# No merge commits
git shortlog --no-merges
Formatting:
# Group by committer instead of author
git shortlog -c
# Show email addresses
git shortlog -e
# Custom format
git shortlog --format="%h %s"
Real-World Shortlog Examples:
Example 1: Generate Release Notes
# Commits since last release
git shortlog v1.0.0..HEAD --no-merges
# Summarize contributions
git shortlog v1.0.0..HEAD --no-merges -sn
Example 2: Contributor Recognition
# All-time contributors
git shortlog -sn
# Contributors this year
git shortlog --since="2024-01-01" -sn
# New contributors this release
git shortlog v1.0..HEAD -sn
Example 3: Team Activity Report
# Activity last month
git shortlog --since="1 month ago" -sn
# Detailed activity
git shortlog --since="1 week ago"
git describe - Describe Commit Using Tags
Purpose: Give human-readable name to commit based on available tags.
Basic Usage:
# Describe current commit
git describe
# Describe specific commit
git describe abc123
# Describe with tags
git describe --tags
# Always show long format
git describe --long
Understanding Output:
$ git describe
v1.2.0-5-gabc123d
# Breakdown:
# v1.2.0: Most recent tag
# 5: Number of commits since tag
# g: Prefix indicating Git
# abc123d: Abbreviated commit hash
If on exact tag:
$ git describe
v1.2.0 # Just the tag name
Options:
# Abbreviated commit hash length
git describe --abbrev=10
# Only show tag (no commit count)
git describe --exact-match # Fails if not on tag
# Always show tag-commits-hash
git describe --long
# Use all refs, not just tags
git describe --all
# Use any tag (annotated or lightweight)
git describe --tags
# Add dirty indicator for modified working directory
git describe --dirty
git describe --dirty=-modified
# Output: v1.2.0-5-gabc123d-modified
Real-World Describe Usage:
Example 1: Version Numbering
# Get version for build
VERSION=$(git describe --tags --always --dirty)
echo "Building version: $VERSION"
# Use in code
echo "#define VERSION \"$VERSION\"" > version.h
Example 2: Release Identification
# What release is this commit from?
git describe abc123
# Output: v1.2.0-3-gabc123
# It's 3 commits after v1.2.0
Example 3: CI/CD Versioning
# In CI/CD pipeline
GIT_VERSION=$(git describe --tags --always --dirty)
docker build -t myapp:$GIT_VERSION .
Navigating History
Relative References:
Git provides convenient syntax for referring to commits relative to others:
Parent References:
# Parent of HEAD
HEAD^
HEAD~1
# Grandparent
HEAD^^
HEAD~2
# Great-grandparent
HEAD^^^
HEAD~3
# Show specific parent commit
git show HEAD^
git show HEAD~2
For Merge Commits (Multiple Parents):
# First parent (main branch)
HEAD^1
HEAD^
# Second parent (merged branch)
HEAD^2
# Grandparent through first parent
HEAD^^1
HEAD~2
# Grandparent through second parent
HEAD^2^
Combining References:
# Third commit before HEAD
HEAD~3
# First parent of second parent of HEAD
HEAD^2^1
# Show range
HEAD~5..HEAD~2
Branch References:
# Latest commit on branch
main
origin/main
feature/auth
# Previous commit on branch
main^
main~1
# Branch as of yesterday
main@{yesterday}
# Branch as of specific date
main@{2024-11-15}
Real-World Navigation:
# View previous commit
git show HEAD^
# Compare current with 5 commits ago
git diff HEAD~5 HEAD
# Checkout previous version
git checkout HEAD~3
# Cherry-pick parent's parent
git cherry-pick HEAD~2
# Reset to 3 commits ago
git reset --soft HEAD~3
13. Stashing: Temporary Storage {#stashing}
Stashing saves your work temporarily without committing, allowing you to switch contexts cleanly.
git stash - Stash Changes
Purpose: Temporarily save modified tracked files and staged changes, reverting working directory to clean state.
Basic Usage:
# Stash current changes
git stash
# Stash with message
git stash push -m "Work in progress on feature X"
# Stash including untracked files
git stash -u
git stash --include-untracked
# Stash everything (including ignored files)
git stash -a
git stash --all
# List stashes
git stash list
# Apply most recent stash
git stash apply
# Apply and remove most recent stash
git stash pop
# Remove stash
git stash drop
# Clear all stashes
git stash clear
How Stashing Works:
Before stash:
Working Directory: Modified files
Staging Area: Staged files
Repository: Clean
After git stash:
Working Directory: Clean (matches HEAD)
Staging Area: Clean
Stash: Saved changes (both working directory and staging area)
Stash List:
$ git stash list
stash@{0}: WIP on main: abc123 Latest commit
stash@{1}: On feature: Work in progress
stash@{2}: WIP on develop: def456 Previous work
Stashes are numbered: stash@{0} is most recent, stash@{1} is older, etc.
Stashing Options:
Stash with Message:
# Better than default "WIP on branch"
git stash push -m "Implementing user authentication"
# Or (older syntax, still works)
git stash save "Implementing user authentication"
Partial Stashing:
# Stash specific files
git stash push path/to/file1.txt path/to/file2.txt
# Stash with pattern
git stash push -m "Config changes" -- '*.config'
# Interactive stashing
git stash push -p
git stash push --patch
# Choose which hunks to stash (like git add -p)
Stash Untracked Files:
# Include untracked files
git stash -u
git stash --include-untracked
# Include ignored files too
git stash -a
git stash --all
Stash Keeping Index:
# Stash working directory changes but keep staging area
git stash --keep-index
# Use case: You staged changes for one commit,
# but need to quickly switch branches
Applying Stashes:
# Apply most recent stash (keeps stash in list)
git stash apply
# Apply specific stash
git stash apply stash@{2}
# Apply and remove (pop)
git stash pop
git stash pop stash@{1}
# Apply only file changes, not staging information
git stash apply --index
Viewing Stashes:
# List stashes
git stash list
# Show stash contents
git stash show
# Show detailed diff
git stash show -p
git stash show --patch
# Show specific stash
git stash show stash@{1} -p
Managing Stashes:
# Drop specific stash
git stash drop stash@{1}
# Clear all stashes (CAUTION: irreversible)
git stash clear
# Create branch from stash
git stash branch new-branch-name
git stash branch new-branch stash@{1}
Creating a branch from stash applies the stash and drops it if successful.
Real-World Stash Scenarios:
Scenario 1: Quick Context Switch
# Working on feature, urgent bug reported
git stash push -m "WIP: feature X implementation"
# Switch to main, fix bug
git checkout main
git checkout -b hotfix/critical-bug
# Fix bug, commit, push
# Return to feature work
git checkout feature-branch
git stash pop
Scenario 2: Clean Working Directory for Pull
# Have local changes, need to pull
git stash -u
# Pull updates
git pull
# Reapply changes
git stash pop
# If conflicts, resolve and drop manually
git stash drop
Scenario 3: Experiment Without Committing
# Current work is not ready to commit
git stash
# Try experimental approach
# ... make changes ...
# Doesn't work, discard
git reset --hard
# Restore original work
git stash pop
Scenario 4: Partial Stashing
# Have changes for two different features
git stash push -p -m "Feature A changes"
# Select only Feature A hunks
# Commit Feature B
git commit -am "Implement Feature B"
# Restore Feature A work
git stash pop
Scenario 5: Transfer Work Between Branches
# Started work on wrong branch
git stash
# Switch to correct branch
git checkout correct-branch
# Apply work
git stash pop
Advanced Stash Techniques:
Stash and Create Branch:
# Stash causes conflicts when popping
git stash pop
# CONFLICT...
# Instead, create branch from stash
git stash branch fix-conflicts
# Git creates branch, applies stash, drops it if successful
# You're now on new branch with changes applied
Recover Dropped Stash:
# Accidentally dropped important stash
git stash drop stash@{0} # Oops!
# Find it in reflog
git fsck --unreachable | grep commit | cut -d ' ' -f3 | xargs git log --merges --no-walk --grep=WIP
# Or search reflog
git log --graph --oneline --decorate $(git fsck --no-reflog | awk '/dangling commit/ {print $3}')
# Apply recovered stash
git stash apply <commit-hash>
Stash Part of Staging Area:
# Some files staged, some not
git status
# Changes to be committed: file1.txt
# Changes not staged: file2.txt
# Stash only unstaged changes
git stash --keep-index
# Now only staged changes remain
Create Stash Without Changing Working Directory:
# Create stash but don't clean working directory
git stash create
# Returns stash commit hash
# Useful for scripting
Comparing Stashes:
# Diff between stash and current state
git diff stash@{0}
# Diff between two stashes
git diff stash@{0} stash@{1}
# Diff stash with specific commit
git diff stash@{0} main
14. Tagging: Marking Important Points {#tagging}
Tags mark specific points in history as important, typically for releases.
git tag - Create, List, Delete Tags
Purpose: Create named references to specific commits, typically for marking releases.
Types of Tags:
1. Lightweight Tags:
- Simple pointer to commit
- Just a name
- Like a branch that doesn't move
2. Annotated Tags:
- Full Git objects
- Contain tagger name, email, date
- Can be signed and verified
- Can have message
- Recommended for releases
Basic Usage:
# List tags
git tag
# List tags with pattern
git tag -l "v1.*"
git tag --list "v1.*"
# Create lightweight tag
git tag v1.0.0
# Create annotated tag
git tag -a v1.0.0 -m "Release version 1.0.0"
git tag --annotate v1.0.0 -m "Release version 1.0.0"
# Tag specific commit
git tag v1.0.0 abc123
# Delete tag
git tag -d v1.0.0
git tag --delete v1.0.0
# Show tag info
git show v1.0.0
Creating Tags:
Lightweight Tag:
# Create lightweight tag on HEAD
git tag v1.0.0
# Create on specific commit
git tag v1.0.0 abc123
Annotated Tag (Recommended):
# Create annotated tag with message
git tag -a v1.0.0 -m "Release version 1.0.0"
# Create annotated tag and open editor for message
git tag -a v1.0.0
# Tag specific commit
git tag -a v1.0.0 abc123 -m "Release 1.0.0"
Signed Tags:
# Create signed tag (requires GPG)
git tag -s v1.0.0 -m "Signed release 1.0.0"
git tag --sign v1.0.0 -m "Signed release 1.0.0"
# Verify signed tag
git tag -v v1.0.0
git tag --verify v1.0.0
Listing Tags:
# List all tags
git tag
# List with pattern
git tag -l "v1.8.*"
git tag -l "v2.*"
# List annotated tags with messages
git tag -n
git tag -n9 # Show up to 9 lines of message
# Sort tags
git tag --sort=-version:refname # Descending version sort
git tag --sort=version:refname # Ascending version sort
git tag --sort=-committerdate # By date
Showing Tag Information:
# Show tag and commit info
git show v1.0.0
# Show only tag object
git cat-file -p v1.0.0
# List tags with commit info
git show-ref --tags
Deleting Tags:
# Delete local tag
git tag -d v1.0.0
# Delete remote tag
git push origin --delete v1.0.0
git push origin :refs/tags/v1.0.0 # Old syntax
Pushing Tags:
# Push specific tag
git push origin v1.0.0
# Push all tags
git push origin --tags
# Push only annotated tags
git push --follow-tags
# Configure to always push annotated tags
git config --global push.followTags true
Checking Out Tags:
# Checkout tag (detached HEAD)
git checkout v1.0.0
# Create branch from tag
git checkout -b version-1.0 v1.0.0
# Switch to tag (Git 2.23+)
git switch --detach v1.0.0
Real-World Tagging Scenarios:
Scenario 1: Release Workflow
# Finalize release commit
git commit -m "Bump version to 1.0.0"
# Create annotated tag
git tag -a v1.0.0 -m "Release 1.0.0
Major features:
- User authentication
- Payment processing
- Admin dashboard
Breaking changes:
- API endpoint /v1/users renamed to /v2/users"
# Push commit and tag
git push origin main
git push origin v1.0.0
Scenario 2: Semantic Versioning
# Major release (breaking changes)
git tag -a v2.0.0 -m "Major release with breaking changes"
# Minor release (new features)
git tag -a v2.1.0 -m "Add new features"
# Patch release (bug fixes)
git tag -a v2.1.1 -m "Fix critical bug"
# Pre-release
git tag -a v2.2.0-beta.1 -m "Beta release"
Scenario 3: Hotfix Tagging
# Critical bug in production (v1.2.0)
git checkout v1.2.0
git checkout -b hotfix/security-fix
# Fix bug
git commit -m "Fix security vulnerability"
# Tag hotfix
git tag -a v1.2.1 -m "Security hotfix"
# Push
git push origin hotfix/security-fix
git push origin v1.2.1
Scenario 4: Build Versioning
# Tag nightly build
DATE=$(date +%Y%m%d)
git tag -a nightly-$DATE -m "Nightly build $DATE"
# Tag release candidate
git tag -a v1.0.0-rc.1 -m "Release candidate 1"
git tag -a v1.0.0-rc.2 -m "Release candidate 2"
Scenario 5: Comparing Releases
# Changes between releases
git log v1.0.0..v2.0.0 --oneline
# Diff between releases
git diff v1.0.0 v2.0.0
# Files changed
git diff --name-status v1.0.0 v2.0.0
# Generate changelog
git log v1.0.0..v2.0.0 --no-merges --pretty=format:"- %s"
Tag Naming Conventions:
Semantic Versioning:
v1.0.0 (or 1.0.0)
vMAJOR.MINOR.PATCH
Examples:
v1.0.0 - Initial release
v1.1.0 - New features
v1.1.1 - Bug fixes
v2.0.0 - Breaking changes
Pre-releases:
v1.0.0-alpha
v1.0.0-beta.1
v1.0.0-rc.1
Date-Based:
release-2024.12.02
v2024.12
Build Numbers:
build-12345
v1.0.0-build.456
Best Practices:
- Use annotated tags for releases:
# Good
git tag -a v1.0.0 -m "Release 1.0.0"
# Avoid for releases
git tag v1.0.0
- Be consistent with naming:
# Choose one format and stick to it
v1.0.0, v1.1.0, v2.0.0 # Good
v1.0.0, 1.1.0, version-2.0 # Inconsistent
- Don't move tags:
# DON'T do this:
git tag -d v1.0.0
git tag v1.0.0 different-commit
# Tags should be permanent markers
- Sign release tags:
# For important releases
git tag -s v1.0.0 -m "Signed release"
- Document releases in tag messages:
git tag -a v1.0.0 -m "Release 1.0.0
Features:
- Feature A
- Feature B
Bug fixes:
- Fix issue #123
Breaking changes:
- API change in module X"
15. Advanced History Modification {#history-modification}
⚠️ Warning: These commands rewrite history. Never use on public/shared branches unless you understand the consequences.
git commit --amend (Covered Earlier)
Quick refresher:
# Modify last commit
git commit --amend
# Amend without changing message
git commit --amend --no-edit
# Amend and change author
git commit --amend --author="New Author <email@example.com>"
git reset - Reset Current HEAD
Purpose: Move HEAD (and optionally branch) to specified commit, with various effects on staging area and working directory.
Three Modes:
1. Soft Reset:
- Moves HEAD and branch pointer
- Keeps staging area unchanged
- Keeps working directory unchanged
- Commits become "uncommitted changes"
2. Mixed Reset (Default):
- Moves HEAD and branch pointer
- Resets staging area to match commit
- Keeps working directory unchanged
- Commits become "unstaged changes"
3. Hard Reset:
- Moves HEAD and branch pointer
- Resets staging area to match commit
- Resets working directory to match commit
- Destroys uncommitted changes
Basic Usage:
# Soft reset (uncommit, keep changes staged)
git reset --soft HEAD~1
# Mixed reset (unstage changes)
git reset HEAD~1
git reset --mixed HEAD~1
# Hard reset (discard everything)
git reset --hard HEAD~1
# Reset to specific commit
git reset --hard abc123
# Reset specific file
git reset HEAD file.txt
Visual Representation:
Initial state:
A---B---C---D (HEAD, main)
After git reset --soft HEAD~2:
A---B---C---D
↑
(HEAD, main)
Changes from C and D are staged
After git reset --mixed HEAD~2:
A---B---C---D
↑
(HEAD, main)
Changes from C and D are unstaged
After git reset --hard HEAD~2:
A---B (HEAD, main)
Commits C and D are "gone" (but recoverable via reflog)
Reset Modes Comparison:
| Mode | HEAD | Staging Area | Working Directory |
|---|---|---|---|
--soft |
Reset | Unchanged | Unchanged |
--mixed |
Reset | Reset | Unchanged |
--hard |
Reset | Reset | Reset |
Common Reset Scenarios:
Scenario 1: Undo Last Commit (Keep Changes)
# Undo commit, keep changes staged
git reset --soft HEAD~1
# Now you can re-commit with different message or add more changes
git commit -m "Better commit message"
Scenario 2: Unstage Files
# 1Accidentally staged wrong file
git add wrong-file.txt
# Unstage it
git reset HEAD wrong-file.txt
# or (Git 2.23+)
git restore --staged wrong-file.txt
Scenario 3: Completely Undo Commits
# Discard last 3 commits entirely
git reset --hard HEAD~3
# WARNING: This destroys commits and changes
# Only do this on unpushed commits
Scenario 4: Squash Commits Before Pushing
# You have 5 messy commits
git log --oneline
# e5 Fix typo
# e4 Fix another typo
# e3 Implement feature
# e2 WIP
# e1 Start feature
# Reset to before commits (keep changes)
git reset --soft HEAD~5
# Now create one clean commit
git commit -m "Implement feature X"
Scenario 5: Discard Local Changes
# Made a mess, want to start over
git reset --hard HEAD
# Or go back to specific commit
git reset --hard origin/main
Reset Specific Paths:
# Reset specific file to specific commit
git reset abc123 -- path/to/file.txt
# Reset multiple files
git reset HEAD -- file1.txt file2.txt
# Reset directory
git reset HEAD -- src/
Recovery from Reset:
If you reset by mistake:
# View reflog
git reflog
# Find commit before reset
# abc123 HEAD@{1}: commit: Important work
# Reset back
git reset --hard abc123
# or
git reset --hard HEAD@{1}
git revert - Revert Commits
Purpose: Create new commit that undoes changes from previous commit(s). Safe for public branches because it doesn't rewrite history.
Basic Usage:
# Revert last commit
git revert HEAD
# Revert specific commit
git revert abc123
# Revert multiple commits
git revert abc123 def456 ghi789
# Revert range
git revert HEAD~3..HEAD
# Revert without committing (stage changes)
git revert -n abc123
git revert --no-commit abc123
How Revert Works:
Original:
A---B---C---D (HEAD, main)
After git revert C:
A---B---C---D---E (HEAD, main)
↑
Revert C (undoes changes from C)
Commit E contains the inverse of changes in commit C.
Revert vs Reset:
| Feature | git revert |
git reset |
|---|---|---|
| History | Creates new commit | Moves HEAD pointer |
| Public branches | ✅ Safe | ❌ Dangerous |
| Undo method | Add inverse changes | Remove commits |
| History rewrite | No | Yes |
Revert Options:
Edit Commit Message:
# Revert and open editor for message
git revert abc123
# Use default message
git revert abc123 --no-edit
# Custom message
git revert abc123 -m "Revert problematic feature"
Revert Merge Commits:
Merge commits have multiple parents, must specify which to revert to:
# Revert merge commit, keeping main history
git revert -m 1 merge-commit-hash
# -m 1: Keep changes from first parent (main)
# -m 2: Keep changes from second parent (merged branch)
Multiple Reverts:
# Revert multiple commits separately
git revert abc123 def456
# Revert range (oldest..newest)
git revert abc123..def456
# Revert without auto-commit (for manual adjustments)
git revert -n abc123 def456 ghi789
git commit -m "Revert multiple changes"
Abort Revert:
# If revert causes conflicts
git revert --abort
# Or if using -n flag
git revert --quit
Continue Revert:
# After resolving conflicts
git add resolved-file.txt
git revert --continue
Real-World Revert Scenarios:
Scenario 1: Revert Bad Commit on Main
# Bad commit pushed to main
git log --oneline
# abc123 (HEAD -> main, origin/main) Bad feature
# def456 Previous commit
# Revert it (safe for public branch)
git revert abc123
# Push revert
git push origin main
Scenario 2: Revert Merge
# Merged feature branch, but it broke production
git log --oneline --graph
# * abc123 (HEAD -> main) Merge branch 'feature'
# |\
# | * def456 Feature commit
# * | ghi789 Main commit
# Revert merge
git revert -m 1 abc123
# Push
git push origin main
Scenario 3: Revert Series of Commits
# Last 5 commits introduced bugs
git revert --no-commit HEAD~4..HEAD
# Review changes
git diff --staged
# Commit all reverts together
git commit -m "Revert commits that introduced bugs"
Scenario 4: Partial Revert
# Revert commit but stage only (no commit)
git revert -n abc123
# Unstage specific files you want to keep
git restore --staged file-to-keep.txt
# Commit partial revert
git commit -m "Partially revert abc123"
git cherry-pick (Covered in Next Section)
Cherry-picking applies specific commits from one branch to another.
16. Cherry-Picking and Patching {#cherry-picking}
git cherry-pick - Apply Changes from Existing Commits
Purpose: Apply changes from specific commits onto current branch, creating new commits.
Basic Usage:
# Cherry-pick single commit
git cherry-pick abc123
# Cherry-pick multiple commits
git cherry-pick abc123 def456 ghi789
# Cherry-pick range
git cherry-pick abc123..def456
# Cherry-pick without committing
git cherry-pick -n abc123
git cherry-pick --no-commit abc123
How Cherry-Pick Works:
Source branch:
A---B---C---D (feature)
Target branch before:
A---B---E---F (main, HEAD)
After git cherry-pick C:
A---B---E---F---C' (main, HEAD)
C' is new commit with same changes as C but different hash
Cherry-Pick Options:
Edit Commit Message:
# Cherry-pick and edit message
git cherry-pick -e abc123
git cherry-pick --edit abc123
# Use original message
git cherry-pick -x abc123 # Adds "cherry picked from" note
Mainline Parent (for Merges):
# Cherry-pick merge commit
git cherry-pick -m 1 merge-commit
# -m 1: Use first parent
# -m 2: Use second parent
Sign Off:
# Add Signed-off-by line
git cherry-pick -s abc123
git cherry-pick --signoff abc123
Continue, Abort, Quit:
# If conflicts occur
git cherry-pick abc123
# CONFLICT...
# Resolve conflicts
git add resolved-file.txt
# Continue
git cherry-pick --continue
# Or abort
git cherry-pick --abort
# Or quit (leave in current state)
git cherry-pick --quit
Multiple Cherry-Picks:
# Cherry-pick range (exclusive start, inclusive end)
git cherry-pick abc123..def456
# Cherry-pick all commits from branch
git cherry-pick feature-branch
# Cherry-pick with strategy
git cherry-pick -X theirs abc123
git cherry-pick -X ours abc123
Real-World Cherry-Pick Scenarios:
Scenario 1: Apply Hotfix to Multiple Branches
# Hotfix committed to main
git checkout main
git commit -m "Fix critical security bug"
# Commit: abc123
# Apply to release branch
git checkout release-2.0
git cherry-pick abc123
# Apply to release branch 1.0
git checkout release-1.0
git cherry-pick abc123
Scenario 2: Extract Commits from Feature Branch
# Feature branch has 10 commits
# Only commits 3 and 7 are ready
git checkout main
git cherry-pick <commit-3-hash>
git cherry-pick <commit-7-hash>
# Leave rest of feature for later
Scenario 3: Move Commit to Different Branch
# Accidentally committed to main instead of feature
git log --oneline
# abc123 (HEAD -> main) Feature work
# Create feature branch
git branch feature abc123
# Remove from main (if not pushed)
git reset --hard HEAD~1
# Or revert if already pushed
git revert abc123
Scenario 4: Apply Commits Between Forks
# Add fork as remote
git remote add other-fork https://github.com/other/repo.git
git fetch other-fork
# Cherry-pick their commit
git cherry-pick other-fork/main~2
Scenario 5: Selective Backporting
# Backport specific fixes to older version
git checkout stable-1.0
git cherry-pick bug-fix-hash-1
git cherry-pick bug-fix-hash-2
git cherry-pick feature-hash # Skip if not compatible
# Test and push
git push origin stable-1.0
git apply - Apply Patch Files
Purpose: Apply changes from patch files (created with git diff or git format-patch).
Basic Usage:
# Apply patch
git apply patch-file.patch
# Apply and stage changes
git apply --index patch-file.patch
# Check if patch can be applied
git apply --check patch-file.patch
# Apply with statistics
git apply --stat patch-file.patch
# Reject conflicts to separate files
git apply --reject patch-file.patch
Creating Patches:
Method 1: Using git diff:
# Create patch from uncommitted changes
git diff > changes.patch
# Create patch from specific commits
git diff abc123 def456 > feature.patch
# Create patch for specific files
git diff -- file1.txt file2.txt > files.patch
Method 2: Using git format-patch:
# Create patch for last commit
git format-patch -1 HEAD
# Create patches for last 3 commits
git format-patch -3
# Create patches since specific commit
git format-patch abc123..HEAD
# Create patch for specific commit
git format-patch -1 abc123
# Output to specific directory
git format-patch -o patches/ abc123..HEAD
Applying Patches:
# Apply patch file
git apply changes.patch
# Apply and stage
git apply --index changes.patch
# Apply with three-way merge
git apply -3 changes.patch
# Apply formatted patches (preserves author, message)
git am patches/0001-feature.patch
# Apply multiple formatted patches
git am patches/*.patch
Patch Options:
Check Before Applying:
# Dry run
git apply --check patch.patch
# Show what would change
git apply --stat patch.patch
# Show actual changes
git apply --summary patch.patch
Handle Whitespace:
# Ignore whitespace changes
git apply --ignore-whitespace patch.patch
# Warn about whitespace errors
git apply --whitespace=warn patch.patch
# Fix whitespace automatically
git apply --whitespace=fix patch.patch
Reverse Apply:
# Unapply patch
git apply --reverse patch.patch
git apply -R patch.patch
Partial Application:
# Apply specific files from patch
git apply --include='*.txt' patch.patch
# Exclude specific files
git apply --exclude='test/*' patch.patch
Real-World Patching Scenarios:
Scenario 1: Email-Based Workflow
# Create patches to email
git format-patch -2 HEAD --stdout > feature.patch
# Email feature.patch
# Recipient applies
git am feature.patch
Scenario 2: Quick Fix Sharing
# Create quick fix
git diff > quickfix.patch
# Send to colleague
# Colleague applies
git apply quickfix.patch
git commit -am "Apply quick fix"
Scenario 3: Code Review via Patches
# Generate patches for review
git format-patch origin/main
# Output: 0001-commit1.patch, 0002-commit2.patch
# Reviewer applies and tests
git am 0001-commit1.patch
# Test
git am 0002-commit2.patch
# Test
Scenario 4: Resolve Conflicts in Patches
# Apply patch with conflicts
git apply changes.patch
# error: patch failed...
# Apply with reject files
git apply --reject changes.patch
# Creates .rej files for failed hunks
# Manually apply .rej files
# Then stage
git add .
git commit
git format-patch - Prepare Patches for Email
Purpose: Create properly formatted patch files suitable for email submission, preserving commit metadata.
Basic Usage:
# Create patch for last commit
git format-patch -1
# Create patches for last N commits
git format-patch -5
# Create patches since commit
git format-patch abc123
# Create patches for range
git format-patch abc123..def456
# Output to directory
git format-patch -o output-dir/ abc123..HEAD
Format-Patch Options:
# Include cover letter
git format-patch --cover-letter -3
# Add threading headers for email
git format-patch --thread -3
# Include signature
git format-patch --signature="My Signature" -1
# Suppress diff output
git format-patch --stdout abc123 > patch.txt
# Include binary files
git format-patch --binary -1
Real-World Format-Patch Usage:
Linux Kernel Style Submission:
# Create patch series with cover letter
git format-patch --cover-letter -3 --thread -o patches/
# Edit cover letter
vim patches/0000-cover-letter.patch
# Send via email
git send-email patches/*
git am - Apply Mailbox Format Patches
Purpose: Apply patches from email (created with git format-patch), preserving commit information.
Basic Usage:
# Apply single patch
git am 0001-feature.patch
# Apply multiple patches
git am patches/*.patch
# Apply from stdin
cat feature.patch | git am
# Apply interactively
git am -i patches/*.patch
Handling Conflicts:
# Apply patch
git am feature.patch
# Conflict occurs
# Resolve conflicts
vim conflicted-file.txt
git add conflicted-file.txt
# Continue
git am --continue
# Skip this patch
git am --skip
# Abort
git am --abort
AM Options:
# Apply with three-way merge
git am -3 patch.patch
# Sign off patches
git am -s patches/*.patch
# Keep non-patch content in commit message
git am -k patches/*.patch
# Ignore whitespace
git am --ignore-whitespace patches/*.patch
This completes the comprehensive sections on Git fundamentals, branching, merging, rebasing, remotes, inspection, stashing, tagging, history modification, and cherry-picking/patching.
Top comments (0)