git commandsgithub tutorialgit cheat sheet 2026+17

Git and GitHub Ultimate Guide: 2026 Complete Reference

Git commands confuse even experienced developers. Most memorize commands without understanding what's happening underneath, then panic when something breaks. This comprehensive guide covers everything from basic concepts to advanced recovery techniques, with real code examples and solutions for common problems. Master branches, merges, rebases, authentication, multiple remotes, and emergency recovery with git reflog. Stop copying Stack Overflow commands blindly.

Parash Panta

Apr 12, 2026
43 min read

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.0

Windows: 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 --list

Set 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 --edit

Essential 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-project

Clone 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.git

Checking 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.txt

Adding 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-edit

Warning: 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 -5

Branch 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-name

Problem: 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 -vv

Merging: 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-branch

Fast-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-branch

Resolving 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-branch

Steps 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 --abort

Choosing 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.txt

Problem: You're getting endless conflicts when merging.

Solution: This usually means branches have diverged significantly. Consider:

  1. Merging more frequently

  2. Rebasing your feature branch onto the latest main before merging

  3. 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:

  1. Look at commit B, calculate the changes it introduced

  2. Create a NEW commit B' with those same changes, but with Y as its parent instead of the original base

  3. Look at commit C, calculate its changes

  4. Create a NEW commit C' with those changes sitting on top of B'

  5. 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 --abort

The 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~5

This opens an editor with options:

  • pick - use commit as-is

  • reword - use commit but edit the message

  • edit - stop at this commit to make changes

  • squash - combine with previous commit

  • fixup - like squash but discard the commit message

  • drop - 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 message

Remote 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 upstream

Working 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 main

Pushing 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 main

Fetching, 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 main

Problem: "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 main

Problem: 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>:main

GitHub 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.pub

Step 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_ed25519

Step 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.pub

Step 5: Add the key to GitHub:

  1. Go to GitHub Settings → SSH and GPG keys

  2. Click "New SSH key"

  3. Give it a descriptive title (e.g., "Work Laptop 2026")

  4. Paste your public key

  5. 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.git

Setting Up Personal Access Tokens

Step 1: Generate a token:

  1. Go to GitHub Settings → Developer settings → Personal access tokens

  2. Click "Generate new token" → "Generate new token (classic)"

  3. Give it a descriptive name

  4. Set expiration (or "No expiration" for convenience)

  5. Select scopes (at minimum: repo for repository access)

  6. Click "Generate token"

  7. 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.txt

No 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 again

Use 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 unstaged

Use 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

--soft

Moves

Unchanged

Unchanged

Squash commits

--mixed

Moves

Reset

Unchanged

Re-stage differently

--hard

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 a1b2c3d

Commit 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

checkout

HEAD only

Safe

Exploring history

reset

Branch (and potentially working directory)

Risky

Reshape local work

revert

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 clear

Tip: 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-branch

Typical 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 a1b2c3d

Scenario 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 main

Preventing 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 main

Comprehensive 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 file

Creating Repositories

bash

git init                    # Initialize new repository
git clone <url>             # Clone existing repository
git clone <url> <folder>    # Clone into specific folder

Basic 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 commit

Viewing 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 commit

Branches

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 branch

Merging 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 rebase

Remote 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 push

Stashing

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 stashes

Undoing 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 commit

Recovery

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 commit

Information

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 author

Common Problems and Solutions Quick Reference

Problem

Solution

"fatal: unable to auto-detect email address"

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

"Updates were rejected because the remote contains work..."

git pull origin main then git push origin main

"You have divergent branches"

git pull --rebase origin main or git config pull.rebase false

"Your branch is behind"

git pull origin main

"Merge conflict in..."

Edit file, remove markers, git add, git commit

Accidentally committed to wrong branch

git reset HEAD~1, git checkout correct-branch, git commit

Need to undo pushed commit

git revert <commit> (creates new commit undoing changes)

Lost commits after reset

git reflog, find hash, git reset --hard <hash>

Deleted branch with unmerged changes

git reflog, find last commit, git branch recovered <hash>

Dropped stash

git fsck --no-reflog, find hash, git stash apply <hash>

Detached HEAD warning

git checkout -b new-branch to save your work

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

  1. Install and configure Git

  2. Create a local project and initialize Git

  3. Connect to GitHub (primary origin)

  4. Create a development branch and make changes

  5. Push to the dev branch, then merge to main

  6. Add a secondary remote (backup or deployment)

  7. 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.0

On Windows:

bash

# Download Git from https://git-scm.com/download/win
# Run the installer, accept defaults
# Open Git Bash and verify
git --version

On Linux (Ubuntu/Debian):

bash

sudo apt update
sudo apt install git

git --version

Step 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 --wait

What 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.css

Step 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 README

What 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:

  1. Go to https://github.com

  2. Click the "+" icon → "New repository"

  3. Name it my-awesome-project

  4. Leave it empty (don't add README, .gitignore, or license)

  5. 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/main

What 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.js

Step 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/main

Step 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 -> dev

Step 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 structure

What 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 -> main

Step 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 -> dev

Step 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 -> dev

What 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; }
# >>>>>>> dev

Edit 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 main

Step 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-branch

Pro 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 dev

Use 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.1

Check 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-thing

Use 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 off

Final 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.

Parash Panta

Content Creator

Creating insightful content about web development, hosting, and digital innovation at Dplooy.