diff --git a/TODO.md b/TODO.md index 35ca0f8..31242f9 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ ## Topics to Write -- [ ] git — common workflows, rebase, stash, bisect +- [x] git — config, daily workflow, stash, remotes, branching, rebase, bisect, recovery - [x] ansible — playbook patterns, inventory, vault, variables, roles - [ ] podman — build, run, compose, volumes - [x] jq — filters, select, map, slurp, recipes, @format diff --git a/topics/git-branching.md b/topics/git-branching.md new file mode 100644 index 0000000..b27c463 --- /dev/null +++ b/topics/git-branching.md @@ -0,0 +1,271 @@ +# Git Branching + +> Branches, merging strategies, rebasing, and history recovery. + +## Branches + +```bash +# List +git branch # local +git branch -r # remote +git branch -a # all +git branch -v # with last commit +git branch --merged # merged into current +git branch --no-merged # not yet merged + +# Create +git branch feature-login +git checkout -b feature-login # create + switch +git switch -c feature-login # modern syntax + +# Switch +git switch main +git checkout main # older syntax + +# Rename +git branch -m old-name new-name +git branch -m new-name # rename current + +# Delete +git branch -d feature-login # safe (refuses if unmerged) +git branch -D feature-login # force delete + +# Cleanup stale remote tracking branches +git fetch --prune +git remote prune origin # same effect +``` + +## Merge + +```bash +# Merge branch into current +git merge feature-login + +# Merge with commit message +git merge feature-login -m "feat: integrate login" + +# No fast-forward (always create merge commit) +git merge --no-ff feature-login + +# Fast-forward only (fail if not possible) +git merge --ff-only feature-login + +# Abort in-progress merge +git merge --abort + +# Squash (combine all commits, don't auto-commit) +git merge --squash feature-login +git commit -m "feat: login feature" +``` + +### Merge Strategies + +| Strategy | When to use | +|-------------------|--------------------------------------------| +| Fast-forward | Linear history, no divergence | +| `--no-ff` | Preserve branch topology in history | +| `--squash` | Collapse noisy branch into single commit | +| `ours` | Keep our side entirely, discard theirs | +| `recursive` / `ort` | Default for diverged branches | + +### Conflict Resolution + +```bash +# After merge conflict +git status # shows conflicted files + +# Edit files, resolve markers: +# <<<<<<< HEAD +# our changes +# ======= +# their changes +# >>>>>>> feature-branch + +# Mark resolved +git add resolved-file.txt +git merge --continue +# or +git commit + +# Use theirs/ours for a file +git checkout --theirs path/to/file +git checkout --ours path/to/file +``` + +## Rebase + +```bash +# Rebase current branch onto main +git rebase main + +# Interactive rebase (rewrite last N commits) +git rebase -i HEAD~5 + +# Continue after resolving conflicts +git rebase --continue + +# Abort rebase +git rebase --abort + +# Rebase onto specific base +git rebase --onto main feature-base feature-branch + +# Auto-squash fixup commits +git commit --fixup abc123 +git rebase -i --autosquash main +``` + +### Interactive Rebase Commands + +| Command | Effect | +|------------|---------------------------------------------| +| `pick` | Keep commit as-is | +| `reword` | Keep commit, edit message | +| `edit` | Pause at commit for amending | +| `squash` | Merge into previous commit, combine messages| +| `fixup` | Merge into previous commit, discard message | +| `drop` | Remove commit entirely | +| `reorder` | Move lines to reorder commits | + +### Rebase vs Merge + +| | Merge | Rebase | +|---|---|---| +| History | Preserves branch topology | Linear, clean | +| Conflicts | Resolve once | May resolve per commit | +| Shared branches | Safe | **Dangerous** (rewrites history) | +| Use case | Integrating feature | Cleaning up before merge | + +## Cherry-Pick + +```bash +# Apply specific commit to current branch +git cherry-pick abc123 + +# Multiple commits +git cherry-pick abc123 def456 + +# Range (exclusive start) +git cherry-pick abc123..def456 + +# Without committing (stage only) +git cherry-pick --no-commit abc123 + +# Abort +git cherry-pick --abort +``` + +## Bisect + +```bash +# Start binary search for a bug +git bisect start +git bisect bad # current commit is broken +git bisect good v1.0 # this tag/commit was working + +# Git checks out middle commit — test it, then: +git bisect good # if this commit works +git bisect bad # if this commit is broken + +# Repeat until git finds the first bad commit + +# Done +git bisect reset + +# Automated bisect with a test script (exit 0 = good, exit 1 = bad) +git bisect start HEAD v1.0 +git bisect run ./test-script.sh +``` + +## Reflog — Recovery Safety Net + +```bash +# Show reflog (all recent HEAD movements) +git reflog +git reflog show feature-branch # specific branch + +# Recover deleted branch +git reflog # find the commit hash +git branch recovered-branch abc123 + +# Undo a rebase +git reflog # find pre-rebase HEAD +git reset --hard abc123 + +# Recover amended commit +git reflog # find pre-amend HEAD +git branch pre-amend abc123 + +# Reflog expires after 90 days (default) +``` + +## Reset + +```bash +# Soft — undo commit, keep staged +git reset --soft HEAD~1 + +# Mixed (default) — undo commit, unstage, keep working tree +git reset HEAD~1 + +# Hard — undo commit, discard everything +git reset --hard HEAD~1 + +# Reset to specific commit +git reset --hard abc123 + +# Reset single file +git restore --staged file.txt # preferred +git reset HEAD file.txt # older syntax +``` + +| Mode | Commit | Staging | Working Tree | +|-----------|--------|---------|--------------| +| `--soft` | Undo | Keep | Keep | +| `--mixed` | Undo | Undo | Keep | +| `--hard` | Undo | Undo | **Undo** | + +## Revert + +```bash +# Create a new commit that undoes a previous one (safe for shared history) +git revert abc123 +git revert HEAD # undo last commit +git revert abc123..def456 # range + +# Without auto-commit +git revert --no-commit abc123 +``` + +## Worktrees + +```bash +# Work on multiple branches simultaneously +git worktree add ../hotfix hotfix-branch +git worktree add ../experiment -b experiment + +# List +git worktree list + +# Remove +git worktree remove ../hotfix + +# Prune stale entries +git worktree prune +``` + +## Gotchas + +- Never rebase commits already pushed to shared branches — it rewrites history +- `git reset --hard` is **permanent** unless you know the reflog hash +- `git branch -D` force-deletes — if unmerged, that work is only in reflog (90 days) +- Cherry-pick creates a **new commit** with a different hash — it duplicates, not moves +- Bisect requires a clean working tree — stash or commit first +- Merge `--squash` does not record the branch as merged — git won't know it was integrated +- Reflog is **local only** — it doesn't sync to remotes +- `revert` of a merge commit needs `-m 1` to specify which parent to keep + +## See Also + +- `git` — core workflow, config, stash, remotes, tags +- [Pro Git: Branching](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) diff --git a/topics/git.md b/topics/git.md new file mode 100644 index 0000000..2a9db29 --- /dev/null +++ b/topics/git.md @@ -0,0 +1,258 @@ +# Git + +> Distributed version control — track, branch, merge, and collaborate. + +## Config + +```bash +# Identity +git config --global user.name "Your Name" +git config --global user.email "you@example.com" + +# Editor +git config --global core.editor vim + +# Default branch name +git config --global init.defaultBranch main + +# Useful defaults +git config --global pull.rebase true +git config --global push.autoSetupRemote true +git config --global rerere.enabled true +git config --global diff.algorithm histogram +git config --global merge.conflictstyle zdiff3 + +# Aliases +git config --global alias.st status +git config --global alias.co checkout +git config --global alias.br branch +git config --global alias.lg "log --oneline --graph --all" + +# Show current config +git config --list --show-origin +``` + +### Config Scope + +| Flag | File | Purpose | +|------------|-------------------------|------------------| +| `--system` | `/etc/gitconfig` | All users | +| `--global` | `~/.gitconfig` | Current user | +| `--local` | `.git/config` | Current repo | + +Local overrides global overrides system. + +## Init and Clone + +```bash +git init +git init --bare repo.git # server-side (no working tree) +git clone https://github.com/user/repo.git +git clone --depth 1 https://... # shallow (latest commit only) +git clone --branch v2.0 https://... # specific branch/tag +``` + +## Daily Workflow + +```bash +# Status +git status +git status -s # short format + +# Stage +git add file.txt # specific file +git add src/ # directory +git add -p # interactive hunk staging +git add -u # stage modified/deleted, not new + +# Unstage +git restore --staged file.txt +git reset HEAD file.txt # older syntax, same effect + +# Discard working tree changes +git restore file.txt +git checkout -- file.txt # older syntax + +# Commit +git commit -m "feat: add login page" +git commit -am "fix: typo" # stage tracked + commit +git commit --amend # rewrite last commit +git commit --amend --no-edit # amend without changing message +git commit --allow-empty -m "trigger" # empty commit (CI triggers) +``` + +## Diff + +```bash +# Working tree vs staging +git diff + +# Staging vs last commit +git diff --cached +git diff --staged # same thing + +# Between commits +git diff abc123..def456 +git diff HEAD~3..HEAD + +# Between branches +git diff main..feature + +# Specific file +git diff -- path/to/file.txt + +# Stats only +git diff --stat +git diff --name-only +git diff --name-status # with A/M/D markers + +# Word-level diff +git diff --word-diff +``` + +## Log + +```bash +# Compact +git log --oneline +git log --oneline -10 # last 10 + +# Graph +git log --oneline --graph --all + +# Detailed +git log -p # with diffs +git log --stat # with file stats + +# Filter +git log --author="alice" +git log --since="2025-01-01" +git log --after="2 weeks ago" +git log --grep="fix:" # search commit messages +git log -S "function_name" # search for string in diffs (pickaxe) +git log -G "regex_pattern" # search diffs with regex +git log -- path/to/file # commits touching file + +# Format +git log --pretty=format:"%h %an %ar %s" +git log --pretty=format:"%C(yellow)%h%Creset %s %C(dim)(%ar)%Creset" + +# Show single commit +git show abc123 +git show HEAD~2:path/to/file # file at specific commit +``` + +## Stash + +```bash +# Save work in progress +git stash +git stash push -m "wip: api refactor" +git stash push path/to/file # stash specific files + +# List stashes +git stash list + +# Restore +git stash pop # apply + remove from stash +git stash apply # apply, keep in stash +git stash apply stash@{2} # specific stash + +# Inspect +git stash show # stat +git stash show -p # full diff + +# Drop +git stash drop stash@{0} +git stash clear # drop all + +# Stash untracked files too +git stash -u +git stash --include-untracked +``` + +## Remotes + +```bash +# List +git remote -v + +# Add +git remote add origin https://github.com/user/repo.git +git remote add upstream https://github.com/original/repo.git + +# Change URL +git remote set-url origin git@github.com:user/repo.git + +# Fetch +git fetch # default remote +git fetch --all # all remotes +git fetch --prune # remove stale remote branches + +# Pull +git pull # fetch + merge (or rebase if configured) +git pull --rebase # fetch + rebase + +# Push +git push +git push -u origin main # set upstream +git push origin --delete feature-branch # delete remote branch +git push --tags # push all tags +git push origin v1.0 # push specific tag +``` + +## Tags + +```bash +# List +git tag +git tag -l "v1.*" + +# Create +git tag v1.0 # lightweight +git tag -a v1.0 -m "Release 1.0" # annotated (preferred) +git tag -a v1.0 abc123 # tag specific commit + +# Delete +git tag -d v1.0 +git push origin --delete v1.0 # delete remote tag + +# Show +git show v1.0 +``` + +## .gitignore + +```gitignore +# Patterns +*.log # all .log files +build/ # directory +!important.log # exception (don't ignore this one) +**/temp # temp in any subdirectory +doc/*.txt # doc/notes.txt but not doc/sub/notes.txt +``` + +```bash +# Ignore already-tracked file +git rm --cached file.txt # untrack without deleting +echo "file.txt" >> .gitignore + +# Debug ignore rules +git check-ignore -v path/to/file +``` + +## Gotchas + +- `git pull` without `--rebase` creates merge commits — set `pull.rebase true` globally +- `git add .` stages everything including untracked — review with `git status` first +- `--amend` rewrites history — never amend commits already pushed to shared branches +- `git stash` does not stash untracked files unless `-u` is passed +- `-S` (pickaxe) finds where a string was added/removed, not where it appears +- `git log -- file` stops at renames — add `--follow` to track across renames +- Tags aren't pushed by default — use `--tags` or push individually + +## See Also + +- `git-branching` — branches, merge, rebase, cherry-pick, recovery +- [Pro Git book](https://git-scm.com/book/en/v2) +- `git help ` — built-in manual pages