Source

hgbook-ru / ru / ch13-mq-collab.xml

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

<chapter id="chap:mq-collab">
  <?dbhtml filename="advanced-uses-of-mercurial-queues.html"?>
  <title>Расширенное использование Mercurial Queues</title>

  <para id="x_15d">Хотя это легко просто использовать Mercurial Queues, используя минимум возможностей и некоторые менее часто используемые возможности MQ для работы в сложных условиях разработки.</para>

  <para id="x_15e">В этой главе я буду использовать в качестве примера технику которую я использовал для управления процессом создания драйверов устройства infiniband для ядра linux. Драйвер в большом вопросе (по крайней мере как драйвер запускается), с 25000 строк кода, распространяемых через 35 исходных файлов. Он поддерживается небольшой группой разработчиков.</para>

  <para id="x_15f">Хотя значительная часть материала в этой главе, специфична для linux, те же принципы применяются к любой кодовой базе, для которых вы не основной владелец, и в котором вам нужно сделать много.</para>

  <sect1>
    <title>Проблема множества целей</title>

    <para id="x_160">Ядро linux меняется очень быстро, и никогда не было внутренне стабильным; разработчики часто делают резкие изменения между версиями. Это означает, что версия драйвера, который хорошо работает с определенной версией ядра не будет даже правильно <emphasis>собираться</emphasis>, как правило, на какой-нибудь другой версией.</para>

    <para id="x_161">Чтобы разрабатывать драйвер, мы должны иметь различные версий ядра linux в виду.</para>
    <itemizedlist>
      <listitem><para id="x_162">Первой целью является основная ветка разработки ядра linux. Разработка кода в этом случае частично совместима с другими разработчиками в сообществе ядра, которые делают <quote>drive-by</quote> модификации для драйверов по мере их развития и совершенствования подсистем ядра.</para>
      </listitem>
      <listitem><para id="x_163">Мы также поддерживали некоторое количество <quote>backports</quote> для старых версий ядра linux, в целях удовлетворения потребностей клиентов, которые работали под управлением более старых дистрибутивов linux, которые не имеют наших драйверов. (Чтобы <emphasis>портировать</emphasis> кусок кода, чтобы модифицировать его работу для старой версии целевого окружения, чем версия для которой был разработана.)</para>
      </listitem>
      <listitem><para id="x_164">Наконец, мы делаем выпусков программ по графику, не обязательно совпадающего с тем, который используется дистрибьюторами linux и разработчиками ядра, так что мы можем добавлять новые возможности для клиентов, не заставляя их обновлять всё их ядро и дистрибутив.</para>
      </listitem></itemizedlist>

    <sect2>
      <title>Соблазнительные подходы, которые работают не очень хорошо</title>

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

      <para id="x_166">Во-первых, поддерживать ряд ветвей, каждая предназначена для единственной цели. Проблема такого подхода заключается в том, что необходимо поддерживать железную дисциплину в потоке изменений между хранилищами. Новые функции или исправления ошибок должны начинать жизнь в <quote>первозданном</quote> репозитории, а затем проходить в каждый бекпортированый репозиторий. backport изменения, более ограничены в ветках они должны передаваться по наследству; портирование применённые изменения в ветку, к которой оно не принадлежит, вероятно, вызовет  остановку компиляции драйвера.</para>

      <para id="x_167">Вторая заключается в сохранении единого дерева исходников наполненных условными выражениями, которые в свою очередь включают или выключают блоки кода в зависимости от поставленной цели. Поскольку эти <quote>ifdefs</quote> не допускаются в дереве ядра linux, ручной или автоматический процесс должен последовательно вырезать их и давать на выходе чистое дерево. Код основной разработки таким образом быстро становится крысиным гнездом условных блоков, которые трудно понимать и поддерживать.</para>

      <para id="x_168">Ни один из этих подходов не подходит хорошо для ситуации, когда вы используете не <quote>свою</quote> каноническую копию исходного дерева. В случае драйверов linux, которые распространяются со стандартным ядром, дерево Линуса содержит копию кода, который будет рассматриваться в мире как канонический. Апстрим версия <quote>моих</quote> драйверов может быть изменена людьми, которых я не знаю, без меня, я даже узнаю об этом только после отображения изменений в дереве Линуса.</para>

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

      <para id="x_16a">В принципе, Mercurial Queues кажется хорошим кандидатом для управления сценарием развития, таким как выше. Хотя это действительно так, MQ  содержит несколько дополнительных функций, которые делают работу более приятной.</para>

    </sect2>
  </sect1>
  <sect1>
    <title>Условное применение патчей с защитой</title>

    <para id="x_16b">Возможно, лучший способом поддерживать здраво так много задач является возможность выбирать конкретные патчи применяемые для той или иной ситуации. MQ обеспечивает функция называющуюся <quote>стражи</quote> (guards) (которая берет свое начало с командой quilt's <literal>guards</literal>), которая делает тоже самое. Для начала, давайте создадим простой репозиторий для экспериментов</para>

    &interaction.mq.guards.init;

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

    <para id="x_16d">Идея применения условия, которое вы можете указать в <quote>тэг</quote> патча со <emphasis>стражем</emphasis>, который является простой текстовой строкой по вашему выбору, которая скажет MQ, выбрать конкретного стража используемого при применении исправлений. MQ потом либо применит, либо пропустить, охраняемый патч, в зависимости от охранника, которого вы выбрали.</para>

    <para id="x_16e">Патч может иметь произвольное число охранников, и каждый из них <emphasis>положительный</emphasis> (<quote>применить этот патч, если выбран этот охранник</quote>) или <emphasis>отрицательным</emphasis> (<quote>пропустить этот патч, если выбран этот охранник</quote>). Патч, без охранника всегда применяется.</para>

  </sect1>
  <sect1>
    <title>Управление защитой патча</title>

    <para id="x_16f">Команда <command role="hg-ext-mq">qguard</command> позволяет определить, какая охрана должна распространяться на патч, или отобразит охранников, которые уже действуют. Без аргументов, он отображает охранников на текущий верхний патч.</para>

      &interaction.mq.guards.qguard;

    <para id="x_170">Чтобы установить положительного охранника на патч, используйте префикс <quote><literal>+</literal></quote> перед именем стража.</para>

      &interaction.mq.guards.qguard.pos;

    <para id="x_171">Чтобы установить положительного охранника на патч, используйте префикс <quote><literal>-</literal></quote> перед именем стража.</para>

    &interaction.mq.guards.qguard.neg;

    <para id="x_74a">Notice that we prefixed the arguments to the <command>hg qguard</command> command with a <literal>--</literal> here, so that Mercurial would not interpret the text <literal>-quux</literal> as an option.</para>

    <note>
      <title>Setting vs. modifying</title>

      <para id="x_172">Команда <command role="hg-ext-mq">qguard</command> <emphasis>устанавливает</emphasis> охранников на патч, но не <emphasis>изменять</emphasis> их. Это означает, что при запуске <command role="hg-cmd">hg qguard +a +b</command> на патч, а затем <command role="hg-cmd">hg qguard +c</command> на тот же патч, <emphasis>единственным</emphasis> охранником, который будет установлен на него будет <literal>+c</literal>.</para>
    </note>

    <para id="x_173">Mercurial сохраняет охранников магазинов в файле <filename role="special">series</filename>; том виде, в котором они хранятся их легко понять и править руками. (Другими словами, вы не должны использовать <command role="hg-ext-mq">qguard</command> команду, если вы не хотите, достаточно просто редактировать файл <filename role="special">series</filename>).</para>

    &interaction.mq.guards.series;

  </sect1>
  <sect1>
    <title>Выбор используемых охранников</title>

    <para id="x_174">Команда <command role="hg-ext-mq">qselect</command> определяет, какие охранники активны в данный момент времени. Результатом этого является определение, какие патчи MQ будет применяться при следующем запуске <command role="hg-ext-mq">qpush</command>. Команда не имеет другого эффекта, в частности, она не делает ничего, с уже применёнными патчами.</para>

    <para id="x_175">Без аргументов <command role="hg-ext-mq">qselect</command> перечисляет охранников применённых в настоящее время, по одному на строку. Каждый аргумент рассматривается как имя применённого охранника.</para>

      &interaction.mq.guards.qselect.foo;

    <para id="x_176">Если вам интересно применённые охранники хранятся в файле <filename role="special">guards</filename>.</para>

    &interaction.mq.guards.qselect.cat;

    <para id="x_177">Эффект от выбранных охранников мы увидим когда запустим <command role="hg-ext-mq">qpush</command>.</para>

    &interaction.mq.guards.qselect.qpush;

    <para id="x_178">Охранник не может начинаться с символа <quote><literal>+</literal></quote> или <quote><literal>-</literal></quote>. Имя охранника не должно содержать пробелов, но большинство других символов разрешены. Если вы пытаетесь использовать неверное имя сторожа, MQ будет ругаться:</para>

    &interaction.mq.guards.qselect.error;
      
    <para id="x_179">Изменение выбранных охранников изменяет применяемые патчи.</para>

    &interaction.mq.guards.qselect.quux;

    <para id="x_17a">Вы можете увидеть в приведенном ниже примере, что негативные охранники имеют приоритет над положительными охранниками.</para>

    &interaction.mq.guards.qselect.foobar;

  </sect1>
  <sect1>
    <title>Правила применения патчей в MQ</title>

    <para id="x_17b">Правила, которые использует MQ при решении вопроса о применении патча состоят в следующем.</para>
    <itemizedlist>
      <listitem><para id="x_17c">Патч, который не имеет охраны применяется всегда.</para>
      </listitem>
      <listitem><para id="x_17d">Если патч имеет негативного охранника, который соответствует любому выбранному охраннику, патч будет пропущен.</para>
      </listitem>
      <listitem><para id="x_17e">Если патч имеет положительного охранника, который соответствует любому выбранному охраннику, патч будет применён.</para>
      </listitem>
      <listitem><para id="x_17f">Если патч имеет положительного или отрицательного охранников, но никто из них не  выбран, патч будет пропущен.</para>
      </listitem></itemizedlist>

  </sect1>
  <sect1>
    <title>Обрезка рабочего окружения</title>

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

    <para id="x_181">Я тогда выбрал <quote>основную</quote> версию поверх которой применял патчи. Это снимок дерева ядра linux содержал выбранные мной ревизии. Когда я беру снимок, я записываю id ревизии из репозитория ядра в сообщении фиксации. Поскольку снимок сохраняет <quote>форму</quote> и содержание соответствующих разделов дерева ядра, и я могу применить мои патчи поверх и моего маленького репозитория, и нормальный дерева ядра.</para>

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

  </sect1>
  <sect1>
    <title>Разделение файла <filename role="special">series</filename></title>

    <para id="x_183">Я классифицировал патчи в файле <filename role="special">series</filename> на несколько логических групп. Каждый раздел патчей начинается с блока комментариев, которые описывают цель исправлении, которые за ним следуют.</para>

    <para id="x_184">Последовательность групп патчей я поддерживаю следующую. Порядок этих групп является важным, и я расскажу, почему после того, как опишу группы.</para>
    <itemizedlist>
      <listitem><para id="x_185">Группа <quote>принято</quote>. Патчи, которые команда разработчиков представила управляющему подсистемы infiniband, и которые он принял, но которые ещё не применены к снимку на основе малого репозитория. Эти патчи доступны <quote>только для чтение</quote>, присутствуют только в трансформированном дереве в таком же состоянии, как они представлены сопровождающему верхнего репозитория.</para>
      </listitem>
      <listitem><para id="x_186">Группа <quote>доработки</quote>. Патчи, которые я принял, но вышележащий апстрим сопровождающий попросил изменить, до того как он их примет.</para>
      </listitem>
      <listitem><para id="x_187">Группа <quote>ожидают решения</quote>. Патчи, которые я еще не представили вышестоящему сопровождающему, но они готовы к работе. Они будут на некоторое время доступны <quote>только для чтения</quote>. Если верхний сопровождающий их примет, при представлении, я перемещу их окончательно в группы <quote>принято</quote>. Если он попросит меня изменить какой-либо из них, я перемещу их сначала в группу <quote>доработки</quote>.</para>
      </listitem>
      <listitem><para id="x_188">Группа <quote>в процессе разработки</quote>. Патчи, которые активно развиваются, и не должны быть представлены еще нигде.</para>
      </listitem>
      <listitem><para id="x_189">Группа <quote>backport</quote>. Патчи, которые адаптированные для дерева исходных текстов с более старыми версиями ядра.</para>
      </listitem>
      <listitem><para id="x_18a">Группа <quote>Не распростронять</quote>. Патчи, которые почему-то никогда не должны быть представлены в апстриме. Например, один такой патч может изменить встроенный идентификатор  драйвера, чтобы было легче различать, области, между версией драйвера вне дерева и версией распространяемой поставщиками.</para>
      </listitem></itemizedlist>

    <para id="x_18b">Теперь вернемся к причинам сортировки групп патчей таким образом. Мы хотели бы, самые низкие патчи в стеке были бы максимально устойчивы, так чтоб не нужно было перерабатывать патчи выше в связи с изменениями в соответствующем контексте. Введение патчей, которые никогда не будут изменятся первыми в файле <filename role="special">series</filename> служит именно этой цели.</para>

    <para id="x_18c">Мы хотели бы также чтоб патчи, которые мы знаем, что нужно изменить должны применяться в верхней части дерева исходных текстов, которая напоминает апстрим дерево, насколько это возможно. Именно поэтому мы постоянно принимаем патчи близко к верху.</para>

    <para id="x_18d">В группах <quote>backport</quote> и <quote>Не распростронять</quote> патчи находятся в конце файла <filename role="special">series</filename>. backport патчи должны применяться поверх всех других исправлений, также патчи из группы <quote>Не распростронять</quote> лучше держать от греха подальше.</para>

  </sect1>
  <sect1>
    <title>Поддержка серии патчей</title>

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

    <itemizedlist>
      <listitem><para id="x_18f"><literal>accepted</literal> защищает <quote>принятые</quote> патчи. Я включаю эту охрану большую часть времени. Когда я применяю патчи поверх дерева, где эти патчи уже есть, я отключаю эти патчи, и следующие за ними применяются чисто.</para>
      </listitem>
      <listitem><para id="x_190">Патчи, которые <quote>завершены</quote>, но пока не отправлены, не имеют охраны. Если я применяю стек патчей к копию апстрим дерева, мне не нужно включать какого либо из охранников для получения достаточно безопасного исходного дерева.</para>
      </listitem>
      <listitem><para id="x_191">Те, патчи, которые требуют доработки, прежде чем вновь отправится охраняются с помощью <literal>rework</literal>.</para>
      </listitem>
      <listitem><para id="x_192">Для тех патчей, которые находятся в стадии разработки, я использую <literal>devel</literal>.</para>
      </listitem>
      <listitem><para id="x_193">backport патчи имеют различных охранников, по одному для каждой версии ядра, к которому они относятся. Например, backports патча у кода ядра 2.6.9 будет охранник <literal>2.6.9</literal>.</para>
      </listitem></itemizedlist>
    <para id="x_194">Такое разнообразие охранников дает мне значительную гибкость в определении вида дерева исходников, к которому я хочу в итоге применить патчи. В большинстве случаев, выбор соответствующих охранников автоматизирован в процессе сборки, но я могу вручную настроить охранников для использования в менее общих обстоятельствах.</para>

    <sect2>
      <title>Искусство писать backport патчи</title>

      <para id="x_195">Использование MQ, для написания backport патчей простой процесс. Все что нужно сделать в патче, это изменить фрагмент кода, который использует функции ядра которой нет в старой версии ядра, так что драйвер продолжает работать правильно в соответствии с этой старой версией.</para>

      <para id="x_196">Полезная цель при написании хорошего backport патча это сделать код выглядящим так, будто он была написан для старой версии ядра, на которую вы ориентируетесь. Чем менее навязчив патч, тем легче будет его понять и поддерживать. Если вы пишете коллекцию backport патчей, чтобы избежать эффекта <quote>крысиного гнезда</quote> — большого количества <literal>#ifdef</literal>s (блоков исходного кода, которые будут использоваться только условно) в коде, не вводите зависимые от версии <literal>#ifdef</literal>s в патчи. Вместо этого, напишите несколько патчей, каждый из которых дает безусловные изменения, а также контролируйте их примеением с помощью охранников.</para>

      <para id="x_197">Есть две причины выделить backport патчи в отдельную группу, подальше от <quote>обычных</quote> патчей, последствия которых они модифицируют. Во-первых, их смешение делает более сложным в использовании инструменты такие, как расширение <literal role="hg-ext">patchbomb</literal> для автоматизации процесса передачи патчей сопровождающим апстрима. Во-вторых, портированое исправление может нарушить контекст, в котором применяются обычные патчи, что делает невозможным применение очередного патча чисто, если ранее применился backport патч.</para>

    </sect2>
  </sect1>
  <sect1>
    <title>Полезные советы для разработки с MQ</title>

    <sect2>
      <title>Организация патчей в каталогах</title>

      <para id="x_198">Если вы работаете на крупный проект с MQ, не трудно накопить большое количество патчей. Например, у меня есть репозиторий патчей, который содержит более 250 патчей.</para>

      <para id="x_199">Если вы можете сгруппировать эти патчи на отдельные логические категории, вы можете, если хотите хранить их в разных каталогах; MQ не имеет проблем с именами патчей, которые содержат разделители пути.</para>

    </sect2>
    <sect2 id="mq-collab:tips:interdiff">
      <title>Просмотр истории патча</title>

      <para id="x_19a">Если вы разрабатываете набор патчей в течение длительного времени, хорошая идея сохранить их в репозитории, о чем говорится в разделе <xref linkend="sec:mq:repo"/>. Если вы это сделаете, вы быстро обнаружите, что использование команды <command role="hg-cmd">hg diff</command>, чтобы взглянуть на историю изменений патча обреченно на провал. Это отчасти потому, что вы смотрите на вторую производную от реального кода (список различий различий), но также и потому, MQ добавляет шуму в этот процесс, меняя временные метки и имена каталогов при обновлении патча.</para>

      <para id="x_19b">Тем не менее, вы можете использовать расширение <literal role="hg-ext">extdiff</literal>, которое поставляется вместе с Mercurial, для просмотра различий двух версий патча в читабельном виде. Для этого вам понадобится сторонний пакет называющийся <literal role="package">patchutils</literal> <citation>web:patchutils</citation>. Он дает команду <command>interdiff</command>, которая показывает разницу между двумя diff-ами как diff. Используется на двух версиях одного и того же патча, она выдаёт diff, который представляет собой различия с первой по вторую версии.</para>

      <para id="x_19c">Вы можете включить расширение <literal role="hg-ext">extdiff</literal> обычным способом, путем добавления строки в раздел <literal role="rc-extensions">extensions</literal> вашего <filename role="special">~/.hgrc</filename>.</para>
      <programlisting>[extensions]
extdiff =</programlisting>
      <para id="x_19d">Команда <command>interdiff</command> ожидает два аргумента — два названия файлов, но расширение <literal role="hg-ext">extdiff</literal> передаёт в запускаемую программу пару каталогов, каждый из которых может содержать произвольное количество файлов. Таким образом, потребуется небольшая программа, которая будет запускать <command>interdiff</command> к каждой паре файлов в этих двух каталогах. Эта программа существует и называется <filename role="special">hg-interdiff</filename> в каталоге <filename class="directory">examples</filename> репозитория исходного кода, прилагаемый к этой книге.</para>

      <para id="x_19e">Запустить программу <filename role="special">hg-interdiff</filename> в в вашей консоли, вы можете запустить его следующим образом, внутри каталога патчей MQ:</para>
      <programlisting>hg extdiff -p hg-interdiff -r A:B my-change.patch</programlisting>
      <para id="x_19f">Так как вы, наверное, хотите, чтобы использовать эту многословную команду часто, вы можете получить <literal role="hg-ext">hgext</literal> чтобы сделать её доступной как обычную Mercurial команду, опять же, редактированием <filename role="special">~/.hgrc</filename>.</para>
      <programlisting>[extdiff]
cmd.interdiff = hg-interdiff</programlisting>
      <para id="x_1a0">Эта строка приказывает <literal role="hg-ext">hgext</literal> сделать команду <literal>interdiff</literal> доступной, поэтому теперь вы можете сократить предыдущую команду <command role="hg-ext-extdiff">extdiff</command> до чего-то чуть более простого.</para>
      <programlisting>hg interdiff -r A:B my-change.patch</programlisting>

      <note>
	<para id="x_1a1">Команда <command>interdiff</command> хорошо работает только если лежащее в основе патчей остаются теми же. Если при создании патча, изменить основные файлы, а затем сгенерировать патч, <command>interdiff</command> не сможет произвести полезный вывод.</para>
      </note>

      <para id="x_1a2">Расширение <literal role="hg-ext">extdiff</literal> полезно для более просто улучшение отображения патча MQ. Чтобы узнать больше о ней, перейдите в раздел <xref linkend="sec:hgext:extdiff"/>.</para>

    </sect2>
  </sect1>
</chapter>

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