# The Complete Hands-On Git Cheat Sheet

> **How to use this file:** every command is copy-paste ready. Replace the `[BRACKETED]` tokens
> with your own values and run. Nothing here needs editing beyond those tokens.
>
> Commands marked **⚠️ DESTRUCTIVE** rewrite history or discard work. Read the note before running.
> Commands marked **🔐** touch credentials/secrets — see [Part 3 §35](#35-secrets-hygiene--security-).

---

## Placeholder Legend

| Token | Meaning | Example |
|---|---|---|
| `[USER]` | Your Git host username or org | `ashekur-ssl` |
| `[REPO]` | Repository name | `payment-gateway-api` |
| `[HOST]` | Git host | `github.com`, `gitlab.com` |
| `[EMAIL]` | Your commit email | `you@example.com` |
| `[NAME]` | Your display name | `Ashekur Rahman` |
| `[BRANCH]` | Working/feature branch | `feature/refund-api` |
| `[BASE]` | Mainline branch | `main`, `develop` |
| `[REMOTE]` | Remote name | `origin`, `upstream` |
| `[FILE]` | File or path | `src/pay/refund.go` |
| `[MSG]` | Commit message | `fix: null merchant id` |
| `[TAG]` | Tag name | `v1.4.0` |
| `[SHA]` | Commit hash (7–40 chars) | `9fceb02` |
| `[N]` | A count | `5` |
| `[PLACEHOLDER]` | A secret value — never paste the real one into a shared doc | |

---

# PART 1 — BASIC OPERATIONS

## 1. Install, Version & Help

```bash
git --version                                  # confirm install + version
git help [COMMAND]                             # full man page, e.g. git help rebase
git [COMMAND] -h                               # quick flag summary
git help -a                                    # list every available subcommand
git help -g                                    # list concept guides
git help everyday                              # the "useful minimum" guide
```

Install:

```bash
brew install git                               # macOS (Homebrew)
sudo apt-get install -y git                    # Debian / Ubuntu
sudo dnf install -y git                        # Fedora / RHEL
winget install --id Git.Git -e                 # Windows
xcode-select --install                         # macOS CLT (ships git)
```

---

## 2. Configuration & Identity

Scopes, narrowest wins: `--local` (this repo) → `--global` (your user) → `--system` (whole machine).

```bash
git config --global user.name "[NAME]"
git config --global user.email "[EMAIL]"
git config user.email "[EMAIL]"                # repo-only override (work vs personal)

git config --list --show-origin                # every setting + which file set it
git config --global --list                     # only your user-level settings
git config --get user.email                    # read one value
git config --global --unset [KEY]              # remove a setting
git config --global --edit                     # open ~/.gitconfig in $EDITOR
```

### Sensible defaults worth setting once

```bash
git config --global init.defaultBranch main
git config --global core.editor "code --wait"          # or "vim", "nano"
git config --global pull.rebase true                   # linear history on pull
git config --global fetch.prune true                   # drop deleted remote branches
git config --global push.default current
git config --global push.autoSetupRemote true          # git push works on new branches
git config --global rerere.enabled true                # remember conflict resolutions
git config --global diff.colorMoved zebra              # highlight moved code
git config --global merge.conflictStyle zdiff3         # show the common ancestor
git config --global rebase.autoStash true
git config --global rebase.autoSquash true
git config --global log.date iso
git config --global column.ui auto
git config --global help.autocorrect prompt
git config --global core.autocrlf input                # macOS/Linux
git config --global core.autocrlf true                 # Windows
git config --global core.ignorecase false              # avoid case-rename bugs
git config --global credential.helper osxkeychain      # macOS; see §35
```

### Per-directory identity (conditional includes)

Keep work and personal commits separate automatically. In `~/.gitconfig`:

```ini
[includeIf "gitdir:~/Documents/work/"]
    path = ~/.gitconfig-work
```

```bash
# ~/.gitconfig-work
git config -f ~/.gitconfig-work user.email "[EMAIL]"
git config -f ~/.gitconfig-work commit.gpgsign true
```

---

## 3. Create & Clone Repositories

```bash
git init                                        # init in current directory
git init [REPO] && cd [REPO]                    # create dir + init
git init --initial-branch=main
git init --bare [REPO].git                      # server-side repo (no working tree)

git clone https://[HOST]/[USER]/[REPO].git
git clone git@[HOST]:[USER]/[REPO].git          # SSH (preferred, see §35)
git clone https://[HOST]/[USER]/[REPO].git [DIR]        # clone into custom dir
git clone --branch [BRANCH] https://[HOST]/[USER]/[REPO].git
git clone --branch [TAG] --single-branch https://[HOST]/[USER]/[REPO].git
git clone --depth 1 https://[HOST]/[USER]/[REPO].git    # shallow, fastest
git clone --filter=blob:none https://[HOST]/[USER]/[REPO].git   # blobless
git clone --recurse-submodules https://[HOST]/[USER]/[REPO].git
git clone --mirror https://[HOST]/[USER]/[REPO].git     # exact backup incl. all refs
git clone --bare https://[HOST]/[USER]/[REPO].git
```

---

## 4. Stage & Snapshot

```bash
git status                                      # what changed
git status -s                                   # short form
git status -sb                                  # short + branch/tracking line
git status --ignored                            # include ignored files

git add [FILE]
git add .                                       # everything under cwd
git add -A                                      # everything in the repo
git add -u                                      # only already-tracked files
git add -p                                      # interactively pick hunks  ← learn this one
git add -i                                      # full interactive staging menu
git add -N [FILE]                               # "intent to add": makes a new file diffable
git add '*.go'                                  # glob (quote it so git expands, not the shell)

git commit -m "[MSG]"
git commit -am "[MSG]"                          # stage tracked changes + commit
git commit                                      # open editor for a full message
git commit -m "[MSG]" -m "Longer body paragraph."
git commit --amend -m "[MSG]"                   # ⚠️ rewrites the last commit
git commit --amend --no-edit                    # fold staged changes into last commit
git commit --no-verify                          # skip hooks (avoid; see §25)
git commit --allow-empty -m "[MSG]"             # trigger CI with no code change
git commit --date="2026-07-25T10:00:00" -m "[MSG]"
git commit -S -m "[MSG]"                        # signed commit (§26)
git commit --fixup [SHA]                        # marks a future autosquash (§15)
git commit --squash [SHA]

git rm [FILE]                                   # delete + stage the deletion
git rm --cached [FILE]                          # untrack but KEEP on disk
git rm -r --cached .                            # re-apply .gitignore to whole repo
git mv [FILE] [NEWPATH]                         # rename + stage
```

Conventional commit message shape:

```
<type>(<scope>): <summary in imperative mood, ≤50 chars>

Why this change is needed, wrapped at 72 chars. What behaviour changed.

Refs: [TICKET-ID]
```
Types: `feat` `fix` `docs` `style` `refactor` `perf` `test` `build` `ci` `chore` `revert`

---

## 5. Inspect History

```bash
git log
git log --oneline
git log --oneline --graph --decorate --all      # the mental-model view
git log -[N]                                    # last N commits
git log -p                                      # with patches
git log -p -- [FILE]                            # full history of one file, with diffs
git log --stat                                  # files + insertion counts
git log --shortstat
git log --name-only
git log --name-status                           # A/M/D per file
git log --follow -- [FILE]                      # keep history across renames

git log --author="[NAME]"
git log --grep="[MSG]"                          # search commit messages
git log --grep="refund" --grep="fix" --all-match
git log -S"[STRING]"                            # pickaxe: commits that add/remove a string
git log -G"[REGEX]"                             # pickaxe by regex
git log --since="2 weeks ago" --until="yesterday"
git log --after="2026-01-01" --before="2026-07-01"
git log [BASE]..[BRANCH]                        # in BRANCH, not in BASE
git log [BASE]...[BRANCH]                       # symmetric difference
git log [REMOTE]/[BASE]..HEAD                   # your unpushed commits
git log --merges
git log --no-merges
git log --first-parent                          # mainline only, skip merged-in detail
git log -L 10,25:[FILE]                         # history of specific lines  ← underrated
git log -L :[FUNCNAME]:[FILE]                   # history of one function
git log --reverse
git log --cherry-pick --right-only [BASE]...[BRANCH]   # what's genuinely unmerged

git log --pretty=format:'%C(yellow)%h%Creset %C(cyan)%ad%Creset %s %C(green)(%an)%Creset' --date=short
git shortlog -sn                                # commit count per author
git shortlog -sn --no-merges --since="1 year ago"

git show [SHA]                                  # a commit's message + diff
git show [SHA]:[FILE]                           # a file as of that commit
git show [SHA] --stat
git show [BRANCH]:[FILE]
git show HEAD~3                                 # 3 commits back
git show [TAG]

git reflog                                      # every HEAD move — your undo history
git reflog show [BRANCH]
git rev-parse HEAD                              # full SHA of HEAD
git rev-parse --short HEAD
git rev-parse --abbrev-ref HEAD                 # current branch name
git rev-parse --show-toplevel                   # repo root path
git describe --tags                             # nearest tag + distance
git describe --tags --always --dirty
git count-objects -vH                           # repo size
```

Revision shorthand: `HEAD` `HEAD~1` (parent) `HEAD~3` `HEAD^` `HEAD^2` (2nd parent of a merge)
`@{-1}` (previous branch) `[BRANCH]@{u}` (its upstream) `[BRANCH]@{yesterday}` `:/[MSG]` (search)

---

## 6. Diff & Compare

```bash
git diff                                        # working tree vs index (unstaged)
git diff --staged                               # index vs HEAD (what commit will contain)
git diff HEAD                                   # everything uncommitted
git diff [FILE]
git diff [SHA1] [SHA2]
git diff [BASE]..[BRANCH]
git diff [BASE]...[BRANCH]                      # vs merge-base — use for PR review
git diff [REMOTE]/[BASE] HEAD
git diff --name-only [BASE]..[BRANCH]
git diff --name-status
git diff --stat
git diff -w                                      # ignore all whitespace
git diff --ignore-blank-lines
git diff --word-diff                             # per-word, great for prose
git diff --word-diff=color
git diff --color-words
git diff -U10                                    # 10 lines of context
git diff --find-renames --find-copies
git diff --diff-filter=M                         # only Modified (A/C/D/M/R/T/U/X)
git diff --submodule=diff
git diff --check                                 # flag whitespace errors before commit
git diff --no-index [FILE] [FILE2]               # diff two files outside git
git difftool -d [BASE]..[BRANCH]                 # open in your GUI diff tool

git range-diff [BASE]...[BRANCH]                 # diff two versions of a series (post-rebase)
git range-diff [SHA1]..[SHA2] [SHA3]..[SHA4]
git merge-base [BASE] [BRANCH]                   # common ancestor
git merge-base --is-ancestor [SHA] HEAD && echo merged
git cherry -v [BASE] [BRANCH]                    # which commits are not upstream
```

---

## 7. Branching

```bash
git branch                                      # local branches
git branch -a                                   # + remote-tracking
git branch -r                                   # remote-tracking only
git branch -v                                   # + last commit
git branch -vv                                  # + upstream tracking & ahead/behind
git branch --merged [BASE]                      # safe to delete
git branch --no-merged [BASE]
git branch --contains [SHA]                     # which branches include this commit
git branch --sort=-committerdate                # most recently active first
git branch --show-current

git branch [BRANCH]                             # create, stay put
git switch -c [BRANCH]                          # create + switch (modern)
git checkout -b [BRANCH]                        # create + switch (classic)
git switch -c [BRANCH] [BASE]                   # branch off a specific base
git switch -c [BRANCH] [SHA]
git switch -c [BRANCH] --track [REMOTE]/[BRANCH]
git switch [BRANCH]
git switch -                                    # back to previous branch
git checkout [SHA]                              # detached HEAD (read-only browsing)
git switch --detach [TAG]

git branch -m [NEWNAME]                         # rename current branch
git branch -m [BRANCH] [NEWNAME]
git branch -d [BRANCH]                          # delete if merged
git branch -D [BRANCH]                          # ⚠️ force delete (unmerged work lost)
git push [REMOTE] --delete [BRANCH]             # delete on the remote
git branch -u [REMOTE]/[BRANCH]                 # set upstream for current branch
git branch --unset-upstream
```

Rename `master` → `main`:

```bash
git branch -m master main
git push -u [REMOTE] main
git push [REMOTE] --delete master               # after updating the default branch in the UI
```

---

## 8. Merge & Resolve Conflicts

```bash
git merge [BRANCH]
git merge --no-ff [BRANCH]                      # always create a merge commit
git merge --ff-only [BRANCH]                    # refuse if a merge commit is needed
git merge --squash [BRANCH]                     # one flat commit, then git commit
git merge --no-commit [BRANCH]                  # stage the merge, inspect before committing
git merge -m "[MSG]" [BRANCH]
git merge [REMOTE]/[BASE]
git merge --abort                               # bail out of a conflicted merge
git merge --continue
git merge -X ours [BRANCH]                      # prefer our side in conflicting hunks
git merge -X theirs [BRANCH]
git merge -X ignore-space-change [BRANCH]
git merge --allow-unrelated-histories [BRANCH]
```

Conflict workflow:

```bash
git status                                      # "both modified" = conflicts
git diff --diff-filter=U                        # just the conflicted hunks
git diff --name-only --diff-filter=U            # list conflicted files
git checkout --ours [FILE]                      # take our whole version
git checkout --theirs [FILE]                    # take their whole version
git checkout --merge [FILE]                     # restore the conflict markers
git mergetool                                   # open configured 3-way tool
git show :1:[FILE]                              # base version
git show :2:[FILE]                              # ours
git show :3:[FILE]                              # theirs
git add [FILE]                                  # mark resolved
git commit                                      # finish the merge
```

Conflict marker anatomy (`zdiff3` style):

```
<<<<<<< HEAD          ← your version
||||||| base          ← common ancestor (only with diff3/zdiff3)
=======
>>>>>>> [BRANCH]      ← incoming version
```

---

## 9. Remotes

```bash
git remote -v                                   # list remotes + URLs
git remote show [REMOTE]                        # detailed, hits the network
git remote add [REMOTE] https://[HOST]/[USER]/[REPO].git
git remote add upstream https://[HOST]/[USER]/[REPO].git   # fork workflow
git remote set-url [REMOTE] git@[HOST]:[USER]/[REPO].git
git remote set-url --push [REMOTE] [URL]        # different push target
git remote rename [REMOTE] [NEWNAME]
git remote remove [REMOTE]
git remote prune [REMOTE]                       # drop stale remote-tracking refs
git remote prune [REMOTE] --dry-run
git remote get-url [REMOTE]
git ls-remote [REMOTE]                          # refs on the server, no clone needed
git ls-remote --tags [REMOTE]
git ls-remote --heads https://[HOST]/[USER]/[REPO].git
```

Switch HTTPS → SSH:

```bash
git remote set-url origin git@[HOST]:[USER]/[REPO].git
git remote -v                                   # verify
```

---

## 10. Fetch, Pull & Push

```bash
git fetch                                       # update remote-tracking refs, touch nothing else
git fetch [REMOTE]
git fetch --all
git fetch --prune                               # + delete refs whose branch is gone
git fetch --tags
git fetch --prune --prune-tags
git fetch [REMOTE] [BRANCH]
git fetch [REMOTE] [BRANCH]:[BRANCH]            # fetch straight into a local branch
git fetch --unshallow                           # convert a shallow clone to full
git fetch --depth=1

git pull                                        # fetch + integrate
git pull --rebase                               # replay your commits on top (linear)
git pull --rebase=interactive
git pull --ff-only                              # safest: fail instead of merging
git pull --no-rebase                            # force a merge
git pull [REMOTE] [BRANCH]
git pull --autostash                            # stash dirty tree, pull, unstash

git push
git push [REMOTE] [BRANCH]
git push -u [REMOTE] [BRANCH]                   # push + set upstream
git push [REMOTE] HEAD                          # push current branch by same name
git push [REMOTE] HEAD:[BRANCH]                 # push to a differently named branch
git push --all [REMOTE]
git push --tags
git push --follow-tags                          # commits + annotated tags they reach
git push [REMOTE] [TAG]
git push [REMOTE] --delete [BRANCH]
git push [REMOTE] --delete [TAG]
git push --force-with-lease                     # ⚠️ safe force: aborts if remote moved
git push --force-with-lease=[BRANCH]:[SHA]      # ⚠️ strictest form
git push --force                                # ⚠️⚠️ can destroy teammates' commits — avoid
git push --dry-run
git push [REMOTE] :[BRANCH]                     # old-style delete
git push --atomic [REMOTE] [BRANCH] [TAG]       # all-or-nothing
git push -o merge_request.create [REMOTE] [BRANCH]   # GitLab: open MR from push
```

**Rule:** never `--force`. Always `--force-with-lease` — it refuses the push if someone else
updated the branch since your last fetch.

---

## 11. Undo & Recover (basic)

Pick by *what you want to undo*:

```bash
# Discard unstaged changes in a file
git restore [FILE]
git checkout -- [FILE]                          # classic equivalent

# Discard ALL unstaged changes
git restore .                                   # ⚠️ unrecoverable
git checkout -- .

# Unstage a file (keep the edits)
git restore --staged [FILE]
git reset [FILE]                                # classic equivalent

# Unstage everything
git reset

# Restore a file as of another commit
git restore --source=[SHA] [FILE]
git restore --source=[BRANCH] --staged --worktree [FILE]
git checkout [SHA] -- [FILE]

# Undo last commit, keep changes staged
git reset --soft HEAD~1

# Undo last commit, keep changes unstaged
git reset HEAD~1                                # --mixed is the default

# Undo last commit and throw the changes away
git reset --hard HEAD~1                         # ⚠️ DESTRUCTIVE

# Undo a pushed commit safely (new commit that reverses it)
git revert [SHA]
git revert --no-commit [SHA]
git revert -m 1 [MERGE_SHA]                     # revert a merge, keep parent 1
git revert [SHA1]..[SHA2]                       # a range
git revert --abort

# Recover a deleted branch / lost commit
git reflog                                      # find the SHA
git switch -c [BRANCH] [SHA]
git reset --hard [SHA]

# Recover a commit no ref points to
git fsck --lost-found
git show [SHA]

# Nuke untracked files
git clean -n                                    # dry run FIRST, always
git clean -f                                    # ⚠️ delete untracked files
git clean -fd                                   # ⚠️ + untracked directories
git clean -fdx                                  # ⚠️⚠️ + ignored files (kills .env, node_modules)
git clean -fdi                                  # interactive

# Full reset to match the remote exactly
git fetch [REMOTE] && git reset --hard [REMOTE]/[BRANCH]   # ⚠️ DESTRUCTIVE
```

Reset modes at a glance:

| Mode | HEAD | Index | Working tree |
|---|---|---|---|
| `--soft` | moves | kept | kept |
| `--mixed` (default) | moves | reset | kept |
| `--hard` | moves | reset | **reset ⚠️** |
| `--keep` | moves | reset | kept, aborts if it would clobber |

---

## 12. Ignoring Files

```bash
git check-ignore -v [FILE]                      # which rule ignores this file?
git status --ignored
git ls-files --others --ignored --exclude-standard   # list ignored files
git ls-files --others --exclude-standard             # list untracked files

# Already-committed file that should be ignored:
echo "[FILE]" >> .gitignore
git rm --cached [FILE]
git commit -m "chore: stop tracking [FILE]"

# Re-apply .gitignore across the whole repo:
git rm -r --cached . && git add . && git commit -m "chore: apply gitignore"

# Ignore locally without touching the shared .gitignore:
echo "[FILE]" >> .git/info/exclude

# Global ignore for editor/OS cruft:
git config --global core.excludesfile ~/.gitignore_global
printf '.DS_Store\n*.swp\n.idea/\n.vscode/\n' >> ~/.gitignore_global

# Stop tracking local changes to a tracked config file (use sparingly):
git update-index --skip-worktree [FILE]
git update-index --no-skip-worktree [FILE]
git ls-files -v | grep '^S'                     # list skip-worktree files
```

`.gitignore` pattern syntax:

```gitignore
build/            # directory anywhere
/build            # only at repo root
*.log             # extension
!keep.log         # negate a previous rule
docs/**/tmp       # any depth
[Bb]uild          # character class
*.env             # 🔐 never commit environment files
!*.env.example
```

---

## 13. Tags & Releases

```bash
git tag                                         # list
git tag -l "v1.*"
git tag --sort=-v:refname                       # newest semver first
git tag -n9                                     # list with messages

git tag [TAG]                                   # lightweight
git tag -a [TAG] -m "[MSG]"                     # annotated — use this for releases
git tag -a [TAG] [SHA] -m "[MSG]"               # tag a past commit
git tag -s [TAG] -m "[MSG]"                     # GPG-signed tag
git tag -f [TAG] [SHA]                          # ⚠️ move an existing tag

git show [TAG]
git tag --points-at HEAD
git tag --contains [SHA]
git verify-tag [TAG]

git push [REMOTE] [TAG]
git push [REMOTE] --tags
git push [REMOTE] --follow-tags
git tag -d [TAG]                                # delete locally
git push [REMOTE] --delete [TAG]                # delete on remote
git fetch --prune --prune-tags                  # sync deletions inbound

git checkout [TAG]                              # detached HEAD at the tag
git switch -c hotfix/[TAG] [TAG]                # branch off a release
```

Auto-changelog between two tags:

```bash
git log --oneline --no-merges [TAG]..HEAD
git log --pretty=format:'- %s (%h)' [TAG]..[TAG2] --no-merges
```

---

## 14. Stash

```bash
git stash                                       # shelve tracked changes
git stash push -m "[MSG]"                       # named stash
git stash -u                                    # + untracked files
git stash -a                                    # + ignored files too
git stash push [FILE]                           # stash only certain paths
git stash push -p                               # interactively choose hunks
git stash --staged                              # stash only what's staged (git 2.35+)
git stash -k                                    # keep the index intact

git stash list
git stash show                                  # summary of latest
git stash show -p stash@{1}                     # full patch
git stash apply                                 # restore, keep the stash
git stash apply stash@{2}
git stash pop                                   # restore + delete the stash
git stash branch [BRANCH] stash@{0}             # pop onto a fresh branch
git stash drop stash@{0}
git stash clear                                 # ⚠️ delete every stash
```

Recover a dropped stash:

```bash
git fsck --unreachable | grep commit | cut -d' ' -f3 | xargs -n1 git log --merges --no-walk
git stash apply [SHA]
```

---

# PART 2 — ADVANCED OPERATIONS

## 15. Rebase & Interactive Rebase

```bash
git rebase [BASE]                               # replay current branch on BASE
git rebase [REMOTE]/[BASE]
git rebase -i HEAD~[N]                          # edit the last N commits
git rebase -i [BASE]
git rebase -i --root                            # include the very first commit
git rebase --onto [NEWBASE] [OLDBASE] [BRANCH]  # transplant a branch
git rebase --onto [BASE] HEAD~3                 # drop the last 3 commits' base
git rebase --autosquash -i [BASE]               # apply fixup!/squash! markers
git rebase --autostash [BASE]
git rebase --update-refs [BASE]                 # move stacked branches too (git 2.38+)
git rebase --rebase-merges [BASE]               # preserve merge topology
git rebase -X theirs [BASE]                     # conflict strategy
git rebase --exec "npm test" [BASE]             # run a command after every commit ← CI locally
git rebase --continue
git rebase --skip
git rebase --abort                              # always safe while rebasing
git rebase --quit                               # stop but keep the current state
```

Interactive rebase verbs (in the todo editor):

| Verb | Short | Effect |
|---|---|---|
| `pick` | `p` | keep the commit |
| `reword` | `r` | keep changes, edit message |
| `edit` | `e` | stop to amend the commit |
| `squash` | `s` | fold into previous, combine messages |
| `fixup` | `f` | fold into previous, discard this message |
| `drop` | `d` | remove the commit |
| `exec` | `x` | run a shell command |
| `break` | `b` | pause here |
| `reset` / `label` / `merge` | | rebuild topology (`--rebase-merges`) |

The clean fixup loop — no manual todo editing:

```bash
git commit --fixup [SHA]                        # or --fixup=amend:[SHA] to also edit the message
git rebase -i --autosquash [SHA]~1
```

**⚠️ Golden rule:** rebase only commits that exist solely on your machine, or on a branch only
you use. Rebasing shared history forces everyone else to recover manually.

---

## 16. Cherry-pick

```bash
git cherry-pick [SHA]
git cherry-pick [SHA1] [SHA2] [SHA3]
git cherry-pick [SHA1]..[SHA2]                  # exclusive of SHA1
git cherry-pick [SHA1]^..[SHA2]                 # inclusive
git cherry-pick -n [SHA]                        # stage only, don't commit
git cherry-pick -x [SHA]                        # add "(cherry picked from …)" line
git cherry-pick -e [SHA]                        # edit the message
git cherry-pick -m 1 [MERGE_SHA]                # pick a merge commit
git cherry-pick --ff [SHA]
git cherry-pick -S [SHA]                        # sign the new commit
git cherry-pick --continue
git cherry-pick --skip
git cherry-pick --abort
git cherry-pick --quit

# Backport a fix from main to a release branch:
git switch release/[TAG] && git cherry-pick -x [SHA] && git push [REMOTE] release/[TAG]
```

---

## 17. Reflog & Disaster Recovery

```bash
git reflog                                      # HEAD movements (default 90 days)
git reflog --date=iso
git reflog show [BRANCH]
git reflog show --all
git reflog expire --expire=now --all            # ⚠️ removes your safety net

git reset --hard HEAD@{1}                       # undo the last history-changing op
git reset --hard [BRANCH]@{yesterday}
git switch -c recovered [SHA]                   # rescue an orphaned commit

git fsck --full --unreachable --lost-found      # find dangling objects
ls .git/lost-found/commit/
git show [SHA]

git cat-file -p [SHA]                           # inspect any object
git rev-list --all --objects | grep [FILE]      # which commit holds this blob?
```

**Recovery decision tree**

| Situation | Fix |
|---|---|
| Bad `reset --hard` | `git reset --hard HEAD@{1}` |
| Deleted branch | `git reflog` → `git switch -c [BRANCH] [SHA]` |
| Bad rebase | `git reset --hard [BRANCH]@{1}` (or `ORIG_HEAD`) |
| Bad merge (unpushed) | `git reset --hard ORIG_HEAD` |
| Bad merge (pushed) | `git revert -m 1 [MERGE_SHA]` |
| Force-pushed over a teammate | they run `git reflog` and re-push their SHA |
| Lost stash | `git fsck --unreachable` → `git stash apply [SHA]` |
| Lost uncommitted, never staged | not recoverable — git never saw it |
| Lost staged-but-uncommitted | `git fsck --lost-found`, look in `dangling blob` |

---

## 18. Rewrite History

```bash
git commit --amend --no-edit                    # ⚠️ last commit only
git commit --amend --author="[NAME] <[EMAIL]>"
git commit --amend --reset-author
git rebase -i [BASE]                            # ⚠️ arbitrary rewriting (§15)
```

### Purge a file (or leaked secret) from all history — `git-filter-repo`

`filter-branch` is deprecated and slow. Use `git-filter-repo` (`brew install git-filter-repo`).
Always run it on a **fresh clone**.

```bash
git clone --mirror https://[HOST]/[USER]/[REPO].git && cd [REPO].git

git filter-repo --path [FILE] --invert-paths            # ⚠️ remove a path everywhere
git filter-repo --path-glob '*.env' --invert-paths
git filter-repo --strip-blobs-bigger-than 10M
git filter-repo --replace-text replacements.txt         # 🔐 redact secret strings
git filter-repo --mailmap mailmap.txt                   # fix author names/emails
git filter-repo --subdirectory-filter [DIR]             # promote a subdir to repo root
git filter-repo --to-subdirectory-filter [DIR]          # move everything into a subdir
git filter-repo --path [DIR] --path [FILE]              # keep only these paths

git push --force --all                                  # ⚠️⚠️ rewrites the remote
git push --force --tags
```

`replacements.txt` format:

```
literal:AKIA[PLACEHOLDER]==>***REMOVED***
regex:sk_live_[A-Za-z0-9]+==>***REMOVED***
```

**🔐 After purging a secret, the secret is still compromised.** Rotate the credential at the
source, invalidate the old one, and audit access logs. History rewriting only removes the copy in
your repo — forks, clones, CI caches, and host-side cached views may retain it.

Alternative — BFG (faster on huge repos):

```bash
bfg --delete-files [FILE] [REPO].git
bfg --replace-text replacements.txt [REPO].git
bfg --strip-blobs-bigger-than 50M [REPO].git
cd [REPO].git && git reflog expire --expire=now --all && git gc --prune=now --aggressive
```

Rewrite author across history (fresh clone + mailmap is safer than a shell filter):

```bash
git filter-repo --commit-callback '
  if commit.author_email == b"old@example.com":
      commit.author_email = b"[EMAIL]"
      commit.author_name  = b"[NAME]"
'
```

---

## 19. Bisect — Find the Commit That Broke It

```bash
git bisect start
git bisect bad                                  # current commit is broken
git bisect good [SHA]                           # this one worked
# git checks out a midpoint; test it, then:
git bisect good
git bisect bad
git bisect skip                                 # can't test this one
git bisect reset                                # back to where you started

git bisect start [BAD_SHA] [GOOD_SHA]           # one-liner setup
git bisect log > bisect.log                     # save the session
git bisect replay bisect.log
git bisect visualize

# Fully automated — script exits 0 for good, 1 for bad, 125 to skip:
git bisect start HEAD [GOOD_SHA]
git bisect run ./scripts/reproduce.sh
git bisect run npm test -- [TESTNAME]
git bisect run sh -c 'go build ./... && go test ./pkg/pay/'
git bisect reset
```

---

## 20. Blame & Code Archaeology

```bash
git blame [FILE]
git blame -L 40,80 [FILE]                       # a line range
git blame -w [FILE]                             # ignore whitespace-only changes
git blame -C -C [FILE]                          # detect moved/copied lines
git blame -M [FILE]                             # detect moves within the file
git blame --since=3.months [FILE]
git blame [SHA] -- [FILE]                       # blame as of a past commit
git blame --reverse [SHA]..HEAD -- [FILE]       # when did each line disappear?
git blame --ignore-rev [SHA] [FILE]             # skip a reformat commit
git config blame.ignoreRevsFile .git-blame-ignore-revs
git blame -e [FILE]                             # show emails

git log -S"[STRING]" --oneline -- [FILE]        # when was this string introduced?
git log -G"[REGEX]" -p
git log --diff-filter=D --name-only -- [FILE]   # which commit deleted it?
git log --all --full-history -- [FILE]          # full history incl. deleted paths
git grep "[STRING]"                             # grep the working tree, fast
git grep -n "[STRING]"
git grep -i -w "[STRING]"
git grep "[STRING]" [SHA]                       # grep a past commit
git grep "[STRING]" $(git rev-list --all)       # grep ALL history (slow)
git grep -l "[STRING]"                          # filenames only
git grep -c "[STRING]"                          # match counts
git grep --untracked "[STRING]"
git grep -e "[STRING]" --and -e "[STRING2]"
```

---

## 21. Worktrees — Multiple Branches Checked Out at Once

Better than stashing when you need to jump to a hotfix mid-task.

```bash
git worktree list
git worktree list --porcelain
git worktree add ../[REPO]-[BRANCH] [BRANCH]    # existing branch
git worktree add -b [BRANCH] ../[REPO]-[BRANCH] [BASE]   # new branch + dir
git worktree add --detach ../review [SHA]
git worktree add --track -b [BRANCH] ../wt [REMOTE]/[BRANCH]
git worktree remove ../[REPO]-[BRANCH]
git worktree remove --force ../[REPO]-[BRANCH]
git worktree prune                              # clean up deleted worktree metadata
git worktree lock ../[REPO]-[BRANCH]
git worktree unlock ../[REPO]-[BRANCH]
git worktree move ../old ../new
git worktree repair
```

---

## 22. Submodules

```bash
git submodule add https://[HOST]/[USER]/[REPO].git [DIR]
git submodule add -b [BRANCH] https://[HOST]/[USER]/[REPO].git [DIR]
git submodule init
git submodule update
git submodule update --init --recursive         # the one you'll use after cloning
git submodule update --remote                   # pull each submodule's latest
git submodule update --remote --merge
git submodule update --recursive --remote
git clone --recurse-submodules [URL]
git submodule status
git submodule status --recursive
git submodule summary
git submodule foreach 'git switch [BASE] && git pull'
git submodule foreach --recursive 'git clean -fd'
git submodule sync --recursive                  # after a submodule URL change
git submodule set-url [DIR] [URL]
git submodule set-branch --branch [BRANCH] [DIR]
git submodule deinit -f [DIR]
git rm [DIR] && rm -rf .git/modules/[DIR]       # fully remove a submodule
git config --global submodule.recurse true      # auto-recurse on pull/checkout
git push --recurse-submodules=on-demand
git diff --submodule=log
```

---

## 23. Subtrees — Vendor a Repo Without Submodule Pain

```bash
git subtree add --prefix=[DIR] https://[HOST]/[USER]/[REPO].git [BASE] --squash
git subtree pull --prefix=[DIR] https://[HOST]/[USER]/[REPO].git [BASE] --squash
git subtree push --prefix=[DIR] https://[HOST]/[USER]/[REPO].git [BRANCH]
git subtree split --prefix=[DIR] -b [BRANCH]    # extract a subdir into its own branch
git subtree merge --prefix=[DIR] [REMOTE]/[BASE] --squash

# Extract a subdirectory into a brand-new standalone repo:
git subtree split --prefix=[DIR] -b split-out
git clone . ../[REPO]-new -b split-out
```

---

## 24. Big Repos — Shallow, Partial, Sparse

```bash
# Shallow
git clone --depth 1 [URL]
git clone --depth 50 --no-single-branch [URL]
git fetch --deepen 100
git fetch --unshallow
git clone --shallow-since="2026-01-01" [URL]
git clone --shallow-exclude=[TAG] [URL]

# Partial clone (blobs on demand — best for monorepos)
git clone --filter=blob:none [URL]
git clone --filter=blob:limit=1m [URL]
git clone --filter=tree:0 [URL]                 # treeless, shallowest useful form

# Sparse checkout (only materialize some paths)
git sparse-checkout init --cone
git sparse-checkout set [DIR] [DIR2]
git sparse-checkout add [DIR3]
git sparse-checkout list
git sparse-checkout reapply
git sparse-checkout disable
git clone --filter=blob:none --sparse [URL]     # the modern monorepo clone

# Speed-ups
git config core.fsmonitor true                  # filesystem monitor
git config core.untrackedCache true
git config feature.manyFiles true
git commit-graph write --reachable              # faster log/merge-base
git maintenance start                           # background gc/prefetch (git 2.30+)
git maintenance run --task=prefetch
```

---

## 25. Hooks

Local hooks live in `.git/hooks` (not committed). Share them via a tracked directory:

```bash
git config core.hooksPath .githooks
mkdir -p .githooks && chmod +x .githooks/*
git config --global core.hooksPath ~/.githooks   # machine-wide defaults
git config --unset core.hooksPath
git commit --no-verify                           # bypass pre-commit/commit-msg
git push --no-verify                             # bypass pre-push
```

Common hooks: `pre-commit` `prepare-commit-msg` `commit-msg` `post-commit` `pre-rebase`
`post-checkout` `post-merge` `pre-push` `pre-receive` `update` `post-receive` `post-update`

Example `pre-commit` — block secrets and oversized files (🔐 recommended for any repo touching
payment or cardholder systems):

```bash
#!/usr/bin/env bash
set -euo pipefail

# Block anything that looks like a credential
if git diff --cached -U0 | grep -nE '(AKIA[0-9A-Z]{16}|sk_live_|BEGIN [A-Z ]*PRIVATE KEY|password\s*=\s*["'\''][^"'\'']{6,})'; then
  echo "✖ possible secret in staged changes — remove it or use a secret manager" >&2
  exit 1
fi

# Block accidental env files
if git diff --cached --name-only | grep -E '(^|/)\.env(\..*)?$'; then
  echo "✖ .env files must not be committed" >&2
  exit 1
fi

# Block files > 5 MB
for f in $(git diff --cached --name-only --diff-filter=AM); do
  [ -f "$f" ] || continue
  if [ "$(wc -c < "$f")" -gt 5242880 ]; then
    echo "✖ $f exceeds 5 MB — use Git LFS" >&2; exit 1
  fi
done

# Reject leftover conflict markers and whitespace errors
git diff --cached --check
```

Framework-managed hooks:

```bash
pipx install pre-commit && pre-commit install && pre-commit run --all-files
npx husky init
gitleaks protect --staged --verbose             # 🔐 dedicated secret scanner
git secrets --install && git secrets --register-aws
```

---

## 26. Signing & Verification 🔐

Signed commits give you cryptographic authorship — an audit requirement in many regulated
environments.

### SSH signing (simplest, git 2.34+)

```bash
ssh-keygen -t ed25519 -C "[EMAIL]"              # 🔐 protect the key with a passphrase
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
printf '[EMAIL] namespaces="git" ssh-ed25519 [PLACEHOLDER_PUBKEY]\n' >> ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers
```

### GPG signing

```bash
gpg --full-generate-key
gpg --list-secret-keys --keyid-format=long
git config --global user.signingkey [KEYID]
git config --global commit.gpgsign true
gpg --armor --export [KEYID]                    # paste the PUBLIC key into your Git host
export GPG_TTY=$(tty)                           # add to ~/.zshrc if signing hangs
```

### Verify

```bash
git commit -S -m "[MSG]"
git tag -s [TAG] -m "[MSG]"
git log --show-signature
git log --pretty="%h %G? %aN %s"                # G=good, B=bad, U=unknown, N=none
git verify-commit [SHA]
git verify-tag [TAG]
git merge --verify-signatures [BRANCH]
git pull --verify-signatures
git rebase --exec 'git commit --amend --no-edit -S' [BASE]   # sign a whole series
```

---

## 27. Patches & Email Workflow

```bash
git format-patch -1 [SHA]
git format-patch [BASE]..HEAD
git format-patch -[N]
git format-patch [BASE] --stdout > series.patch
git format-patch --cover-letter -o outgoing/ [BASE]
git format-patch -v2 [BASE]                     # version 2 of a series

git am series.patch                             # apply patches, preserve authorship
git am --3way series.patch
git am --continue / --skip / --abort
git am --show-current-patch=diff

git diff > changes.patch                        # unstaged
git diff --staged > staged.patch
git diff [BASE]..[BRANCH] > branch.patch
git apply changes.patch
git apply --check changes.patch                 # test without applying
git apply --stat changes.patch
git apply -R changes.patch                      # reverse it
git apply --3way changes.patch
git apply --reject changes.patch                # leave .rej files for manual work
git apply -p2 changes.patch                     # strip 2 leading path components

git send-email --to="[EMAIL]" outgoing/*.patch
git request-pull [TAG] https://[HOST]/[USER]/[REPO].git [BRANCH]
```

Patch vs `format-patch`: `git diff` produces a plain patch (no author/message);
`format-patch` + `am` preserves the full commit metadata.

---

## 28. Archive, Bundle, Notes, Replace

```bash
# Archive — export a snapshot with no .git
git archive --format=zip --output=[REPO]-[TAG].zip [TAG]
git archive --format=tar.gz -o release.tar.gz HEAD
git archive HEAD [DIR] | tar -x -C /tmp/out
git archive --prefix=[REPO]/ -o out.tar.gz [TAG]
git archive --remote=[URL] [TAG] | tar -t

# Bundle — a whole repo in one file (airgapped transfer / offline backup)
git bundle create [REPO].bundle --all
git bundle create incr.bundle [TAG]..HEAD
git bundle verify [REPO].bundle
git bundle list-heads [REPO].bundle
git clone [REPO].bundle [REPO]
git fetch [REPO].bundle [BRANCH]:[BRANCH]

# Notes — attach metadata without changing SHAs
git notes add -m "[MSG]" [SHA]
git notes append -m "[MSG]" [SHA]
git notes show [SHA]
git notes list
git notes remove [SHA]
git log --show-notes=*
git push [REMOTE] refs/notes/*
git fetch [REMOTE] refs/notes/*:refs/notes/*

# Replace — graft history without rewriting
git replace [SHA] [SHA2]
git replace --edit [SHA]
git replace -l
git replace -d [SHA]
git --no-replace-objects log
```

---

## 29. Aliases — Make It Yours

```bash
git config --global alias.st "status -sb"
git config --global alias.co checkout
git config --global alias.sw switch
git config --global alias.br "branch -vv"
git config --global alias.ci commit
git config --global alias.cm "commit -m"
git config --global alias.ca "commit --amend --no-edit"
git config --global alias.unstage "restore --staged"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.ll "log --pretty=format:'%C(yellow)%h %C(cyan)%ad %C(reset)%s %C(green)(%an)' --date=short"
git config --global alias.who "shortlog -sn --no-merges"
git config --global alias.aliases "config --get-regexp ^alias\."
git config --global alias.wip "commit -am 'wip' --no-verify"
git config --global alias.undo "reset --soft HEAD~1"
git config --global alias.nuke "reset --hard HEAD"
git config --global alias.pushf "push --force-with-lease"
git config --global alias.sync '!git fetch --all --prune && git pull --rebase'
git config --global alias.cleanup '!git branch --merged | grep -vE "^\*|main|master|develop" | xargs -r git branch -d'
git config --global alias.root "rev-parse --show-toplevel"
git config --global alias.today "log --since=midnight --oneline --author=$(git config user.email)"
git config --global alias.contributors "shortlog -sne"
git config --global alias.tags "tag --sort=-v:refname"
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.fixup '!f() { git commit --fixup "$1"; }; f'
```

A shell-escaping alias starts with `!`. Read them all back with `git aliases`.

---

## 30. Maintenance, Size & LFS

```bash
git gc                                          # garbage collect
git gc --aggressive --prune=now                 # slow, thorough
git prune
git prune-packed
git repack -Ad
git fsck --full                                 # integrity check
git count-objects -vH
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20   # biggest objects

# Find the largest blobs and which paths they are
git rev-list --objects --all \
  | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
  | awk '/^blob/ {print $3, $4}' | sort -rn | head -20

git maintenance start
git maintenance run --task=gc --task=commit-graph
git commit-graph write --reachable
git multi-pack-index write

# Git LFS — for binaries, media, large fixtures
git lfs install
git lfs track "*.psd"
git lfs track "*.zip"
git add .gitattributes
git lfs ls-files
git lfs status
git lfs pull
git lfs fetch --all
git lfs migrate import --include="*.zip" --everything    # ⚠️ rewrites history
git lfs prune
git lfs env
```

---

## 31. Merge Strategies, rerere & Attributes

```bash
git merge -s ort [BRANCH]                       # default since git 2.34
git merge -s recursive [BRANCH]                 # legacy
git merge -s ours [BRANCH]                      # keep our tree entirely, record the merge
git merge -s subtree [BRANCH]
git merge -s octopus [B1] [B2] [B3]             # merge many at once
git merge -X ours / -X theirs [BRANCH]          # per-hunk preference only
git merge -X diff-algorithm=patience [BRANCH]
git merge -X renormalize [BRANCH]
git merge -X subtree=[DIR] [BRANCH]

# rerere — reuse recorded conflict resolutions (huge for long-lived branches)
git config --global rerere.enabled true
git config --global rerere.autoUpdate true
git rerere status
git rerere diff
git rerere forget [FILE]                        # discard a bad recorded resolution
```

`.gitattributes` — declare how git treats paths:

```gitattributes
* text=auto                       # normalize line endings
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
*.pdf binary diff=astextplain
*.lock -diff -merge               # never diff/merge lockfiles by hand
package-lock.json merge=ours
*.generated.go linguist-generated=true
vendor/** linguist-vendored
*.sql diff=sql
*.md diff=markdown
CHANGELOG.md merge=union          # append both sides instead of conflicting
secrets.enc filter=git-crypt diff=git-crypt
```

```bash
git check-attr -a [FILE]                        # which attributes apply?
git add --renormalize .                         # fix line endings repo-wide
```

---

## 32. Refspecs & Surgical Push/Fetch

```bash
git push [REMOTE] [LOCAL_REF]:[REMOTE_REF]
git push [REMOTE] HEAD:refs/heads/[BRANCH]
git push [REMOTE] [SHA]:refs/heads/[BRANCH]     # push one commit as a new branch
git push [REMOTE] :refs/heads/[BRANCH]          # delete remote branch
git push [REMOTE] refs/tags/[TAG]
git push [REMOTE] 'refs/heads/feature/*:refs/heads/feature/*'

git fetch [REMOTE] refs/heads/[BRANCH]:refs/heads/[BRANCH]
git fetch [REMOTE] '+refs/pull/*/head:refs/remotes/[REMOTE]/pr/*'   # GitHub PR refs
git fetch [REMOTE] refs/merge-requests/*/head:refs/remotes/[REMOTE]/mr/*   # GitLab
git config --add remote.[REMOTE].fetch '+refs/pull/*/head:refs/remotes/[REMOTE]/pr/*'
git switch pr/[N]

git update-ref refs/heads/[BRANCH] [SHA]
git update-ref -d refs/heads/[BRANCH]
git symbolic-ref HEAD refs/heads/[BRANCH]
git for-each-ref --sort=-committerdate refs/heads/ \
  --format='%(refname:short) %(committerdate:relative) %(authorname)'
git for-each-ref --format='%(refname)' refs/tags/
git show-ref
git show-ref --tags
git rev-list --count [BASE]..[BRANCH]           # how many commits ahead
git rev-list --left-right --count [BASE]...[BRANCH]   # behind<TAB>ahead
```

---

## 33. Plumbing & Internals

```bash
git cat-file -t [SHA]                           # object type
git cat-file -p [SHA]                           # pretty-print any object
git cat-file -s [SHA]                           # size
git cat-file --batch-all-objects --batch-check
git hash-object [FILE]                          # compute the SHA
git hash-object -w [FILE]                       # …and write it into the object store
git ls-tree HEAD
git ls-tree -r HEAD --name-only
git ls-tree -r -l [SHA]                         # with sizes
git ls-files -s                                 # index contents with mode + SHA
git ls-files --stage
git write-tree
git commit-tree [TREE_SHA] -p [SHA] -m "[MSG]"
git rev-list --all --count                      # total commits
git rev-parse [BRANCH]^{tree}
git rev-parse --git-dir
git rev-parse --is-inside-work-tree
git rev-parse --verify [SHA]
git cat-file --batch < <(git rev-list --all)
git diff-tree --no-commit-id --name-only -r [SHA]
git name-rev --name-only [SHA]                  # human name for a SHA
git var -l                                      # git's own variables
```

Object model in one line: **commit → tree → (blobs | trees)**, each addressed by the SHA-1/SHA-256
of its content. Branches and tags are just files containing a SHA.

---

## 34. Debugging Git Itself

```bash
GIT_TRACE=1 git [COMMAND]                       # what git actually ran
GIT_TRACE_PACKET=1 git fetch                    # wire protocol
GIT_TRACE_PERFORMANCE=1 git status
GIT_CURL_VERBOSE=1 git fetch                    # HTTPS debugging
GIT_SSH_COMMAND="ssh -vvv" git fetch            # SSH debugging
GIT_TRACE2_PERF=1 git status
GIT_TRACE2_EVENT=/tmp/trace.json git fetch
GIT_PAGER=cat git log                           # disable the pager
GIT_EDITOR=vim git rebase -i HEAD~3
GIT_DIR=[PATH]/.git GIT_WORK_TREE=[PATH] git status
GIT_CONFIG_GLOBAL=/dev/null git config --list   # reproduce without your config
git -c user.email="[EMAIL]" commit -m "[MSG]"   # one-off config override
git -C [PATH] status                            # run against another directory
git --no-pager log --oneline -20
git --paginate diff
git version --build-options
```

---

## 35. Secrets Hygiene & Security 🔐

**Never commit:** API keys, private keys, `.env` files, database dumps, connection strings,
merchant credentials, tokens, cardholder data, or anything resembling a PAN/CVV. Git history is
effectively permanent and is replicated to every clone, fork, and CI cache.

```bash
# Scan before you commit / before you go public
gitleaks detect --source . --verbose
gitleaks protect --staged
trufflehog git file://. --only-verified
git log -p | grep -nE 'AKIA|sk_live_|BEGIN .*PRIVATE KEY|password='
git grep -nE '(secret|token|passwd|password|api[_-]?key)\s*[=:]' $(git rev-list --all) | head -50
```

If a secret was committed:

1. **Rotate the credential first** — assume it is already compromised.
2. Purge it from history with `git filter-repo --replace-text` (§18).
3. Force-push, then have every collaborator re-clone.
4. Ask your Git host to expire cached views and delete forks.
5. Audit access logs for use of the exposed credential.
6. Record the incident per your organisation's reporting obligations — for a Bangladesh Bank–
   regulated payment operator, a credential exposure is typically a reportable security event and
   PCI-DSS Req. 3/6/10 evidence may be requested. Loop in your security/compliance team before
   deciding it was benign.

Store secrets outside git: a secret manager (Vault, AWS/GCP Secrets Manager), CI secret variables,
or an encrypted-in-repo tool:

```bash
# SOPS + age
sops --encrypt --age [PLACEHOLDER_AGE_PUBKEY] secrets.yaml > secrets.enc.yaml
sops --decrypt secrets.enc.yaml

# git-crypt
git-crypt init && git-crypt add-gpg-user [EMAIL]
echo 'secrets/** filter=git-crypt diff=git-crypt' >> .gitattributes
```

### Authentication

```bash
# SSH keys (preferred)
ssh-keygen -t ed25519 -C "[EMAIL]"              # 🔐 always set a passphrase
ssh-add --apple-use-keychain ~/.ssh/id_ed25519  # macOS
cat ~/.ssh/id_ed25519.pub                       # paste PUBLIC key into your Git host
ssh -T git@[HOST]                               # verify
ssh -T -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 git@[HOST]

# Credential helpers — never store tokens in plaintext
git config --global credential.helper osxkeychain      # macOS
git config --global credential.helper manager          # Windows
git config --global credential.helper libsecret        # Linux
git config --global credential.helper 'cache --timeout=3600'
git credential-osxkeychain erase                       # then supply host= / protocol=
git config --global --unset credential.helper          # if it was set to `store` (plaintext) ⚠️
```

Never do this: `git config --global credential.helper store` writes your token unencrypted to
`~/.git-credentials`. Never put a token in a remote URL (`https://user:[PLACEHOLDER]@host/...`) —
it lands in `.git/config`, your shell history, and every log that records the URL.

`~/.ssh/config` for multiple identities:

```
Host [HOST]-work
  HostName [HOST]
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes
```

```bash
git clone git@[HOST]-work:[USER]/[REPO].git
```

---

## 36. Team Workflows

### Feature branch → PR

```bash
git switch [BASE] && git pull --rebase
git switch -c [BRANCH]
# …work…
git add -p && git commit -m "[MSG]"
git fetch [REMOTE] && git rebase [REMOTE]/[BASE]
git push -u [REMOTE] [BRANCH]
```

### Fork → upstream sync

```bash
git remote add upstream https://[HOST]/[USER]/[REPO].git
git fetch upstream
git switch [BASE]
git rebase upstream/[BASE]
git push [REMOTE] [BASE]
```

### Keep a long-lived branch current

```bash
git fetch [REMOTE] --prune
git rebase [REMOTE]/[BASE]                      # or: git merge [REMOTE]/[BASE]
git push --force-with-lease
```

### Clean up a messy branch before review

```bash
git rebase -i --autosquash [REMOTE]/[BASE]
git rebase --exec "npm test" [REMOTE]/[BASE]    # verify every commit still builds
git push --force-with-lease
```

### Release + hotfix

```bash
git switch -c release/[TAG] [BASE]
git tag -a [TAG] -m "Release [TAG]"
git push --follow-tags [REMOTE] release/[TAG]

git switch -c hotfix/[MSG] [TAG]
git commit -am "fix: [MSG]"
git switch [BASE] && git cherry-pick -x [SHA]
```

### Delete every merged local branch

```bash
git fetch --prune
git branch --merged [BASE] | grep -vE '^\*|main|master|develop|release' | xargs -r git branch -d
git remote prune [REMOTE]
```

### GitHub CLI shortcuts

```bash
gh auth login
gh repo clone [USER]/[REPO]
gh repo create [REPO] --private --source=. --push
gh pr create --base [BASE] --head [BRANCH] --title "[MSG]" --body "…"
gh pr create --fill --draft
gh pr list --state open --author "@me"
gh pr checkout [N]
gh pr diff [N]
gh pr review [N] --approve
gh pr merge [N] --squash --delete-branch
gh pr status
gh release create [TAG] --generate-notes
gh run watch
gh browse
```

---

## 37. Troubleshooting Playbook

| Symptom | Command |
|---|---|
| `fatal: not a git repository` | `git rev-parse --show-toplevel` — you're outside the repo |
| `Updates were rejected` | `git pull --rebase` then push |
| `refusing to merge unrelated histories` | `git merge --allow-unrelated-histories [BRANCH]` |
| `Your branch and origin have diverged` | `git pull --rebase` or `git reset --hard [REMOTE]/[BRANCH]` ⚠️ |
| Detached HEAD, want to keep work | `git switch -c [BRANCH]` |
| Committed to the wrong branch | `git reset --soft HEAD~1` → switch → commit |
| Committed to `main` by mistake (unpushed) | `git branch [BRANCH] && git reset --hard HEAD~1 && git switch [BRANCH]` |
| Wrong author on last commit | `git commit --amend --author="[NAME] <[EMAIL]>"` |
| Need someone else's WIP branch | `git fetch [REMOTE] && git switch [BRANCH]` |
| Filename case change ignored | `git mv -f [FILE] [NEWPATH]`; set `core.ignorecase false` |
| CRLF/LF churn everywhere | `git config core.autocrlf input && git add --renormalize .` |
| `index.lock` exists | `rm -f .git/index.lock` (only if no git process is running) |
| Repo suddenly huge | `git count-objects -vH`, then §30's biggest-blob command |
| `Permission denied (publickey)` | `ssh -T git@[HOST]`; check `ssh-add -l` and `~/.ssh/config` |
| Push hangs / asks for a password repeatedly | credential helper misconfigured — §35 |
| Merge conflict in a lockfile | regenerate it: `git checkout --theirs [FILE] && npm install` |
| Accidentally `git clean -fdx`'d | untracked files are gone; only committed work survives |
| Rebase went sideways | `git rebase --abort`, or `git reset --hard ORIG_HEAD` |
| Want the state before the last risky op | `git reset --hard HEAD@{1}` |

---

## Quick Reference — The 20 Commands That Cover 90% of Days

```bash
git status -sb
git add -p
git commit -m "[MSG]"
git commit --amend --no-edit
git switch -c [BRANCH]
git switch -
git fetch --prune
git pull --rebase
git push -u [REMOTE] [BRANCH]
git push --force-with-lease
git log --oneline --graph --decorate --all
git diff --staged
git restore [FILE]
git restore --staged [FILE]
git reset --soft HEAD~1
git stash push -m "[MSG]" && git stash pop
git rebase -i --autosquash [REMOTE]/[BASE]
git cherry-pick -x [SHA]
git revert [SHA]
git reflog
```
