Source

hgbook-ru / ru / ch08-branch.xml

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

<chapter id="chap:branch">
  <?dbhtml filename="managing-releases-and-branchy-development.html"?>
  <title>Управление релизами и ветками</title>

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

  <para id="x_36a">Многие програмные проекты выпускают переодические <quote>major</quote> релизы, которые содержат некоторые новые существенные возможности. В тоже время они выпускают множество <quote>minor</quote> релизов. Они, как правило, идентичны основным релизам на которых они основаны, но исправляют ошибки.</para>

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

  <sect1>
    <title>Задание постоянного имени для ревизии</title>

    <para id="x_36c">Как только вы решаете что хотели бы сделать определенную ревизию релизом &emdash; хорошая идея сделать идентифицирующую запись об этом. Это позволит вам позднее получить релиз в любое время (воспроизвести баг, портировать на новую платформу и т.д.).</para>
      
      &interaction.tag.init;

    <para id="x_36d">Mercurial дает вам возможность указать постоянное имя любой ревизии используя команду <command role="hg-cmd">hg tag</command>. Не удивительно что эти имена называют <quote>тегами</quote>.</para>

    &interaction.tag.tag;

    <para id="x_36e">Тег это ничто иное как <quote>символическое имя</quote> для ревизии. Теги существуют просто для вашего удобства, чтобы у вас был простой способ обратиться к ревизии. Mercurial никак не обрабатывает имена тегов, которые вы указываете. Mercurial не устанавливает ограничения на имена тегов, кроме некоторых, чтобы гарантировать что тег может быть проанализирован однозначно. Имя тега не может содержать ни один из следующих символов:</para>
    <itemizedlist>
      <listitem><para id="x_36f">Двоеточие (ASCII 58, <quote><literal>:</literal></quote>)</para>
      </listitem>
      <listitem><para id="x_370">Возврат каретки (ASCII 13, <quote><literal>\r</literal></quote>)</para>
      </listitem>
      <listitem><para id="x_371">Перевод строки (ASCII 10, <quote><literal>\n</literal></quote>)</para>
      </listitem></itemizedlist>

    <para id="x_372">Вы можете использовать  <command role="hg-cmd">hg tags</command> для показа тегов в вашем репозитории. В выводе каждая тегированая ревизия идентифицируется сначала по имени, затем по номеру ревизии, и потом по уникальному хешу ревизии.</para>

    &interaction.tag.tags;

    <para id="x_373">Обратите внимание, что в выводе <command role="hg-cmd">hg tags</command> показан тег <literal>tip</literal> (окончание ветки). Тег <literal>tip</literal> это специальный <quote>плавающий</quote> тег, который всегда указывает на новейшую ревизию в репозитории.</para>

    <para id="x_374">В выводе команды <command role="hg-cmd">hg tags</command>  теги показаны в обратном (относительно номеров ревизий) порядке. Обычно это значит, что более новые теги будут показаны перед более старыми. Также это означает, что <literal>tip</literal> всегда будет показан первым в выводе <command role="hg-cmd">hg tags</command>.</para>

    <para id="x_375">Когда вы запускаете  <command role="hg-cmd">hg log</command>, если она показывает номер ревизии, которая имеет ассоциацию с тегами, то печатаются и эти теги:</para>

    &interaction.tag.log;

    <para id="x_376">Всегда, когда вам необходимо подставить идентификатор ревизии в команду Mercurial, можно подставлять имя соответствующего тега. Внутри себя Mercurial переводит имя тега в идентификатор ревизии.</para>

    &interaction.tag.log.v1.0;

    <para id="x_377">Для отдельной ревизии или всего репозитория не существует лимита на количество тегов, которые вы можете использовать. На практике это означает что <quote>слишком много тегов</quote> (число которое будет сильно изменяться от проекта к проекту) не очень хорошая идея, просто потому что теги, как предполагается, помогают вам находить ревизии. Если же у вас много тегов, простота их использования очень сильно уменьшается.</para>

    <para id="x_378">К примеру, если ваш проект будет создавать вехи каждые пару дней совершенно разумно помечать их тегами. Но если вы используете систему непрерывной интеграции, которая гарантирует чистоту сборки каждой ревизии, тегирование каждой успешной сборки внесет неясности. Взамен вы можете помечать тегом ревизии, чья сборка завершилась неудачей (при условии что они редки!), или просто не использовать теги для отслеживания процесса сборки.</para>

    <para id="x_379">Если вы хотите удалить более не используемый тег используйте команду <command role="hg-cmd">hg tag --remove</command>.</para>

    &interaction.tag.remove;

    <para id="x_37a">Вы также можете изменять тег в любое время, т.о. для идентифицирования разных ревизий выполните новую команду <command role="hg-cmd">hg tag</command>. Вы также можете использовать опцию <option role="hg-opt-tag">-f</option>, чтобы указать что вы <emphasis>действительно</emphasis> хотите изменить тег.</para>

    &interaction.tag.replace;

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

    <para id="x_37c">Mercurial хранит теги как обычный файл с контролем ревизий в вашем репозитории. Если вы создаете любые теги, вы сможете найти их все в файле <filename role="special">.hgtags</filename>. Когда вы запускаете <command role="hg-cmd">hg tag</command>, Mercurial модифицирует этот файл, а затем автоматически комитит изменения в нем. Это означает что на каждый запуск <command role="hg-cmd">hg tag</command> вы можете найти соответствующий набор изменений в выводе команды <command role="hg-cmd">hg log</command>.</para>

    &interaction.tag.tip;

    <sect2>
      <title>Обработка конфликтов слияния тегов</title>

      <para id="x_37d">Вы не должны особо заботиться о файле <filename role="special">.hgtags</filename>, но иногда он дает о себе знать при слиянии изменений. Формат файла очень прост: он состоит из строк. Каждая строка начинается с хеша набора изменений, за который через пробел следует имя тега.</para>

      <para id="x_37e">Если вы исправляете конфликт в файле <filename role="special">.hgtags</filename>, есть одно замечание про его модификацию: когда Mercurial читает теги в репозитории, он <emphasis>никогда</emphasis> не читает рабочую копию файла  <filename role="special">.hgtags</filename>. Вместо этого он читает <emphasis>наиболее старшую</emphasis> ревизию этого файла.</para>

      <para id="x_37f">Неудачное последствие такого дизайна состоит в том, что вы не можете проверить правильность вашего файла <filename role="special">.hgtags</filename> <emphasis>после</emphasis> слияния до тех пор, пока вы не закомитите изменения. Таким образом, если вы решаете конфликт в <filename role="special">.hgtags</filename> во время слияния убедитесь что вы запустили <command role="hg-cmd">hg tags</command> после слияния. Если эта команда найдет ошибку в файле <filename role="special">.hgtags</filename>, она сообщит о месте ее возникновения. Эта информация поможет вам в исправлении ошибки и повторном комите. Вы должны запустить <command role="hg-cmd">hg tags</command> вновь, как только будете уверены в правильности файла.</para>
    </sect2>

    <sect2>
      <title>Теги и клонирование</title>

      <para id="x_380">Возможно вы обратили внимание, что команда <command role="hg-cmd">hg clone</command> имеет опцию <option role="hg-opt-clone">-r</option>, которая позволяет клонировать точную копию репозитория как частичные наборы изменений. Новый клон не будет содержать истории проекта, которая появилась позже указанной вами ревизии. Это относится и к тегам, что может стать неожиданностью для неосторожных.</para>

      <para id="x_381">Вспомните что тег сохранен как ревизия в файле <filename role="special">.hgtags</filename>, так что когда вы создаете тег, набору изменений в котором он записан нужно обратиться к старшему набору изменений. Когда вы запускаете команду <command role="hg-cmd">hg clone -r foo</command> для клонирования репозитория по имени тега <literal>foo</literal>, новый клон <emphasis>не будет содержать истории создания клона</emphasis>. Результатом этого станет то, что вы получите полное подмножество истории проекта в новом репозитории, а <emphasis>не</emphasis> тег, который вы, возможно, ожидали.</para>
    </sect2>

    <sect2>
      <title>Когда тегов становится слишком много</title>

      <para id="x_382">Итак, так как теги в Mercurial подвержены версионности и связаны с историей проекта, любой кто работает с ними может видеть созданые вами теги. Именованая ревизия <literal>4237e45506ee</literal> будет доступна по простому тегу <literal>v2.0.2</literal>. Если же вы пытаетесь отыскать сложноуловимую ошибку, вы возможно захотите чтобы тег напоминал вам нечто вроде <quote>Анна видела признаки ошибки в этой ревизии</quote> (речь тут идет о <quote>пометках на полях для себя</quote>, прим. переводчика).</para>

      <para id="x_383">Для подобных целей вы можете захотеть использовать <emphasis>локальные</emphasis> теги. Вы можете создать локальный тег используя опцию <option role="hg-opt-tag">-l</option> команды <command role="hg-cmd">hg tag</command>. Это позволит записать тег в файл <filename role="special">.hg/localtags</filename>, вместо <filename role="special">.hgtags</filename>. Версия файла <filename role="special">.hg/localtags</filename> не отслеживается. Любые теги созданые с опцией <option role="hg-opt-tag">-l</option> остаются локальными для вашего репозитория.</para>
    </sect2>
  </sect1>

  <sect1>
    <title>Поток изменений &emdash; <quote>большая картинка</quote> против <quote>маленькой</quote></title>

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

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

    <para id="x_386">Обычно люди называют разные конкурирующие направления <quote>ветками</quote>. Однако мы уже видели несколько раз, что Mercurial обрабатывает <emphasis>всю историю</emphasis> как серию <quote>веток</quote> и <quote>слияний</quote>. На самом дела у нас есть 2 идеи, которые плохо связаны, но имеют одинаковые названия.</para>
    <itemizedlist>
      <listitem><para id="x_387"><quote>Большие картинки</quote> веток представляют собой вариацию проекта; люди дают им имена и говорят о них.</para>
      </listitem>
      <listitem><para id="x_388"><quote>Маленькие картинки</quote> веток являются артефактами каждодневной разработки и слияния изменений. Они показывают, как код разрабатывался.</para>
      </listitem></itemizedlist>
  </sect1>

  <sect1>
    <title>Управление ветками <quote>больших картинок</quote> в репозитории (хранилище)</title>

    <para id="x_389">Самый простой путь изолировать <quote>большую картинку</quote> веток в Mercurial это отдельное хранилище. Если у вас уже есть созданное общее хранилище &emdash; скажем, с именем <literal>myproject</literal> &emdash;  которое достигло контрольной точки <quote>1.0</quote>, вы можете начинать подготовку для будующих <quote>основных</quote> релизов сверху версии <quote>1.0</quote> добавляя ревизию, где вы готовы для релиза <quote>1.0</quote>.</para>

    &interaction.branch-repo.tag;

    <para id="x_38a">Затем вы можете клонировать новое хранилище проекта в <literal>myproject-1.0.1</literal>.</para>

    &interaction.branch-repo.clone;

    <para id="x_38b">После этого, если кому-то будет нужно работать для устранения багов к предстоящему <literal>1.0.1</literal> релизу, то можно клонировать хранилище <literal>myproject-1.0.1</literal>, сделать нужные изменения и вернуть их обратно.</para>

    &interaction.branch-repo.bugfix;

    <para id="x_38c">Тем временем разработчики следующего <quote>большого</quote> релиза могут продолжать работать, изолированные в хранилище <literal>myproject</literal>.</para>

    &interaction.branch-repo.new;
  </sect1>

  <sect1>
    <title>Не повторяйте сами себя: слияния между <quote>ветками</quote></title>

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

    <para id="x_38e">В простейшнм случае всё, что вам нужно сделать &emdash; это извлечь изменения с <quote>основной</quote> ветки в вашу локальную копию этой ветки.</para>

    &interaction.branch-repo.pull;

    <para id="x_38f">Затем вам нужно соединить заголовки 2-х веток и вернуться к <quote>основной</quote> ветке.</para>

    &interaction.branch-repo.merge;
  </sect1>

  <sect1>
    <title>Наименование веток в одном репозитории(хранилище)</title>

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

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

    <para id="x_392">Ключ к работе в этом направлении в том, что Mercurial позволяет вам дать постоянное <emphasis>имя</emphasis> ветке. И всегда существует ветка, названная <literal>default</literal> (по-умолчанию). Даже перед тем, как вы переименуете ветку сами, вы можете найти историю ветки <literal>default</literal>, если поищите.</para>

    <para id="x_393">И как пример, когда вы запускаете команду <command role="hg-cmd">hg commit</command>, и вы попадаете в редактор, где вы можете ввести commit, посмотрите на верхнюю линию, которая содержит текст <quote><literal>HG: branch default</literal></quote>. Это говорит вам о том, что ваш commit случится с веткой, названной <literal>default</literal>.</para>

    <para id="x_394">Чтобы начать работу с именованными ветками используйте команду <command role="hg-cmd">hg branches</command>. Эта команда покажет вам список именованных веток, которые есть в хранилище, расскажет, какая ветка есть изменение другой.</para>

    &interaction.branch-named.branches;

    <para id="x_395">Пока вы не создали ни одной именованной ветки, существует только одна, названная <literal>default</literal>.</para>

    <para id="x_396">Найти, какая <quote>текущая</quote> ветка сейчас, запустите команду <command role="hg-cmd">hg branch</command> без аргументов. Вы узнаете, какая ветка является <quote>родительской</quote> для <quote>текущей</quote>.</para>

    &interaction.branch-named.branch;

    <para id="x_397">Чтобы создать новую ветку запустите команду <command role="hg-cmd">hg branch</command> снова. В этот раз с аргументом: именем ветки, которую вы создаёте.</para>

    &interaction.branch-named.create;

    <para id="x_398">После создания новой ветки вы спросите, что сделала команда <command role="hg-cmd">hg branch</command>? Что показывают команды <command role="hg-cmd">hg status</command> и <command role="hg-cmd">hg tip</command>?</para>

    &interaction.branch-named.status;

    <para id="x_399">Ничего не изменилось в рабочей папке и не было создано новой истории. Запуск команды  <command role="hg-cmd">hg branch</command> не производит постоянного эффекта; она только говорит Mercurial, ветку с каким именем использовать для <emphasis>последующих</emphasis> фиксаций изменений (commit).</para>

    <para id="x_39a">Когда вы подтверждаете изменения, Mercurial записывает имя ветки, которую вы изменяете. Один раз переключив ветку <literal>default</literal> на другую и подтвердив, вы увидите имя новой ветки в результатах <command role="hg-cmd">hg log</command>, <command role="hg-cmd">hg tip</command>, и других комманд, которые показывают эту информацию.</para>

    &interaction.branch-named.commit;

    <para id="x_39b">Команды как <command role="hg-cmd">hg log</command> напечатают имя ветки для каждого изменения, которое не в ветке <literal>default</literal>. Как результат, если вы никогда не именовали ветки &emdash; вы никогда не увидите эту информацию.</para>

    <para id="x_39c">Один раз дав имя ветке и подтвердив изменение с этим именем каждое последующее изменение будет иметь то же имя ветки. Вы можете сменить имя ветки в любое время, используя команду <command role="hg-cmd">hg branch</command>.</para>

    &interaction.branch-named.rebranch;

    <para id="x_39d">На практике это то, что вы не должны делать часто, имена веток имеют тенденцию существовать долгое время(это не правило, лишь наблюдение).</para>
  </sect1>

  <sect1>
    <title>Работа с несколькими поименованными ветками в хранилище.</title>

    <para id="x_39e">Если у вас болльше чем 1 ветка с именем в хранилище, Mercurial будет помнить ветку вашей рабочей папки когда вы запустите такую команду, как <command role="hg-cmd">hg update</command> или <command role="hg-cmd">hg pull -u</command>. Это обновит рабочую папку с этой веткой. Обновить ветку с другим именем вы можете использовать опцию <option role="hg-opt-update">-C</option> с командой <command role="hg-cmd">hg update</command>.</para>

    <para id="x_39f">Посмотрим это на практике. Сперва вспомните, какая ветка сейчас <quote>текущая</quote> и какие ветки есть в нашем хранилище.</para>

    &interaction.branch-named.parents;

    <para id="x_3a0">Мы в <literal>bar</literal> ветке, но так же существует старая ветка <command role="hg-cmd">hg foo</command>.</para>

    <para id="x_3a1">Мы можем использовать <command role="hg-cmd">hg update</command> назад и вперед между <literal>foo</literal> и <literal>bar</literal> ветками без нужды использовать опцию <option role="hg-opt-update">-C</option>, потому-что это всего лишь перемещение по линейной истории наших изменений.</para>

    &interaction.branch-named.update-switchy;

    <para id="x_3a2">Если мы вернёмся к ветке <literal>foo</literal> и затем запустим <command role="hg-cmd">hg update</command>, мы останемся в <literal>foo</literal>, не перемещаясь к вершине <literal>bar</literal>.</para>

    &interaction.branch-named.update-nothing;

    <para id="x_3a3">Внесение нового изменения в ветку <literal>foo</literal> создаст новую голову.</para>

    &interaction.branch-named.foo-commit;
  </sect1>

  <sect1>
    <title>Имена веток и слияние</title>

    <para id="x_3a4">Как вам вероятно уже известно, слияния в Mercurial не симметричны. Давайте представим, что у нашего репозитория две головы: 17 и 23. Тогда если я запущу <command role="hg-cmd">hg update</command> для 17, и затем <command role="hg-cmd">hg merge</command> с 23, Mercurial запишет 17 в качестве первого родителя слияния, и 23 в качестве второго. Тогда как если я запущу <command role="hg-cmd">hg update</command> для 23 и затем <command role="hg-cmd">hg merge</command> с 17, это запишет 23 первым родителем, а 17 &emdash; вторым.</para>

    <para id="x_3a5">Это влияет на то, какое имя ветки выберет Mercurial для результата вашего слияния. После слияния, mercurial сохраняет имя ветки первого родителя, когда вы фиксируете результат слияния. Если имя ветки вашего первого родителя <literal>foo</literal>, и вы слили с <literal>bar</literal>, после слияния веткой по-прежнему будет <literal>foo</literal>.</para>

    <para id="x_3a6">Это необычно, если хранилище хранит несколько голов, с одним именем ветки. Скажем, я работаю с веткой <literal>foo</literal>, как и вы. Мы сохраняем разные изменения; я забираю ваши изменения; у меня сейчас головы, обе претендующие быть в ветке <literal>foo</literal>. Результатом слияния будет одна голова в ветке <literal>foo</literal>, как вы могли надеяться.</para>

    <para id="x_3a7">Но если я работаю с веткой <literal>bar</literal>, то результатом слияния bar и <literal>foo</literal> будет ветка <literal>bar</literal>.</para>

    &interaction.branch-named.merge;

    <para id="x_3a8">Более конкретный пример: если я работаю с веткой <literal>bleeding-edge</literal> и хочу внести в неё последние фиксы из ветки <literal>stable</literal>, то Mercurial выберет имя <quote>правильной</quote> ветки (<literal>bleeding-edge</literal>), когда вытащу и объединю изменения из ветки <literal>stable</literal>.</para>
  </sect1>

  <sect1>
    <title>Именнованные ветки &emdash; это очень удобно.</title>

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

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

    <para id="x_3ab">Если вы работаете с общедоступным хранилищем, то вы можете задать <literal role="hook">pretxnchangegroup</literal> перехватчик, который будет блокировать все приходящие изменения с <quote>неправильным</quote> именем ветки. Это простой, но эффективный способ против случайного слияния изменений с <quote>нестабильной</quote> ветки в <quote>стабильную</quote>. Так перехватчик может находится внутри общедоступного <filename role="special"> /.hgrc</filename> репозитория.</para>
    <programlisting>[hooks]
pretxnchangegroup.branch = hg heads --template '{branches} ' | grep mybranch</programlisting>
  </sect1>
</chapter>

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