Git and GitHub Ultimate Guide: 2026 Complete Reference
Why Most Developers Fear Git
Here's an uncomfortable truth: many experienced developers don't actually understand Git. They memorize commands, but they don't know what's happening underneath. When something breaks, they're copying commands from Stack Overflow at 11 PM, praying they don't make it worse.
Real developer confession: "I spent two hours trying to recover work after running git reset --hard. Those commits were recoverable the whole time with git reflog. I just didn't know."
This guide changes that. We'll cover Git from zero to advanced, explaining not just what commands do, but why they work. By the end, you'll think in graphs and pointers, not memorized commands. When something breaks, you'll know exactly what happened and how to fix it.
Understanding Git: The Mental Model That Changes Everything
Git Is a Database of Snapshots
Forget everything you think you know. Git is a database, and the fundamental unit of that database is the commit.
What is a commit? It's a snapshot, a complete photograph of your entire project at one moment in time. Not the changes you made, the entire state of every file.
Each commit contains three things:
1. A pointer to that complete snapshot - every file exactly as it existed at that moment
2. Metadata - who created it, when, and the commit message
3. A pointer to the parent commit - the commit that came directly before
When you make a new commit, Git saves the full state of your project and links it back to where you were. This creates a chain. Each commit points to its parent. Parents point to their parents, all the way back to the very first commit.
That first commit is special. It has no parent. It's the origin point of your project history. When you merge branches, you'll create commits with two parents. But remember: commits point backwards, always backwards. Children know their parents, but parents never know their future children.
The Directed Acyclic Graph (DAG)
If everyone just committed one after another, you'd have a straight line. Simple history. But real development is messier. You branch off for a feature. A colleague branches for a bug fix. Now you have commits sharing the same parent but going different directions. Then you merge. Now you have a commit with two parents.
This structure has a name: a DAG (Directed Acyclic Graph). Sounds intimidating. It's not.
Think of it like a family tree:
Directed means relationships only go one way. Children point to parents. Never the reverse.
Acyclic means no loops. Nobody can be their own grandparent. You can't create a cycle in history.
Graph just means nodes and connections, commits and the links between them.
This graph is your project's history. Every branch, every merge, every decision any developer ever made captured in the structure. Because every commit is a complete snapshot, you can jump to any point in this graph and see your project exactly as it existed. No reconstruction, no playing back changes, just there.
Branches Are Just Sticky Notes
When learning Git, people assume branches are heavy, complex things, a whole separate copy of the codebase. This is completely wrong.
A branch is just a sticky note, a pointer, a tiny text file that contains one piece of information: the hash of a commit. That's it.
When you create a new branch called feature-login, Git creates a small file that says "this branch points to commit A1B23." Nothing more. The commits themselves have no idea what branches exist. Branches don't contain commits. They point at them.
When you make a new commit while on a branch, Git creates the new commit (pointing back to where you were), then moves the sticky note forward to the new commit. That's branching. The entire concept.
This is why creating a branch is instant. You're not copying anything. You're placing a sticky note.
And what about main? Not special. Just another sticky note that by convention we've agreed is the primary line of work.
HEAD: Your Current Location
So we have commits, we have branches pointing at commits. But how does Git know where you are, what you're working on?
Meet HEAD. HEAD is Git's way of tracking your location. It's another pointer, but usually instead of pointing at a commit, it points at a branch.
When you're on the main branch, HEAD points to main. Main points to a commit. That's your current location.
Run git checkout feature and HEAD moves to point at the feature branch. You're now working on that branch.
Detached HEAD State:
What if you check out a specific commit, not a branch, a raw commit hash? Now HEAD points directly to that commit. No branch in between. Git calls this "detached HEAD state."
Sounds scary. It's not, if you understand it.
Here's what happens: You can still work. You can still commit. But no branch is following along. When you switch away, those commits are orphaned. No branch points to them. They're floating in space. Eventually, Git's garbage collection will clean them up.
Real disaster scenario: A developer checked out an old commit to test something, found a bug while there, fixed it, committed the fix, then ran git checkout main to merge their fix. The fix vanished. It was never on a branch. They couldn't find it. It was orphaned, then garbage collected. Two hours of work gone.
This is why Git warns you. Not because you're broken, but because anything you commit won't be saved unless you create a branch to hold it.
Git Installation and First-Time Setup
Installing Git
macOS and Linux: Git comes pre-installed. Verify with:
bash
git --version
# git version 2.43.0Windows: Download Git Bash from git-scm.com. This includes Git and a terminal environment.
Configuring Your Identity
Before you can commit, Git requires your name and email. This information is baked into every commit you make.
Set global configuration (applies to all repositories):
bash
# Set your name
git config --global user.name "Your Full Name"
# Set your email
git config --global user.email "you@example.com"
# Verify your settings
git config --listSet local configuration (for a specific repository only):
bash
# Navigate to your repository first
cd your-project
# Set repository-specific name
git config user.name "Different Name"
# Set repository-specific email
git config user.email "work@company.com"Problem: You get "fatal: unable to auto-detect email address" when trying to commit.
Solution: You haven't configured your identity. Run the global config commands above.
Common use case: You use a personal email for open source projects but a work email for company repositories. Set global config to your personal email, then override with local config in work repositories.
Configuration Hierarchy
Git configuration works in layers, each level overriding the previous:
System level (/etc/gitconfig) - applies to all users on the machine
Global level (~/.gitconfig) - applies to your user account
Local level (.git/config) - applies to the specific repository
Worktree level - applies to specific worktrees
bash
# View all settings with their origins
git config --list --show-origin
# View specific setting
git config user.email
# Edit global config directly
git config --global --editEssential Configuration Options
bash
# Set default branch name for new repositories
git config --global init.defaultBranch main
# Set default text editor
git config --global core.editor "code --wait" # VS Code
git config --global core.editor "vim" # Vim
git config --global core.editor "nano" # Nano
# Enable helpful colorized output
git config --global color.ui auto
# Set up useful aliases
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.lg "log --oneline --graph --decorate"The Three Areas: Working Directory, Staging, Repository
Before we talk about undoing things, you need to understand one more concept that confuses people. Git actually has three areas where your code can live:
1. Working Directory - the actual files on your disk, what you see in your code editor
2. Staging Area (Index) - a waiting room where you prepare what will go into your next commit
3. Repository - the database of commits, the permanent history
When you edit a file, it changes in your working directory. Git notices but doesn't care yet.
When you run git add, you're moving those changes to the staging area. "I want this in my next commit."
When you run git commit, Git takes everything in staging and creates a commit in permanent history.
Why does this matter? Because the commands we'll discuss manipulate these layers differently. Understanding the three layers is the key to understanding reset, checkout, and every other Git command.
Essential Git Commands: The Complete Reference
Creating and Cloning Repositories
Initialize a new repository:
bash
# Create a new Git repository in the current folder
git init
# Create a new repository in a specific folder
git init my-projectClone an existing repository:
bash
# Clone via HTTPS
git clone https://github.com/username/repository.git
# Clone via SSH (recommended)
git clone git@github.com:username/repository.git
# Clone into a specific folder
git clone https://github.com/username/repository.git my-folder
# Clone a specific branch
git clone -b develop https://github.com/username/repository.gitChecking Status and Viewing Changes
bash
# See the state of your working directory and staging area
git status
# Short format status
git status -s
# See what changes you've made but haven't staged
git diff
# See what changes are staged for the next commit
git diff --staged
# See changes between two commits
git diff commit1 commit2
# See changes for a specific file
git diff filename.txtAdding and Committing Changes
bash
# Stage a specific file
git add filename.txt
# Stage multiple specific files
git add file1.txt file2.txt file3.txt
# Stage all changes in current directory and subdirectories
git add .
# Stage all changes in the entire repository
git add -A
# Stage only parts of a file (interactive)
git add -p filename.txt
# Commit staged changes with a message
git commit -m "Your commit message"
# Commit with a multi-line message
git commit -m "Short summary" -m "Longer description of changes"
# Stage all tracked files and commit in one command
git commit -am "Commit message"
# Amend the last commit (change message or add files)
git commit --amend -m "New commit message"Problem: You committed but forgot to add a file.
Solution:
bash
git add forgotten-file.txt
git commit --amend --no-editWarning: Never amend commits that have been pushed to a shared repository. This rewrites history and will cause problems for collaborators.
Viewing History
bash
# View commit history
git log
# Compact one-line format
git log --oneline
# Show graph of branch structure
git log --oneline --graph --decorate
# Show commits by a specific author
git log --author="Name"
# Show commits from the last week
git log --since="1 week ago"
# Show commits affecting a specific file
git log -- filename.txt
# Show the actual changes in each commit
git log -p
# Limit to last N commits
git log -5Branch Management: The Complete Guide
Creating, Switching, and Listing Branches
bash
# List all local branches
git branch
# List all branches including remote
git branch -a
# List remote branches only
git branch -r
# Create a new branch (stay on current branch)
git branch feature-name
# Create and switch to a new branch
git checkout -b feature-name
# Modern way to create and switch (Git 2.23+)
git switch -c feature-name
# Switch to an existing branch
git checkout branch-name
git switch branch-name
# Switch to the previous branch
git checkout -
git switch -Renaming and Deleting Branches
bash
# Rename current branch
git branch -m new-name
# Rename a different branch
git branch -m old-name new-name
# Delete a branch (safe - only if merged)
git branch -d branch-name
# Force delete a branch (even if unmerged)
git branch -D branch-name
# Delete a remote branch
git push origin --delete branch-nameProblem: You can't delete a branch because it has unmerged changes.
Solution: Either merge the branch first, or use -D for force delete if you're sure you don't need those changes.
Branch Tracking
bash
# Set upstream branch for current branch
git branch -u origin/main
# Push and set upstream in one command
git push -u origin feature-name
# See tracking information for all branches
git branch -vvMerging: Combining Work from Different Branches
Basic Merge
bash
# First, switch to the branch you want to merge INTO
git checkout main
# Then merge the feature branch
git merge feature-branch
# Merge with a commit message
git merge feature-branch -m "Merge feature into main"
# Merge without fast-forward (always create merge commit)
git merge --no-ff feature-branchFast-Forward vs True Merge
When the branch you're merging hasn't diverged from your current branch, Git can simply move the pointer forward. This is a "fast-forward" merge. No new commit is created.
When branches have diverged (both have unique commits), Git creates a new merge commit with two parents.
bash
# Force a merge commit even when fast-forward is possible
git merge --no-ff feature-branch
# Only merge if fast-forward is possible (fail otherwise)
git merge --ff-only feature-branchResolving Merge Conflicts
When Git can't automatically merge because the same lines were edited differently, you get a conflict.
Conflict markers look like this:
<<<<<<< HEAD
Your changes on the current branch
=======
Changes from the branch being merged
>>>>>>> feature-branchSteps to resolve:
bash
# 1. See which files have conflicts
git status
# 2. Open conflicted files in your editor
# 3. Edit the file to resolve conflicts (remove markers, keep desired code)
# 4. Stage the resolved file
git add filename.txt
# 5. Complete the merge
git commit
# Alternative: Abort the merge and start over
git merge --abortChoosing one side entirely:
bash
# Keep your version (current branch)
git checkout --ours filename.txt
git add filename.txt
# Keep their version (branch being merged)
git checkout --theirs filename.txt
git add filename.txtProblem: You're getting endless conflicts when merging.
Solution: This usually means branches have diverged significantly. Consider:
Merging more frequently
Rebasing your feature branch onto the latest main before merging
Having better communication about who's editing which files
Rebase: Rewriting History for Cleaner Merges
Understanding Rebase
Let's set the scene. You created a feature branch from main. You made commits B and C. Meanwhile, main moved forward with commits X and Y.
Option 1: Merge - Create a merge commit with two parents. History shows the truth: two parallel lines of work that joined together.
Option 2: Rebase - Take your commits and replay them on top of the new main.
But you must understand this: A commit's identity is its hash. That hash is generated from the content, the metadata, AND the parent pointer. Change any of those, including the parent, and you get a completely different hash. A different commit.
Git can't "move" commits. That's not a thing.
So what rebase actually does:
Look at commit B, calculate the changes it introduced
Create a NEW commit B' with those same changes, but with Y as its parent instead of the original base
Look at commit C, calculate its changes
Create a NEW commit C' with those changes sitting on top of B'
Move your feature branch to point at C'
The old B and C? Orphaned. They'll eventually be garbage collected.
bash
# Rebase current branch onto main
git rebase main
# Interactive rebase to edit/squash commits
git rebase -i HEAD~3
# Continue after resolving conflicts
git rebase --continue
# Skip a problematic commit
git rebase --skip
# Abort and go back to before rebase started
git rebase --abortThe Golden Rule of Rebase
Never rebase commits that others have seen.
If your colleague has the old commits and you push new commits with the same content but different hashes, Git sees them as completely unrelated work. Merging becomes a nightmare. Duplicate changes appear. Conflicts explode.
But on local branches that you haven't shared, rebase is powerful. It keeps history linear and clean. Just know the trade-off: you're choosing a clean story over the messy truth.
Interactive Rebase
bash
# Interactively rebase the last 5 commits
git rebase -i HEAD~5This opens an editor with options:
pick- use commit as-isreword- use commit but edit the messageedit- stop at this commit to make changessquash- combine with previous commitfixup- like squash but discard the commit messagedrop- remove the commit entirely
Common use case: Squash multiple "WIP" commits into one clean commit before merging:
bash
git rebase -i HEAD~4
# Change "pick" to "squash" for all but the first commit
# Edit the combined commit messageRemote Repositories: Connecting to GitHub
Understanding Remotes
A remote is a URL where your local Git repository can push changes and pull updates. When you clone a repository, Git automatically sets up a remote called origin pointing to where you cloned from.
bash
# View all remotes
git remote -v
# Get detailed information about a remote
git remote show origin
# Add a new remote
git remote add origin https://github.com/username/repo.git
# Add a secondary remote
git remote add upstream https://github.com/original/repo.git
# Change a remote's URL
git remote set-url origin https://github.com/username/new-repo.git
# Rename a remote
git remote rename origin github
# Remove a remote
git remote remove upstreamWorking with Multiple Remotes
Common scenario: You forked a project and need to keep your fork updated with the original.
bash
# Add the original repository as "upstream"
git remote add upstream https://github.com/original/repo.git
# Fetch changes from upstream
git fetch upstream
# Merge upstream changes into your main branch
git checkout main
git merge upstream/main
# Push to your fork
git push origin mainPushing to multiple repositories at once:
bash
# Create an "all" remote that pushes to both
git remote add all https://github.com/username/repo.git
git remote set-url --add --push all https://github.com/username/repo.git
git remote set-url --add --push all https://gitlab.com/username/repo.git
# Now "git push all main" pushes to both
git push all mainFetching, Pulling, and Pushing
bash
# Fetch changes from remote (doesn't modify local branches)
git fetch origin
# Fetch from all remotes
git fetch --all
# Pull changes (fetch + merge)
git pull origin main
# Pull with rebase instead of merge
git pull --rebase origin main
# Push changes to remote
git push origin main
# Push and set upstream tracking
git push -u origin feature-branch
# Push all branches
git push --all origin
# Push all tags
git push --tags
# Force push (dangerous - overwrites remote history)
git push --force origin main
# Safer force push (fails if remote has new commits)
git push --force-with-lease origin mainProblem: "Updates were rejected because the remote contains work that you do not have locally."
Solution: Someone else pushed changes. Pull first, then push:
bash
git pull origin main
git push origin mainProblem: You need to undo a force push that overwrote someone's work.
Solution: The remote's reflog isn't accessible, but if you or a colleague still has the original commits locally:
bash
git reflog # Find the commit before the bad push
git push --force origin <commit-hash>:mainGitHub Authentication: SSH Keys and Personal Access Tokens
Why You Need This
Since August 2021, GitHub removed password authentication for Git operations. You must use either:
SSH Keys - More secure, no token expiration, works seamlessly once configured
Personal Access Tokens (PAT) - Easier initial setup, must be renewed periodically
Setting Up SSH Keys
Step 1: Check for existing SSH keys:
bash
ls -la ~/.ssh
# Look for id_ed25519.pub or id_rsa.pubStep 2: Generate a new SSH key:
bash
# Generate key using Ed25519 (recommended)
ssh-keygen -t ed25519 -C "your_email@example.com"
# If your system doesn't support Ed25519
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"When prompted:
Press Enter to accept the default file location
Enter a secure passphrase (recommended)
Step 3: Add the key to the SSH agent:
bash
# Start the SSH agent
eval "$(ssh-agent -s)"
# Add your key
ssh-add ~/.ssh/id_ed25519Step 4: Copy your public key:
bash
# macOS
pbcopy < ~/.ssh/id_ed25519.pub
# Linux
cat ~/.ssh/id_ed25519.pub
# Then manually copy the output
# Windows (Git Bash)
clip < ~/.ssh/id_ed25519.pubStep 5: Add the key to GitHub:
Go to GitHub Settings → SSH and GPG keys
Click "New SSH key"
Give it a descriptive title (e.g., "Work Laptop 2026")
Paste your public key
Click "Add SSH key"
Step 6: Test the connection:
bash
ssh -T git@github.com
# Should see: "Hi username! You've successfully authenticated..."Step 7: Use SSH URLs for repositories:
bash
# Clone with SSH
git clone git@github.com:username/repo.git
# Change existing repo from HTTPS to SSH
git remote set-url origin git@github.com:username/repo.gitSetting Up Personal Access Tokens
Step 1: Generate a token:
Go to GitHub Settings → Developer settings → Personal access tokens
Click "Generate new token" → "Generate new token (classic)"
Give it a descriptive name
Set expiration (or "No expiration" for convenience)
Select scopes (at minimum:
repofor repository access)Click "Generate token"
Copy the token immediately - you won't see it again!
Step 2: Use the token:
bash
# When Git asks for password, paste your token instead
git clone https://github.com/username/private-repo.git
Username: your-username
Password: <paste your token here>Step 3: Cache the token (so you don't have to enter it every time):
bash
# macOS (uses Keychain)
git config --global credential.helper osxkeychain
# Windows (uses Credential Manager)
git config --global credential.helper wincred
# Linux (cache for 1 hour)
git config --global credential.helper 'cache --timeout=3600'Undo Mistakes: Reset, Revert, and Checkout
Three commands that seem to undo things, but they do completely different operations. Confusing them can cost you work.
Checkout: Moving Your Viewpoint
Checkout moves HEAD. That's its job.
bash
# Move HEAD to point at main
git checkout main
# Move HEAD directly to a commit (detached HEAD)
git checkout a1b2c3d
# Restore a file to its state in another commit
git checkout a1b2c3d -- filename.txt
# Discard changes to a file in working directory
git checkout -- filename.txtNo commits change. No branches move. History is untouched. You're just looking around. Safe, non-destructive, just moving your viewpoint.
Reset: Moving a Branch (Dangerous)
Reset moves a branch. When you're on main and run git reset C1, you're saying "move main to point at this commit."
The commits that were ahead still exist in the database, but orphaned. No branch pointing to them.
Reset has three modes, affecting the three areas differently:
Soft Reset - moves the branch; staging area unchanged; working directory unchanged:
bash
git reset --soft HEAD~1
# Your "undone" changes appear staged and ready to commit againUse case: You made three commits but want to combine them into one. Soft reset then recommit.
Mixed Reset (default) - moves the branch; staging area reset; working directory unchanged:
bash
git reset HEAD~1
# Your changes still exist in your files, just unstagedUse case: You committed something but want to restage it differently. Maybe split it into multiple commits.
Hard Reset - moves the branch; staging area reset; working directory reset:
bash
git reset --hard HEAD~1
# Your files change. Uncommitted work gone from your disk.Warning: I have watched developers lose days of work because they ran git reset --hard thinking they could undo it. The orphan commits are recoverable for a while if you know Git secrets (reflog), but your uncommitted changes? The stuff you never committed? Gone forever.
Use case: You want to completely abandon work and start fresh. You're sure.
Quick reference:
Mode | Branch | Staging | Working Dir | Use Case |
|---|---|---|---|---|
| Moves | Unchanged | Unchanged | Squash commits |
| Moves | Reset | Unchanged | Re-stage differently |
| Moves | Reset | Reset | Abandon everything |
Revert: Safe Undo for Shared History
Revert takes a completely different philosophy. It doesn't move anything. It doesn't abandon anything.
Revert creates a new commit that does the opposite of an old commit.
bash
# Create a new commit that undoes the changes from a specific commit
git revert a1b2c3d
# Revert multiple commits
git revert a1b2c3d b2c3d4e
# Revert without automatically committing
git revert --no-commit a1b2c3dCommit C added 50 lines. git revert C creates commit D that removes those same 50 lines.
History is preserved. The original commit still exists. You've just recorded "we decided to undo what we did earlier."
Use case: You need to undo something that's already been pushed and shared. You can't rewrite shared history, but you can add to it.
Quick Summary:
Command | What Moves | Safety | Use Case |
|---|---|---|---|
| HEAD only | Safe | Exploring history |
| Branch (and potentially working directory) | Risky | Reshape local work |
| Nothing (new commit created) | Safe | Undo shared history |
Git Stash: Saving Work Temporarily
Basic Stash Operations
bash
# Save current changes to stash
git stash
# Save with a descriptive message
git stash save "Work in progress on login feature"
# Save including untracked files
git stash -u
# List all stashes
git stash list
# Apply most recent stash (keep it in stash list)
git stash apply
# Apply specific stash
git stash apply stash@{2}
# Apply and remove from stash list
git stash pop
# View what's in a stash
git stash show stash@{0}
# View detailed diff of stash
git stash show -p stash@{0}
# Delete a specific stash
git stash drop stash@{0}
# Delete all stashes
git stash clearTip: Use git stash apply instead of git stash pop. Apply doesn't delete the stash automatically, giving you a safety net.
Problem: You accidentally dropped or cleared a stash with important work.
Solution: The stash commit still exists temporarily. Find it with:
bash
# Find dangling commits (including dropped stashes)
git fsck --no-reflog | grep commit
# Or search more specifically
git log --graph --oneline --decorate $(git fsck --no-reflog | awk '/dangling commit/ {print $3}')
# Once you find the hash, recover it
git stash apply <commit-hash>Git Reflog: Your Emergency Recovery Tool
The reflog shows everywhere HEAD has pointed recently. Every checkout, every commit, every reset. Those lost commits from your reset, the old commits before you rebased - they're probably still here.
bash
# View reflog
git reflog
# Detailed reflog view
git log -g --pretty=fuller
# Reflog for a specific branch
git reflog show feature-branchTypical reflog output:
a1b2c3d HEAD@{0}: commit: Add new feature
b2c3d4e HEAD@{1}: checkout: moving from main to feature
c3d4e5f HEAD@{2}: reset: moving to HEAD~2
d4e5f6g HEAD@{3}: commit: Old commit that was "lost"Recovery Scenarios
Scenario 1: Recover from hard reset:
bash
# You ran git reset --hard and lost commits
git reflog
# Find the commit hash before the reset (HEAD@{N})
git reset --hard HEAD@{3}
# Or create a branch at that point
git branch recovery HEAD@{3}Scenario 2: Recover deleted branch:
bash
git reflog
# Find the last commit on the deleted branch
git branch recovered-branch a1b2c3dScenario 3: Undo a bad rebase:
bash
git reflog
# Find the commit before the rebase started
git reset --hard HEAD@{5}Important caveats:
Reflog is local only - you can't access someone else's reflog
Entries expire (usually 90 days for reachable, 30 for unreachable commits)
After garbage collection, orphaned commits may be permanently deleted
Don't wait months to recover - act quickly when disaster strikes
Working with Git When Multiple Developers Edit the Same Code
The Scenario
You and a colleague are both working on the same repository. You make changes, they make changes. Neither of you has pushed. What happens?
When you try to push:
bash
git push origin main
# ! [rejected] main -> main (fetch first)
# error: failed to push some refs to 'origin'Git tells you the remote has commits you don't have.
Solution:
bash
# Option 1: Pull and merge
git pull origin main
# Resolve any conflicts
git push origin main
# Option 2: Pull with rebase (cleaner history)
git pull --rebase origin main
# Resolve any conflicts
git push origin mainPreventing Conflicts
Strategy 1: Communicate - Let teammates know what files you're working on
Strategy 2: Pull frequently - Start each work session with git pull
Strategy 3: Use feature branches - Each developer works on their own branch, merge when complete
Strategy 4: Smaller, focused commits - Easier to merge and resolve conflicts
When the Same Git Repo Is Open in Two Locations
Scenario: You have the repo open on your laptop and desktop, made changes on both, neither synced.
Solution:
bash
# On your laptop (let's say this has the changes you want to keep)
git add .
git commit -m "Laptop changes"
git push origin main
# On your desktop
git fetch origin
# See the divergence
git log --oneline --graph main origin/main
# Now choose how to integrate:
# Option A: Merge remote changes
git merge origin/main
# Option B: Rebase your local changes on top
git rebase origin/main
# Then push
git push origin mainComprehensive Git Command Cheat Sheet
Setup and Configuration
bash
git config --global user.name "Name" # Set your name
git config --global user.email "email" # Set your email
git config --global init.defaultBranch main # Set default branch name
git config --list # View all config settings
git config --global --edit # Edit global config fileCreating Repositories
bash
git init # Initialize new repository
git clone <url> # Clone existing repository
git clone <url> <folder> # Clone into specific folderBasic Workflow
bash
git status # Check status
git add <file> # Stage specific file
git add . # Stage all changes
git commit -m "message" # Commit with message
git commit -am "message" # Stage tracked + commit
git commit --amend # Modify last commitViewing History
bash
git log # View commit history
git log --oneline # Compact view
git log --graph # Show branch graph
git log -p # Show changes in each commit
git diff # Unstaged changes
git diff --staged # Staged changes
git show <commit> # Show specific commitBranches
bash
git branch # List local branches
git branch -a # List all branches
git branch <name> # Create branch
git checkout -b <name> # Create and switch
git switch <name> # Switch to branch
git switch -c <name> # Create and switch (modern)
git branch -d <name> # Delete branch (safe)
git branch -D <name> # Delete branch (force)
git branch -m <new> # Rename current branchMerging and Rebasing
bash
git merge <branch> # Merge branch into current
git merge --no-ff <branch> # Force merge commit
git merge --abort # Abort merge in progress
git rebase <branch> # Rebase onto branch
git rebase -i HEAD~3 # Interactive rebase
git rebase --continue # Continue after conflict
git rebase --abort # Abort rebaseRemote Repositories
bash
git remote -v # List remotes
git remote add <n> <url> # Add remote
git remote remove <name> # Remove remote
git remote set-url <n> <u> # Change remote URL
git fetch <remote> # Fetch from remote
git pull <remote> <branch> # Pull changes
git push <remote> <branch> # Push changes
git push -u origin <branch> # Push and set upstream
git push --force-with-lease # Safer force pushStashing
bash
git stash # Stash changes
git stash save "message" # Stash with message
git stash -u # Include untracked files
git stash list # List stashes
git stash apply # Apply most recent
git stash pop # Apply and remove
git stash drop # Delete stash
git stash clear # Delete all stashesUndoing Changes
bash
git checkout -- <file> # Discard file changes
git restore <file> # Discard file changes (modern)
git reset HEAD <file> # Unstage file
git restore --staged <file> # Unstage file (modern)
git reset --soft HEAD~1 # Undo commit, keep staged
git reset HEAD~1 # Undo commit, keep changes
git reset --hard HEAD~1 # Undo commit, discard all
git revert <commit> # Create undo commitRecovery
bash
git reflog # View HEAD history
git reset --hard HEAD@{n} # Restore to reflog point
git fsck --no-reflog # Find dangling commits
git cherry-pick <commit> # Apply specific commitInformation
bash
git blame <file> # Who changed each line
git show <commit> # Show commit details
git log --follow <file> # File history with renames
git shortlog # Summarize commits by authorCommon Problems and Solutions Quick Reference
Problem | Solution |
|---|---|
"fatal: unable to auto-detect email address" |
|
"Updates were rejected because the remote contains work..." |
|
"You have divergent branches" |
|
"Your branch is behind" |
|
"Merge conflict in..." | Edit file, remove markers, |
Accidentally committed to wrong branch |
|
Need to undo pushed commit |
|
Lost commits after reset |
|
Deleted branch with unmerged changes |
|
Dropped stash |
|
Detached HEAD warning |
|
"Permission denied (publickey)" | SSH key not set up or not added to GitHub |
"Repository not found" | Check URL, authentication, and repository permissions |
Real-World Git Workflow: Complete Step-by-Step Example
Let's walk through exactly how developers use Git in real-world projects. We'll start from absolute zero: installing Git, configuring your identity, creating a project, connecting to GitHub, working with branches, and managing multiple remote repositories.
Scenario Overview
You're starting a new web project. You'll:
Install and configure Git
Create a local project and initialize Git
Connect to GitHub (primary origin)
Create a development branch and make changes
Push to the dev branch, then merge to main
Add a secondary remote (backup or deployment)
Sync everything across multiple remotes
Step 1: Install Git and Verify Installation
On macOS (using Homebrew):
bash
# Install Homebrew if you don't have it
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install Git
brew install git
# Verify installation
git --version
# Output: git version 2.43.0On Windows:
bash
# Download Git from https://git-scm.com/download/win
# Run the installer, accept defaults
# Open Git Bash and verify
git --versionOn Linux (Ubuntu/Debian):
bash
sudo apt update
sudo apt install git
git --versionStep 2: Configure Your Identity
This is required before you can make any commits. Git embeds this information in every commit.
bash
# Set your name (use your real name)
git config --global user.name "John Developer"
# Set your email (use the email associated with your GitHub account)
git config --global user.email "john.developer@email.com"
# Set default branch name to 'main'
git config --global init.defaultBranch main
# Set your preferred text editor
git config --global core.editor "code --wait" # VS Code
# Or: git config --global core.editor "vim" # Vim
# Verify your configuration
git config --list
# Output:
# user.name=John Developer
# user.email=john.developer@email.com
# init.defaultbranch=main
# core.editor=code --waitWhat just happened? Git stored these settings in ~/.gitconfig (your home directory). Every repository on your computer will use these settings unless overridden locally.
Step 3: Create Your Project and Initialize Git
bash
# Create project folder
mkdir my-awesome-project
cd my-awesome-project
# Initialize Git repository
git init
# Output:
# Initialized empty Git repository in /Users/john/my-awesome-project/.git/What just happened? Git created a hidden .git folder that stores all version control data. Your project is now a Git repository.
bash
# Create some initial project files
echo "# My Awesome Project" > README.md
echo "This is a web application built with modern technologies." >> README.md
# Create a simple HTML file
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Awesome Project</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Welcome to My Project</h1>
<p>Version 1.0</p>
</body>
</html>
EOF
# Create a CSS file
cat > styles.css << 'EOF'
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #333;
}
EOF
# Check status - see what Git notices
git status
# Output:
# On branch main
# No commits yet
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
# README.md
# index.html
# styles.cssStep 4: Make Your First Commit
bash
# Stage all files
git add .
# Check what's staged
git status
# Output:
# On branch main
# No commits yet
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
# new file: README.md
# new file: index.html
# new file: styles.css
# Create your first commit
git commit -m "Initial commit: Add project structure with HTML, CSS, and README"
# Output:
# [main (root-commit) a1b2c3d] Initial commit: Add project structure with HTML, CSS, and README
# 3 files changed, 28 insertions(+)
# create mode 100644 README.md
# create mode 100644 index.html
# create mode 100644 styles.css
# View your commit history
git log --oneline
# Output:
# a1b2c3d (HEAD -> main) Initial commit: Add project structure with HTML, CSS, and READMEWhat just happened? You took a snapshot of your project. Git now has a complete record of all three files at this moment in time.
Step 5: Create a GitHub Repository and Connect as Origin
First, create a repository on GitHub:
Go to https://github.com
Click the "+" icon → "New repository"
Name it
my-awesome-projectLeave it empty (don't add README, .gitignore, or license)
Click "Create repository"
Now connect your local repository to GitHub:
bash
# Add GitHub as the remote "origin"
git remote add origin git@github.com:johndeveloper/my-awesome-project.git
# Verify the remote was added
git remote -v
# Output:
# origin git@github.com:johndeveloper/my-awesome-project.git (fetch)
# origin git@github.com:johndeveloper/my-awesome-project.git (push)
# Push your main branch to GitHub and set upstream tracking
git push -u origin main
# Output:
# Enumerating objects: 5, done.
# Counting objects: 100% (5/5), done.
# Delta compression using up to 8 threads
# Compressing objects: 100% (4/4), done.
# Writing objects: 100% (5/5), 612 bytes | 612.00 KiB/s, done.
# Total 5 (delta 0), reused 0 (delta 0)
# To github.com:johndeveloper/my-awesome-project.git
# * [new branch] main -> main
# Branch 'main' set up to track remote branch 'main' from 'origin'.What just happened? Your local repository is now connected to GitHub. The -u flag sets up tracking, so future git push and git pull commands know where to go.
Step 6: Create a Development Branch
In real-world projects, you never work directly on main. You create feature or development branches.
bash
# Create and switch to a new 'dev' branch
git checkout -b dev
# Output:
# Switched to a new branch 'dev'
# Verify you're on the dev branch
git branch
# Output:
# main
# * dev
# See all branches including remote
git branch -a
# Output:
# main
# * dev
# remotes/origin/mainWhat just happened? You created a new branch called dev that points to the same commit as main. You're now working on dev.
Step 7: Make Changes on the Dev Branch
bash
# Add a new feature - a JavaScript file
cat > app.js << 'EOF'
// Main application logic
document.addEventListener('DOMContentLoaded', function() {
console.log('App initialized');
// Add dynamic greeting
const greeting = document.createElement('p');
greeting.textContent = 'JavaScript is working!';
greeting.style.color = 'green';
document.body.appendChild(greeting);
});
EOF
# Update index.html to include the JavaScript
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Awesome Project</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Welcome to My Project</h1>
<p>Version 1.1 - Now with JavaScript!</p>
<script src="app.js"></script>
</body>
</html>
EOF
# Check what changed
git status
# Output:
# On branch dev
# Changes not staged for commit:
# modified: index.html
# Untracked files:
# app.js
# See the actual changes in index.html
git diff index.html
# Output shows the line changes with - and + markers
# Stage all changes
git add .
# Commit with descriptive message
git commit -m "Add JavaScript functionality and update to version 1.1"
# Output:
# [dev b2c3d4e] Add JavaScript functionality and update to version 1.1
# 2 files changed, 15 insertions(+), 1 deletion(-)
# create mode 100644 app.jsStep 8: Push the Dev Branch to GitHub
bash
# Push dev branch to origin and set upstream
git push -u origin dev
# Output:
# Enumerating objects: 6, done.
# Counting objects: 100% (6/6), done.
# Delta compression using up to 8 threads
# Compressing objects: 100% (4/4), done.
# Writing objects: 100% (4/4), 548 bytes | 548.00 KiB/s, done.
# Total 4 (delta 1), reused 0 (delta 0)
# remote: Resolving deltas: 100% (1/1), completed with 1 local object.
# remote: Create a pull request for 'dev' on GitHub by visiting:
# remote: https://github.com/johndeveloper/my-awesome-project/pull/new/dev
# To github.com:johndeveloper/my-awesome-project.git
# * [new branch] dev -> dev
# Branch 'dev' set up to track remote branch 'dev' from 'origin'.
# Verify all branches
git branch -a
# Output:
# main
# * dev
# remotes/origin/dev
# remotes/origin/mainStep 9: Simulate Team Workflow - Pull Before Working
In a real team environment, always pull latest changes before starting work.
bash
# Make sure you have the latest changes from remote
git pull origin dev
# Output (if no changes):
# Already up to date.
# Output (if there were changes):
# remote: Enumerating objects: 5, done.
# Updating b2c3d4e..c3d4e5f
# Fast-forward
# styles.css | 5 +++++
# 1 file changed, 5 insertions(+)Make another change on dev:
bash
# Add more styling
cat >> styles.css << 'EOF'
/* New button styles */
.btn {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.btn:hover {
background-color: #0056b3;
}
EOF
# Stage and commit
git add styles.css
git commit -m "Add button styling components"
# Push to dev
git push origin dev
# Output:
# Enumerating objects: 5, done.
# To github.com:johndeveloper/my-awesome-project.git
# b2c3d4e..c3d4e5f dev -> devStep 10: Merge Dev Branch into Main
Your feature is complete and tested. Time to merge it into the main branch.
bash
# Switch to main branch
git checkout main
# Output:
# Switched to branch 'main'
# Your branch is up to date with 'origin/main'.
# Pull latest main (in case teammates made changes)
git pull origin main
# Output:
# Already up to date.
# Merge dev into main
git merge dev
# Output:
# Updating a1b2c3d..c3d4e5f
# Fast-forward
# app.js | 11 +++++++++++
# index.html | 3 ++-
# styles.css | 12 ++++++++++++
# 3 files changed, 25 insertions(+), 1 deletion(-)
# create mode 100644 app.js
# View the merged history
git log --oneline --graph
# Output:
# * c3d4e5f (HEAD -> main, origin/dev, dev) Add button styling components
# * b2c3d4e Add JavaScript functionality and update to version 1.1
# * a1b2c3d (origin/main) Initial commit: Add project structureWhat just happened? Since main hadn't changed since we branched off, Git did a "fast-forward" merge—it simply moved the main pointer forward to match dev.
bash
# Push the updated main to GitHub
git push origin main
# Output:
# Total 0 (delta 0), reused 0 (delta 0)
# To github.com:johndeveloper/my-awesome-project.git
# a1b2c3d..c3d4e5f main -> mainStep 11: Add a Secondary Remote (Backup/Deployment)
Real-world scenario: You want to also push to GitLab as a backup, or to AWS CodeCommit for deployment.
bash
# Add GitLab as a secondary remote called "backup"
git remote add backup git@gitlab.com:johndeveloper/my-awesome-project.git
# Verify both remotes
git remote -v
# Output:
# backup git@gitlab.com:johndeveloper/my-awesome-project.git (fetch)
# backup git@gitlab.com:johndeveloper/my-awesome-project.git (push)
# origin git@github.com:johndeveloper/my-awesome-project.git (fetch)
# origin git@github.com:johndeveloper/my-awesome-project.git (push)
# Push main to the backup remote
git push backup main
# Output:
# Enumerating objects: 14, done.
# Counting objects: 100% (14/14), done.
# To gitlab.com:johndeveloper/my-awesome-project.git
# * [new branch] main -> main
# Push dev branch to backup as well
git push backup dev
# Output:
# Total 0 (delta 0), reused 0 (delta 0)
# To gitlab.com:johndeveloper/my-awesome-project.git
# * [new branch] dev -> devStep 12: Set Up Push to Both Remotes Simultaneously
bash
# Create an "all" remote that pushes to both GitHub and GitLab
git remote add all git@github.com:johndeveloper/my-awesome-project.git
git remote set-url --add --push all git@github.com:johndeveloper/my-awesome-project.git
git remote set-url --add --push all git@gitlab.com:johndeveloper/my-awesome-project.git
# Verify the configuration
git remote -v
# Output:
# all git@github.com:johndeveloper/my-awesome-project.git (fetch)
# all git@github.com:johndeveloper/my-awesome-project.git (push)
# all git@gitlab.com:johndeveloper/my-awesome-project.git (push)
# backup git@gitlab.com:johndeveloper/my-awesome-project.git (fetch)
# backup git@gitlab.com:johndeveloper/my-awesome-project.git (push)
# origin git@github.com:johndeveloper/my-awesome-project.git (fetch)
# origin git@github.com:johndeveloper/my-awesome-project.git (push)Step 13: Continue Development with Multi-Remote Workflow
bash
# Switch back to dev for new feature
git checkout dev
# Make a new change
cat > config.js << 'EOF'
// Application configuration
const CONFIG = {
appName: 'My Awesome Project',
version: '1.2.0',
apiEndpoint: 'https://api.example.com',
debug: false
};
export default CONFIG;
EOF
# Stage, commit, and push to all remotes
git add config.js
git commit -m "Add configuration file for app settings"
# Push to all remotes at once
git push all dev
# Output:
# Enumerating objects: 4, done.
# To github.com:johndeveloper/my-awesome-project.git
# c3d4e5f..d4e5f6g dev -> dev
# Enumerating objects: 4, done.
# To gitlab.com:johndeveloper/my-awesome-project.git
# c3d4e5f..d4e5f6g dev -> devWhat just happened? A single git push all dev command pushed your changes to both GitHub and GitLab simultaneously.
Step 14: Handle a Merge Conflict (Real-World Scenario)
Let's simulate what happens when two developers edit the same file.
bash
# You're on dev, editing styles.css
echo "/* Developer 1's footer styles */" >> styles.css
echo ".footer { background: #333; color: white; }" >> styles.css
git add styles.css
git commit -m "Add footer styles"
# Meanwhile, switch to main and simulate another developer's change
git checkout main
# Make a conflicting change to the same file
echo "/* Main branch footer update */" >> styles.css
echo ".footer { background: navy; padding: 20px; }" >> styles.css
git add styles.css
git commit -m "Update footer in main branch"
# Now try to merge dev into main
git merge dev
# Output:
# Auto-merging styles.css
# CONFLICT (content): Merge conflict in styles.css
# Automatic merge failed; fix conflicts and then commit the result.Resolving the conflict:
bash
# Check which files have conflicts
git status
# Output:
# On branch main
# You have unmerged paths.
# (fix conflicts and run "git commit")
# (use "git merge --abort" to abort the merge)
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
# both modified: styles.css
# Open the file - you'll see conflict markers
cat styles.css
# Output shows something like:
# ...existing styles...
# <<<<<<< HEAD
# /* Main branch footer update */
# .footer { background: navy; padding: 20px; }
# =======
# /* Developer 1's footer styles */
# .footer { background: #333; color: white; }
# >>>>>>> devEdit the file to resolve (keep both changes combined):
bash
# Edit styles.css to look like this (remove conflict markers, combine code):
cat > styles.css << 'EOF'
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #333;
}
/* Button styles */
.btn {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.btn:hover {
background-color: #0056b3;
}
/* Combined footer styles from both branches */
.footer {
background: #333;
color: white;
padding: 20px;
}
EOF
# Mark as resolved by staging
git add styles.css
# Complete the merge
git commit -m "Merge dev into main: combine footer styles"
# Output:
# [main e5f6g7h] Merge dev into main: combine footer styles
# Push to all remotes
git push all mainStep 15: Fetch Changes from All Remotes and Sync
bash
# Fetch from all remotes to see what exists everywhere
git fetch --all
# Output:
# Fetching origin
# Fetching backup
# Fetching all
# See the state of all branches
git branch -a -v
# Output:
# * main e5f6g7h Merge dev into main: combine footer styles
# dev d4e5f6g Add configuration file for app settings
# remotes/backup/dev d4e5f6g Add configuration file for app settings
# remotes/backup/main e5f6g7h Merge dev into main: combine footer styles
# remotes/origin/dev d4e5f6g Add configuration file for app settings
# remotes/origin/main e5f6g7h Merge dev into main: combine footer styles
# Everything is in sync!Complete Workflow Summary
Here's the daily workflow pattern used by professional developers:
bash
# === START OF WORKDAY ===
# 1. Switch to your development branch
git checkout dev
# 2. Pull latest changes from remote
git pull origin dev
# 3. Make your changes (edit files, add features)
# ... coding ...
# 4. Check what you changed
git status
git diff
# 5. Stage your changes
git add .
# Or stage specific files: git add file1.js file2.css
# 6. Commit with meaningful message
git commit -m "Add user authentication feature"
# 7. Push to remote
git push origin dev
# === WHEN FEATURE IS COMPLETE ===
# 8. Switch to main
git checkout main
# 9. Pull latest main
git pull origin main
# 10. Merge your branch
git merge dev
# 11. Resolve conflicts if any (edit files, git add, git commit)
# 12. Push updated main
git push origin main
# 13. (Optional) Push to backup/secondary remote
git push backup main
# Or push to all: git push all main
# === CLEANUP ===
# 14. Delete merged feature branch (optional)
git branch -d feature-branch
git push origin --delete feature-branchPro Tips from Real-World Experience
Commit frequently, push regularly:
bash
# Small, focused commits are easier to review and revert
git commit -m "Add login form validation"
git commit -m "Add password strength indicator"
git commit -m "Add remember me checkbox"
# NOT: git commit -m "Add login feature with validation and stuff"Always pull before you push:
bash
git pull origin dev
# Resolve any conflicts
git push origin devUse meaningful branch names:
bash
git checkout -b feature/user-authentication
git checkout -b bugfix/login-redirect-issue
git checkout -b hotfix/security-patch-v1.2.1Check status obsessively:
bash
git status # Run this constantly. It's your friend.When in doubt, create a backup branch:
bash
git branch backup-before-scary-thing
# Now do the scary thing
# If it goes wrong: git checkout backup-before-scary-thingUse git stash when switching contexts:
bash
# You're mid-feature but need to fix a bug on main
git stash save "WIP: halfway through login feature"
git checkout main
# Fix the bug...
git checkout dev
git stash pop
# Continue where you left offFinal Wisdom: The Git Mindset
Don't think in commands. Think in graphs and pointers.
Commits are snapshots pointing backwards to parents.
Branches are just sticky notes pointing at commits.
HEAD tells Git where you are.
Reset moves branches.
Checkout moves HEAD.
Revert adds history.
Reflog remembers everything, at least for a while.
Git almost never truly deletes anything immediately. It just hides things. The reflog is your map. When everything goes wrong, git reflog.
Every developer makes mistakes. The difference between panic and recovery is knowing how to undo them.
You've got this.