Статьи

Переписывание истории с помощью Git Rebase

В фундаментальном рабочем процессе Git вы разрабатываете новую функцию в отдельной ветке тем, а затем объединяете ее с производственной веткой после ее завершения. Это делает git merge неотъемлемым инструментом для объединения веток. Тем не менее, это не единственный, который предлагает Git.

Объединяя ветви, объединяя их
Объединяя ветви, объединяя их

В качестве альтернативы вышеприведенному сценарию вы можете объединить ветви с помощью команды git rebase . Вместо того, чтобы связывать ветви вместе с фиксацией слияния, перебазирование перемещает всю ветвь объекта к кончику master как показано ниже.

Объединение веток с git rebase
Объединение веток с git rebase

Это служит той же цели, что и git merge , объединяя коммиты из разных веток. Но есть две причины, по которым мы можем захотеть сделать ребаз по сравнению со слиянием:

  • Это приводит к линейной истории проекта.
  • Это дает нам возможность очистить местные коммиты.

В этом уроке мы рассмотрим эти два распространенных варианта использования git rebase . К сожалению, преимущества git rebase приходят к компромиссу. При неправильном использовании это может быть одной из самых опасных операций, которые вы можете выполнять в репозитории Git. Итак, мы также будем внимательно смотреть на опасность перебазирования.

В этом руководстве предполагается, что вы знакомы с основными командами Git и рабочими процессами совместной работы. У вас должно быть удобное размещение и фиксация снимков, разработка функций в изолированных ветвях, объединение ветвей и добавление / извлечение веток в / из удаленных репозиториев.

Первый вариант использования, который мы рассмотрим, связан с расходящейся историей проекта. Рассмотрим репозиторий, в котором ваша производственная ветвь продвинулась вперед, пока вы разрабатывали функцию:

Разработка функции в ветви функций

Чтобы переместить ветвь feature в master ветвь, вы должны выполнить следующие команды:

1
2
git checkout feature
git rebase master

Это трансплантирует ветвь feature от ее текущего местоположения до вершины master ветки:

Пересадка ветки признака на вершину ветки мастера

Есть два сценария, где вы хотели бы сделать это. Во-первых, если бы функция полагалась на новые коммиты в master , она теперь имела бы к ним доступ. Во-вторых, если бы функция была завершена, она теперь была бы настроена для быстрого слияния с master . В обоих случаях перебазирование приводит к линейной истории, тогда как git merge приведет к ненужным коммитам слияния.

Например, рассмотрим, что произойдет, если вы объедините восходящие коммиты слиянием вместо ребазирования:

1
2
git checkout feature
git merge master

Это дало бы нам дополнительный коммит в ветке возможностей. Более того, это будет происходить каждый раз, когда вы захотите включить восходящие коммиты в вашу функцию. В конце концов, история вашего проекта будет завалена бессмысленными слияниями.

Интеграция восходящих коммитов слиянием
Интеграция восходящих коммитов слиянием

Это же преимущество можно увидеть при слиянии в другом направлении. Без перебазирования интеграция готовой ветви feature в master требует фиксации слияния. Хотя на самом деле это значимый коммит слияния (в том смысле, что он представляет завершенную функцию), итоговая история полна разветвлений:

Интеграция завершенной функции с объединением
Интеграция завершенной функции с объединением

Когда вы выполняете ребазинг перед слиянием, Git может быстро перенести master до конца feature . Вы увидите линейную историю о том, как ваш проект продвинулся в выводе git log — коммиты в feature аккуратно сгруппированы вместе поверх коммитов в master . Это не обязательно тот случай, когда ветви связаны между собой коммитом слияния.

Перебазирование перед слиянием
Перебазирование перед слиянием

Когда вы запускаете git rebase , Git берет каждый коммит в ветке и перемещает их один за другим на новую базу. Если любой из этих коммитов изменяет ту же строку (и) кода, что и вышестоящий коммит, это приведет к конфликту.

Команда git merge позволяет вам разрешить все конфликты ветви в конце слияния, что является одной из основных целей фиксации слияния. Тем не менее, это немного по-другому, когда вы перебазируете. Конфликты разрешаются на основе принятия. Таким образом, если git rebase обнаружит конфликт, он остановит процедуру rebase и выведет предупреждение:

1
2
3
4
5
6
7
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Failed to merge in the changes.
….
When you have resolved this problem, run «git rebase —continue».
If you prefer to skip this patch, run «git rebase —skip» instead.
To check out the original branch and stop rebasing, run «git rebase —abort».

Визуально, вот как выглядит история вашего проекта, когда git rebase сталкивается с конфликтом:

Конфликты можно проверить, запустив git status . Вывод выглядит очень похоже на конфликт слияния:

1
2
3
4
5
6
7
Unmerged paths:
  (use «git reset HEAD <file>…» to unstage)
  (use «git add <file>…» to mark resolution)
 
    both modified: readme.txt
 
no changes added to commit (use «git add» and/or «git commit -a»)

Чтобы разрешить конфликт, откройте конфликтующий файл (readme.txt в приведенном выше примере), найдите затронутые строки и вручную отредактируйте их до нужного результата. Затем скажите Git, что конфликт разрешен путем размещения файла:

1
git add readme.txt

Обратите внимание, что это точно так же, как вы помечаете конфликт git merge как разрешенный. Но помните, что вы находитесь в середине перебазирования — вы не хотите забывать об остальных коммитах, которые нужно перенести. Последний шаг — сказать Git закончить перебазирование с опцией --continue :

1
git rebase —continue

Это переместит остальные коммиты, один за другим, и если возникнут какие-либо другие конфликты, вам придется повторить этот процесс снова и снова.

Если вы не хотите разрешать конфликт, вы можете выбрать флаги --skip или --abort . Последнее особенно полезно, если вы не знаете, что происходит, и просто хотите вернуться к безопасности.

1
2
3
4
5
# Ignore the commit that caused the conflict
git rebase —skip
 
# Abort the entire rebase and go back to the drawing board
git rebase —abort

До сих пор мы использовали только git rebase для перемещения веток, но он намного мощнее. Передав флаг -i , вы можете начать сеанс интерактивной перебазировки. Интерактивный перебазирование позволяет точно определить, как каждый коммит будет перемещен на новую базу. Это дает вам возможность очистить историю функции, прежде чем делиться ею с другими разработчиками.

Например, предположим, что вы закончили работу над своей веткой feature и готовы интегрировать ее в master . Чтобы начать сеанс интерактивного перебазирования, выполните следующую команду:

1
2
git checkout feature
git rebase -i master

Откроется редактор, содержащий все коммиты в feature которые собираются переместить:

1
2
3
pick 5c43c2b [Description for oldest commit]
pick b8f3240 [Description for 2nd oldest commit]
pick c069f4a [Description for most recent commit]

Этот листинг определяет, как будет выглядеть ветвь компонента после перебазирования. Каждая строка представляет коммит, а команда pick перед каждым хэшем коммита определяет, что произойдет с ним во время перебазирования. Обратите внимание, что коммиты перечислены от самых старых до самых последних. Изменяя этот список, вы получаете полный контроль над историей вашего проекта.

Если вы хотите изменить порядок коммитов, просто измените порядок строк. Если вы хотите изменить сообщение коммита, используйте команду reword . Если вы хотите объединить два коммита, измените команду pick на squash . Это свернет все изменения в этом коммите в один над ним. Например, если вы раздавили второй коммит в приведенном выше листинге, после сохранения и закрытия редактора ветвь feature будет выглядеть следующим образом:

Раздавление 2-го коммита с помощью интерактивной перебазировки
Раздавление 2-го коммита с помощью интерактивной перебазировки

Команда edit особенно мощная. Когда он достигает указанного коммита, Git приостанавливает процедуру перебазирования, так же, как когда он сталкивается с конфликтом. Это дает вам возможность изменить содержимое коммита с помощью git commit --amend или даже добавить больше git commit командами git add / git commit . Любые новые коммиты, которые вы добавите, будут частью новой ветки.

Интерактивный перебазирование может оказать глубокое влияние на ваш рабочий процесс разработки. Вместо того, чтобы беспокоиться о разбиении ваших изменений на инкапсулированные коммиты, вы можете сосредоточиться на написании своего кода. Если вы закончили фиксацию того, что должно быть одним изменением, в четыре отдельных снимка, то это не проблема — переписать историю с помощью git rebase -i и объединить их в один значимый коммит.

Теперь, когда у вас есть понимание git rebase , мы можем поговорить о том, когда его не использовать. Внутренне, перебазирование фактически не перемещает коммиты в новую ветку. Вместо этого он создает новые коммиты, которые содержат желаемые изменения. Учитывая это, перебазирование лучше визуализировать следующим образом:

После перебазирования коммиты в feature будут иметь разные хеши коммитов. Это означает, что мы не просто переставили ветку — мы буквально переписали историю нашего проекта. Это очень важный побочный эффект git rebase .

Когда вы работаете в одиночку над проектом, переписывание истории не имеет большого значения. Однако, как только вы начнете работать в среде совместной работы, это может стать очень опасным. Если вы переписываете коммиты, которые используют другие разработчики (например, коммиты в master ветке), это будет выглядеть так, как будто эти коммиты исчезли в следующий раз, когда они попытаются выполнить вашу работу. Это приводит к запутанному сценарию, от которого трудно оправиться.

Имея это в виду, вы никогда не должны перебазировать коммиты, которые были перенесены в публичный репозиторий, если вы не уверены, что никто не основывал свою работу на них.

В этом руководстве представлены два наиболее распространенных варианта использования git rebase . Мы много говорили о перемещении веток, но имейте в виду, что перебазирование — это действительно контроль истории вашего проекта. Возможность переписывать коммиты после факта позволяет вам сосредоточиться на задачах разработки, а не разбивать свою работу на отдельные снимки.

Обратите внимание, что перебазирование является совершенно необязательным дополнением к вашей панели инструментов Git. Вы все еще можете делать все, что вам нужно, с помощью простых старых команд git merge . На самом деле, это безопаснее, так как избегает возможности переписывания публичной истории. Однако, если вы понимаете риски, git rebase может быть гораздо более чистым способом интеграции веток по сравнению со слиянием коммитов.