Объединение изменений#
Объединение изменений технически называется объединением веток. Это возникает, когда мы хотим интегрировать свои изменения в общую с остальными ветку. В большинстве случаев объединение происходит автоматически (без вмешательства человека), если изменения не пересекаются, т.е. выполнены в разных файлах или в разных строках одного файла. Следует быть осторожными при объединении изменений: не потерять данные и не нарушить целостность проекта. Это ответственная часть работы, которая многих пугает.
В этом уроке мы узнаем:
о трех способах объединения изменений: слиянии, перемотке и перебазировании;
о команде
git merge;как разрешать конфликты слияния;
об утилите KDiff3;
об обслуживании веток.
Перенос изменений коммитом слияния#
Команда git merge переносит изменения из одной ветки в другую.
Представим ситуацию, когда есть дерево коммитов с основной веткой master и тематической bugfix.
В bugfix двумя коммитами исправлена ошибка.
За время работы над bugfix в основной ветке появился один коммит.
Указатель на текущую ветку HEAD показывает на master.
В этом состоянии команда git merge bugfix переносит изменения из ветки bugfix в текущую ветку master через коммит слияния с идентификатором 2a5.
Текущая ветка master перемещается на этот новый коммит.
Указатель тематической ветки bugfix после объединения не переместился.
Этой веткой еще можно воспользоваться и развивать ее, но смысла в этом нет, так как она выполнила свою роль.
Она может быть удалена.
При удалении мы не потеряем ее коммиты, так как они теперь стали достижимыми из основной ветки.
Удаление ветки приведет только к удалению указателя на коммит.
Команда git branch -d bugfix успешно удалит ветку bugfix.
Возникает вопрос, а не хранил ли именованный указатель ветки информацию об исправленной ошибке?
Может ли пользователь Git по отдельным описаниям коммитов тематической ветки сделать вывод, что здесь происходило исправление конкретной ошибки?
Так вот, информация об исправлении ошибки должна быть ясно прописана в описании коммита слияния 2a5.
Коммит слияния как бы вносит изменения в ветку скопом.
Перемотка вперед#
Объединение веток упростится, если в ветке, куда переносятся изменения, отсутствуют коммиты.
Пример такой ситуации показан на рисунке ниже.
Здесь достаточно переместить указатель ветки master к последнему коммиту bugfix.
Этот прием называется перемоткой вперед, от английского fast forward.
Но и в этой ситуации можно создать коммит слияния, если явно отказаться от перемотки опцией --no-ff.
git merge --no-ff bugfix
Такой выбор объясняется тем, что автор не хочет, чтобы коммиты его ветки не стали коммитами основной ветки, так как каждая из них не удовлетворяет условию завершенности. А таким условием обладает только коммит слияния.
Перебазирование#
Перебазирование – это перенос последовательности коммитов на другой коммит. Графически это выглядит как отрезание части ветки с места ветвления и перенос ее в другое место. Те коммиты, от которых ветка была отсоединена и к которым закреплена, называют базовыми. Отметим, что у переносимых коммитов меняются идентификаторы, так как меняются родители.
Представим знакомую ситуацию на рисунке ниже с двумя ветками master и bugfix.
Она отличается от вышеприведенного тем, что текущая ветка здесь bugfix.
Команда git rebase master перенесет текущую ветку bugfix на ветку master.
Чтобы закончить объединение следует или перемотать ветку master до bugfix или создать коммит слияния.
Перебазирование, в отличие от перемотки и слияния, изменяет историю, удаляя существующие коммиты и связи между ними. Это может усложнить объединение изменений, если кто-то еще использовал удаляемые коммиты. Отсюда вытекает важное правило.
Warning
Нельзя перебазировать публичные ветки, (ветки, которые кто-то еще использует). В противном случае, во время объединения дерева коммитов двух хранилищ, удаленные коммиты вернутся обратно. В результате в истории будут две копии коммитов, вносящих одни и те же изменения.
Перебазирование подходит, если вы каждым коммитом вносите в проект полностью оформленную работу. Примеры изменений, которые не удовлетворяют этому условию:
не компилируются;
не позволяют запустится программе;
вносят дополнительные ошибки.
Перебазирование, как и перемотка, сохраняет историю изменений линейной. Такая история проще для просматривания и изучения.
Разрешение конфликтов слияния#
Конфликтом слияния называют ситуацию, при которой изменения не могут быть слиты автоматически. Для разрешения конфликта требуется внимание разработчика. В каких случаях невозможно автоматически слить изменения?
Если в текстовом файле была по-разному изменена одна и та же строка.
Если одновременно изменены двоичные файлы как изображения, аудио и видео. Здесь остается только выбрать один из двух вариантов или заново создать файл, который включает оба изменения.
Если измененный файл был удален.
Вместе с этим курсом доступны примеры однотипных хранилищ с коллизиями при объединении ветки branching с main.
Загрузите одно из них, разархивируйте в домашний каталог и перейдите в рабочий каталог.
Удостоверьтесь, что вы находитесь на ветке main командой git branch:
$ git branch
branching
* main
Если вы на ветке branching, то переключитесь на main командой git checkout main.
Объединение веток выполняет команда git merge.
Ветка, куда вносятся изменения, является текущей.
Ветка, откуда берутся изменения, передается в качестве аргумента.
Мы хотим внести изменения из branching в main, тогда выполним команду git branch branching:
$ git merge branching
Автослияние RunCMakeTest.cmake
КОНФЛИКТ (содержимое): Конфликт слияния в RunCMakeTest.cmake
Сбой автоматического слияния; исправьте конфликты, затем зафиксируйте результат.
В нашем случае команда прервала свою работу с сообщением о конфликте и указала файл, где это произошло. Посмотрим состояние файлов:
$ git status
Текущая ветка: main
У вас есть не слитые пути.
(разрешите конфликты, затем запустите «git commit»)
(используйте «git merge --abort», чтобы остановить операцию слияния)
Не слитые пути:
(используйте «git add <файл>...», чтобы пометить разрешение конфликта)
оба изменены: RunCMakeTest.cmake
индекс пуст (используйте «git add» и/или «git commit -a»)
То же состояние, но с опцией -s покажет следующее:
$ git status -s
UU RunCMakeTest.cmake
Обозначенный таким образом файл содержит специальную разметку, указывающую на конфликт слияния. Откройте файл и найдите размеченное следующим образом содержимое:
<<<<<<< HEAD
...
=======
...
>>>>>>> branching
Разметка показывает два блока фрагмента, которые не получилось слить: из текущей и сливаемой веток. Ваша задача отредактировать содержимое так, чтобы оно содержало актуальные изменения из обоих веток. Также не забудьте удалить разметку. Пример кода из хранилища:
<<<<<<< HEAD
run_TestRepeat(UntilFail RETURN_VALUE:1 REPEAT UNTIL_FAIL:3)
run_TestRepeat(UntilPass RETURN_VALUE:0 REPEAT UNTIL_PASS:3)
run_TestRepeat(AfterTimeout RETURN_VALUE:0 REPEAT AFTER_TIMEOUT:3)
# test repeat and not run tests interact correctly
set(CASE_CMAKELISTS_SUFFIX_CODE [[
add_test(NAME testNotRun
COMMAND ${CMAKE_COMMAND}/doesnt_exist)
set_property(TEST testNotRun PROPERTY TIMEOUT 5)
]])
run_TestRepeat(NotRun RETURN_VALUE:1 REPEAT UNTIL_PASS:3)
unset(CASE_CMAKELISTS_SUFFIX_CODE)
=======
run_TestRepeat(UntilFail REPEAT UNTIL_FAIL:3)
run_TestRepeat(UntilPass REPEAT UNTIL_PASS:3)
run_TestRepeat(AfterTimeout REPEAT AFTER_TIMEOUT:3)
# test --stop-on-failure
function(run_stop_on_failure)
set(CASE_CTEST_TEST_ARGS EXCLUDE RunCMakeVersion)
set(CASE_CMAKELISTS_SUFFIX_CODE [[
add_test(NAME StoppingTest COMMAND ${CMAKE_COMMAND} -E false)
add_test(NAME NotRunTest COMMAND ${CMAKE_COMMAND} -E true)
]])
run_ctest_test(stop-on-failure STOP_ON_FAILURE)
endfunction()
run_stop_on_failure()
# Make sure environment gets logged
function(run_environment)
set(ENV{BAD_ENVIRONMENT_VARIABLE} "Bad environment variable")
set(CASE_CMAKELISTS_SUFFIX_CODE [[
set_property(TEST RunCMakeVersion PROPERTY ENVIRONMENT "ENV1=env1;ENV2=env2")
]])
run_ctest(TestEnvironment)
endfunction()
run_environment()
>>>>>>> branching
После редактирования сохраните файл и добавьте его в индекс командой git add RunCMakeTest.cmake.
Затем зафиксируйте изменения.
Можно оставить стандартное сообщение коммита “Merge branch ‘branching’”, которое предложит Git или предоставить свой.
Разрешение конфликта с KDiff3#
Разрешение конфликтов слияния – задача сложная и ответственная. Существуют специализированные инструменты, которые позволят облегчить и частично автоматизировать этот процесс. Один из них – это графическая утилита KDiff3, доступная на платформах Linux и Windows. Утилита установлена в поставляемой с курсом виртуальной машине. Если вы находитесь внутри хранилища с неразрешенным конфликтом, вызовите утилиту командой
git mergetool --tool=kdiff3
Появится диалоговое окно с сообщением о количестве разрешенных и неразрешенных конфликтов.
Область окна утилиты разбита на 4 части – 4 версии файла. Нижнюю область занимает результирующий файл, который войдет в коммит слияния. Ее и следует редактировать. Верхняя область окна поделена на три части, показывающих три состояния одного файла:
A, Base – файл до ветвления;
B, Local – файл из текущей ветки;
C, Remote – файл из сливаемой ветки.
Обслуживание веток#
Список веток, слитых с текущей, покажет команда git branch --merged.
Эти ветки можно безопасно удалить.
Команда git branch --no-merged покажет список веток, неслитых с текущей.
Такие ветки нельзя удалить, не потеряв их коммиты.
Если все-таки нужно удалить такую ветку, то выполните команду git branch -D <branch-name>.