Source

hgbook-ru / ru / ch03-tour-merge.xml

Full commit
<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : -->

<chapter id="chap:tour-merge">
  <?dbhtml filename="a-tour-of-mercurial-merging-work.html"?>
  <title>Экскурсия по Mercurial: слияние результатов работы</title>

  <para id="x_338">К этому моменту мы рассмотрели клонирование репозитория, внесение изменений в репозиторий и получение или передачу изменений из одного репозитория в другой. Следующим нашим шагом будет <emphasis>слияние</emphasis> изменений из независимых репозиториев.</para>

  <sect1>
    <title>Слияние потоков работы</title>

    <para id="x_339">Слияние &emdash; это основная часть работы с инструментами распределённого контроля версий. Вот несколько случаев, когда возникает необходимость объединить работу:</para>
    <itemizedlist>
      <listitem>
	<para id="x_33a">Элис и Боб имеют собственные репозитории проекта, над которым они вместе работают. Элис пофиксила ошибку в своем репозитории, а Боб добавил новую фичу в своём. Они хотят чтобы общий репозиторий содержал и багфикс и новую фичу.</para>
      </listitem>
      <listitem>
	<para id="x_33b">Синтия часто работает в одном проекте одновременно над несколькими разными задачами, каждая из которых изолирована в собственном репозитории. Работая таким образом, приходится частенько производить слияние одной части работы с другой.</para>
      </listitem>
    </itemizedlist>

    <para id="x_33c">Поскольку слияние очень частая операция, Mercurial имеет простые средства её осуществления. Давайте рассмотрим процесс слияния. Начнем с еще одного клонирования репозитория (заметили насколько часто мы это делаем?) и внесения изменений в него.</para>

    &interaction.tour.merge.clone;

    <para id="x_33d">Теперь у нас есть две копии <filename>hello.c</filename> c разным содержимым. История изменения этих двух репозиториев тоже различается, как показано на <xref linkend="fig:tour-merge:sep-repos"/>. Вот копия нашего файла из одного репозитория:</para>

    &interaction.tour.merge.cat1;

    <para id="x_722">А здесь немного отличающаяся копия из другого хранилища:</para>

    &interaction.tour.merge.cat2;

    <figure id="fig:tour-merge:sep-repos">
      <title>Расхождение историй репозиториев <filename class="directory">my-hello</filename> и <filename class="directory">my-new-hello</filename></title>
      <mediaobject>
	<imageobject><imagedata fileref="figs/tour-merge-sep-repos.png"/></imageobject>
	<textobject><phrase>XXX add text</phrase></textobject>
      </mediaobject>
    </figure>

    <para id="x_33f">Мы уже знаем, что получение изменений из репозитория <filename class="directory">my-hello</filename> не изменит состояния рабочего каталога.</para>

    &interaction.tour.merge.pull;

    <para id="x_340">При этом команда <command role="hg-cmd">hg pull</command> говорит что-то о <quote>головах</quote> (heads), мол их стало на одну больше (+1 heads).</para>

    <sect2>
      <title>Головная ревизия</title>

      <para id="x_341">Напомним, что каждая ревизия в Mercurial, имеет родительскую ревизию. Если у ревизии есть родитель, мы называем её потомком. У головной (head) ревизии нет потомков. Главная (tip) ревизия тоже головная, потому что самая свежая ревизия в хранилище не может иметь потомков. Случаются моменты, когда репозиторий может содержать более одной головной ревизии.</para>

      <figure id="fig:tour-merge:pull">
	<title>Содержимое хранилища <filename class="directory">my-new-hello</filename> после получения изменений из <filename class="directory">my-hello</filename></title>
	<mediaobject>
	  <imageobject>
	    <imagedata fileref="figs/tour-merge-pull.png"/>
	  </imageobject>
	  <textobject><phrase>XXX add text</phrase></textobject>
	</mediaobject>
      </figure>

      <para id="x_343">На <xref linkend="fig:tour-merge:pull"/> показан результат получения изменений из <filename class="directory">my-hello</filename> в <filename class="directory">my-new-hello</filename>. История, уже имеющаяся в <filename class="directory">my-new-hello</filename> не затронута, но добавлена новая ревизия. По сравнению с <xref linkend="fig:tour-merge:sep-repos"/>, видно, что <emphasis>ID ревизии</emphasis> остался прежним, а <emphasis>номер ревизии</emphasis> изменён. (Это, кстати, хороший пример, почему использование номеров ревизий при обсуждении наборов изменений не надёжно) Посмотреть головы в хранилище позволяет команда <command role="hg-cmd">hg heads</command>.</para>

      &interaction.tour.merge.heads;
    </sect2>

    <sect2>
      <title>Выполнение слияния</title>

      <para id="x_344">Что произойдёт, если мы попытаемся выполнить обычную команду <command role="hg-cmd">hg update</command> для обновления до новой головной ревизии?</para>

      &interaction.tour.merge.update;

      <para id="x_345">Mercurial говорит нам, что команда <command role="hg-cmd">hg update</command> не выполняет слияния. Она думает, что мы ожидаем слияния, и не обновит рабочий каталог, если мы не принудим её к этому. (В данном случае, принудительное обновление с помощью <command>hg update -C</command> удалит все не сохраненные изменения в рабочем каталоге).</para>

      <para id="x_723">Для объединения двух голов мы воспользуемся командой <command role="hg-cmd">hg merge</command>.</para>

      &interaction.tour.merge.merge;

      <para id="x_347">Произошло слияние содержимого файла <filename>hello.c</filename>. Это привело к обновлению рабочего каталога &emdash; он теперь содержит изменения от <emphasis>обоих</emphasis> голов, что будет отражено в выводе <command role="hg-cmd">hg parents</command> и в содержании файла <filename>hello.c</filename>.</para>

      &interaction.tour.merge.parents;
    </sect2>

    <sect2>
      <title>Фиксация результатов слияния</title>

      <para id="x_348">Каждый раз, когда мы делаем слияние, <command role="hg-cmd">hg parents</command> показывает двух родителей пока мы не закрепим результат командой <command role="hg-cmd">hg commit</command>.</para>

	&interaction.tour.merge.commit;

      <para id="x_349">Теперь у нас есть новая главная ревизия. Заметим, что <emphasis>обе</emphasis> бывшие головы теперь родители. Это те же ревизии, что раньше отображались командой <command role="hg-cmd">hg parents</command>.</para>

      &interaction.tour.merge.tip;

      <para id="x_34a">На <xref linkend="fig:tour-merge:merge"/> вы можете увидеть представление того, что происходит с рабочим каталогом при слиянии, и как это влияет на хранилище, когда происходит коммит. Во время слияния, рабочий каталог состоял из двух родительских ревизий, и они стали родителями новой ревизии.</para>

      <figure id="fig:tour-merge:merge">
	<title>Рабочий каталог и репозиторий во время и после совершения слияния</title>
	<mediaobject>
	  <imageobject>
	    <imagedata fileref="figs/tour-merge-merge.png"/>
	  </imageobject>
	  <textobject><phrase>XXX add text</phrase></textobject>
	</mediaobject>
      </figure>

      <para id="x_69c">Иногда мы говорим о слиянии по <emphasis>сторонам</emphasis>: в левой части первый родитель, указанный в выводе <command role="hg-cmd">hg parents</command>, а в правой части &emdash; второй. Если до слияния рабочий каталог был таким как в ревизии 5, то ревизия будет с левосторонним слиянием.</para>
    </sect2>
  </sect1>

  <sect1>
    <title>Слияние конфликтующих изменений</title>

    <para id="x_34b">Большая часть слияний проста, но иногда при слиянии возможны конфликты, когда участники изменили одинаковые части одного и того же файла. Если изменения не идентичны, то произойдет <emphasis>конфликт</emphasis> и вам придется решать, как согласовать изменения во что-то связное.</para>

    <figure id="fig:tour-merge:conflict">
      <title>Конфликт изменений в документе</title>
      <mediaobject>
	<imageobject><imagedata fileref="figs/tour-merge-conflict.png"/></imageobject>
	<textobject><phrase>XXX add text</phrase></textobject>
      </mediaobject>
    </figure>

    <para id="x_34d"><xref linkend="fig:tour-merge:conflict"/> показывает пример конфликта двух изменений в документе. Мы начали с одной версии файла, затем сделали несколько изменений, в то время, как кто-то другой также изменял этот текст. Наша задача в разрешении конфликта изменений &emdash; решить, как должен выглядеть окончательный вариант файла.</para>

    <para id="x_34e">Mercurial не содержит встроенных средств обработки конфликтов. Вместо этого, он запускает внешнюю программу, обычно одну из графических утилит решения конфликтов. По умолчанию Mercurial пытается найти один из инструментов слияния, которые могут быть установлены в вашей системе. Вначале делается попытка слияния с помощью автоматических инструментов. Если это не удаётся (разрешить конфликт может только человек) или нет подходящего инструмента, сценарий пытается запустить один из графических инструментов.</para>

    <para id="x_34f">Можно указать Mercurial использовать определённую программу, установив переменную окружения <envar>HGMERGE</envar> со значением имени необходимой программы.</para>

    <sect2>
      <title>Использование графического инструмента слияния</title>

      <para id="x_350">Мой любимый графический инструмент слияния это <command>kdiff3</command>, и его я буду использовать для описания возможностей, которые являются общими для графических инструментов слияния. На <xref linkend="fig:tour-merge:kdiff3"/> показан снимок экрана <command>kdiff3</command> в работе. Выполняемое таким образом слияние называется <emphasis>тройственным</emphasis> (three-way), потому что есть три различные версии файла, интересующие нас. В инструменте сравнения верхняя часть окна поделена на три панели:</para>
      <itemizedlist>
	<listitem><para id="x_351">Слева <emphasis>базовая</emphasis> версия файла, т.е. самая последняя версия, после которой произошло разделение на те две версии, которые мы пытаемся объединить.</para>
	</listitem>
	<listitem><para id="x_352">Посередине <quote>наша</quote> версия файла, содержащая наши изменения.</para>
	</listitem>
	<listitem><para id="x_353">Справа <quote>их</quote> версия файла, то есть версия из ревизии, с которой мы производим слияние.</para>
	</listitem></itemizedlist>
      <para id="x_354">На панели снизу располагается текущий <emphasis>результат</emphasis> слияния. Наша задача &emdash; заменить весь красный текст, означающий неразрешенные конфликты, на осмысленный результат слияния <quote>нашей</quote> и <quote>их</quote> версий файла.</para>

      <para id="x_355">Все четыре панели <emphasis>связаны друг с другом</emphasis>. Если мы начнем прокручивать любую из них по вертикали или по горизонтали, остальные панели последуют за нами и будут показывать соответствующие части файлов.</para>

      <figure id="fig:tour-merge:kdiff3">
	<title>Использование <command>kdiff3</command> для слияния версий файлов</title>
	<mediaobject>
	  <imageobject>
	    <imagedata width="100%" fileref="figs/kdiff3.png"/></imageobject>
	  <textobject>
	    <phrase>XXX add text</phrase>
	  </textobject>
	</mediaobject>
      </figure>

      <para id="x_357">Для каждого конфликтующего участка файла можно выбрать для разрешения конфликта любое сочетание текстов из базовой, нашей и их версий. Мы также можем вручную отредактировать результирующий файл в любое время, если требуются дополнительные изменения.</para>

      <para id="x_358">Существует <emphasis>множество</emphasis> инструментов слияния файлов, слишком много, чтобы их здесь описать. Они различаются доступностью для разных платформ и имеют свои слабые и сильные стороны. Большинство предназначены для слияния файлов, содержащих простой текст, но некоторые &emdash; для специализированных форматов (обычно XML).</para>
    </sect2>

    <sect2>
      <title>Рабочий пример</title>

      <para id="x_359">В этом примере мы воспроизведем историю модификации файла с <xref linkend="fig:tour-merge:conflict"/>. Давайте начнем с создания пустого репозитория с базовой версией нашего документа.</para>

      &interaction.tour-merge-conflict.wife;

      <para id="x_35a">Мы клонируем репозиторий и изменим файл.</para>

      &interaction.tour-merge-conflict.cousin;

      <para id="x_35b">Добавим еще одну копию и сымитируем, будто кто-то еще сделал изменение этого файла. Это намёк, что объединять свои же изменения &emdash; обычное дело, особенно когда вы разносите задачи по отдельным хранилищам, и вам нужно находить и разрешать конфликты между ними.</para>

      &interaction.tour-merge-conflict.son;

      <para id="x_35c">Создав две разных версии файла, создадим окружение, в котором можно будет произвести наше объединение.</para>

      &interaction.tour-merge-conflict.pull;

      <para id="x_35d">В данном случае, я установил такое  значение <envar>HGMERGE</envar>, чтобы Mercurial использовал консольную команду <command>merge</command>. Она встроена во многие Unix-подобные системы. Если вы выполняете этот пример на своём компьютере, то можете себя этим не утруждать. Вы просто передадите файл графическому инструменту для слияний, что гораздо предпочтительнее.</para>

      &interaction.tour-merge-conflict.merge;

      <para id="x_35f">Так как <command>merge</command> не может самостоятельно выбрать правильное из противоречащих изменений, она оставляет <emphasis>маркеры слияния</emphasis> в файле с конфликтами, обозначая наши и их строки, содержащие противоречие.</para>

      <para id="x_360">Mercurial может определить, как завершилась <command>merge</command>, и если слияние не удалось, то он говорит, какие команды надо запустить, чтобы выполнить слияние по новой. Это может быть полезно, если мы запустили графическую утилиту объединения и вышли из нее, если что-то оказалось непонятно, или мы сделали ошибку.</para>

      <para id="x_361">Если автоматическое или ручное объединение не удалось, то ничто не мешает нам самим <quote>поправить</quote> пострадавшие файлы и закоммитить результаты слияния:</para>

      &interaction.tour-merge-conflict.commit;

      <note>
	<title>Где команда <command>hg resolve</command>?</title>

	<para id="x_724">Команда <command>hg resolve</command> была добавлена в Mercurial 1.1, выпущенный в декабре 2008 года. Если вы используете старую версию Mercurial (запустите <command>hg version</command>, чтобы узнать номер версии), эта команда вам недоступна. Если вы используете Mercurial версии ниже 1.1, вам следует подумать об обновлении прежде, чем пытаться решать сложные слияния.</para>
      </note>
    </sect2>
  </sect1>

  <sect1 id="sec:tour-merge:fetch">
    <title>Упрощение последовательности pull-merge-commit</title>

    <para id="x_362">Процесс слияния изменений, как говорилось выше, прост, но требует выполнения последовательности из трёх команд.</para>
    <programlisting>hg pull -u
hg merge
hg commit -m 'Merged remote changes'</programlisting>
    <para id="x_363">В случае финального коммита вам также необходимо ввести комментарий, который в большинстве случаев &emdash; кусок неинтересного <quote>стереотипного</quote> текста.</para>

    <para id="x_364">Хорошо было бы, по возможности, сократить количество шагов. И действительно, Mercurial поставляется с расширением <literal role="hg-ext">fetch</literal>, которое делает именно это.</para>

    <para id="x_365">Mercurial имеет гибкий механизм расширений, который позволяет расширять функциональность, оставляя ядро Mercurial небольшим и легким для использования. Некоторые расширения добавляют новые команды, которые вы можете использовать из командной строки, другие работают <quote>за кулисами</quote> &emdash; например, расширение, добавляющее возможности во встроенный в Mercurial сервер.</para>

    <para id="x_366">Расширение <literal role="hg-ext">fetch</literal> добавляет новую команду <command role="hg-cmd">hg fetch</command>. По сути, это комбинация команд <command role="hg-cmd">hg pull -u</command>, <command role="hg-cmd">hg merge</command> и <command role="hg-cmd">hg commit</command>. Выполнение команды начинается с получения изменении из необходимого репозитория в текущий. Если находятся изменения, добавляющие новую голову в репозиторий, то начинается слияние, затем, если слияние прошло успешно, происходит коммит результата с автоматической генерацией комментария. Если новых голов не было, расширение просто обновляет рабочую директорию.</para>

    <para id="x_367">Подключить расширение <literal role="hg-ext">fetch</literal> просто: откройте в текстовом редакторе файл <filename role="special">.hgrc</filename>, и добавьте в секцию <literal role="rc-extensions">extensions</literal> строку <quote><literal>fetch=</literal></quote>, либо сначала создайте такую секцию.</para>

    <programlisting>[extensions]
fetch =</programlisting>

    <para id="x_368">Обычно с правой стороны от <quote><literal>=</literal></quote> указывается местоположение расширения, но так как расширение <literal role="hg-ext">fetch</literal> входит в стандартный пакет установки, Mercurial знает, где его искать.</para>
  </sect1>

  <sect1>
    <title>Переименование, копирование и слияние</title>

    <para id="x_729">Во время жизни проекта мы будем часто изменять структуру своих файлов и каталогов. Это может быть такое простое изменение, как переименование файла, или же сложное, как перестройка всей иерархии файлов в рамках проекта.</para>

    <para id="x_72a">Mercurial свободно поддерживает такого рода сложные изменения, при условии, что мы сообщаем ему о том, что делаем. Если мы хотим переименовать файл, мы должны использовать команду <command>hg rename</command><footnote><para id="x_72b">Если вы пользователь Unix, вы будете рады узнать, что команда <command>hg rename</command> может быть сокращена, как <command>hg mv</command>.</para></footnote>. Команда переименует его, так что Mercurial будет знать что делать позже при слиянии.</para>

    <para id="x_72c">Мы расскажем об использовании этих команд более подробно в <xref linkend="chap:daily.copy"/>.</para>
  </sect1>
</chapter>

<!--
local variables: 
sgml-parent-document: ("00book.xml" "book" "chapter")
end:
-->