daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

git.md (36237B)


      1 ---
      2 title: "Git"
      3 description: "Everyday Git: staging, commits, remotes, log/diff, stash, merge/rebase and recovery."
      4 category: git-workflow
      5 tags: [git, version-control, workflow]
      6 tools: [git]
      7 difficulty: beginner
      8 updated: "2026-08-09"
      9 source: "vault:Git/git-cheatsheet.md"
     10 ---
     11 
     12 # Git
     13 
     14 > **Scope —** Covers the full GitByBit core curriculum taught as *workflows* rather than a bare command list, plus a Real-World Workflows section (history wipe + re-publish, branch create/merge, conflict resolution). For deeper dives see Git Branching and Git Reset & Undo.
     15 
     16 The mental model: Git tracks snapshots across three areas. Every command moves changes between them.
     17 
     18 <figure class="flow plate corners">
     19   <figcaption class="flow__cap"><span class="flow__kind">The git object model</span><span class="flow__dir">LR</span></figcaption>
     20   <div class="flow__body">
     21     <svg class="flow-svg" viewBox="0 0 900 200" role="img" aria-label="Working tree to staging to repository to remote, with checkout, restore, pull and fetch flowing back">
     22       <!-- forward chain -->
     23       <path class="fedge" d="M185,82 L263,82" marker-end="url(#flow-arrow)" />
     24       <path class="fedge" d="M415,82 L493,82" marker-end="url(#flow-arrow)" />
     25       <path class="fedge" d="M645,82 L723,82" marker-end="url(#flow-arrow)" />
     26       <!-- back edges -->
     27       <path class="fedge is-back" d="M540,106 L540,172 L110,172 L110,108" marker-end="url(#flow-arrow)" />
     28       <path class="fedge is-back" d="M800,106 L800,148 L600,148 L600,108" marker-end="url(#flow-arrow)" />
     29       <!-- nodes -->
     30       <g class="fnode"><rect class="fnode__box" x="35" y="58" width="150" height="48" /><text class="fnode__label" x="110" y="79" text-anchor="middle">Working tree<tspan class="sub" x="110" dy="15">your edits</tspan></text></g>
     31       <g class="fnode"><rect class="fnode__box" x="265" y="58" width="150" height="48" /><text class="fnode__label" x="340" y="79" text-anchor="middle">Staging area<tspan class="sub" x="340" dy="15">index</tspan></text></g>
     32       <g class="fnode"><rect class="fnode__box" x="495" y="58" width="150" height="48" /><text class="fnode__label" x="570" y="79" text-anchor="middle">Repository<tspan class="sub" x="570" dy="15">.git history</tspan></text></g>
     33       <g class="fnode is-goal"><rect class="fnode__box" x="725" y="58" width="150" height="48" /><text class="fnode__label" x="800" y="79" text-anchor="middle">Remote<tspan class="sub" x="800" dy="15">GitHub</tspan></text></g>
     34       <!-- edge labels -->
     35       <g class="felabel"><rect class="felabel__box" x="199" y="74" width="50" height="16" /><text class="felabel__text" x="224" y="85" text-anchor="middle">git add</text></g>
     36       <g class="felabel"><rect class="felabel__box" x="422" y="74" width="64" height="16" /><text class="felabel__text" x="454" y="85" text-anchor="middle">git commit</text></g>
     37       <g class="felabel"><rect class="felabel__box" x="655" y="74" width="58" height="16" /><text class="felabel__text" x="684" y="85" text-anchor="middle">git push</text></g>
     38       <g class="felabel"><rect class="felabel__box" x="248" y="164" width="164" height="16" /><text class="felabel__text" x="330" y="175" text-anchor="middle">git checkout / restore</text></g>
     39       <g class="felabel"><rect class="felabel__box" x="650" y="140" width="100" height="16" /><text class="felabel__text" x="700" y="151" text-anchor="middle">git pull / fetch</text></g>
     40     </svg>
     41   </div>
     42 </figure>
     43 
     44 ## 1. Intro & Setup
     45 
     46 Git is a distributed version control system: every clone is a full repository with complete history. Before your first commit, tell Git who you are and set sane defaults.
     47 
     48 ```bash
     49 # Identity (used in every commit) — --global writes to ~/.gitconfig
     50 git config --global user.name  "Netrunner"
     51 git config --global user.email "you@example.com"
     52 
     53 # Make new repos use 'main' instead of 'master'
     54 git config --global init.defaultBranch main
     55 
     56 # Line endings: 'input' on Linux/macOS, 'true' on Windows
     57 git config --global core.autocrlf input
     58 
     59 # Handy quality-of-life
     60 git config --global pull.rebase false          # merge on pull (default, explicit)
     61 git config --global core.editor "nvim"         # editor for commit messages
     62 
     63 git config --list                              # verify everything
     64 ```
     65 
     66 > **Tip — Per-repo overrides:** Drop `--global` to set a value for the current repo only (e.g. a work email on a work project). Local config lives in `.git/config` and wins over global.
     67 
     68 ## 2. Getting Files Into the Repo
     69 
     70 The core loop you'll run hundreds of times a day: **edit → status → add → commit**.
     71 
     72 ```bash
     73 git init                    # turn a folder into a repo (creates .git/)
     74 git init -b main            # ...and name the initial branch 'main'
     75 
     76 git status                  # what's changed, staged, untracked — run this constantly
     77 git status -s               # short format
     78 
     79 git add file.py             # stage one file
     80 git add src/                # stage a directory
     81 git add -A                  # stage everything (new, modified, deleted)
     82 git add -p                  # interactively stage hunks (great for clean commits)
     83 
     84 git commit -m "Add login handler"       # commit staged changes with a message
     85 git commit -am "Fix typo"               # add (tracked files) + commit in one step
     86 git commit                              # opens editor for a multi-line message
     87 ```
     88 
     89 <figure class="flow plate corners">
     90   <figcaption class="flow__cap"><span class="flow__kind">The edit-commit loop</span><span class="flow__dir">LR</span></figcaption>
     91   <div class="flow__body">
     92     <svg class="flow-svg" viewBox="0 0 900 190" role="img" aria-label="Edit files to git status to git add to git commit, then back to edit files">
     93       <path class="fedge" d="M185,82 L263,82" marker-end="url(#flow-arrow)" />
     94       <path class="fedge" d="M415,82 L493,82" marker-end="url(#flow-arrow)" />
     95       <path class="fedge" d="M645,82 L723,82" marker-end="url(#flow-arrow)" />
     96       <path class="fedge is-back" d="M800,106 L800,158 L110,158 L110,108" marker-end="url(#flow-arrow)" />
     97       <g class="fnode is-entry"><rect class="fnode__box" x="35" y="58" width="150" height="48" /><text class="fnode__label" x="110" y="86" text-anchor="middle">Edit files</text></g>
     98       <g class="fnode"><rect class="fnode__box" x="265" y="58" width="150" height="48" /><text class="fnode__label" x="340" y="86" text-anchor="middle">git status</text></g>
     99       <g class="fnode"><rect class="fnode__box" x="495" y="58" width="150" height="48" /><text class="fnode__label" x="570" y="86" text-anchor="middle">git add</text></g>
    100       <g class="fnode"><rect class="fnode__box" x="725" y="58" width="150" height="48" /><text class="fnode__label" x="800" y="86" text-anchor="middle">git commit -m</text></g>
    101     </svg>
    102   </div>
    103 </figure>
    104 
    105 > **Note — A good commit:** Stage related changes together and write a message in the imperative mood ("Add", "Fix", "Refactor") describing *why*, not just *what*. Use `git add -p` to split unrelated edits into separate commits.
    106 
    107 **Ignoring files** — create a `.gitignore` before your first commit:
    108 
    109 ```bash
    110 cat > .gitignore <<'EOF'
    111 __pycache__/
    112 *.pyc
    113 .env
    114 loot/
    115 *.ccache
    116 EOF
    117 ```
    118 
    119 ## 3. Resetting Unwanted Changes
    120 
    121 Undo operations, from safest (working tree) to most destructive (history). Know which area you're touching. Full treatment in Git Reset & Undo.
    122 
    123 ```bash
    124 # See exactly what changed before undoing anything
    125 git diff                    # working tree vs staging (unstaged changes)
    126 git diff --staged           # staging vs last commit (what a commit would include)
    127 
    128 # Discard UNSTAGED changes in the working tree (irreversible)
    129 git restore file.py         # modern
    130 git checkout -- file.py     # older syntax, same effect
    131 
    132 # Unstage a file (keep the edit, just remove from index)
    133 git restore --staged file.py
    134 git reset HEAD file.py      # older syntax
    135 
    136 # Throw away ALL local uncommitted changes
    137 git restore .
    138 git reset --hard            # nukes working tree + index to last commit
    139 
    140 # Remove untracked files/dirs (careful!)
    141 git clean -n                # dry-run: show what would be deleted
    142 git clean -fd               # force-delete untracked files and directories
    143 
    144 # Fix the LAST commit (message or forgotten file)
    145 git add forgotten.py
    146 git commit --amend                       # opens editor to edit message too
    147 git commit --amend --no-edit             # keep message, just add the file
    148 ```
    149 
    150 > **Warning — `--amend` and `reset --hard` rewrite/discard:** `--amend` creates a *new* commit replacing the last one — never amend a commit you've already pushed to a shared branch. `reset --hard` permanently drops uncommitted work.
    151 
    152 ## 4. Tagging & Branching
    153 
    154 A branch is just a movable pointer to a commit. Branching lets you develop a feature in isolation, then merge it back.
    155 
    156 ### Referencing commits & tags
    157 
    158 ```bash
    159 git tag v1.0                         # lightweight tag on current commit
    160 git tag -a v1.0 -m "First release"   # annotated tag (has message/author)
    161 git tag                              # list tags
    162 git push origin v1.0                 # push a tag (tags aren't pushed by default)
    163 git push origin --tags               # push all tags
    164 
    165 # Refer to commits: HEAD (current), HEAD~1 (one back), HEAD~3, or a hash
    166 git show HEAD~2
    167 ```
    168 
    169 ### Creating & switching branches
    170 
    171 ```bash
    172 git branch                       # list local branches (* = current)
    173 git branch feature-login         # create a branch (doesn't switch)
    174 git switch feature-login         # switch to it (modern)
    175 git switch -c feature-login      # create AND switch in one step
    176 git checkout -b feature-login    # older syntax, same as above
    177 
    178 git switch main                  # go back to main
    179 git branch -d feature-login      # delete a merged branch (safe)
    180 git branch -D feature-login      # force-delete (unmerged — careful)
    181 git branch -m old new            # rename a branch
    182 ```
    183 
    184 ### Merging branches
    185 
    186 <figure class="flow plate corners">
    187   <figcaption class="flow__cap"><span class="flow__kind">Feature branch merge</span><span class="flow__dir">TD</span></figcaption>
    188   <div class="flow__body">
    189     <div class="flow__diagram" data-dir="td">
    190       <div class="flow-rank"><div class="flow-node is-entry">init</div></div>
    191       <div class="flow-edge"></div>
    192       <div class="flow-rank"><div class="flow-node">base</div></div>
    193       <div class="flow-branches">
    194         <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">branch feature-login</span></div><div class="flow-node">form</div><div class="flow-edge"></div><div class="flow-node">validate</div></div>
    195         <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">checkout main</span></div><div class="flow-node">hotfix</div></div>
    196       </div>
    197       <div class="flow-join"></div>
    198       <div class="flow-rank"><div class="flow-node">merge feature-login</div></div>
    199       <div class="flow-edge"></div>
    200       <div class="flow-rank"><div class="flow-node is-goal">release</div></div>
    201     </div>
    202   </div>
    203 </figure>
    204 
    205 ```bash
    206 # 1. Finish work on the feature branch, commit it
    207 git switch feature-login
    208 git add -A && git commit -m "Add login form + validation"
    209 
    210 # 2. Switch to the target branch and merge
    211 git switch main
    212 git merge feature-login          # brings the feature commits into main
    213 
    214 # 3. Clean up
    215 git branch -d feature-login
    216 ```
    217 
    218 > **Tip — Fast-forward vs merge commit:** If `main` hasn't moved since you branched, Git just moves the pointer forward (fast-forward, no extra commit). If both diverged, Git creates a *merge commit*. Force a merge commit for a clear history with `git merge --no-ff feature-login`.
    219 
    220 See Git Branching and Git Reset & Undo for advanced branch management.
    221 
    222 ## 5. History
    223 
    224 Read the past, compare states, and undo old commits safely.
    225 
    226 ```bash
    227 git log                              # full history
    228 git log --oneline                    # compact, one line per commit
    229 git log --oneline --graph --all      # visual branch/merge graph
    230 git log --stat                       # files changed per commit
    231 git log -p                           # show the actual diffs
    232 git log --author="Netrunner" --since="2 weeks ago"
    233 
    234 # Compare things
    235 git diff main..feature-login         # difference between two branches
    236 git diff HEAD~3 HEAD                 # last 3 commits' combined change
    237 git show <hash>                      # one commit in full
    238 
    239 # Who changed this line, and when
    240 git blame file.py
    241 ```
    242 
    243 ### Undoing old commits — `revert` vs `reset`
    244 
    245 ```bash
    246 # REVERT: safe, creates a NEW commit that undoes an old one. Use on shared branches.
    247 git revert <hash>                    # undo one commit, keep history intact
    248 git revert HEAD                      # undo the latest commit safely
    249 
    250 # RESET: rewrites history by moving the branch pointer. Local branches only.
    251 git reset --soft HEAD~1              # undo last commit, KEEP changes staged
    252 git reset --mixed HEAD~1             # undo last commit, keep changes unstaged (default)
    253 git reset --hard HEAD~1              # undo last commit AND discard its changes
    254 ```
    255 
    256 > **Warning — revert = public, reset = private:** On a branch others have pulled, **always `revert`** — it adds history rather than rewriting it. Save `reset` for cleaning up local commits you haven't pushed.
    257 
    258 ## 6. Remotes & GitHub
    259 
    260 A remote is a copy of the repo hosted elsewhere (usually GitHub). You sync with `push` and `pull`.
    261 
    262 ```bash
    263 git clone https://github.com/USER/REPO.git       # copy a remote repo locally
    264 git clone https://github.com/USER/REPO.git dir   # into a named folder
    265 
    266 git remote -v                                     # list remotes
    267 git remote add origin https://github.com/YOU/REPO.git   # link a remote named 'origin'
    268 git remote set-url origin git@github.com:YOU/REPO.git   # switch HTTPS -> SSH
    269 
    270 git push -u origin main          # first push: -u sets upstream tracking
    271 git push                         # subsequent pushes (tracking already set)
    272 
    273 git fetch                        # download remote changes WITHOUT merging
    274 git pull                         # fetch + merge into current branch
    275 git pull --rebase                # fetch + replay your commits on top (linear history)
    276 ```
    277 
    278 > **Tip — SSH vs HTTPS + credentials:** HTTPS needs a Personal Access Token (not your password) since 2021. SSH (`git@github.com:...`) uses your key pair and avoids the prompt entirely. Set up a key with `ssh-keygen -t ed25519` and add the public key to GitHub.
    279 
    280 > **Warning — Dangers of rewriting public history:** `git push --force` overwrites the remote branch and can destroy teammates' commits. Prefer `git push --force-with-lease`, which refuses if the remote moved since you last fetched. Never force-push shared branches like `main`.
    281 
    282 ## 7. Real-World Workflows
    283 
    284 ### 7a. Set up a brand-new repo (local + GitHub)
    285 
    286 Two ways to start a fresh project. Pick based on whether the code already exists on your machine.
    287 
    288 <figure class="flow plate corners">
    289   <figcaption class="flow__cap"><span class="flow__kind">New repo path choice</span><span class="flow__dir">TD</span></figcaption>
    290   <div class="flow__body">
    291     <div class="flow__diagram" data-dir="td">
    292       <div class="flow-rank"><div class="flow-node is-decision">Code already<span class="sub">on disk?</span></div></div>
    293       <div class="flow-branches">
    294         <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">Yes</span></div><div class="flow-node">Local-first:<span class="sub">git init here,</span><span class="sub">then link empty GitHub repo</span></div></div>
    295         <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">No</span></div><div class="flow-node">GitHub-first:<span class="sub">create repo on site,</span><span class="sub">git clone it down</span></div></div>
    296       </div>
    297     </div>
    298   </div>
    299 </figure>
    300 
    301 **Step 1 — create the repo on the GitHub website**
    302 
    303 1. Go to **github.com → New** (the `+` menu, top-right) → **New repository**.
    304 2. Name it (e.g. `voidwalker`), add a description, choose **Public** or **Private**.
    305 3. **If you already have local code:** leave "Add a README / .gitignore / license" **unchecked** — an empty repo avoids a first-push conflict.
    306    **If you're starting fresh on GitHub:** tick README/.gitignore so there's something to clone.
    307 4. Click **Create repository**. GitHub shows you the repo URL (HTTPS or SSH) — copy it.
    308 
    309 **Path A — local-first (you already have files)**
    310 
    311 ```bash
    312 cd ~/projects/voidwalker            # your existing project folder
    313 git init -b main                    # start a repo, initial branch = main
    314 git add -A                          # stage everything
    315 git commit -m "Initial commit"      # first commit
    316 
    317 # Link the empty GitHub repo you just made:
    318 git remote add origin https://github.com/YOU/voidwalker.git
    319 git remote -v                       # verify: origin -> your URL (fetch + push)
    320 
    321 git push -u origin main             # -u sets upstream so later 'git push' just works
    322 ```
    323 
    324 **Path B — GitHub-first (repo created with a README)**
    325 
    326 ```bash
    327 git clone https://github.com/YOU/voidwalker.git   # brings repo down + wires origin
    328 cd voidwalker
    329 # ...add your files...
    330 git add -A
    331 git commit -m "Add project files"
    332 git push                            # origin/main already tracked by clone
    333 ```
    334 
    335 > **Tip — `git init` vs `git clone`:** `git init` makes a repo from a folder you already have and you wire the remote yourself with `git remote add`. `git clone` does init **plus** `remote add origin` **plus** the first fetch in one step. Use init when the code is local first, clone when it lives on GitHub first.
    336 
    337 > **Warning — "Updates were rejected" on first push:** This means the GitHub repo already has commits (a README you added at creation) that your local repo doesn't. Either recreate the repo empty, or reconcile once:
    338 > ```bash
    339 > git pull --rebase origin main      # replay your commit on top of GitHub's README
    340 > git push -u origin main
    341 > ```
    342 
    343 > **Note — Authentication reminder:** HTTPS pushes need a **Personal Access Token** (Settings → Developer settings → PAT), not your account password. Or switch to SSH:
    344 > ```bash
    345 > ssh-keygen -t ed25519 -C "you@example.com"   # then add ~/.ssh/id_ed25519.pub to GitHub
    346 > git remote set-url origin git@github.com:YOU/voidwalker.git
    347 > ```
    348 
    349 ### 7b. Clone a project, wipe its history, publish as your own
    350 
    351 Strip all prior history (and any commit trailers) and start a clean repo under your account.
    352 
    353 ```bash
    354 git clone https://gitlab.com/DAEMON-404/voidwalker.py.git fresh
    355 cd fresh
    356 rm -rf .git                                   # wipes all history + trailers
    357 git init -b main                              # -b main so it isn't "master"
    358 git add -A
    359 git commit -m "Initial commit"
    360 git remote add origin https://github.com/YOU/NEWREPO.git
    361 git push -u origin main
    362 ```
    363 
    364 <figure class="flow plate corners">
    365   <figcaption class="flow__cap"><span class="flow__kind">Wipe history &amp; re-publish</span><span class="flow__dir">LR</span></figcaption>
    366   <div class="flow__body">
    367     <div class="flow__diagram" data-dir="lr">
    368       <div class="flow-rank"><div class="flow-node is-entry">git clone ...</div></div>
    369       <div class="flow-edge"></div>
    370       <div class="flow-rank"><div class="flow-node is-danger">rm -rf .git<span class="sub">history gone</span></div></div>
    371       <div class="flow-edge"></div>
    372       <div class="flow-rank"><div class="flow-node">git init -b main</div></div>
    373       <div class="flow-edge"></div>
    374       <div class="flow-rank"><div class="flow-node">git add -A<span class="sub">git commit</span></div></div>
    375       <div class="flow-edge"></div>
    376       <div class="flow-rank"><div class="flow-node">remote add origin</div></div>
    377       <div class="flow-edge"></div>
    378       <div class="flow-rank"><div class="flow-node is-goal">git push -u origin main</div></div>
    379     </div>
    380   </div>
    381 </figure>
    382 
    383 > **Warning — This is destructive and one-way:** `rm -rf .git` permanently deletes every commit, branch, and tag locally. Only do this when you deliberately want a clean slate. Make sure `origin` points at *your* new empty repo before pushing, or you'll try to overwrite the original.
    384 
    385 > **Note — Alternative: keep files but squash to one commit:** If you'd rather keep the remote link but collapse history:
    386 > ```bash
    387 > git checkout --orphan clean      # new branch with no history
    388 > git add -A && git commit -m "Initial commit"
    389 > git branch -D main               # drop old branch
    390 > git branch -m main               # rename clean -> main
    391 > git push -f origin main          # force-replace remote history
    392 > ```
    393 
    394 ### 7c. Feature branch, from creation to merged PR
    395 
    396 ```bash
    397 git switch main && git pull            # start from an up-to-date main
    398 git switch -c feature/xyz              # branch off
    399 
    400 # ...work...
    401 git add -A && git commit -m "Implement xyz"
    402 git push -u origin feature/xyz         # publish the branch
    403 
    404 # Open a Pull Request on GitHub (web UI), get it reviewed, then:
    405 git switch main && git pull            # bring the merged change down locally
    406 git branch -d feature/xyz              # delete local branch
    407 git push origin --delete feature/xyz   # delete remote branch
    408 ```
    409 
    410 ### 7d. Resolving a merge conflict
    411 
    412 ```bash
    413 git switch main
    414 git merge feature/xyz
    415 # CONFLICT (content): Merge conflict in app.py
    416 ```
    417 
    418 Git marks conflicts inside the file:
    419 
    420 ```text
    421 <<<<<<< HEAD
    422 current_code_on_main()
    423 =======
    424 incoming_code_from_feature()
    425 >>>>>>> feature/xyz
    426 ```
    427 
    428 ```bash
    429 # 1. Edit each conflicted file: keep the right code, delete the <<<< ==== >>>> markers
    430 # 2. Stage the resolved files
    431 git add app.py
    432 # 3. Complete the merge
    433 git commit                             # (message pre-filled) — or: git merge --continue
    434 
    435 git merge --abort                      # ...or bail out entirely and undo the merge
    436 ```
    437 
    438 > **Tip — Let tools help:** `git mergetool` opens a three-way visual diff. In VS Code, the conflict UI gives "Accept Current / Incoming / Both" buttons over each block.
    439 
    440 ### 7e. Oops — undo safely
    441 
    442 ```bash
    443 git reflog                             # your safety net: every HEAD move, even "lost" commits
    444 git reset --hard HEAD@{2}              # jump back to a state from reflog
    445 git revert HEAD                        # undo the last commit on a shared branch (safe)
    446 git restore file.py                    # discard local edits to one file
    447 ```
    448 
    449 > **Note — `reflog` is the undo button for Git itself:** Even after a bad `reset --hard`, the old commit usually still exists and `git reflog` shows its hash for ~90 days. You can almost always recover.
    450 
    451 ### 7f. Stash — park work without committing
    452 
    453 You're mid-edit on `feature/x` and need to jump to `main` to fix something. Stashing shelves your changes cleanly.
    454 
    455 ```bash
    456 git stash                          # shelve tracked changes, clean working tree
    457 git stash -u                       # also include untracked files
    458 git stash push -m "wip: parser"    # named stash
    459 git stash list                     # see all stashes (stash@{0}, stash@{1}...)
    460 
    461 # ...do the urgent thing on main, then come back...
    462 git switch feature/x
    463 git stash pop                      # re-apply latest stash AND drop it
    464 git stash apply stash@{1}          # re-apply a specific stash, KEEP it in the list
    465 git stash drop stash@{0}           # delete one stash
    466 git stash clear                    # delete all stashes
    467 ```
    468 
    469 > **Tip — Stash conflicts:** If files changed underneath you, `pop` can conflict. Resolve the markers exactly like a merge, then `git add` the files. The stash isn't dropped automatically on conflict, so `git stash drop` once you're happy.
    470 
    471 ### 7g. Rebase — replay commits for a linear history
    472 
    473 Rebasing moves your branch's commits on top of the latest `main`, producing a straight line instead of a merge commit.
    474 
    475 <figure class="flow plate corners">
    476   <figcaption class="flow__cap"><span class="flow__kind">Rebase: before &amp; after</span><span class="flow__dir">LR</span></figcaption>
    477   <div class="flow__body">
    478     <div class="flow__diagram" data-dir="lr">
    479       <div class="flow-branches">
    480         <div class="flow-lane"><div class="flow-node is-note">Before</div><div class="flow-edge"></div><div class="flow-node">main: A-B-C</div><div class="flow-edge"></div><div class="flow-node">feat: B-D-E</div></div>
    481         <div class="flow-lane"><div class="flow-node is-note">After</div><div class="flow-edge"></div><div class="flow-node">main: A-B-C</div><div class="flow-edge"></div><div class="flow-node">feat: C-D'-E'</div></div>
    482       </div>
    483     </div>
    484   </div>
    485 </figure>
    486 
    487 ```bash
    488 git switch feature/x
    489 git fetch origin
    490 git rebase origin/main             # replay feat commits on top of latest main
    491 
    492 # If a commit conflicts:
    493 #   fix the files, then:
    494 git add <files>
    495 git rebase --continue
    496 git rebase --skip                  # skip the current commit
    497 git rebase --abort                 # bail out, back to pre-rebase state
    498 
    499 git push --force-with-lease        # rebased history differs — safe force needed
    500 ```
    501 
    502 > **Warning — Golden rule of rebasing:** Never rebase commits that others have already pulled. Rebase only your *own* un-pushed (or personal-branch) work. On shared branches, merge instead.
    503 
    504 ### 7h. Interactive rebase — clean up before a PR
    505 
    506 Squash, reorder, reword, or drop your last few commits into a tidy set.
    507 
    508 ```bash
    509 git rebase -i HEAD~4               # edit the last 4 commits
    510 ```
    511 
    512 In the editor, change the verb before each commit:
    513 
    514 ```text
    515 pick   a1b2c3 Add parser
    516 squash b4c5d6 fix typo            # fold into previous commit
    517 reword e7f8g9 Add tests          # change this commit's message
    518 drop   h1i2j3 debug print        # remove this commit entirely
    519 ```
    520 
    521 > **Tip — Squash a whole branch into one commit:** `git rebase -i main` then mark every commit after the first as `squash` (or `fixup` to discard their messages). Great for turning 12 messy WIP commits into one clean commit before opening a PR.
    522 
    523 ### 7i. Cherry-pick — grab one commit from another branch
    524 
    525 You need a single bug-fix commit from `dev` on your `release` branch, without merging everything.
    526 
    527 ```bash
    528 git switch release
    529 git cherry-pick a1b2c3d            # apply that one commit here
    530 git cherry-pick a1b2c3d..e4f5g6h   # a range (exclusive of the first)
    531 git cherry-pick --continue         # after resolving a conflict
    532 git cherry-pick --abort            # bail out
    533 ```
    534 
    535 ### 7j. Undo a commit you already pushed
    536 
    537 ```bash
    538 # SAFE (shared branch): add an inverse commit
    539 git revert <hash>
    540 git push
    541 
    542 # Undo a MERGE commit specifically (pick the mainline parent, usually -m 1)
    543 git revert -m 1 <merge-hash>
    544 git push
    545 
    546 # NUCLEAR (only if you're sure nobody has pulled): rewrite remote
    547 git reset --hard HEAD~1
    548 git push --force-with-lease
    549 ```
    550 
    551 ### 7k. Sync a fork with upstream
    552 
    553 Keep your fork of someone else's repo current.
    554 
    555 ```bash
    556 git remote add upstream https://github.com/ORIGINAL/REPO.git   # one-time
    557 git remote -v                                                  # origin=you, upstream=source
    558 
    559 git fetch upstream
    560 git switch main
    561 git merge upstream/main            # or: git rebase upstream/main
    562 git push origin main               # update your fork on GitHub
    563 ```
    564 
    565 ### 7l. Find the commit that broke something — `git bisect`
    566 
    567 Binary-search through history to pin down a regression.
    568 
    569 ```bash
    570 git bisect start
    571 git bisect bad                     # current commit is broken
    572 git bisect good v1.0               # this old tag was fine
    573 # Git checks out a midpoint commit — test it, then tell Git:
    574 git bisect good                    # ...or 'git bisect bad'
    575 # repeat until Git prints "<hash> is the first bad commit"
    576 git bisect reset                   # return to where you started
    577 
    578 # Automate it with a test script (exit 0 = good, non-zero = bad):
    579 git bisect run ./test.sh
    580 ```
    581 
    582 ### 7m. Work on two branches at once — `git worktree`
    583 
    584 Check out a second branch into a separate folder without stashing or re-cloning.
    585 
    586 ```bash
    587 git worktree add ../repo-hotfix hotfix     # hotfix branch in a sibling dir
    588 git worktree add -b experiment ../exp      # create a new branch in a new worktree
    589 git worktree list
    590 git worktree remove ../repo-hotfix         # clean up when done
    591 ```
    592 
    593 ### 7n. Tag a release and publish it
    594 
    595 ```bash
    596 git switch main && git pull
    597 git tag -a v2.1.0 -m "Release 2.1.0: adds PMKID cracking"
    598 git push origin v2.1.0             # push this tag
    599 # On GitHub: Releases -> Draft a new release -> pick the tag -> add notes
    600 
    601 git tag                            # list
    602 git tag -d v2.1.0                  # delete locally
    603 git push origin --delete v2.1.0    # delete on remote
    604 git checkout v2.0.0                # inspect the code at a tag (detached HEAD)
    605 ```
    606 
    607 ### 7o. Recover a deleted branch or commit
    608 
    609 ```bash
    610 git reflog                         # find the last commit hash the branch pointed to
    611 git switch -c recovered <hash>     # recreate the branch at that commit
    612 
    613 # Deleted a branch you hadn't merged?
    614 git branch feature/x <hash-from-reflog>
    615 
    616 # Find dangling commits reflog forgot
    617 git fsck --lost-found
    618 ```
    619 
    620 ### 7p. Selectively unstage / partial commits
    621 
    622 ```bash
    623 git add -p                         # stage hunks interactively (y/n/s to split)
    624 git reset -p                       # unstage hunks interactively
    625 git restore --staged --worktree file.py   # unstage AND discard edits to a file
    626 git checkout <hash> -- path/file   # restore ONE file from an old commit
    627 ```
    628 
    629 ### 7q. `warning: adding embedded git repository` — nested clones
    630 
    631 Hit this on any tooling folder built by cloning other people's repos into it (kali arsenal, dotfiles with vendored plugins, a `tools/` dir full of `git clone`).
    632 
    633 ```text
    634 warning: adding embedded git repository: arsenal/Lateral/impacket
    635 hint: You've added another git repository inside your current repository.
    636 hint: Clones of the outer repository will not contain the contents of
    637 hint: the embedded repository and will not know how to obtain it.
    638 ```
    639 
    640 **What Git actually did:** it saw `arsenal/Lateral/impacket/.git/`, refused to recurse, and stored a bare **gitlink** — a pointer to a commit hash with no recorded URL. Push it and anyone who clones gets an **empty directory**. Not fatal, just silently useless.
    641 
    642 **Diagnose — list every nested repo and its upstream:**
    643 
    644 ```bash
    645 # every embedded repo under the current dir
    646 find . -mindepth 2 -name .git -not -path "./.git/*" -exec dirname {} \;
    647 
    648 # same, with the URL each one came from (this is your rebuild manifest)
    649 for d in $(find . -mindepth 2 -name .git -not -path "./.git/*" -exec dirname {} \;); do
    650   printf '%s\t%s\n' "$d" "$(git -C "$d" remote get-url origin 2>/dev/null)"
    651 done
    652 
    653 # how big is this actually
    654 du -sh .            # >1 GB or any single file >100 MB => GitHub will reject it
    655 find . -type f -size +100M -not -path "./.git/*"
    656 ```
    657 
    658 **Pick one of three fixes:**
    659 
    660 | Fix | When | Cost |
    661 | :-- | :-- | :-- |
    662 | **Ignore + rebuild script** | Third-party tools you only consume. **Default choice.** | Repo stays tiny; one script to maintain |
    663 | **Submodule** | You need an exact pinned upstream commit reproducible for others | Everyone must `clone --recurse-submodules`; detached HEADs |
    664 | **Flatten** (delete inner `.git`) | You forked the code and will edit it yourself | Lose upstream history and the ability to pull updates |
    665 
    666 **Fix A — ignore it, script the rebuild (recommended):**
    667 
    668 ```bash
    669 git rm -r --cached arsenal            # only if already staged/tracked
    670 printf 'arsenal/\n' >> .gitignore     # trailing slash = the dir and all under it
    671 # then commit a build/fetch-arsenal.sh that loops the URL manifest from above:
    672 #   git clone --depth 1 "$url" "$dest"   ||  git -C "$dest" pull --ff-only
    673 git add -A && git status --short      # verify: no warnings, no arsenal/ entries
    674 ```
    675 
    676 **Fix B — make them real submodules:**
    677 
    678 ```bash
    679 git rm -r --cached arsenal/Lateral/impacket        # drop the bare gitlink
    680 rm -rf arsenal/Lateral/impacket                     # remove the loose clone
    681 git submodule add https://github.com/fortra/impacket.git arsenal/Lateral/impacket
    682 git commit -m "Add impacket as a submodule"
    683 # consumers: git clone --recurse-submodules <url>
    684 #            git submodule update --init --recursive   (after a plain clone)
    685 #            git submodule update --remote             (bump to upstream tip)
    686 ```
    687 
    688 **Fix C — flatten into your own history:**
    689 
    690 ```bash
    691 find arsenal -mindepth 2 -name .git -exec rm -rf {} +   # destroys upstream history
    692 git add -A                                              # now the files are really tracked
    693 ```
    694 
    695 > **Warning — GitHub hard limits:** 100 MB per file (hard reject), 2 GB per push, ~5 GB per repo (soft). Compiled offensive binaries (`sliver-server.exe`, `PingCastle.exe`, `winPEAS`) blow past these **and** get flagged by AV / secret scanners. Fetch them from upstream releases at build time; never commit them.
    696 
    697 > **Tip — Already committed a huge blob?** Deleting the file in a new commit does **not** shrink the repo — the blob lives in history forever. With no commits worth keeping, `rm -rf .git && git init -b main` is the fastest reset (working tree untouched — verify with `git rev-list --all --count` first). Otherwise rewrite history with `git filter-repo --path arsenal --invert-paths`.
    698 
    699 ### 7r. Pre-flight before the first push — don't leak secrets
    700 
    701 New repos get scraped by credential bots within seconds of going public, and a force-push does **not** un-leak anything already fetched.
    702 
    703 ```bash
    704 # 1. What is actually about to ship, and how big?
    705 git status --short
    706 git ls-files | xargs du -ch | tail -1
    707 
    708 # 2. Grep the STAGED tree (not the working dir) for secrets
    709 git grep -nEi "password|passwd|api[_-]?key|secret|token|BEGIN.*PRIVATE KEY" HEAD
    710 
    711 # 3. Confirm the env file is genuinely excluded
    712 git check-ignore -v .env       # prints the .gitignore rule that catches it
    713 git ls-files | grep -c '^\.env$'   # must be 0
    714 ```
    715 
    716 Ship a `.env.example` with placeholder values, gitignore the real `.env`:
    717 
    718 ```gitignore
    719 .env
    720 *.pem
    721 *.key
    722 
    723 # Ignore CONTENTS but keep the dir, so bind-mounts exist on a fresh clone.
    724 # (Git tracks files, not directories — an empty dir disappears on clone.)
    725 loot/*
    726 !loot/.gitkeep
    727 ```
    728 
    729 > **Danger — If a credential was already pushed:** **Rotate the credential first** — that is the only step that actually helps. Then clean history (`git filter-repo --invert-paths --path .env`) and force-push. Assume the old value is compromised regardless.
    730 
    731 ## 8. Quick Reference
    732 
    733 | Task | Command |
    734 | :-- | :-- |
    735 | New repo | `git init -b main` |
    736 | Clone | `git clone <url>` |
    737 | Stage all | `git add -A` |
    738 | Commit | `git commit -m "msg"` |
    739 | Status | `git status -s` |
    740 | Diff (unstaged) | `git diff` |
    741 | Discard file edits | `git restore <file>` |
    742 | Unstage | `git restore --staged <file>` |
    743 | New branch + switch | `git switch -c <name>` |
    744 | Merge branch | `git merge <name>` |
    745 | Delete branch | `git branch -d <name>` |
    746 | Compact log | `git log --oneline --graph --all` |
    747 | Undo last commit (keep work) | `git reset --soft HEAD~1` |
    748 | Undo old commit (safe) | `git revert <hash>` |
    749 | Add remote | `git remote add origin <url>` |
    750 | First push | `git push -u origin main` |
    751 | Fetch only | `git fetch` |
    752 | Pull | `git pull` |
    753 | Safe force push | `git push --force-with-lease` |
    754 | Recover lost commit | `git reflog` |
    755 | Shelve work | `git stash` / `git stash pop` |
    756 | Rebase onto main | `git rebase origin/main` |
    757 | Clean up commits | `git rebase -i HEAD~N` |
    758 | Copy one commit | `git cherry-pick <hash>` |
    759 | Undo pushed commit (safe) | `git revert <hash>` |
    760 | Sync a fork | `git fetch upstream && git merge upstream/main` |
    761 | Find a regression | `git bisect start` |
    762 | Second working dir | `git worktree add ../dir <branch>` |
    763 | Tag a release | `git tag -a v1.0 -m "..."` |
    764 | Restore one old file | `git checkout <hash> -- <file>` |
    765 | Find nested repos | `find . -mindepth 2 -name .git -not -path "./.git/*"` |
    766 | Untrack an added dir | `git rm -r --cached <dir>` |
    767 | Why is this ignored? | `git check-ignore -v <path>` |
    768 | Add a submodule | `git submodule add <url> <path>` |
    769 | Clone with submodules | `git clone --recurse-submodules <url>` |
    770 | Find oversized files | `find . -type f -size +100M -not -path "./.git/*"` |
    771 | Scan staged tree for secrets | `git grep -nEi "password\|api_key\|secret" HEAD` |
    772 | Purge a file from history | `git filter-repo --path <f> --invert-paths` |
    773 
    774 ## See Also
    775 
    776 - Git Branching — branching deep dive
    777 - Git Reset & Undo — restore / reset / clean in detail
    778 - [GitByBit course](https://gitbybit.com/)