Объяснение полезных Git команд с помощью визуализации
Это перевод замечательной статьи Лидии Холли, где она объясняет основные гит команды в очень понятном виде с помощью визуализаций. Начнем Хотя Git является очень мощным инструментом, я думаю, что большинство людей согласятся, когда я скажу, работа с ним может быть так же полной неразберихой Мне всегда было очень полезно визуализировать в своей голове, что происходит при работе с Git: как ветки, взаимодействующие, когда я выполняю определенную команду, и как это повлияет на историю? Почему мой коллега заплакал, когда я сделала hard reset на master , сделав force push к origin и rimraf нула .git папку? Я подумала, что это будет идеальный вариант для создания наглядных примеров наиболее распространенных и полезных команд! Многие из описываемых мной команд имеют необязательные аргументы, которые вы можете использовать для изменения их поведения. В моих примерах я расскажу о поведении команд по умолчанию, не добавляя (слишком много) параметров конфигурации!
Merge
Наличие нескольких веток чрезвычайно удобно для того, чтобы новые изменения были отделены друг от друга, а также чтобы вы случайно не запушили несанкционированные или поврежденные изменения в продакшин. Как только изменения будут одобрены, мы хотим получить эти изменения в нашей прод ветке! Один из способов получить изменения из одной ветки в другую — выполнить git merge ! Есть два типа менж команд, которые может выполнять Git: fast-forward или no-fast-forward
Fast-forward ( —ff )
Fast-forward merge когда текущая ветка не имеет дополнительных коммитов по сравнению с веткой, которую мы мержим. Git у нас ленив и сначала попытается выполнить самый простой вариант: Fast-forward! Этот тип менжа не создает новый коммит, а скорее объединяет коммит(ы) в ветку, которую мы объединяем прямо в текущей ветке Отлично! Теперь у нас есть все изменения, которые были сделаны в ветке dev , в ветке master . Итак, что же такое no-fast-forward?
No-fast-foward ( —no-ff )
Хорошо, если ваша текущая ветка не имеет каких-либо дополнительных коммитов по сравнению с веткой, которую вы хотите смержить, но, к сожалению, это случается редко! Если мы зафиксировали изменения в текущей ветке, которых нет в ветке, которую мы хотим объединить, git выполнит объединение без fast-forward merge. При слиянии без fast-forward Git создает новый коммит мержа в активную ветку. Родительский коммит указывает на активную ветку и ветку, которую мы хотим объединить!
Merge конфликты
Хотя Git хорошо решает, как объединять ветки и добавлять изменения в файлы, он не всегда может принять это решение сам по себе. Это может произойти, когда две ветки, которые мы пытаемся смержить, имеют изменения в одной строке в одном и том же файле, или если одна ветка удалила файл, который изменила другая ветка, и так далее. В этом случае Git попросит вас помочь решить, какой из двух вариантов мы хотим сохранить. Допустим, что в обеих ветках мы отредактировали первую строку в файле README.md. Если мы хотим смержить dev в master , это приведет к конфликту: хотите, чтобы заголовок был Hello! или hey!? При попытке объединить ветки, Git покажет вам, где происходит конфликт. Мы можем вручную удалить изменения, которые не хотим сохранять, сохранить изменения, снова добавить файл и закоммитить изменения.
Rebase
Мы только что увидели, как можно применить изменения из одной ветки в другую, выполнив git merge . Другой способ добавить изменения из одной ветки в другую — выполнить git rebase . Git rebase копирует коммиты из текущей ветки и помещает эти скопированные коммиты поверх указанной ветки. Отлично, теперь у нас есть все изменения, которые были сделаны в master ветке и в dev ветке. Большая разница по сравнению с мержем заключается в том, что Git не будет пытаться выяснить, какие файлы сохранить и не сохранить. В ветке, которую мы обновляем, всегда есть последние изменения, которые мы хотим сохранить! Таким образом, вы не столкнетесь ни с какими мерж конфликтами и у вас будет хорошая линейная история. Этот пример показывает rebase в master ветке. Однако в больших проектах вы обычно не захотите этого делать. Git rebase изменяет историю проекта, поскольку для скопированных коммитов создаются новые хэши. Rebase отлично подходит, когда вы работаете над feature branch, а master ветка была обновлена. Вы можете получить все обновления в своей ветке, которые предотвратят будущие merge конфликты
Interactive Rebase
- reword : Изменить коммит меседж
- edit : Изменить коммит
- squash : Объеденить коммит в предыдущий коммит
- fixup : Объединить коммит с предыдущим коммитом, не сохраняя commit’s log message
- exec : Запустить команду для каждого коммита, который мы хотим rebase
- drop : Удалить коммит
Таким образом, мы можем иметь полный контроль над нашими коммитами. Если мы хотим удалить коммит, мы можем просто drop’нуть его.
Или, если мы хотим объединить несколько коммитов вместе, чтобы получить более чистую историю, нет проблем!
Interactive rebasing дает вам большой контроль над коммитами, которые вы пытаетесь rebase’нуть, даже в текущей активной ветке.
Reset
Может случиться так, что мы допустили изменения, которые мы не хотели заливать. Может быть, это коммит еще в работе или коммит, в котором есть ошибки! В этом случае мы можем выполнить git reset .
git reset избавляет от всех текущих промежуточных файлов и дает нам контроль над тем, куда должен указывать HEAD.
Soft reset
Soft reset перемещает HEAD к указанному коммиту (или индексу коммита по сравнению с HEAD), не избавляясь от изменений, которые были внесены в коммиты позже.
Допустим, мы не хотим сохранять коммит 9e78i , в который был добавлен файл style.css , и мы также не хотим сохранять коммит 035cc , в который был добавлен файл index.js . Однако мы хотим сохранить недавно добавленные файлы style.css и index.js. Идеальный кейс для sort reset.
Набрав git status, вы увидите, что у нас все еще есть доступ ко всем изменениям, которые были сделаны во время предыдущих коммитов. Это здорово, так как это означает, что мы можем исправить содержимое этих файлов и закоммитить их позже.
Hard reset
Иногда мы не хотим сохранять изменения, внесенные некоторыми коммитами. В отличие от soft reset, нам не нужно больше иметь к ним доступ. Git должен просто сбросить свое состояние обратно туда, где он был в указанном коммите: это даже включает изменения в вашей working directory и stage файлах!
Git отменил изменения, которые были внесены в 9e78i и 035cc, и сбросил свое состояние до того, где он был при коммите ec5be.
Revert
Другой способ отменить изменения — выполнить git revert . Отменяя определенный коммит, мы создаем новый коммит, который содержит отмененные изменения.
Допустим, ec5be добавил файл index.js . Позже мы на самом деле понимаем, что больше не хотим, чтобы это изменения были в ветке! Давайте ревертнем коммит ec5be .
Cherry-pick
Когда определенная ветка содержит коммит, который внес изменения, которые нам нужны в нашей активной ветке, мы можем черипинуть коммит в нашу ветку. Cherry-pick создает новый коммит в нашей активной ветке, который содержит изменения, которые были в чери-пикнутом коммите.
Предположим, что коммит 76d12 в ветке dev добавил изменение в файл index.js , которое мы хотим добавить в master:
Fetch
Если у нас есть remote Git ветка, например ветка на Github, может случиться так, что remote ветка имеет коммиты, которых нет у текущей ветки! Возможно, другая ветка была объединена, или ваш коллега добавил hot fix и так далее.
Мы можем получить эти изменения локально, выполнив git fetch на remote ветке. Это никак не влияет на вашу локальную ветку: fetch просто загружает новые данные.
Pull
Хотя git fetch очень полезен для получения remote информации о ветке, мы также можем выполнить git pull . Git pull — это две команды в одной: git fetch и git merge . Когда мы извлекаем изменения из origin, мы сначала fetch’им все данные, как мы делали с помощью git fetch, после чего последние изменения автоматически мержатся в локальную ветку.
Reflog
Все делают ошибки, и это совершенно нормально! Иногда может показаться, что вы испортили свой репозиторий Git настолько сильно, что просто хотите полностью его удалить.
git reflog — очень полезная команда для отображения журнала всех выполненных действий! Это включает в себя слияния, перезагрузки, возвраты: в основном, любые изменения в вашей ветке.
Если вы допустили ошибку, вы можете легко отменить ее, сбросив HEAD на основе информации, которую нам предоставляет reflog.
Скажем, мы на самом деле не хотели мержить origin ветку. Когда мы выполняем команду git reflog , мы видим, что состояние репозитория до мержа- HEAD@ . Давайте выполним git reset , чтобы указать HEAD туда, где он был.
Как мы видим, последнее действие было перенесено в рефлог.
3.6 Ветвление в Git — Перебазирование
В Git есть два способа внести изменения из одной ветки в другую: слияние и перебазирование. В этом разделе вы узнаете, что такое перебазирование, как его осуществлять и в каких случаях этот удивительный инструмент использовать не следует.
Простейшее перебазирование
Если вы вернётесь к более раннему примеру из Основы слияния, вы увидите, что разделили свою работу и сделали коммиты в две разные ветки.
Рисунок 35. История коммитов простого разделения
Как мы выяснили ранее, простейший способ выполнить слияние двух веток — это команда merge . Она осуществляет трёхстороннее слияние между двумя последними снимками сливаемых веток ( C3 и C4 ) и самого недавнего общего для этих веток родительского снимка ( C2 ), создавая новый снимок (и коммит).
Рисунок 36. Слияние разделённой истории коммитов
Тем не менее есть и другой способ: вы можете взять те изменения, что были представлены в C4 , и применить их поверх C3 . В Git это называется перебазированием. С помощью команды rebase вы можете взять все коммиты из одной ветки и в том же порядке применить их к другой ветке.
В данном примере переключимся на ветку experiment и перебазируем её относительно ветки master следующим образом:
$ git checkout experiment $ git rebase master First, rewinding head to replay your work on top of it. Applying: added staged command
Это работает следующим образом: берётся общий родительский снимок двух веток (текущей, и той, поверх которой вы выполняете перебазирование), определяется дельта каждого коммита текущей ветки и сохраняется во временный файл, текущая ветка устанавливается на последний коммит ветки, поверх которой вы выполняете перебазирование, а затем по очереди применяются дельты из временных файлов.
Рисунок 37. Перебазирование изменений из C4 поверх C3
После этого вы можете переключиться обратно на ветку master и выполнить слияние перемоткой.
$ git checkout master $ git merge experiment
Рисунок 38. Перемотка ветки master
Теперь снимок, на который указывает C4′ абсолютно такой же, как тот, на который указывал C5 в примере с трёхсторонним слиянием. Нет абсолютно никакой разницы в конечном результате между двумя показанными примерами, но перебазирование делает историю коммитов чище. Если вы взглянете на историю перебазированной ветки, то увидите, что она выглядит абсолютно линейной: будто все операции были выполнены последовательно, даже если изначально они совершались параллельно.
Часто вы будете делать так для уверенности, что ваши коммиты могут быть бесконфликтно слиты в удалённую ветку — возможно, в проекте, куда вы пытаетесь внести вклад, но владельцем которого вы не являетесь. В этом случае вам следует работать в своей ветке и затем перебазировать вашу работу поверх origin/master , когда вы будете готовы отправить свои изменения в основной проект. Тогда владельцу проекта не придётся делать никакой лишней работы — всё решится простой перемоткой или бесконфликтным слиянием.
Учтите, что снимок, на который ссылается ваш последний коммит — является ли он последним коммитом после перебазирования или коммитом слияния после слияния — в обоих случаях это один и тот же снимок, отличаются только истории коммитов. Перебазирование повторяет изменения из одной ветки поверх другой в том порядке, в котором эти изменения были сделаны, в то время как слияние берет две конечные точки и сливает их вместе.
Более интересные перемещения
Также возможно сделать так, чтобы при перебазировании воспроизведение коммитов применялось к совершенно другой ветке. Для примера возьмём История разработки с тематической веткой, ответвлённой от другой тематической ветки. Вы создаёте тематическую ветку server , чтобы добавить в проект некоторую функциональность для серверной части, и делаете коммит. Затем вы выполнили ответвление, чтобы сделать изменения для клиентской части, и создали несколько коммитов. Наконец, вы вернулись на ветку server и сделали ещё несколько коммитов.
Рисунок 39. История разработки с тематической веткой, ответвлённой от другой тематической ветки
Предположим, вы решили, что хотите внести изменения клиентской части в основную линию разработки для релиза, но при этом не хотите добавлять изменения серверной части до полного тестирования. Вы можете взять изменения из ветки client , которых нет в server ( C8 и C9 ), и применить их на ветке master при помощи опции —onto команды git rebase :
$ git rebase --onto master server client
В этой команде говорится: «Переключись на ветку client , найди изменения относительно ветки server и примени их для ветки master ». Несмотря на некоторую сложность этого способа, результат впечатляет.
Рисунок 40. Перемещение тематической ветки, ответвлённой от другой тематической ветки
Теперь вы можете выполнить перемотку (fast-forward) для ветки master (см Перемотка ветки master для добавления изменений из ветки client ):
$ git checkout master $ git merge client
Рисунок 41. Перемотка ветки master для добавления изменений из ветки client
Представим, что вы решили добавить наработки и из ветки server . Вы можете выполнить перебазирование ветки server относительно ветки master без предварительного переключения на неё при помощи команды git rebase , которая извлечёт тематическую ветку (в данном случае server ) и применит изменения в ней к базовой ветке ( master ):
$ git rebase master server
Это повторит работу, сделанную в ветке server поверх ветки master , как показано на рисунке:
Рисунок 42. Перебазирование ветки server на вершину ветки master
После чего вы сможете выполнить перемотку основной ветки ( master ):
$ git checkout master $ git merge server
Теперь вы можете удалить ветки client и server , поскольку весь ваш прогресс уже интегрирован и тематические ветки больше не нужны, а полную историю вашего рабочего процесса отражает рисунок Окончательная история коммитов:
$ git branch -d client $ git branch -d server
Рисунок 43. Окончательная история коммитов
Опасности перемещения
Но даже перебазирование, при всех своих достоинствах, не лишено недостатков, которые можно выразить одной строчкой:
Не перемещайте коммиты, уже отправленные в публичный репозиторий
Если вы будете придерживаться этого правила, всё будет хорошо. Если не будете, люди возненавидят вас, а ваши друзья и семья будут вас презирать.
Когда вы что-то перемещаете, вы отменяете существующие коммиты и создаёте новые, похожие на старые, но являющиеся другими. Если вы куда-нибудь отправляете свои коммиты и другие люди забирают их себе и в дальнейшем основывают на них свою работу, а затем вы переделываете эти коммиты командой git rebase и выкладываете их снова, то ваши коллеги будут вынуждены заново выполнять слияние для своих наработок. В итоге, когда вы в очередной раз попытаетесь включить их работу в свою, вы получите путаницу.
Давайте рассмотрим пример того, как перемещение публично доступных наработок может вызвать проблемы. Предположим, вы клонировали репозиторий с сервера и сделали какую-то работу. И ваша история коммитов выглядит так:
Рисунок 44. Клонирование репозитория и выполнение в нём какой-то работы
Теперь кто-то другой внёс свои изменения, слил их и отправил на сервер. Вы стягиваете их к себе, включая новую удалённую ветку, что изменяет вашу историю следующим образом:
Рисунок 45. Извлекаем ещё коммиты и сливаем их со своей работой
Затем автор коммита слияния решает вернуться назад и перебазировать свою ветку; выполнив git push —force , он перезаписывает историю на сервере. При получении изменений с сервера вы получите и новые коммиты.
Рисунок 46. Кто-то выложил перебазированные коммиты, отменяя коммиты, на которых основывалась ваша работа
Теперь вы оба в неловком положении. Если вы выполните git pull , вы создадите коммит слияния, включающий обе линии истории, и ваш репозиторий будет выглядеть следующим образом:
Рисунок 47. Вы снова выполняете слияние для той же самой работы в новый коммит слияния
Если вы посмотрите git log в этот момент, вы увидите два коммита с одинаковыми авторами, датой и сообщением, что может сбить с толку. Помимо этого, если вы отправите свою историю на удалённый сервер в таком состоянии, вы вернёте все эти перебазированные коммиты на сервер, что ещё больше всех запутает. Логично предположить, что разработчик не хочет, чтобы C4 и C6 были в истории, и именно поэтому она перебазируется в первую очередь.
Меняя базу, меняй основание
Если вы попали в такую ситуацию, у Git есть особая магия чтобы вам помочь. Если кто-то в вашей команде форсирует отправку изменений на сервер, переписывающих работу, на которых базировалась ваша работа, то ваша задача будет состоять в определении того, что именно было ваше, а что было переписано ими.
Оказывается, что помимо контрольной суммы коммита SHA-1, Git также вычисляет контрольную сумму отдельно для патча, входящего в этот коммит. Это контрольная сумма называется «patch-id».
Если вы скачаете перезаписанную историю и перебазируете её поверх новых коммитов вашего коллеги, в большинстве случаев Git успешно определит, какие именно изменения были внесены вами, и применит их поверх новой ветки.
К примеру, если в предыдущем сценарии вместо слияния в Кто-то выложил перебазированные коммиты, отменяя коммиты, на которых основывалась ваша работа мы выполним git rebase teamone/master , Git будет:
- Определять, какая работа уникальна для вашей ветки (C2, C3, C4, C6, C7)
- Определять, какие коммиты не были коммитами слияния (C2, C3, C4)
- Определять, что не было перезаписано в основной ветке (только C2 и C3, поскольку C4 — это тот же патч, что и C4′)
- Применять эти коммиты к ветке teamone/master
Рисунок 48. Перемещение в начало force-pushed перемещённой работы
Это возможно, если C4 и C4′ фактически являются одним и тем же патчем, который был сделан вашим коллегой. В противном случае rebase не сможет определить дубликат и создаст ещё один патч, подобный C4 (который с большой вероятностью не удастся применить чисто, поскольку в нём уже присутствуют некоторые изменения).
Вы можете это упростить, применив git pull —rebase вместо обычного git pull . Или сделать это вручную с помощью git fetch , а затем git rebase teamone/master .
Если вы используете git pull и хотите использовать —rebase по умолчанию, вы можете установить соответствующее значение конфигурации pull.rebase с помощью команды git config —global pull.rebase true .
Если вы рассматриваете перебазирование как способ наведения порядка и работаете с коммитами локально до их отправки или ваши коммиты никогда не будут доступны публично — у вас всё будет хорошо. Однако, если вы перемещаете коммиты, отправленные в публичный репозиторий, и есть вероятность, что работа некоторых людей основывается на этих коммитах, то ваши действия могут вызвать существенные проблемы, а вы — вызвать презрение вашей команды.
Если в какой-то момент вы или ваш коллега находите необходимость в этом, убедитесь, что все знают, как применять команду git pull —rebase для минимизации последствий от подобных действий.
Перемещение vs. Слияние
Теперь, когда вы увидели перемещение и слияние в действии, вы можете задаться вопросом, что из них лучше. Прежде чем ответить на этот вопрос, давайте вернёмся немного назад и поговорим о том, что означает история.
Одна из точек зрения заключается в том, что история коммитов в вашем репозитории — это запись того, что на самом деле произошло. Это исторический документ, ценный сам по себе, и его нельзя подделывать. С этой точки зрения изменение истории коммитов практически кощунственно; вы лжёте о том, что на самом деле произошло. Но что, если произошла путаница в коммитах слияния? Если это случается, репозиторий должен сохранить это для потомков.
Противоположная точка зрения заключается в том, что история коммитов — это история того, как был сделан ваш проект. Вы не публикуете первый черновик книги или инструкции по поддержке вашего программного обеспечения, так как это нуждается в тщательном редактировании. Сторонники этого лагеря считают использование инструментов rebase и filter-branch способом рассказать историю проекта наилучшим образом для будущих читателей.
Теперь к вопросу о том, что лучше — слияние или перебазирование: надеюсь, вы видите, что это не так просто. Git — мощный инструмент, позволяющий вам делать многое с вашей историей, однако каждая команда и каждый проект индивидуален. Теперь, когда вы знаете, как работают оба эти приёма, выбор — какой из них будет лучше в вашей ситуации — зависит от вас.
При этом, вы можете взять лучшее от обоих миров: использовать перебазирование для наведения порядка в истории ваших локальных изменений, но никогда не применять его для уже отправленных куда-нибудь изменений.
Merging vs. rebasing
Командой git rebase , словно опасным заклинанием, часто пугают новичков. В действительности она может значительно облегчить жизнь команде разработчиков, если использовать ее правильно. В этой статье проводится сравнение команды git rebase со схожей командой git merge и описываются все возможные ситуации, в которых уместно включить перебазирование в стандартный рабочий процесс Git.
Conceptual overview
В первую очередь нужно понимать, что команда git rebase помогает решить ту же проблему, что и команда git merge . Обе команды предназначены для включения изменений из одной ветки в другую, но делают это по-разному.
Представьте, что вы начали работать над новой функциональной возможностью в отдельной ветке, после чего другой участник команды добавляет новые коммиты в главную ветку main . Возникает история форков, знакомая каждому, кто использовал Git для совместной работы.
А теперь предположим, что новые коммиты в главной ветке main затрагивают функцию, над которой вы работаете. Для включения новых коммитов в свою функциональную ветку feature можно использовать два варианта: слияние или перебазирование.
Связанные материалы
Перемещение полного репозитория Git
СМ. РЕШЕНИЕ
Изучите Git с помощью Bitbucket Cloud
The merge option
Проще всего слияние ветки main в функциональную ветку выполняется с помощью следующей команды:
git checkout feature
git merge main
При желании этот код можно записать в одну строку:
git merge feature main
Эта операция создает в ветке feature новый «коммит слияния», связывающий истории обеих веток. Структура веток будет выглядеть так:
Слияние (merge) — это отличная неразрушающая операция. Существующие ветки никак не изменяются. Эта операция позволяет избегать потенциальных проблем, связанных с выполнением команды rebase (и описанных ниже).
С другой стороны, это означает, что каждый раз, когда вам будет необходимо включить вышестоящие изменения, в функциональную ветку feature будет попадать внешний коммит слияния. Если работа в главной ветке main ведется активно, история вашей функциональной ветки быстро засорится. Хотя эту проблему можно устранить, используя продвинутые варианты команды git log , другим разработчикам будет тяжело разобраться в истории проекта.
The rebase option
Вместо слияния можно выполнить перебазирование функциональной ветки feature на главную ветку main с помощью следующих команд:
git checkout feature
git rebase main
В результате вся функциональная ветка feature окажется поверх главной ветки main , включая в себя все новые коммиты в ветке main . Если вместо команды merge при коммитах используется rebase, эта команда перезаписывает историю проекта, создавая новые коммиты для каждого коммита в исходной ветке.
Главное преимущество rebase — более чистая история проекта. Во-первых, эта команда устраняет ненужные коммиты слияния, необходимые для git merge . Во-вторых, как показано на рисунке выше, команда rebase создает идеальную линейную историю проекта — вы сможете отследить функционал до самого начала проекта без каких-либо форков. Это упрощает навигацию в проекте с помощью таких команд, как git log , git bisect и gitk .
Однако такая безупречная история коммитов требует определенных жертв: жертвовать приходится безопасностью и отслеживаемостью. Если не следовать Золотому правилу Rebase, перезапись истории проекта может обернуться катастрофическими последствиями для совместных рабочих процессов. Кроме того, при выполнении rebase теряется контекст, доступный в коммите со слиянием: вы не сможете увидеть, когда вышестоящие изменения были включены в функционал.
Interactive rebasing
Интерактивная операция rebase позволяет изменять коммиты при их перемещении в новую ветку. Этот вариант предоставляет еще больше возможностей, чем автоматическое выполнение rebase, поскольку дает полный контроль над историей коммитов ветки. Обычно его используют для очистки запутанной истории, перед тем как сливать функциональную ветку в главную ветку main .
Чтобы запустить интерактивное перебазирование, передайте параметр i команде git rebase :
git checkout feature
git rebase -i main
Откроется текстовый редактор. В нем будут перечислены все коммиты, подготовленные к перемещению:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Этот список точно отражает, как будет выглядеть ветка после перебазирования. Изменяя команду pick и (или) порядок коммитов, вы можете придать истории ветки нужный вид. Так, если второй коммит содержит исправление небольшой проблемы в первом, их можно объединить с помощью команды fixup :
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Когда вы сохраните и закроете файл, Git выполнит перебазирование в соответствии с вашими указаниями. История проекта затем примет следующий вид:
Удаление незначительных коммитов помогает быстрее разобраться в истории функциональной ветки. Команда git merge просто не в состоянии этого сделать.
The golden rule of rebasing
Разобравшись с возможностями rebase, необходимо в первую очередь понять, когда эту команду не нужно использовать. Золотое правило для команды git rebase — никогда не использовать ее в публичных ветках.
К примеру, представьте, что произойдет, если вы выполните rebase главной ветки main на свою функциональную ветку feature .
Перебазирование перемещает все коммиты ветки main в конец ветки feature . Проблема в том, что это происходит только в вашем репозитории, в то время как другие разработчики продолжают работать с исходной веткой main . Поскольку в результате перебазирования создаются абсолютно новые коммиты, Git будет считать, что история вашей главной ветки main разошлась с остальными.
Единственный способ синхронизировать две главные ветки main — выполнить их обратное слияние. Это приведет к дополнительному коммиту слияния и двум наборам коммитов, которые содержат одни и те же изменения (исходные изменения и изменения из вашей ветки после rebase). Нужно ли говорить, что ситуация получится крайне запутанная?
Поэтому перед выполнением команды git rebase следует убедиться, что текущую ветку не просматривает кто-то другой. Если в ней действительно ведется работа, прекратите любые действия и подумайте, как можно внести изменения неразрушающим способом (например, с помощью команды git revert ). В остальных случаях вы можете свободно перезаписывать историю при необходимости.
Force-pushing
Git заблокирует попытку поместить перебазированную ветку main обратно в удаленный репозиторий, поскольку она вступит в конфликт с удаленной веткой main . Но эту операцию можно выполнить принудительно, добавив флаг —force :
# Be very careful with this command! git push --force
При этом удаленная ветка main станет соответствовать ветке в вашем репозитории после rebase. В итоге путаться начнут и ваши коллеги. Поэтому будьте внимательны и используйте эту команду только в том случае, если полностью понимаете, чего хотите добиться.
Одна из немногих ситуаций, требующих форсированного помещения кода, — это локальная очистка после помещения частной функциональной ветки в удаленный репозиторий (например, для создания резервной копии). Это равноценно заявлению: «Ой, я ведь не хотел отправлять исходную версию этой функциональной ветки. Лучше возьмите текущую версию». Здесь также важно, чтобы никто после коммитов не начал работу из исходной версии функциональной ветки.
Workflow walkthrough
Перебазирование можно использовать при работе с Git ровно в том объеме, который подходит команде. В этом разделе вы узнаете о преимуществах перебазирования на разных этапах разработки функций продукта.
В любом рабочем процессе с применением команды git rebase сначала нужно создать отдельную ветку для каждой функции. Полученная структура веток необходима для безопасного перебазирования:
Local cleanup
Один из самых эффективных сценариев перебазирования в рабочем процессе — это очистка локальных функциональных веток, в которых еще ведется работа. Если периодически проводить интерактивное перебазирование, ни один коммит в вашей ветке не потеряет смысла. Вы сможете быть уверены, что ваш код не распадется на изолированные коммиты. Если это случится, ситуацию всегда можно будет исправить.
При использовании команды git rebase есть два варианта для нового положения ветки: вышестоящая ветка для функциональной ветки (например, ветка main ) или более ранний коммит в функциональной ветке. Первый вариант описывался в примере в разделе Интерактивное перебазирование. Второй вариант удобен, когда нужно исправить лишь несколько недавних коммитов. Например, следующая команда запускает интерактивную операцию rebase только для трех последних коммитов.
git checkout feature git rebase -i HEAD~3
Указав HEAD~3 в качестве нового положения, вы не перемещаете ветку как таковую, а лишь интерактивно переписываете 3 последующих коммита. Следует отметить, что при этой операции вышестоящие изменения не включаются в функциональную ветку.
Если с помощью этого способа вы хотите переписать всю функциональную ветку, найти начальное положение функциональной ветки поможет команда git merge-base . Следующая команда возвращает ID коммита начального положения, который затем можно передать в команду git rebase :
git merge-base feature main
Такое использование интерактивной операции rebase отлично поможет включить git rebase в рабочий процесс, поскольку затронет только локальные ветки. Другие разработчики увидят только законченную версию с простой и отслеживаемой историей функциональной ветки.
Повторим, что это работает только для частных функциональных веток. Если вы работаете в одной функциональной ветке с другими разработчиками, она является публичной и переписывать ее историю запрещено.
Варианта очистки локальных коммитов с интерактивным использованием rebase с помощью команды git merge не существует.
Incorporating upstream changes into a feature
В разделе Обзор основных моментов рассматривалось включение вышестоящих изменений из главной ветки main в функциональную ветку с помощью команды git merge или git rebase . Операцию слияния можно выполнять безопасно, поскольку при этом сохраняется вся история репозитория. При выполнении перебазирования создается линейная структура: функциональная ветка перемещается в конец ветки main .
В этом случае команда git rebase используется так же, как и локальная очистка (которую можно осуществить одновременно), но при ее выполнении включаются вышестоящие изменения из главной ветки main .
Помните, что можно еще выполнять rebase в удаленную ветку, отличную от ветки main . Например, при совместной работе над функциональной возможностью с другим разработчиком, когда требуется включить его изменения в ваш репозиторий.
Например, если вы вместе с разработчиком Джоном добавляли коммиты в ветку feature , то после получения удаленной ветки feature из репозитория Джона ваш репозиторий может выглядеть следующим образом:
С таким ветвлением можно работать так же, как и при включении вышестоящих изменений из ветки main : либо выполнить слияние в локальную ветку feature для ветки john/feature , либо выполнить перебазирование локальной ветки feature на конец ветки john/feature .
Обратите внимание: такое перебазирование не нарушает «золотое правило», поскольку перемещаются только коммиты вашей ветки feature , тогда как предшествующие элементы остаются нетронутыми. В сущности, эта операция позволяет лишь добавить ваши изменения к наработкам Джона. Чаще всего это проще, чем выполнять синхронизацию с удаленной веткой посредством коммита слияния.
По умолчанию команда git pull выполняет слияние. Однако если передать ей параметр —rebase , будет выполнено перебазирование для удаленной ветки.
Reviewing a feature with a pull request
Если в процессе проверки кода используются пул-реквесты, не используйте команду git rebase после создания пул-реквеста. Сразу после создания пул-реквеста другие разработчики смогут видеть ваши коммиты, то есть ваша ветка станет публичной. В случае перезаписи ее истории Git и ваши коллеги не смогут отслеживать последующие коммиты в функциональную ветку.
Все изменения, сделанные другими разработчиками, нужно добавлять командой git merge , а не git rebase .
Поэтому хорошей идеей является очистка кода с помощью интерактивной операции rebase перед созданием пул-реквеста.
Integrating an approved feature
После одобрения функциональной ветки коллегой вы можете перебазировать ее на конец ветки main , а затем использовать команду git merge для включения функциональной ветки в основную базу кода.
Это похоже на включение вышестоящих изменений в функциональную ветку, но поскольку переписывать коммиты в главной ветке main запрещено, вам придется использовать для включения функциональной ветки команду git merge . Выполняя перебазирование перед слиянием, вы обеспечиваете ускоренное слияние и идеальную линейную историю. Такой подход также позволяет склеивать любые последующие коммиты, добавленные до закрытия запроса pull.
Если вы еще не привыкли работать с командой git rebase , всегда можно выполнить rebase во временную ветку. В таком случае, если вы случайно запутаете историю функциональной ветки, всегда можно будет переключиться в исходную ветку и попробовать снова. Например:
git checkout feature
git checkout -b temporary-branch
git rebase -i main
# [Clean up the history]
git checkout main
git merge temporary-branch
Резюме
Теперь у вас достаточно информации, чтобы начать использовать rebase для своих веток. Если вы предпочитаете иметь чистую линейную историю без ненужных коммитов слияния, используйте команду git rebase вместо git merge при включении изменений из другой ветки.
Но если вам нужно сохранить полную историю проекта и избежать перезаписи публичных коммитов, воспользуйтесь командой git merge . Теперь вы можете не только применить слияние, но и оценить преимущества команды git rebase — оба варианта эффективны в работе.
Git rebase — перебазирование коммитов и веток
Знакомим с git rebase: рассказываем о преимуществах команды и показываем, в каких случаях ее стоит использовать и почему.
Эта инструкция — часть курса «Введение в Git».
Смотреть весь курс
Введение
Rebase (перебазирование) — один из способов в git, позволяющий объединить изменения двух веток. У этого способа есть преимущество перед merge (слияние) — он позволяет переписать историю ветки, придав тот истории тот вид, который нам нужен.
В этой инструкции мы познакомим вас поближе с командой git rebase, расскажем о преимуществах и тонкостях работы с ней, покажем, в каких случаях ее стоит использовать и почему.
Git rebase — что это
Из документации — это наложение коммитов поверх другого базового коммита. Под базовым понимается тот коммит, к которому применяются коммиты выбранной ветки.
git rebase [ []]
Первый аргумент обязательный (upstream) — это базовый коммит, к которому применятся коммиты выбранной ветки. Второй аргумент можно не задавать, если HEAD указывает на ветку, которая будет нами перебазирована.
Как работает git rebase
Чтобы понимать процесс работы перебазирования, обратимся к рисунку 1.
У нас есть две ветки — master и my_branch. Мы находимся на ветке my_branch (HEAD указывает на ветку my_branch). Выполняем команду:
git rebase master
После этого git удалит и последовательно переместит коммиты C, D, F из ветки my_branch в ветку master — сначала C, затем D и F. Новые коммиты C’, D’, F’ полностью идентичны удаленным, меняется только хеш.
Сначала для ветки my_branch базовым коммитом был B, но после стал коммит E. Это и есть процесс под названием перебазирование.
Как использовать git rebase
Перебазирование в git используется для придания линейности истории ветки, чтобы удобно отслеживать изменения, или для обновления ветки разработки последними изменениями из основной ветки. Также есть и другие варианты использования — с помощью интерактивного режима и параметра —onto.
Линейная история — реинтеграция тематической ветки после выполнения git rebase master
После того как мы использовали команду git rebase, можно перемотать ветку master командой git merge:
git checkout master git merge my_branch
К команде слияния можно добавить флаг либо —ff (fast-forward merge), чтобы не создавать коммит слияния, или добавить —no-ff — для его создания. Создание коммита слияния помогает определить, когда ветки объединились, и какие коммиты тематической ветки были сделаны.
Когда работа с тематической веткой закончена, удаляем ее:
git branch -d my_branch
Это приведет к законченному виду истории, когда мы внедрили изменения из тематической ветки в основную часть проекта.
Конфликты
Так как git rebase последовательно переприменяет коммиты, то могут возникнуть конфликты слияния (merge conflicts). Первая причина появления конфликта — объединение коммитов, содержащих изменения в одних и тех же файлах. Вторая причина — несколько человек изменяют одинаковый файл на одной расшаренной ветке. Чтобы узнать, в каких файлах есть конфликтующие изменения, проверим статус.
git status
Нам будет предложено решить конфликтные коммиты, затем пометить их решенными:
git add/rm
Дальше нужно продолжить перебазирование:
git rebase --continue
Или еще откатить изменения — вернуться в состояние до использования команды rebase.
git rebase --abort
Есть и третий вариант с перезапуском шага и перезагрузкой процесса перебазирования:
git rebase --skip
Но будьте аккуратны, skip пропустит (удалит) конфликтный коммит.
Git rebase interactive
Интерактивный режим rebase используется для перезаписи истории посредством изменения самих коммитов, а также информации в них. Переход в интерактивный режим перебазирования делается при помощи флага -i или —interactive.
git rebase [-i | --interactive]
Выполнение этой команды создаст список коммитов в хронологическом порядке добавления, чтобы пользователь мог по своему желанию отредактировать их перед последующим перебазированием. Дальше мы рассмотрим подробнее, как это происходит.
Как пользоваться интерактивным режимом
Например, вот созданная ветка master с пятью коммитами:
2hqsibn selected new method in script.js (HEAD -> master) 4kq5jn2 changes to the script.j qk01ru3 resolved conflict fmjgyu6 added new files z2zgn0c initial commit (origin/master)
Поставлено две задачи:
- Поменять местами коммиты qk01ru3 и 4kq5jn2;
- исправить ошибку в комментарии четвертого коммита (4kq5jn2).
Для этого мы включаем интерактивный режим rebase. В нем можно указать определенное количество коммитов для изменения. Чтобы это сделать, необходимо передать в аргумент коммит, предшествующий тому, который мы будем изменять (в данном случае это fmjgyu6), либо задать “HEAD~[x]”, где вместо [x] — нужное нам число коммитов.
git rebase -i HEAD~3
Откроется текстовый редактор по умолчанию, где также будет приведено описание команд, используемых в интерактивном режиме rebase:
pick qk01ru3 resolved conflict pick 4kq5jn2 changes to the sсript.j pick 2hqsibn selected new method in script.js # Rebase fmjgyu6..2hqsibn onto fmjgyu6 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop = remove commit
Обратите еще раз внимание, что коммиты в интерактивном режиме расположены в другом порядке, от более старого к новому. Приступим к решению задач.
- Для того чтобы поменять коммиты местами, мы просто меняем местами их строчки.
- Чтобы исправить ошибку в комментарии, мы меняем команду “pick” в 4kq5jn2 на “reword” и в следующем окне переписываем комментарий на “changes to the script.js”.
В итоге имеем следующий порядок коммитов:
reword 4kq5jn2 changes to the sсript.js pick qk01ru3 resolved conflict pick 2hqsibn selected new method in script.js
Не забываем сохранять изменения, как вы обычно делаете при сохранении в вашем текстовом редакторе. Выходим из интерактивного режима закрытием текстового редактора.
Теперь наша ветка имеет вид:
vh9xwf3 selected new method in script.js (HEAD -> master) s70zmpx resolved conflict b0jemdh changes to the script.js fmjgyu6 added new files z2zgn0c initial commit (origin/master)
Имейте в виду, что хеши коммитов, с которыми работал rebase, станут иными.
Какой режим выбрать: стандартный или интерактивный
Интерактивный rebase — продвинутая версия обычного rebase, которая дает возможность большего взаимодействия с коммитами. Если нужно разделить, объединить, удалить коммиты, изменить их описание и порядок, интерактивный режим справится на отлично. Если же такой потребности нет, быстрее будет использовать стандартный режим rebase, в котором от пользователя не требуются лишние действия до возникновения конфликтов.
Git rebase —onto
Onto относится к возможностям, раскрывающих rebase во всей красе.
git rebase --onto [ []]
Эта опция позволяет указать коммит, с которого будут перебазироваться коммиты (первый аргумент), иначе это называется новая база. Третий аргумент (branch) можно не указывать, если HEAD указывает на ветку, которая будет нами перебазирована.
Пример
У нас есть три ветки: master, feature-1, develop.
Нам нужно интегрировать изменения из ветки develop в master. Воспользуемся обычным rebase:
git checkout develop
git rebase master
Тогда получим вот такой результат с двумя одинаковыми коммитами D:
Это произошло потому, что перебазирование переприменило коммиты D, G, H, I, так как по отношению к ветке master коммиты ветки develop начинаются с коммита D как связывающего коммита. Теперь решим задачу с интеграцией изменений из develop немного по-другому. Чтобы избежать таких случаев как с коммитом D, воспользуемся новой командой:
git rebase --onto master feature-1 develop
Результат проиллюстрирован на рисунке ниже.
Rebase удаленного репозитория
При работе с удаленным репозиторием в тематической ветке, например на GitHub, следует быть осторожным, используя rebase. Как вы уже знаете, rebase перезаписывает историю, в процессе изменяются хеши коммитов, а это может привести к конфликтам в работе с веткой у других членов команды. Поэтому, если вы работаете над тематической веткой не одни, стоит прибегнуть к нескольким правилам для предотвращения возможных проблем.
- Синхронизировать изменения. Перед тем как вы будете заливать свой код на тот же GitHub, выполните git pull изменений, чтобы избежать конфликтных ситуаций.
- Не перебазировать давно созданные ветки. Количество шагов в rebase равно количеству коммитов на перебазируемой ветке, если не указаны иные опции. Поэтому с увеличением количества неперебазированных коммитов, растет и вероятность появления конфликта.
- Чтобы внедрить изменения в мастер, стоит создать свою локальную ветку и перебазировать ее поверх origin/master. Тогда останется лишь сделать перемотку или бесконфликтное слияние для владельца.
- Не проводить rebase уже отправленных коммитов в публичный репозиторий. Коллегам придется выполнить слияние, что приведет к путанице.
Pull rebase
Чтобы запушить свою ветку, когда git не знает, как объединить ветки, используется режим force:
git push origin --force
С этим режимом будут скопированы родительские коммиты feature на origin, указатель перемещается, как он установлен на локальном репозитории. Важно указать идентификатор ветки в , иначе запушатся все локальные ветки ориджина.
А чтобы извлечь изменения из удаленного репозитория, вместо обычного pull можно использовать режим rebase:
git pull --rebase origin
Локальные merge коммиты не образуются, а история будет выглядеть линейно.
Дополнительные опции перебазирования
Ниже приведена таблица некоторых опций, которые могут быть полезны для работы с rebase.
Опции | Пояснение |
-s —strategy= | Использовать стратегию слияния вместо дефолтного “ort”, что изменит поведение rebase. Подробнее в документации. |
-X —strategy-option= | Эта опция для применения более одной стратегии в порядке, заданном пользователем. |
-x —exec | Выполнение одной или более shell-команд после каждого шага rebase в интерактивном режиме. Если выполнение команды неудачно, перебазирование остановится. |
—no-keep-empty | Не оставлять пустые коммиты. То есть убирать те коммиты, которые ничего не меняют по отношению к родителю. |
—allow-empty-message | Позволяет перебазировать пустые коммиты с пустым сообщением. |
—autosquash | В интерактивном режиме берет коммиты, которые начинаются с fixup! или squash! и ставит соответствующую команду, чтобы объединить коммит с предыдущим.Чтобы это значение всегда работало по умолчанию, можно прописать следующее:“git config —global rebase.autosquash true” |
Заключение
В этой инструкции мы рассмотрели, как сделать rebase ветки в git, узнали про возможность более продвинутой манипуляции с коммитами в режиме interactive и onto, а также тонкости, которые следует соблюдать при работе с rebase.
Работа с ветками в Git (git branch)