Git on Linux: The Complete Guide for Beginners and Beyond

Tested on: Ubuntu 26.04 LTS · Debian 12 · Fedora 44 · Arch Linux — Last updated: June 2026
Git was written by Linus Torvalds in 2005 to manage the Linux kernel — the most complex software project on earth. Today it underpins virtually every codebase, from solo hobby projects to large distributed teams. If you're working on Linux, Git is a first-class citizen: it ships in every major distro's package manager, integrates cleanly with SSH keys, and pairs naturally with shell scripting. This guide takes you from installation through real workflows, covering branching, remotes, rebasing, undoing mistakes, and the commands you'll use every single day.
Prerequisites
- A Linux terminal with sudo access (Ubuntu, Debian, Fedora, Arch, or compatible)
- Basic familiarity with the command line — navigating directories, creating files
- A GitHub or GitLab account if you want to follow the remote/SSH sections
Install Git
Git is available in every major distro's default repositories. Install it with your package manager:
# Ubuntu / Debian:
sudo apt update && sudo apt install git
# Fedora:
sudo dnf install git
# Arch Linux:
sudo pacman -S git
# openSUSE:
sudo zypper install git
# Verify the installed version:
git --versionExpected output:
git version 2.45.2If you need a newer version than your distro ships, on Ubuntu/Debian you can add the official Git PPA:
sudo add-apt-repository ppa:git-core/ppa
sudo apt update && sudo apt install gitInitial Configuration
Before making any commits, Git needs to know who you are. This information is embedded in every commit you make — it cannot be changed after the fact without rewriting history.
# Set your identity — use the email tied to your GitHub/GitLab account:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
# Default branch name for new repositories (modern standard):
git config --global init.defaultBranch main
# Set your preferred editor for commit messages:
git config --global core.editor nano # or: vim, nvim, code --wait, etc.
# Define what 'git pull' does by default (merge, not rebase):
git config --global pull.rebase false
# Enable colored output:
git config --global color.ui auto
# Helpful diff algorithm (better at detecting moved code):
git config --global diff.algorithm histogram
# Verify your settings:
git config --list --global
# Config is stored at:
cat ~/.gitconfigYour ~/.gitconfig will look like this:
[user]
name = Your Name
email = you@example.com
[init]
defaultBranch = main
[core]
editor = nano
[pull]
rebase = false
[color]
ui = auto
[diff]
algorithm = histogramCore Concepts: The Three Areas
Every confusion about Git traces back to not understanding these three areas:
- Working Directory — the files you see and edit on disk. This is your live, messy work-in-progress state.
- Staging Area (Index) — a curated snapshot of changes you've explicitly selected for the next commit. You control exactly what goes in using
git add. - Repository (.git/) — the permanent database of all commits, branches, tags, and history. Lives in the hidden
.git/folder in your project root.
The flow is always: edit files in the Working Directory → git add moves changes into the Staging Area → git commit saves the staged snapshot permanently into the Repository.
Create Your First Repository
# Initialize a new repository in an existing directory:
mkdir myproject && cd myproject
git init
# Output: Initialized empty Git repository in /home/user/myproject/.git/
# Clone an existing remote repository (HTTPS):
git clone https://github.com/username/reponame.git
# Clone via SSH (requires SSH key setup — see below):
git clone git@github.com:username/reponame.git
# Clone into a specific directory name:
git clone git@github.com:username/reponame.git my-local-name
# After cloning, Git automatically configures 'origin' as the remote.Staging and Committing
# Create a file and check status:
echo "# My Project" > README.md
git status
# Output:
# On branch main
# Untracked files:
# (use "git add ..." to include in what will be committed)
# README.md
# Stage a specific file:
git add README.md
# Stage everything in the current directory:
git add .
# Stage interactively — review and select individual hunks:
git add -p
# See what's staged vs what's still unstaged:
git diff # changes NOT yet staged
git diff --staged # changes staged and ready to commit
# Commit with an inline message:
git commit -m "docs: add initial README"
# Commit and open your editor for a longer message:
git commit Writing Good Commit Messages
Your commit messages are the primary documentation for why code changed. Write for your future self debugging a problem at 2am.
# Bad — useless in git log:
git commit -m "fix"
git commit -m "changes"
git commit -m "wip"
# Good — self-documenting:
git commit -m "Fix login form not clearing password field after failed attempt"
git commit -m "Add rate limiting to /api/auth endpoints (100 req/min)"
# Conventional Commits format (widely adopted standard):
git commit -m "feat: add JWT refresh token rotation"
git commit -m "fix: resolve null pointer in user profile loader"
git commit -m "docs: update API authentication examples"
git commit -m "refactor: extract payment processing into service class"
git commit -m "chore: upgrade dependencies to latest patch versions"Viewing History
# Full log:
git log
# Compact, one line per commit:
git log --oneline
# Visual branch graph across all branches:
git log --oneline --graph --all
# Filter by author:
git log --author="Jane"
# Search commit messages:
git log --grep="rate limit"
# Show commits affecting a specific file:
git log -- src/auth.py
# Show full diff of a specific commit:
git show a3f8c12
# Annotate a file — who last changed each line:
git blame src/auth.py
# Binary search to find which commit introduced a bug:
git bisect start
git bisect bad # current state is broken
git bisect good v1.2.0 # this tag/commit was working
# Git checks out the midpoint — test it, then mark:
git bisect good # or: git bisect bad
# Repeat until Git identifies the culprit commit
git bisect reset # return to HEAD when doneBranches
Branches are lightweight pointers to commits. Creating one is instantaneous. Use them freely — one branch per feature, per bug fix, per experiment.
# List local branches (* marks current):
git branch
# List all branches including remotes:
git branch -a
# Create and switch to a new branch (two equivalent forms):
git checkout -b feature/user-auth
git switch -c feature/user-auth # modern syntax (Git 2.23+)
# Switch to an existing branch:
git switch main
# Delete a merged branch:
git branch -d feature/user-auth
# Force-delete an unmerged branch:
git branch -D feature/user-auth
# Rename the current branch:
git branch -m better-name
# Push a new branch to remote and set upstream tracking:
git push -u origin feature/user-authMerging and Rebasing
Merging
Merging joins two branch histories. It creates a merge commit that preserves the full record of when work diverged and rejoined — honest history.
# Merge a feature branch into main:
git switch main
git merge feature/user-auth
# When there are conflicts, Git marks them in the affected files:
# <<<<<<< HEAD
# your version on main
# =======
# incoming version from feature/user-auth
# >>>>>>> feature/user-auth
# Edit each file to resolve the conflict, then:
git add resolved-file.py
git commit # Git pre-fills the merge commit message
# Changed your mind mid-merge:
git merge --abortRebasing
Rebasing replays your commits on top of another branch, producing a clean linear history with no merge commits. Use it on private feature branches before merging — never on branches others have already pulled.
# Rebase your feature branch onto the latest main:
git switch feature/user-auth
git rebase main
# Conflicts during rebase: edit, add, then continue:
git add resolved-file.py
git rebase --continue
# Abort a rebase in progress:
git rebase --abort
# Interactive rebase — squash, reorder, edit, or drop commits:
git rebase -i HEAD~4 # edit the last 4 commits
# In the editor, change 'pick' to:
# squash (s) — merge into previous commit
# reword (r) — change the commit message
# drop (d) — delete the commit entirely
# edit (e) — stop and amend the commitWorking with Remotes
# Add a remote (standard name is 'origin'):
git remote add origin https://github.com/username/repo.git
# View configured remotes:
git remote -v
# origin git@github.com:username/repo.git (fetch)
# origin git@github.com:username/repo.git (push)
# First push — sets upstream tracking branch:
git push -u origin main
# Push subsequent commits:
git push
# Pull (fetch + merge into current branch):
git pull
# Fetch changes without merging (safe to inspect first):
git fetch origin
git log origin/main --oneline # see what's there
git merge origin/main # integrate when ready
# Push a feature branch:
git push -u origin feature/user-auth
# Delete a remote branch:
git push origin --delete feature/user-auth
# Change remote URL (e.g., HTTPS → SSH):
git remote set-url origin git@github.com:username/repo.gitSSH Keys for GitHub and GitLab
SSH key authentication eliminates password prompts and is more secure than HTTPS with a stored password. If most of your workflow is on GitHub specifically, GitHub CLI can generate and upload this key for you as part of gh auth login, skipping the manual steps below.
# Generate a new ED25519 key (preferred algorithm):
ssh-keygen -t ed25519 -C "you@example.com"
# Accept default path (~/.ssh/id_ed25519), set a passphrase
# Start the SSH agent and load your key:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Print your public key — copy this entire line:
cat ~/.ssh/id_ed25519.pub
# Add to GitHub: Settings → SSH and GPG keys → New SSH key → paste
# Add to GitLab: Preferences → SSH Keys → paste
# Test authentication:
ssh -T git@github.com
# Hi username! You've successfully authenticated, but GitHub does
# not provide shell access.
ssh -T git@gitlab.com
# Welcome to GitLab, @username!To persist the SSH agent across reboots on systemd-based systems, add the following to your ~/.bashrc or ~/.bash_profile:
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
fiUndoing Changes
This is where Git saves you. Knowing these commands removes the fear of experimenting.
| Situation | Command |
|---|---|
| Unstage a file (keep changes on disk) | git restore --staged filename |
| Discard working directory changes | git restore filename |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 |
| Undo last commit, keep changes unstaged | git reset HEAD~1 |
| Undo last commit and discard changes | git reset --hard HEAD~1 |
| Safely undo a pushed commit | git revert abc1234 |
| Fix the last commit message | git commit --amend -m "corrected message" |
| Add a forgotten file to last commit | git add file && git commit --amend --no-edit |
| Stash work-in-progress temporarily | git stash / git stash pop |
| List all stashes | git stash list |
Rule of thumb: git revert is always safe on shared branches — it adds a new commit. git reset --hard destroys history and should only be used on local commits you haven't pushed.
.gitignore
The .gitignore file lists patterns for files Git should never track. Create it in the root of your repository and commit it.
# Python project:
__pycache__/
*.pyc
*.pyo
.env
venv/
.venv/
*.egg-info/
dist/
build/
.pytest_cache/
# Node.js project:
node_modules/
npm-debug.log*
.env
dist/
.next/
# OS artifacts:
.DS_Store
Thumb
Further Reading
