Управление историей git
СкопированоПредставим, что у вас есть git-репозиторий с несколькими ветками. Вы работаете в одной из них и хотите перенести свои изменения в другую ветку. Такое приходится делать очень часто, если хотите актуализировать свои изменения.
Рассмотрим несколько распространённых сценариев.
Вы начали разрабатывать фичу и создали ветку my
из основной main
. За время разработки в ветку main
приехали изменения коллег. Как переместить ваши изменения на актуальный main
?
В вашей команде есть фронтенд- и бэкенд-разработчики. Вы работаете в одном репозитории, создали ветку frontend
из основной ветки main
, а ваш коллега — ветку backend
тоже из main
. В ветке frontend
два коммита: Кнопочка 1 и Тултипчик 2, в backend
их тоже два — API 1 и API 2. Теперь вы хотите потестировать ваш фронтенд с новым бэкендом. Как это сделать?
В вашей ветке есть несколько коммитов, которые нужны коллеге: Кнопочка 1 и Тултипчик 2. Кроме них в ветке есть коммит, который сломает ветку коллеги — Собака-всё-ломака. Как потестировать только не ломающие изменения?
Все эти задачи решаются с помощью команды git rebase
.
Немного теории
СкопированоGit хранит коммиты (commits). Коммит — всего лишь ссылка на некоторое состояние репозитория. Состояние — это немножко метаданных и ссылка на предыдущий коммит. Сам по себе коммит — это 40 символов SHA-1 хеша этого состояния.
Ветка (branch) — это ссылка на определённый коммит. У вас может быть только одна активная ветка. На неё (т. е. на определённый коммит) указывает специальный указатель HEAD
.
Многие команды гит принимают в качестве параметра ревизию. Так как всё в гите это захешированные объекты, то в качестве ревизии можете передавать:
- длинный хеш, например вот так
29aeb39eac6b8d9e0f4cdc1459376599d1aba43c
; - короткий хеш, например вот так
29aeb39e
; - тег, если он имеется, например вот так
v1
;. 0 . 0 - имя ссылки; в основном используется для веток, например
main
,origin
,/ dev refs
./ heads / foo
Можете добавлять магические символы (@{n}
) к этим параметрам, чтобы ещё эффективнее выбирать нужные коммиты. Подробности можно посмотреть в документации про gitrevisions
. Особенно полезен символ ^
. Он позволяет выбирать предыдущие коммиты.
Как готовить
СкопированоПодробности можно найти, например, в официальной документации.
Команда git rebase
с флагом -
принимает несколько аргументов:
-
— коммит, на который нужно перенести изменения. Новые коммиты добавятся сразу после него;- onto from
— коммит, с которого нужно перенести изменения. Коммит, который укажете, не перенесётся, а следующие за ним перенесутся;to
— коммит, до которого нужно перенести изменения. Коммит, который укажете, перенесётся.
Последний аргумент можно не использовать. Тогда перенесутся все коммиты, начиная от следующего за from
коммита до конца ветки.
Например:
git rebase --onto main feature1
git rebase --onto main feature1
Помните, что коммит — это всего лишь хеш? В качестве каждого аргумента можете указывать хеш, например вот так:
git rebase --onto 29aeb39e 5fd88295
git rebase --onto 29aeb39e 5fd88295
Хеши, вообще-то, длиннее 8 символов, но git понимает короткую версию. Можете также указать полный хеш, например вот так:
git rebase --onto 29aeb39eac6b8d9e0f4cdc1459376599d1aba43c 5fd882953924b47a10794619c3063e7a50257af6
git rebase --onto 29aeb39eac6b8d9e0f4cdc1459376599d1aba43c 5fd882953924b47a10794619c3063e7a50257af6
Вы можете указать название ветки вместо хеша, например вот так:
git rebase --onto main feature
git rebase --onto main feature
Если хотите перенести несколько коммитов из основной ветки, можете использовать магию HEAD
нотации.
git rebase --onto main HEAD^3 HEAD^
git rebase --onto main HEAD^3 HEAD^
Эта команда перенесёт 2 коммита из текущей ветки в ветку main
. Самый последний не перенесёт.
Рассмотрим один из примеров выше.
Было так:
А нужно, чтобы коммиты Кнопочка 1 и Тултипчик 2 оказались в ветке backend
.
Для этого выполните команду:
git rebase --onto backend main frontend
git rebase --onto backend main frontend
git rebase
— переносим;- - onto - backend — на последний коммит ветки backend, т. е. на API 2;
- main — все коммиты в моей ветке frontend, начиная с того, которым заканчивается ветка main (Статья 3);
- frontend — заканчивая последним коммитом ветки frontend (Тултипчик 2).
Бонус
СкопированоЕсли хотите посмотреть какой красивой стала ваша история, используйте эту команду:
git log --oneline --decorate --graph --all
git log --oneline --decorate --graph --all
На собеседовании
Скопировано отвечает
СкопированоОбе команды нужны для одной цели, но имеют существенные отличия в том, как работают.
git merge
объединяет изменения из одной ветки в другую, создавая новый коммит слияния merge commit
, который сохраняет полную историю проекта. Это позволяет проследить все коммиты и увидеть, когда и как ветки были объединены, хотя такая история может быть сложнее для чтения. Однако, если целевая ветка не имеет новых коммитов, Git использует fast
, просто перемещая указатель вперёд без создания дополнительного коммита, что сохраняет линейную и более простую для чтения историю.
Обратите внимание, что, при использовании merge
, мы должны находиться в ветке, в которую делаем перемещение.
git checkout maingit merge feature
git checkout main git merge feature
git rebase
перемещает или переписывает базу текущей ветки на указанную базу другой ветки, изменяя хэши коммитов и делая историю линейной, что упрощает чтение последовательности внедрения фич. При этом не создаётся новый коммит слияния merge commit
, но это может затруднить отслеживание точной хронологии. Важно, что git rebase
позволяет переносить только часть коммитов и не требует находиться в целевой ветке — можно использовать флаг -
, чтобы выполнить ребейз на другую ветку или коммит, гибко управляя изменениями. Будьте осторожны, так как команда может иметь негативный эффект.
Обратите внимание, что, при использовании rebase
, мы должны находиться в ветке, из которой делаем перемещение.
git checkout featuregit rebase main
git checkout feature git rebase main
Используйте merge
, когда хотите сохранить всю историю разработки, включая все ветвления и слияния. Это полезно для командной работы, где важно видеть весь контекст изменений.
Используйте rebase
, когда хотите поддерживать чистую и линейную историю. Это особенно полезно для интеграции изменений в основную ветку перед созданием pull request
, чтобы история коммитов была более понятной.