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

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"