Как Git хранит историю
Git хранит не диффы, а снимки. Каждый коммит указывает на дерево файлов, на родителя (или двух родителей у merge-коммита), автора и сообщение. Хеш коммита считается от всего этого, поэтому любое изменение коммита (сообщения, родителя, файлов) даёт новый коммит с новым хешем.
Ветка (branch) это просто подвижный указатель на коммит. HEAD указывает на текущую ветку (или прямо на коммит, тогда это detached HEAD).
Три зоны, через которые проходит изменение:
- рабочая копия (working tree): файлы на диске;
- индекс (staging area): то, что попадёт в следующий коммит (
git add); - репозиторий: коммиты (
git commit).
git status # что изменено и что в индексе
git add -p # добавить изменения кусками
git commit -m "fix: ..."
git log --oneline --graph --all
merge и rebase
Обе команды вливают изменения одной ветки в другую, но по-разному.
merge создаёт merge-коммит с двумя родителями. История честная, ничего не переписывается. Если целевая ветка не ушла вперёд, Git делает fast-forward: просто двигает указатель, без merge-коммита (--no-ff заставит создать его).
rebase берёт твои коммиты и заново применяет их поверх другой ветки. Получаются новые коммиты с новыми хешами, история линейная.
git switch feature
git rebase main # переносим коммиты feature поверх main
git switch main
git merge feature # теперь это fast-forward
Золотое правило: не делай rebase коммитов, которые уже запушены и на которых работают другие. Ты перепишешь историю, и у коллег она разойдётся с твоей. Если всё же переписал свою личную ветку, пушь через git push --force-with-lease: он откажет, если на сервере появились чужие коммиты, которых ты не видел.
git rebase -i HEAD~3 позволяет склеить (squash/fixup), переименовать (reword), удалить (drop) или переставить последние коммиты перед ревью.
reset, revert, restore
git resetдвигает указатель текущей ветки на другой коммит. Режимы:--soft: изменения из «отброшенных» коммитов остаются в индексе;--mixed(по умолчанию): остаются в рабочей копии, индекс сбрасывается;--hard: рабочая копия и индекс приводятся к коммиту, незакоммиченные правки теряются.
git revert <commit>создаёт новый коммит, отменяющий изменения указанного. История не переписывается, поэтому это безопасный способ отменить то, что уже в общей ветке.git restore <file>откатывает файл в рабочей копии к индексу,git restore --staged <file>убирает файл из индекса.
git reset --soft HEAD~1 # «раскоммитить» последний коммит, правки в индексе
git revert a1b2c3d # отменить коммит в main новым коммитом
cherry-pick
git cherry-pick <hash> применяет изменения одного коммита к текущей ветке и создаёт новый коммит (новый хеш). Типичный сценарий: хотфикс сделан в main, его нужно перенести в ветку релиза.
reflog: машина времени
git reflog хранит, куда указывал HEAD (и ветки) за последнее время (по умолчанию записи живут 90 дней, а недостижимые из веток 30 дней), только локально. Если сделал reset --hard или неудачный rebase, коммиты не исчезли сразу:
git reflog # находим состояние до ошибки, например HEAD@{2}
git reset --hard HEAD@{2}
Потерять безвозвратно можно только то, что ни разу не было закоммичено (или застешено).
Конфликты
Конфликт возникает, когда обе стороны изменили одни и те же строки (или одна изменила файл, а другая удалила). Git помечает место маркерами:
<<<<<<< HEAD
const limit = 10;
=======
const limit = 20;
>>>>>>> feature
Порядок: открыть файлы, выбрать итоговый вариант, убрать маркеры, git add и продолжить (git commit для merge, git rebase --continue для rebase). Передумал: git merge --abort или git rebase --abort.
Учти: при rebase «ours» это ветка, на которую переносишь (upstream), а «theirs» это твои переносимые коммиты. При merge наоборот привычно: ours это текущая ветка.
fetch, pull, stash
git fetchскачивает новые коммиты в remote-tracking ветки (origin/main), твои ветки не трогает.git pull=fetch+merge(илиrebase, еслиpull.rebase=trueили--rebase).git stashпрячет незакоммиченные правки,git stash popвозвращает.
Частые ошибки
- rebase общей ветки и
push --forceбез--with-lease. reset --hardпри незакоммиченных правках: их reflog не спасёт.- Путают
revert(новый коммит) иreset(перенос указателя). - Коммитят
.envи секреты: удалить файл новым коммитом мало, он остаётся в истории.
Что спросят на собесе
- Чем merge отличается от rebase и когда что использовать.
- Как отменить коммит, который уже в общей ветке, и почему не reset.
- Что делают
reset --soft,--mixed,--hard. - Как восстановить ветку после неудачного
reset --hardили rebase. - Как разрешить конфликт при rebase и что такое cherry-pick.