SVN merge for dummies

    Простыми словами и с большим количеством картинок на примере Eclipse рассказывается, как сделать svn merge. Статья будет полезна тем, у кого выполнение слияния веток еще не стало повседневной частью работы.

    Статья рассчитана на использование следующего программного обеспечения:
    • SVN 1.4.4
    • Eclipse 3.4.2 SR2
    • Subversive 0.7.7
    • SVN-коннектор Native JavaHL 1.5.4

    Жизненный цикл ветки


    Рассмотрим пример жизни обычной ветки, в которой вы, в роли одного из разработчиков, выполняете порученную вам задачу.
    В начале в репозитории присутствует только основная часть кода (trunk, далее «транк»), текущая ревизия которой — r10. Состояние транка T1 соответствует версии кода на момент создания ветки (branch, далее «ветка»).
    Вы создаете собственную ветку, которая начинается с ревизии r11. Далее начинается параллельная работа над кодом, в течение которой вы вносите в свою ветку объем изменений B1. Одновременно с этим другие разработчики вносят в транк объем изменений T2.
    Далее вы включаете в свою ветку последние изменения, сделанные в транке. Состояние вашей ветки в данный момент T1+B1, поэтому вас интересуют только изменения, произошедшие в транке с того момента, когда вы создали свою ветку. Эти изменения (T2) однозначно задаются номерами начальной и конечной ревизии, в нашем случае это r10 и r12. Вы выполняете слияние изменений, сделанных в транке между 10 и 12 ревизиями, с текущей версией вашей ветки и, при необходимости исправив ошибки, коммитите результат слияния в свою ветку (ревизия r14).
    svntree1.png
    Теперь у вас есть все последние изменения из транка, а состояние вашей ветки равно T1+B1+T2.
    Далее разработка продолжается, и вы вносите в свою ветку объем изменений B2, в то время как другие разработчики вносят в транк объем изменений T3.
    Вы снова включаете в свою ветку последние изменения из транка. Состояние вашей ветки равно T1+B1+T2+B2, поэтому вам необходим только объем изменений T3. Выполняя слияние, в качестве начальной ревизии вы задаете конечную ревизию предыдущего слияния (r12). Таким образом, вы выполняете слияние изменений, сделанных в транке между 12 и 15 ревизиями, с текущей версией вашей ветки и, при необходимости исправив ошибки, коммитите результат слияния в свою ветку (ревизия r17).
    Допустим, что теперь порученная задача вами решена и пришло время внедрить выполненные вами изменения в транк. Операция не должна быть очень сложной благодаря тому, что вы регулярно приводили свою ветку в соответствие с транком и оперативно решали возникающие проблемы. Состояние вашей ветки сейчас равно T1+B1+T2+B2+T3 (плюс исправления B1' и B2', которые вы внесли в процессе слияний). Состояние транка сейчас равно T1+T2+T3. Поэтому вы выполняете слияние изменений, сделанных в вашей ветке между 11 и 17 ревизиями, с текущей версией транка и, при необходимости исправив ошибки, коммитите новую версию транка (r18).
    После этого свою ветку рекомендуется удалить и, продолжая свою работу, создать новую ветку.
    Далее на конкретном примере рассмотрим процесс слияния.

    Исходное состояние репозитория


    Рассмотрим следующую ситуацию. Есть набор проектов test_area, в котором есть проект test_project:
    repo1.png
    Из ревизии 40898 транка был создан бранч, начальная ревизия которого стала равна 40904. После этого были внесены изменения в транк (ревизия 40906) и в бранч (ревизия 40923). Далее рассмотрим процесс слияния бранча с транком на примере включения в бранч последних изменений транка.
    svntree2.png
    В проекте есть три файла: ChangedByMe.java, ChangedByOther.java и Conflicted.java.
    Вы в своей ветке поменяли файл ChangedByMe.java, дописав к значению поля str подстроку "-I changed it". Еще в своей ветке вы поменяли файл Conflicted.java, также дописав к значению поля str подстроку "-I changed it".
    Переключившись в транк, вы (или кто-то еще) аналогичным образом поменяли файлы ChangedByOther.java и Conflicted.java, дописав туда подстроку "-Someone changed it".

    Выполнение слияния


    Шаг 1. Подготовка

    На данном шаге следует для своей ветки выполнить следующие операции:
    • сделать update;
    • устранить все ошибки компиляции;
    • сделать commit.

    updatecommit.png
    Обязательно убедитесь, что ваш проект нацелен на нужную ветку:
    verifylocation.png
    Слияние удобней всего выполнять в перспективе «Team Synchronizing». Переключитесь в эту перспективу и откройте следующие виды: «Synchronize», «History», «Console», «SVN Repositories», «Project Explorer».

    Шаг 2. Определение начальной и конечной ревизии транка для слияния

    В качестве начальной ревизии для первого слияния следует брать ревизию транка, из которой была создана ветка. В нашем случае это 40898. Определить этот номер можно, изучая историю корневой папки своей ветки и используя кнопку «Stop On Copy».
    history1.png
    Для большей наглядности рекомендуется при создании ветки писать комментарий вида:
    Branched from trunk@40898
    В качестве конечной ревизии следует брать последнюю ревизию транка — 40906. Этот номер можно увидеть в виде «SVN Repositories» или в диалоге выбора URL диалога Merge.
    При последующих слияниях в качестве начальной ревизии транка следует брать конечную ревизию транка, указанную в предыдущем слиянии. Чтобы не забыть ее номер, при коммите результатов слияния следует записать комментарий следующего вида:
    Merged with trunk@40898-40906

    Шаг 3. Заполнение диалога Merge

    Делаем видимым вид «Console».
    В виде «Project Explorer» нажимаем на нашем проекте правую кнопку и выбираем Team->Merge. Двигаем диалог Merge так, чтобы была видна консоль.
    В URL выбираем путь к транку: .../test_area/trunk/test_project
    В Revisions задаем: 40898-40906
    merge1.png
    Нажимаем кнопку Preview и контролируем адекватность результатов, появляющихся в консоли и в окне Merge Preview.
    merge2.png
    Нажимаем кнопку OK.
    В этот момент автоматически произойдут следующие действия:
    1. Выполнится команда svn merge.
    2. Все локальные файлы, которые были изменены в транке, но не были изменены в вашей ветке (ChangedByOther.java), будут заменены версией из транка. Таким образом, у вас на диске будет файл из транка, а предыдущая версия этого файла, с которой вы работали, будет лежать в svn в вашей ветке.
    3. Все локальные файлы, которые были изменены и в транке, и в вашей ветке (Conflicted.java), будут заменены diff-файлами вида:

    public class Conflicted {
    <br><<<<<<< .working
    <br>    String str = "Conflicted-I changed it";
    <br>=======
    <br>    String str = "Conflicted-Someone changed it";
    <br>>>>>>>> .merge-right.r40906
    <br>}


    Для каждого конфликтного файла в папке проекта будут созданы три служебных файла:
    [имя_файла].merge-left.r[номер_ревизии] — содержит исходную версию файла (которая была до ваших и чужих изменений)
    [имя_файла].merge-right.r[номер_ревизии] — содержит версию файла с внешними изменениями (из транка)
    [имя_файла].working — содержит версию файла с вашими изменениями

    В виде «Synchronize» будут отображены результаты выполнения команды svn merge:
    sync1.png

    Шаг 4. Обработка внешних изменений

    Сначала обработаем обновившиеся файлы, которые не были изменены нами. Поскольку наша задача — загрузить в свою ветку изменения из транка, имеет смысл нажать кнопку Accept All Incoming Changes.
    При необходимости принять отдельное решение относительно каждого файла, дважды кликаем на него — откроется Compare Editor. В левой части окна будет отображена версия файла из транка, а в правой — версия из вашей ветки.
    compare1.png
    Чтобы отказаться от изменений, следует в Compare Editor включить режим Two-Way Compare (кнопка должна быть нажата) и нажать кнопку Copy All Non-Conflicting Changes from Right to Left, после чего сохранить файл (Ctrl-S). После сохранения файл исчезнет из вида «Synchronize».
    Чтобы принять изменения, следует в виде «Synchronize» нажать на файле правую кнопку и выбрать Accept. Файл исчезнет из вида «Synchronize».

    Шаг 5. Обработка конфликтов

    Поскольку наша задача — загрузить в свою ветку последние изменения из транка, то следует модифицировать конфликтные файлы таким образом, чтобы сохранить там все изменения, выполненные в транке. Если вы считаете эти изменения неудачными — обсудите это с их автором, модифицируйте транк и повторите процесс слияния.
    Обрабатывая каждый файл, дважды кликаем на него — откроется Compare Editor. Имеет смысл выбрать конкретное изменение в верхней панели редактора, тогда в левой части окна будет отображен конфликтный фрагмент из транка, а в правой — конфликтный фрагмент из вашей ветки.
    compare2.png
    Чтобы отказаться от изменений, следует в Compare Editor включить режим Two-Way Compare (кнопка должна быть нажата), выбрать в верхней панели редактора элемент «Compilation Unit» и нажать кнопку Copy All Non-Conflicting Changes from Right to Left, после чего сохранить файл (Ctrl-S). Далее следует в виде «Synchronize» нажать на файле правую кнопку и выбрать Mark As Merged. Файл исчезнет из вида «Synchronize».
    Чтобы принять изменения, следует в виде «Synchronize» нажать на файле правую кнопку и выбрать «Edit Conflicts» — откроется Compare Editor в режиме «Edit Conflicts». В нем (сюрприз!) ваша версия файла будет отображена в левой части окна, версия из транка будет отображена в правой части. После этого следует в Compare Editor включить режим Two-Way Compare (кнопка должна быть нажата) и нажать кнопку Copy All Non-Conflicting Changes from Right to Left, после чего сохранить файл (Ctrl-S). Кроме этого, файл можно отредактировать вручную в левой части окна. Далее следует в виде «Synchronize» нажать на файле правую кнопку и выбрать Mark As Merged. Файл исчезнет из вида «Synchronize».
    compare3.png

    Шаг 6. Откат неудачных изменений

    Если случайно сделали что-то не так, например, приняли изменения вместо того, чтобы отказаться (или наоборот), то всегда все можно легко исправить.
    Для отката к версии файлов из вашей ветки в виде «Project Explorer» нажмите правую кнопку на вашем проекте и выберите Team->Revert.
    Удалить служебные файлы, созданные в процессе слияния можно либо вручную, либо нажав в виде «Project Explorer» правую кнопку на вашем проекте и выбрав Team->Synchronize with Repository. После этого в виде «Synchronize» следует на вашем проекте нажать кнопку «Override and Update...» и потом обновить (F5) вид «Project Explorer».
    Для повторного запуска слияния нет необходимости снова вызывать диалог Merge. Достаточно нажать F5 в виде «Synchronize». При необходимости, следует сначала выбрать нужный экран синхронизации, нажав левую крайнюю кнопку тулбара в виде «Synchronize»:
    sync2.png

    Шаг 7. Коммит в свою ветку

    После того, как принято решение по каждому из файлов, следует для своей ветки выполнить следующие операции:
    • устранить возникшие ошибки компиляции;
    • сделать коммит проекта, обязательно написав комментарий вида:
      Merged with trunk@40898-40906

    Итак, вы загрузили в свою ветку последние обновления из транка. Регулярное выполнение этой операции позволит уменьшить объем возникающих при слиянии проблем.
    PS. За рамки статьи сознательно было вынесено рассмотрение различий между SVN 1.4 и SVN 1.5, и описание способов разрешения более сложных конфликтных ситуаций.
    • +16
    • 23,3k
    • 8
    Поделиться публикацией
    Комментарии 8
      0
      Спасибо большое.

      От себя могу добавить, что при работе SVN 1.4 крайне не рекомендую изменять структуру репозитория в ветках. В первую очередь, не стоит выполнять перемещение файлов. Необходимость в этом возникает, например, при изменении структуры java namespaces в проекте (да, бывает и такое).

      В перемещенные файлы невозможно корректно смержить изменения из, например, trunk'а или других веток. Так же, большие проблемы возникают при back-merge ветви в trunk проекта.
        0
        Перемещение файлов можно мержить вручную, либо обновиться до 1.6, где реализованы tree-конфликты.
        –4
        Stop brainfucking. Use GIT :-)
          0
          Чем он лучше? Да, знаю, распределённая система — а вот в этой конкретной описанной в статье задаче насколько проще будет решить вопрос?
            0
            Да тут дело не в распределенности, а в том, как в гите обстоит работа с ветками и их смерживанием. Все интуитивно понятно, не надо ни одного скриншота, не нужна такая большая (но в контексте СВН — нужная) статья, как эта.

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

            Так что «stop brainfucking» это мой добрый совет всем. Я ведь тоже раньше использовал СВН.
              +1
              Я только За!
              У Вас вполне достаточно возможностей, чтоб доказать это на примере — это ведь нетрудно, повторить сценарий этой статьи с git :-)
              Я ведь не потому вопрос задал, чтобы сказать, что git отстой, а потому, что сам им не пользовался, и интерес тут простой — чтобы опытный в git-е человек, показал на примере, чем он лучше, только и всего :-)
          0
          Отличная статья. Пишите, пожалуйста, еще. Хочется видеть на Хабре побольше вот такой, практической, интересной и профессионально поданой информации.
            0
            А вот не знает ли кто, как попроще бы в SVN найти, куда была смёржена та или иная ревизия, и мёржилась ли вообще?

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое