Git
Повседневные команды и то, что нужно, когда что-то уже сломалось.
#Повседневное
git status -sb
git diff # незастейдженное
git diff --staged # то, что уйдёт в коммит
git diff main...HEAD # что моя ветка добавила относительно main
Три точки в main...HEAD — сравнение с точкой ветвления, а не с текущим состоянием main. Именно это показывает PR.
git add -p # по кускам, интерактивно
git add -u # только отслеживаемые
git restore file.kt # откатить изменения в файле
git restore --staged file.kt # убрать из индекса, изменения оставить
git add -p — способ разложить кашу изменений на осмысленные коммиты. restore заменил невнятный checkout --.
git commit -m "fix: не падать при пустом ответе"
git commit --amend # поправить последний
git commit --amend --no-edit # добавить забытое, сообщение не менять
--amend переписывает коммит: после push потребуется --force-with-lease.
git log --oneline -20
git log --oneline --graph --all -20
git log -p file.kt
git log -S "functionName" # когда появилась/исчезла строка
git log --author="Дмитрий" --since="2 weeks ago"
-S (pickaxe) ищет коммиты, изменившие число вхождений строки — лучший способ найти, когда что-то было добавлено.
git switch feature/login # переключиться
git switch -c feature/new # создать и переключиться
git switch - # на предыдущую ветку
git branch -vv # с указанием upstream
switch для ветвления, restore для файлов — вместо перегруженного checkout.
git stash push -m "недоделанный фильтр"
git stash list
git stash pop # применить и удалить
git stash apply stash@{1} # применить, оставить в списке
git stash push -- file.kt # только один файл
pop удаляет запись, apply оставляет. При конфликте pop запись не удалит — не потеряется.
#Rebase и история
git fetch origin
git rebase origin/main
git rebase --continue
git rebase --abort
--abort возвращает всё как было — запомнить в первую очередь.
git rebase -i HEAD~5
# в редакторе:
# pick — оставить
# reword — изменить сообщение
# squash — слить с предыдущим, объединив сообщения
# fixup — слить с предыдущим, сообщение выбросить
# drop — удалить коммит
# edit — остановиться и дать поправить
Интерактивный rebase. Порядок строк = порядок коммитов, строки можно менять местами.
git commit --fixup=abc1234
git rebase -i --autosquash HEAD~10
Автоматический squash: коммит помечается как правка конкретного, а --autosquash сам расставит fixup в нужные места. Удобнее ручной перестановки строк.
git pull --rebase
git config --global pull.rebase true
Без этого git pull плодит merge-коммиты «Merge branch main into main».
git push --force-with-lease
После переписывания истории. --force-with-lease откажется пушить, если кто-то успел добавить коммиты — в отличие от --force, который затрёт чужую работу.
Правило: --force не использовать никогда, --force-with-lease — только в своей ветке.
git cherry-pick abc1234
git cherry-pick abc1234..def5678
git cherry-pick -x abc1234 # добавить ссылку на источник
Перенос отдельных коммитов, например хотфикса в релизную ветку.
git revert abc1234
git revert -m 1 abc1234 # для merge-коммита
revert создаёт обратный коммит и не переписывает историю — единственный безопасный способ откатить то, что уже в общей ветке.
#Конфликты
git status # список конфликтных файлов
git diff --name-only --diff-filter=U
U = unmerged. Точный список того, что нужно разрулить.
<<<<<<< HEAD
твоя версия (текущая ветка)
=======
их версия (то, что вливается)
>>>>>>> origin/main
Что означают маркеры. При rebase «твоё» и «их» меняются местами относительно интуиции: HEAD — это то, на что перебазируешься.
Отсюда правило: при конфликте в rebase не полагаться на память, а смотреть git log --oneline HEAD -1.
git checkout --ours file.kt # оставить свою версию
git checkout --theirs file.kt # оставить их версию
git add file.kt # пометить решённым
Для файлов, где мержить нечего: сгенерированные, lock-файлы.
git config --global merge.conflictstyle zdiff3
Показывает ещё и общего предка — становится видно, что именно каждая сторона изменила. Резко упрощает разбор нетривиальных конфликтов.
git mergetool
git merge --abort
git rebase --abort
Если запутался — откатиться и начать заново дешевле, чем героически доразруливать.
git config --global rerere.enabled true
Git запомнит, как ты решил конфликт, и применит то же решение при повторе. Спасает при многократном rebase долгой ветки.
#Когда всё сломалось
Главное, что стоит знать: почти всё восстанавливается. Git не удаляет коммиты сразу, а reflog помнит, где HEAD был раньше.
git reflog | head -20
git reset --hard HEAD@{3} # вернуться к состоянию 3 шага назад
Первое действие после «я потерял коммиты». reflog хранит записи по умолчанию 90 дней.
git reset --soft HEAD~1 # коммит отменить, изменения в индексе
git reset --mixed HEAD~1 # коммит отменить, изменения в рабочей копии
git reset --hard HEAD~1 # отменить всё, изменения ПОТЕРЯНЫ
Три режима по возрастанию разрушительности. --soft — если просто рано закоммитил.
git restore --source=HEAD~3 -- file.kt
git show abc1234:path/to/file.kt > file.kt
Достать конкретный файл из прошлого, не трогая остальное.
git fsck --lost-found
git show <hash>
Поиск «висячих» коммитов, на которые не осталось ссылок. Последняя надежда, если reflog не помог.
git checkout -b rescue abc1234
Нашёл нужный коммит в reflog — сразу сделать на нём ветку, чтобы больше не терять.
# случайно закоммитил секрет — недостаточно удалить файл следующим коммитом
git log --all --oneline -S "AKIA" | head
# история переписывается: git filter-repo (или BFG),
# затем ротация самого секрета — он уже утёк
Секрет в истории остаётся доступен по хешу коммита. Переписывание истории обязательно, но первично — отозвать и перевыпустить сам ключ.
#Разбор и поиск
git blame file.kt
git blame -L 40,60 file.kt
git blame -w -C file.kt # игнорировать пробелы и перемещения
-w -C убирает шум от форматирования и переносов кода между файлами.
git bisect start
git bisect bad # текущий сломан
git bisect good v1.4.0 # здесь работало
# git проверяет середину, ты говоришь good/bad
git bisect reset
Двоичный поиск коммита, который сломал поведение. На истории из 1000 коммитов это 10 проверок.
git bisect start HEAD v1.4.0
git bisect run ./gradlew :app:testDebugUnitTest --tests '*LoginTest*'
Автоматический bisect: скрипт с кодом 0 = good, иначе bad. Полностью автоматизированный поиск регрессии.
git show abc1234
git show abc1234 --stat
git show abc1234:file.kt
Коммит целиком, только список файлов, или файл на момент коммита.
git shortlog -sn --since="3 months ago"
git log --diff-filter=D --name-only | head -20 # удалённые файлы
git log --follow file.kt # с учётом переименований
--follow нужен, чтобы история не обрывалась на переименовании.
#Настройки, которые стоит поставить сразу
git config --global user.name "Имя Фамилия"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global core.editor "nvim"
База.
git config --global pull.rebase true # без merge-мусора
git config --global rebase.autosquash true # fixup сам встанет
git config --global rerere.enabled true # помнить решения конфликтов
git config --global merge.conflictstyle zdiff3 # видеть общего предка
git config --global diff.algorithm histogram # осмысленнее diff
git config --global fetch.prune true # чистить удалённые ветки
Набор, который заметно снижает бытовую боль. fetch.prune убирает мёртвые origin/*, которые копятся месяцами.
git config --global alias.st "status -sb"
git config --global alias.lg "log --oneline --graph --decorate -20"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.unstage "restore --staged"
Алиасы.
# .gitignore для Android
*.iml
.gradle/
local.properties
.idea/
build/
captures/
.externalNativeBuild/
.cxx/
*.apk
*.aab
*.keystore
*.jks
local.properties содержит путь к SDK и у каждого свой — коммитить нельзя. Keystore в репозитории — прямая утечка подписи.
git rm --cached local.properties
echo "local.properties" >> .gitignore
git commit -m "chore: убрать local.properties из индекса"
Если файл уже закоммичен, добавление в .gitignore ничего не изменит — нужен rm --cached.
| Задача | Команда |
|---|---|
| Отменить последний коммит, изменения оставить | git reset --soft HEAD~1 |
| Поправить сообщение последнего коммита | git commit --amend |
| Откатить коммит, который уже в main | git revert |
| Найти потерянные коммиты | git reflog |
| Найти, какой коммит сломал тест | git bisect run |
| Найти, когда появилась строка | git log -S "строка" |
| Перенести коммит в другую ветку | git cherry-pick |
| Запушить переписанную историю | git push --force-with-lease |
| Выкинуть файл из индекса, оставив на диске | git rm --cached |