Files
obsidian-vault/personal/tech/git-hygiene-post-merge.md
T

142 lines
7.1 KiB
Markdown
Executable File

# Git-гигиена после мержа PR (форк → апстрим)
> Создан 2026-09-29. Извлечено из уборки после мержа `baldaworks/balda#8` +
> `normahq/runtime#4/#5/#7`.
> Связано: [[personal/plans/balda-mm-pr-plan]], [[personal/plans/balda-runtime-rebuild-mm-validation]],
> [[personal/tech/balda-go-fx-pitfalls]].
---
## 1. Порядок операций (он обязателен)
```
worktree remove --force → worktree prune → branch -D → push origin --delete → remote prune
```
Перестановка шагов = падение. Подробности ниже.
```bash
cd <repo>
git checkout main && git pull --no-rebase <remote> main # merge, НЕ rebase
git worktree list # 🔴 СНАЧАЛА посмотреть
git worktree remove --force <path-with-gone-dir> && git worktree prune
git branch -D <merged-branches>
git push origin --delete <merged-branches>
git remote prune origin && git remote prune upstream
git status --short # ?? — untracked, разобрать отдельно
```
📌 `AGENTS.md` Балды: **синхронизация общих веток — через `merge` (`git pull --no-rebase`), не rebase.**
---
## 2. 🔴 Питфолл: git-регистрация worktree ≠ путь на диске
Самый дорогой сюрприз. `git worktree list` показывает **пути, которых уже нет**:
```
/private/tmp/balda-mm-pr8-review 06b2b8d0 (detached HEAD) ← каталог снесён из /tmp
```
`.git/worktrees/<name>` остаётся жить. Такая регистрация **держит ветку**, и удаление падает
даже при отсутствующем каталоге:
```
error: cannot delete branch 'fix/openai-tool-call-roundtrip-v2' used by worktree at
'/Users/admin/Developer/runtime-local' # ← это про ТЕКУЩИЙ worktree, не про мёртвый
```
Из двух провалов в одной сессии:
- `codex/mattermost-prod-fix` — держал мёртвый `/private/tmp/balda-mm-pr8-prod-fix`;
- `fix/openai-tool-call-roundtrip-v2` — держал **активный** worktree, где я стоял сам.
**Лечение:** `git worktree remove --force <path>` (работает и для несуществующего каталога —
просто убирает регистрацию) → `git worktree prune` → и только потом `git branch -D`.
⚠️ **Проверять `git worktree list` ДО удаления веток.** Иначе получишь список «неудаляемых» веток
и будешь думать, что они не смержены.
⚠️ `git worktree remove` на `/private/tmp/...` **уходит на апрув** — это удаление каталога, а не
безобидная мета-операция. Нормально, просто ожидаемо.
📌 Ещё: `git checkout main` **невозможен**, пока стоишь в worktree удаляемой ветки — отсюда
второй `cannot delete branch ... used by worktree` в логе.
---
## 3. ⚠️ `git pull origin main` → `Already up to date` — это НЕ «всё чисто»
После мержа PR в апстрим **ветка форка остаётся на старом коммите**, а мерж живёт только в
`upstream/main`. `origin` (форк) мержа не содержит вообще.
```bash
git pull --no-rebase origin main # → Already up to date ← вводит в заблуждение
git log --oneline -1 upstream/main # ← здесь правда
git merge-base --is-ancestor <pr-head> upstream/main && echo MERGED # ← доказательство
```
**Как доказывать факт мержа** (не по доке, а фактически):
```bash
gh api repos/<upstream>/pulls/<N> --jq '{merged, merge_commit_sha, head_sha, base_sha}'
git merge-base --is-ancestor <head_sha> upstream/main && echo YES || echo NO
```
Чтобы догнать апстрим локально — `git merge upstream/main` (не rebase).
---
## 4. `git branch -r --merged upstream/main` = список веток под снос
Один вызов отвечает «какие ветки уже в апстриме»:
```bash
git branch -r --merged upstream/main
```
⚠️ В списке появятся ветки, которые **не твои** (`upstream/feature/*`, `upstream/docs/*`) —
их не трогать. Смотреть на `origin/*` (свой форк).
---
## 5. Что не трогать
| Объект | Почему |
|---|---|
| `__dolt_remote_info__` | служебная ветка апстрима (Dolt), появляется в fetch, восстановится сама |
| untracked рабочие инструменты (`tools/*.py`, `deltas-*.json`) | не в git, значит и не «мусор». Проверять `git ls-files tools/` — пусто ≠ удалить |
| файлы вне PR, нужные для локальной сборки | `go.mod`, `Dockerfile.release`, `go.sum`, `app.go`, `preflight.go` — `checkout -- .`/`stash`/`reset --hard` их снесёт |
## 6. Stash
`git stash list` — перед любым решением выгружать содержимое, а не удалять вслепую:
```bash
git stash show --name-only stash@{N}
git stash show -p stash@{N} > /path/patch.diff # восстановимо после drop
```
`git stash drop` — **только с явного подтверждения**. Дата в сообщении stash (`whale-20260623`)
говорит, как давно он лежит; старый stash обычно надо сначала прочитать, потом решать.
---
## 7. 📌 Реакция Alex: бэкап не предлагать
Предложение «сделать бэкап веток перед сносом» получило резкий отказ. **Смерженный код
восстанавливается из апстрима — бэкапить нечего.** Бэкап в этом контексте = лишний шаг,
который читается как неуверенность.
| Предлагать бэкап | НЕ предлагать |
|---|---|
| Продовые конфиги, БД, данные без копии | Смерженные git-ветки |
| Хостовые файлы вне git | Worktree-регистрации в `/tmp` |
| `navidrome.db` (правило: согласовывать КАЖДЫЙ раз) | untracked-мусор на смерженных коммитах |
Формулировать так: «ветка смержена в `upstream/main` (`bf4a6ae4`), сношу» — без слова «бэкап».
---
## 8. Проверка результата
```bash
git worktree list # только рабочий каталог
git branch -a # main + remotes, без дев-веток
git status --short # пусто или объяснимые untracked
```