Введение

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

Виды слияний

Существует два типа слияний: неструктурированные и структурированные.

Рабочий процесс

Автоматическое слияние – это процесс, выполняемый системами контроля версий при согласовании изменений, внесенных одновременно (в логическом смысле). Другие программные продукты также используют автоматическое слияние, если они позволяют одновременно редактировать один и тот же контент. Например, в Википедии два пользователя могут одновременно редактировать одну и ту же статью; когда второй пользователь сохраняет изменения, они добавляются к статье, а не перезаписывают предыдущие. Ручное слияние необходимо, когда требуется согласовать различающиеся файлы, возможно, с использованием специальных инструментов. Например, если две системы содержат немного отличающиеся версии файла конфигурации, и пользователю нужно объединить лучшие части из обеих, это обычно делается путем ручного слияния файлов и выбора нужных изменений из каждого источника (это также называется двухсторонним слиянием). Ручное слияние также требуется, когда автоматическое слияние сталкивается с конфликтом изменений; например, очень мало автоматических инструментов слияния могут объединить два изменения, внесенных в одну и ту же строку кода (например, одно изменение переименовывает функцию, а другое добавляет комментарий). В таких случаях системы контроля версий запрашивают у пользователя указание желаемого результата слияния.

Алгоритмы слияния

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

Трехстороннее слияние

Трехстороннее слияние выполняется после автоматического анализа различий между файлом "А" и файлом "В" с учетом их общего предка – файла "С". Это упрощенный, но широко применимый метод, поскольку для восстановления изменений, подлежащих объединению, требуется только один общий предок. Трехстороннее слияние может выполняться как для обычного текста (последовательности строк), так и для структурированных деревьев. При трехстороннем слиянии ищутся фрагменты, которые совпадают только в двух из трех файлов. В этом случае существует две версии фрагмента, и версия из общего предка "С" отбрасывается, а отличающаяся версия сохраняется в результате. Если файлы "А" и "В" идентичны, то именно эта версия включается в результат. Фрагмент, совпадающий в "А" и "С", заменяется измененной версией из "В", и аналогично, фрагмент, совпадающий в "В" и "С", заменяется версией из "А". Фрагменты, различающиеся во всех трех файлах, помечаются как конфликт и требуют ручного разрешения пользователем. Трехстороннее слияние реализовано в широко известной программе diff3 и стало ключевым нововведением, позволившим перейти от систем контроля версий, основанных на блокировке файлов, к системам, основанным на слиянии. Оно активно используется в системе одновременных версий (CVS).

Рекурсивное трехстороннее слияние

Инструменты контроля версий, основанные на трехстороннем слиянии, широко распространены, но эта техника принципиально зависит от нахождения общего предка объединяемых версий. Существуют сложные случаи, особенно так называемое "перекрестное слияние", когда уникального последнего общего предка измененных версий не существует. К счастью, в этом случае можно показать, что существует не более двух возможных кандидатов в предки, и рекурсивное трехстороннее слияние строит виртуального предка, сначала объединяя не уникальных предков. Само это объединение может столкнуться с той же проблемой, поэтому алгоритм рекурсивно объединяет их. Поскольку количество версий в истории конечно, процесс гарантированно завершится. Этот метод используется системой контроля версий Git. (Рекурсивная реализация слияния в Git также обрабатывает другие сложные случаи, например, когда файл изменен в одной версии и переименован в другой, но это расширения его реализации трехстороннего слияния, а не часть техники поиска трех версий для слияния.) Рекурсивное трехстороннее слияние может применяться только в тех ситуациях, когда инструмент обладает информацией о полной родословной в виде направленного ациклического графа (DAG) объединяемых производных. Следовательно, оно не может использоваться в ситуациях, когда производные или слияния не полностью определяют своих предков.

Нанесение пластыря

Патч — это файл, содержащий описание изменений в файле. В среде Unix исторически изменения в текстовых файлах распространялись в виде патчей в формате, генерируемом командой "diff -u". Этот формат затем может быть использован программой `patch` для повторного применения (или отмены) изменений к текстовому файлу или структуре каталогов, содержащей текстовые файлы. Однако программа `patch` также предоставляет возможности для применения патча к файлу, который не идентичен исходному файлу, использованному для создания патча. Этот процесс называется применением патча с нечетким соответствием, и он приводит к своего рода асимметричному трехстороннему слиянию, при котором изменения в патче отбрасываются, если программа `patch` не может найти место для их применения. Как CVS изначально представлял собой набор скриптов, основанных на diff3, так и GNU arch начинался как набор скриптов, основанных на `patch`. Однако применение патчей с нечетким соответствием — относительно ненадежный метод, который иногда неправильно применяет патчи с недостаточным контекстом (особенно те, которые создают новый файл), а иногда отказывается применять удаления, которые были сделаны в обеих производных версиях.

Коммутация патчей

Коммутация патчей используется в Darcs для слияния изменений, а также реализована в git (но называется "перебазированием"). Коммутативное слияние патчей означает изменение порядка патчей (то есть описаний изменений) таким образом, чтобы они формировали линейную историю. По сути, когда два патча создаются в одной и той же ситуации, при слиянии один из них переписывается так, как будто он был применен поверх другого. Коммутация патчей требует хранения или возможности восстановления точных изменений, внесенных в файлы. На основе этих точных изменений можно вычислить, как один патч следует изменить, чтобы перебазировать его на другой. Например, если патч A добавляет строку "X" после строки 7 файла F, а патч B добавляет строку "Y" после строки 310 файла F, то при перебазировании B на A, B необходимо переписать: строку "Y" нужно добавить после строки 311 файла F, поскольку добавленная в A строка смещает нумерацию строк на единицу. Коммутация патчей была тщательно изучена с формальной точки зрения, однако алгоритмы для разрешения конфликтов слияния при коммутации патчей остаются открытым вопросом исследований. Тем не менее, можно доказать, что коммутация патчей обеспечивает "корректные" результаты слияния, в то время как другие стратегии слияния в основном являются эвристиками, стремящимися предоставить пользователю желаемый результат. Программа Unix flipdiff из пакета "patchutils" реализует коммутацию патчей для традиционных патчей, созданных утилитой diff u.

Слияние тканей

Weave merge – это алгоритм, который не использует общего предка для двух файлов. Вместо этого он отслеживает, как отдельные строки добавляются и удаляются в производных версиях файлов, и на основе этой информации создает объединенный файл. Для каждой строки в производных файлах weave merge собирает следующую информацию: какие строки предшествуют ей, какие следуют за ней, и была ли она удалена на каком-либо этапе истории одной из производных версий. Если строка была удалена в какой-либо момент в одной из производных версий, она не должна присутствовать в объединенной версии. Для остальных строк они должны присутствовать в объединенной версии. Строки сортируются в порядке, при котором каждая строка следует за всеми строками, которые предшествовали ей в какой-либо момент истории, и предшествует всем строкам, которые следовали за ней в какой-либо момент истории. Если эти ограничения не обеспечивают полного упорядочивания всех строк, то строки, не имеющие порядка относительно друг друга, являются конфликтующими добавлениями. Weave merge, по-видимому, использовался в коммерческом инструменте контроля версий BitKeeper и способен обрабатывать некоторые проблемные случаи, когда трехстороннее слияние выдает неверные или некорректные результаты. Он также является одним из вариантов слияния в инструменте контроля версий GNU Bazaar и используется в Codeville.