Git
Every clone is a full repository with complete history. Think of it like a biological lineage: each commit is a generation, branches are parallel evolutionary paths, and merging is hybridisation – combining divergent traits back into one organism.
1. Installation
Arch Linux:
sudo pacman -S git
Ubuntu / Debian:
sudo apt update && sudo apt install git
Rocky Linux / RHEL / Fedora:
sudo dnf install git
macOS:
# Via Homebrew (recommended)
brew install git
# Or via Xcode Command Line Tools
xcode-select --install
Post-Install: First-Time Configuration
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global core.editor "nvim" # set your preferred editor
git config --global init.defaultBranch main # set default branch name
git config --global pull.rebase false # merge on pull (safer default)
git config --global color.ui auto
Verify configuration:
git config --list
git config --global --list
2. Repository Initialisation
Initialise a new local repository:
git init
Initialise with a specific branch name:
git init -b main
Clone an existing repository:
git clone <repository-url>
Clone into a specific directory:
git clone <repository-url> my-folder
Clone only the latest snapshot (no history), faster for large repos:
git clone --depth 1 <repository-url>
Clone a specific branch only:
git clone --branch <branch-name> --single-branch <repository-url>
3. Bare Repositories
A bare repository stores only version history – no working tree, no editable files. It is purely the contents of a normal .git/ folder. Think of it as a pure databank: it receives and stores genetic history but never expresses a phenotype.
Because there is no checked-out branch in the working tree, pushing to it is always safe – there is no conflict between incoming data and an active checkout.
Initialise and Clone Bare
git init --bare <directory.git>
git clone --bare <repository-url>
Use Case 1: Self-Hosted Remote (VPS / Server)
On the remote server:
git init --bare /srv/git/project.git
# Set ownership if needed
chown -R git:git /srv/git/project.git
On your local machine:
git remote add origin ssh://user@server:/srv/git/project.git
git push -u origin main
To allow multiple users to share the repository:
git init --bare --shared /srv/git/project.git
Use Case 2: Dotfiles Management (No-Symlink Method)
A bare repository is the cleanest way to version control $HOME configuration files without making the entire home directory a Git repo, which would cause git status to track every file you own.
# 1. Initialise a bare repo for your dotfiles
git init --bare $HOME/.dotfiles.git
# 2. Add an alias to your shell config (.bashrc / .zshrc)
alias config='/usr/bin/git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME'
# 3. Suppress untracked file noise (only show explicitly added files)
config config --local status.showUntrackedFiles no
# 4. Add and commit config files
config add .config/nvim/init.lua
config add .bashrc
config commit -m "Add nvim config and bashrc"
# 5. Push to a remote
config remote add origin git@github.com:user/dotfiles.git
config push -u origin main
Restoring on a new machine:
git clone --bare git@github.com:user/dotfiles.git $HOME/.dotfiles.git
alias config='/usr/bin/git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME'
config checkout
config config --local status.showUntrackedFiles no
If checkout fails due to pre-existing files, back them up and retry:
mkdir -p ~/.config-backup
config checkout 2>&1 | grep "\s\+\." | awk {'print $1'} | xargs -I{} mv {} ~/.config-backup/{}
config checkout
4. Status and Inspection
Show working tree status:
git status
git status -s # short/compact output
Show full commit history:
git log
Compact one-line history:
git log --oneline
Graphical branch history in the terminal:
git log --oneline --graph --all --decorate
Show last N commits:
git log -5
Show commits by a specific author:
git log --author="Name"
Show commits that touched a specific file:
git log -- path/to/file
Show what changed in each commit (patch view):
git log -p
Show diff of unstaged changes:
git diff
Show diff of staged changes (what will be committed):
git diff --staged
Show diff between two branches:
git diff branch1..branch2
Show diff between two commits:
git diff <commit-a> <commit-b>
Show details of a specific commit:
git show <commit-hash>
Show who last modified each line of a file (annotated blame):
git blame file.txt
git blame -L 10,25 file.txt # blame specific line range
Show the contents of a file at a specific commit:
git show <commit-hash>:path/to/file
Search for a string across all commits:
git log -S "search string"
git log -G "regex pattern"
5. Staging
Stage a specific file:
git add file.txt
Stage all changes in the current directory:
git add .
Stage all changes in the entire repository:
git add -A
Stage parts of a file interactively (hunk-by-hunk):
git add -p file.txt
Unstage a file (keep changes in working tree):
git restore --staged file.txt
Discard changes in a file entirely (destructive – cannot undo):
git restore file.txt
Remove a file from both the index and working tree:
git rm file.txt
Remove a file from tracking only (keep it on disk):
git rm --cached file.txt
6. Committing
Commit staged changes:
git commit -m "commit message"
Stage all tracked files and commit in one step:
git commit -am "commit message"
Amend the last commit (message or staged content):
git commit --amend
git commit --amend -m "corrected message"
git commit --amend --no-edit # amend without changing message
Create an empty commit (useful for triggering CI):
git commit --allow-empty -m "trigger ci"
7. Branching
List local branches (current branch marked with *):
git branch
List all branches including remotes:
git branch -a
List branches with last commit info:
git branch -v
Create a new branch (does not switch):
git branch <branchname>
Switch to an existing branch:
git checkout <branchname>
git switch <branchname> # modern syntax
Create and switch to a new branch:
git checkout -b <branchname>
git switch -c <branchname> # modern syntax
Create a branch from a specific commit or tag:
git checkout -b <branchname> <commit-hash>
git checkout -b <branchname> v1.2.0
Rename the current branch:
git branch -m <new-name>
Create an orphan branch (no history – blank slate):
git checkout --orphan <branchname>
git rm -rf --cached . # clear the index
Useful for separating a clean public-facing branch (e.g., GitHub Pages) from your development history.
Delete a local branch (only if fully merged):
git branch -d <branchname>
Force delete a branch (merged or not):
git branch -D <branchname>
Delete a remote branch:
git push origin --delete <branchname>
Prune stale remote-tracking references:
git fetch --prune
git remote prune origin
8. Merging
Merge a branch into the current branch:
git merge <branchname>
Merge without fast-forward (always creates a merge commit, preserves branch history):
git merge --no-ff <branchname>
Squash all commits from a branch into one staged change, then commit manually:
git merge --squash <branchname>
git commit -m "squashed feature"
Abort an in-progress merge:
git merge --abort
After resolving conflicts, mark the file as resolved and continue:
git add <resolved-file>
git merge --continue
9. Rebasing
Rebase the current branch onto another (rewrites history – dangerous on shared branches):
git rebase <branchname>
Interactive rebase – reorder, squash, edit, or drop commits:
git rebase -i HEAD~5 # last 5 commits
git rebase -i <commit-hash>
Interactive rebase options (inside the editor): pick – keep commit as-is; reword – keep commit, edit message; edit – pause to amend the commit; squash – combine with previous commit; fixup – squash, discard this commit’s message; drop – remove the commit entirely.
Continue after resolving conflicts during a rebase:
git rebase --continue
Abort a rebase and return to the original state:
git rebase --abort
10. Stashing
Save uncommitted changes to a temporary stack:
git stash
git stash push -m "work in progress on login feature"
Stash including untracked files:
git stash -u
Stash including untracked and ignored files:
git stash -a
List all stashes:
git stash list
Apply the most recent stash and remove it from the stack:
git stash pop
Apply a specific stash without removing it:
git stash apply stash@{2}
Show what changes are in a stash:
git stash show -p stash@{0}
Delete a specific stash:
git stash drop stash@{1}
Clear all stashes:
git stash clear
11. Remote Repositories
Add a remote:
git remote add origin <repository-url>
List all remotes with their URLs:
git remote -v
Show detailed information about a remote:
git remote show origin
Change a remote’s URL:
git remote set-url origin <new-url>
Remove a remote:
git remote remove origin
Rename a remote:
git remote rename origin upstream
12. Pushing and Pulling
Push a branch to a remote:
git push origin <branchname>
Push and set the upstream tracking branch:
git push -u origin <branchname>
Force push (rewrites remote history – use only on branches you own alone):
git push --force
git push --force-with-lease # safer: fails if remote has new commits you haven't fetched
Push all local branches to remote:
git push --all origin
Push tags to remote:
git push --tags
Fetch remote changes without merging:
git fetch origin
git fetch --all # fetch all remotes
Pull (fetch + merge) from remote:
git pull origin <branchname>
Pull with rebase instead of merge:
git pull --rebase origin <branchname>
13. Tags
Create a lightweight tag:
git tag v1.0.0
Create an annotated tag (includes tagger, date, message):
git tag -a v1.0.0 -m "Release version 1.0.0"
Tag a specific past commit:
git tag -a v0.9.0 <commit-hash> -m "Retroactive tag"
List all tags:
git tag
git tag -l "v1.*" # filter by pattern
Show tag details:
git show v1.0.0
Delete a local tag:
git tag -d v1.0.0
Delete a remote tag:
git push origin --delete v1.0.0
14. Reset and Recovery
Undo staged changes only (keep working tree):
git reset HEAD file.txt
git restore --staged file.txt
Soft reset – moves HEAD back, keeps changes staged:
git reset --soft <commit-hash>
Mixed reset (default) – moves HEAD back, unstages changes, keeps working tree:
git reset <commit-hash>
git reset HEAD~1
Hard reset – moves HEAD back, discards all changes (destructive):
git reset --hard <commit-hash>
git reset --hard HEAD~1
View the reference log (your safety net – all recent HEAD positions):
git reflog
Recover a lost commit using reflog:
git checkout <lost-commit-hash>
git branch recovery-branch # save it as a new branch
Revert a commit by creating a new inverse commit (safe for shared history):
git revert <commit-hash>
git revert HEAD~2 # revert 2 commits ago
Revert a merge commit:
git revert -m 1 <merge-commit-hash>
Cherry-pick a specific commit from another branch:
git cherry-pick <commit-hash>
git cherry-pick <hash1> <hash2>
git cherry-pick <hash1>..<hash3> # range
15. Submodules
Add a submodule (a repo nested inside your repo):
git submodule add <repository-url> path/to/submodule
Initialise and pull submodules after cloning:
git submodule update --init --recursive
Update all submodules to their latest remote commit:
git submodule update --remote
Clone a repo and initialise all its submodules in one step:
git clone --recurse-submodules <repository-url>
Remove a submodule:
git submodule deinit path/to/submodule
git rm path/to/submodule
rm -rf .git/modules/path/to/submodule
16. Worktrees
Check out multiple branches simultaneously in separate directories – useful when you need to work on a hotfix without stashing your current work.
# Create a new worktree for a branch
git worktree add ../hotfix-dir hotfix-branch
# List all active worktrees
git worktree list
# Remove a worktree
git worktree remove ../hotfix-dir
17. .gitignore
Create a .gitignore in the root of your repo. Files matching these patterns are never tracked.
# Compiled output
*.o
*.pyc
__pycache__/
dist/
build/
# Environment and secrets
.env
*.key
*.pem
# Editor files
.vscode/
.idea/
*.swp
# OS files
.DS_Store
Thumbs.db
Apply .gitignore rules to already-tracked files:
git rm -r --cached .
git add .
git commit -m "Apply .gitignore"
Create a global gitignore (applies to all repos on your machine):
git config --global core.excludesfile ~/.gitignore_global
18. Configuration Reference
# List all config
git config --list --show-origin
# Set merge tool
git config --global merge.tool vimdiff
# Set diff tool
git config --global diff.tool vimdiff
# Enable credential caching (Linux)
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600'
# Store credentials on disk (less secure)
git config --global credential.helper store
# Use SSH signing for commits
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
# Aliases
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.undo "reset --soft HEAD~1"