Системы контроля версий: Fossil, часть II

  • Tutorial
Продолжаем разговор о Fossil.

В первой части мы познакомились с использованием Fossil в однопользовательском режиме на одном рабочем месте. Следующий шаг — перенос репозитория на другой компьютер — с работы на домашний, или на ноутбук, который мы берем с собой в поездку. Самый простой вариант — это просто скопировать репозиторий, благо это всего один файл, на новое рабочее место. Можно так и сделать, но самое простое решение не всегда самое лучшее, есть вероятность, что возникнут небольшие проблемы.

Однопользовательский режим работы на нескольких рабочих местах.
Дело в том, что в репозитории хранится не только история версий, но и кое-какая служебная информация, в частности, имя администратора и пароль. Имя администратора генерируется автоматически при создании репозитория, обычно это имя пользователя, под которым вы вошли в ОС — и вполне может статься, что это имя на компьютере, куда вы копируете репозиторий, отличается от того, с которым репозиторий был создан. В этом случае commit не пройдет, и вам придется добавить требуемого пользователя с помощью fossil ui, или каждый раз добавлять в fossil commit параметр --user. Более «чистый» способ — это клонирование существующего репозитория командой fossil clone. Как и в первой части статьи, мы будем исходить из предположения, что у нас стоит Windows, что все наши репозитории хранятся в отдельном каталоге c:\fossil, что мы работаем над проектом Castle, исходники которого и на другом компьютере хотим разместить в c:\projects\castle\source\. Кроме того, предположим, что переносим репозиторий на флэшке, смонтированной как диск F. Итак, клонируем:

fossil clone f:\castle.fossil c:\fossil\castle.fossil

И открываем для дальнейшего использования:

c:
cd \projects\castle
fossil open c:\fossil\castle.fossil
При этом файлы, включенные в репозиторий, распакуются в текущий каталог.

Прежде чем идти дальше, нам надо разобраться с параметром autosync. Если он включен ( "on", или 1 ), то при выполнении команды fossil update будет предпринята попытка получить сначала обновление из удаленного репозитория (pull), а при выполнении fossil commit — сначала pull и update, а сразу после commitpush ( передача изменений в удаленный репозиторий ), причем адресом удаленного репозитория будет указанный в последней выполненной команде clone, push, pull, sync, remote-url. В нашем случае это будет f:\castle.fossil, т.е., при каждом commit Fossil будет сначала пытаться связаться с файлом на флэшке, и, если она не вставлена, то commit не будет выполнен — вряд ли это то, что мы хотим. Включенный autosync хорош в случае, если наш удаленный репозиторий расположен на сервере в локальной сети, т.е. практически всегда доступен. В случае же, когда удаленным репозиторием фактически является файл на флэшке, с помощью которой мы переносим наработанные изменения с одного места на другое, это может быть не очень удобно. Поэтому, прежде чем начать работу с репозиторием, отключим autosync:

fossil settings autosync off

Теперь можно продолжить работу над проектом на новом рабочем месте, по мере необходимости сохраняя изменения в локальном репозитории с помощью commit. Для передачи изменений на другие рабочие места используем push ( передача изменений из локального в удаленный репозиторий ), pull ( получение изменений из удаленного в локальный репозиторий ). Можно использовать sync — эта команда делает pull и push. Таким образом, наш рабочий процесс (workflow) примерно таков:

Переписали обновления с флэшки:
fossil pull f:\castle.fossil
fossil update
Команда update модифицирует рабочие файлы в соответствии с содержимым локального репозитория. При этом изменения, которые мы сделали в рабочих файлах после последнего commit ( вообще-то, при однопользовательском режиме так делать не надо! ), не пропадут — Fossil постарается осуществить слияние (merge).
Поработали над проектом, сохранили изменения (commit) в локальный репозиторий и отправили их на удаленный:
fossil push f:\castle.fossil
Приходим на другое рабочее место — и опять: pull, update, работа ..., commit, push.

Можно, в принципе, вместо push просто физически копировать файл репозитория на флэшку — в случае вот такого однопользовательского режима работы это допустимо, но использование push — более «чистый» вариант. Чего безусловно не следует делать — так это физически копировать репозиторий на место локального, когда этот локальный открыт — в этом случае может произойти рассогласование локального репозитория со служебным, который был создан в результате его открытия, что может привести к потери информации об изменениях и, соответственно, к неприятным накладкам.

Удобнее, конечно, не носить репозиторий на флэшке или другом переносном физическом носителе, а держать его на сервере. Можно использовать сторонний сервис типа dropbox, а можно, при наличии такой возможности, разместить репозиторий на сервере, доступном вам для администрирования. Именно этот вариант мы и рассмотрим дальше, тем более, что именно он нам понадобится для организации мнопользовательской работы ( можно еще воспользоваться услугами сайтов, предоставляющих возможности хостинга для Fossil — репозиториев, например, http://chiselapp.com/ или Sourceforge, но я не буду останавливаться здесь на этом ).

Установка репозитория на сервере.
Собственно, установка производится так же, как мы это делали с флэшки — переписываем наш локальный репозиторий на флэшку или в доступное место на сервере и делаем fossil clone. Вопрос в том, как обеспечить доступ к этому вновь созданному удаленному репозиторию, какой URL использовать в командах Fossil.
Возможны три варианта подключения к удаленному репозиторию:

  • Самый простой, когда репозиторий находится в расшаренном каталоге,
  • Через ssh-соединение,
  • Используя http — сервер.

Первый вариант, действительно, самый простой, для его реализации вообще ничего не надо делать, кроме как клонировать репозиторий в нужный каталог. Обращение к нему ничем не отличается от варианта с флэшкой, просто используйте путь к тому каталогу, как он смонтирован на вашем компьютере. Но этот способ ограничивает вас пределами вашей локальной сети. Есть и другой недостаток: поскольку в этом случае мы работаем не в режиме клиент-сервер, а как с данными на файловом сервере, при сбое ( отключение питания вашего компьютера, например, или что-то со связью ) в процессе записи в файл удаленного репозитория могут быть проблемы.

Для использования ssh-соединения у вас должен быть, соответственно, ssh-сервер и на этот сервер должен быть скопирован исполняемый файл fossil. В этом случае URL репозитория в команде Fossil будет выглядеть так:

ssh://[userid[:password]@]host[:port]/path/repo.fossil 
Если используется абсолютный путь, то перед path надо поставить еще один слэш. Если исполняемый файл fossil находится в каталоге, неизвестном системе ( т.е., недоступном по SET PATH ), то к URL следует добавить ?fossil=path_to_fossil/fossil.
Предположим, что мы разместили наш репозиторий castle.fossil на Linux-сервере в /usr/local/fossil, а login на этом сервере — alex. Тогда команда push, например, может выглядеть так (наш сервер — 192.168.0.2):
fossil push ssh://alex@192.168.0.2//usr/local/fossil/castle.fossil 
Еще раз отмечу, что в качестве логина в данном случае используется именно логин к серверу, а не имя пользователя репозитория.

Теперь о третьем варианте — использование http — сервера. Самое простое — выполнить на сервере команду fossil server — в результате там будет запущен http — сервер, такой же, как и по уже знакомой нам команде fossil ui, отличие только в том, что теперь доступ к нему возможен не только с локального, но и с любого другого компьютера в сети и что теперь при web — доступе нам придется залогиниться для получения полного доступа к репозиторию. URL в этом случае будет выглядеть так:
http://[userid[:password]@]host[:port]/path 

Напомню, что порт по умолчанию — 8080, его можно изменить опцией --port команды fossil server.
Если на сервере уже поднят http — сервер, поддерживающий CGI, можно использовать исполняемый файл Fossil как CGI — программу ( именно так, согласно документации, работает официальный сайт Fossil ). Для этого надо в CGI — каталог на сервере ( обычно это cgi-bin ) поместить вот такой простенький скрипт:

#!/usr/bin/fossil
repository: /usr/local/fossil/castle.fossil

Мы здесь исходим из предположения, что исполняемый файл fossil помещен в /usr/bin. Не забудьте, конечно, установить нужные права на файлы репозитория, скрипта и соответствующих каталогов. Если мы назвали файл скрипта castle, то URL может выглядеть так:

http://[userid[:password]@]host/cgi-bin/castle
здесь userid — имя пользователя репозитория. И команда push приобретет такой вид:

 fossil push http://alex@192.168.0.2/cgi-bin/castle

Если http — сервер не поддерживает CGI, можно настроить вызов Fossil через SCGI. Можно поднять http — сервис, используя inetd, xinetd или stunnel. Подробнее об этом можно почитать в документации Fossil, здесь я описываю только то, что пробовал своими руками.
Вариант с использованием http, помимо удобства использования с любого компьютера, связанного с сервером по локальной сети или через Интернет, хорош еще и тем, что обеспечивает web — сервис, мы получаем, по сути, web — сервер проекта. Безусловно, первое, что надо будет сделать, это настроить права доступа — в частности, для anonymous — всех тех, кто не зарегистрирован как пользователь в репозитории.

Если мы продолжаем пользоваться репозиторием монопольно, то workflow остается тем же, что и при использовании флэшки в качестве носителя для удаленного репозитория ( меняется только URL ): pull, update, работа ..., commit, push. Хотя, если мы используем в качестве удаленного репозитория постоянно подключенный по одному и тому же адресу ресурс, можно пересмотреть отношение к параметру autosync и установить его в "on". В этом случае рабочий цикл сокращается до update, работа ..., commitpull и push будут выполнены автоматически. Не забудьте только проверить, какой адрес удаленного репозитория будет использоваться ( обычно таковым является последний использованный в командах clone, pull, push или sync ). Это можно сделать командой fossil remote-url. Этой же командой можно установить нужный URL, передав его в качестве параметра: fossil remote-url URL.

И еще одно важное замечание, касающееся как однопользовательского, так и многопользовательского режимов. Следите за правильностью установки времени на компьютерах! При попытке сделать commit, когда текущая версия в локальном репозитории имеет более позднюю дату/время, чем установленные сейчас на компьютере, операция не совершится и будет выдано соответствующее сообщение. Если у вас на компьютере время правильное и вам надо все-таки сделать этот commit, выполните его с параметром --allow-older. Но лучше, конечно, не доводить до такой ситуации.

Многопользовательский режим работы.
Удаленный репозиторий на сервере у нас уже установлен. Создаем клонированием с удаленного локальный репозиторий ( какой использовать URL мы разбирали в предыдущем разделе ), добавляем этого пользователя на удаленном, прописываем его права и — вперед. Рабочий цикл примерно такой же, как и в однопользовательском режиме, только теперь pull и update необходимо делать и перед каждым commit ( это если autosync отключен, если включен — это делается автоматически ), поскольку за время, прошедшее с предыдущего pull удаленный репозиторий мог быть изменен другим пользователем.

Здесь может возникнуть новая, ранее не встречавшаяся нам ситуация. Вы обновили свои рабочие файлы ( pull, update ) и начали их редактировать. Тем временем ваш коллега тоже что-то отредактировал и отправил на сервер ( commit, push ). Вы закончили свою работу и хотите отправить изменения на сервер. Здесь возможны такие варианты:

1) Autosync включен или он выключен, но вы делаете pull, update перед commit, как и положено. Update попытается выполнить слияние (merge) и если оно пройдет нормально, то… все нормально. Если же слияние произвести не удастся ( к примеру, изменения были в одной и той же строке одного и того же файла ), то update выдаст соответствующее сообщение, у вас появятся по 3 новых файла на каждый «неслившийся» с расширениями, оканчивающимися на -baseline, -merge и -original, а в «неслившихся» файлах появятся в каждом спорном месте новые строчки, начиная с "<<<<<<< BEGIN MERGE CONFLICT:..." и заканчивая ">>>>>>> END MERGE CONFLICT...", там будет и предшествующая версия ( COMMON ANCESTOR content ), и ваша версия ( local copy content ), и версия вашего коллеги ( MERGED IN content ). Вам придется сделать необходимые изменения самостоятельно, отредактировав файл, ну и потом сделать commit.

2) Autosync выключен и вы забыли сделать pull, update перед commit. Все пройдет без дополнительных сообщений, без эксцессов, но при этом в результате commit в репозитории будет создан fork (вилка) — по сути, новая ветка проекта. Как сказано в документации, в Fossil fork — это нежелательный, непреднамеренный branch. Плохо то, что ни вы, ни ваш коллега можете это даже не заметить, если не заглянете в Timeline — там раздвоение сразу бросится в глаза. Чтобы исправить сложившееся положение вам надо объединить образовавшиеся ветки командой fossil merge. Если слияние не удастся, то, как и в предыдущем случае, его надо будет произвести самостоятельно и сделать commit.
Эта вторая ситуация может возникнуть не только в результате забывчивости, но и если вы какое-то время работаете offline и вынуждены сохранять изменения в локальный репозиторий commit, не синхронизируя его с сервером. В таком случае имейте ввиду, что у вас может получиться fork и проверяйте Timeline после отправки изменений на сервер. И еще: если Autosync у вас включен, но вы почему-то хотите получить fork вместо объединения ваших изменений с изменениями других разработчиков, вы можете запустить commit с параметром --allow-fork.

Ветки (branches).
Я не буду здесь говорить о том, зачем нужно ветвление проекта, для чего и в каких случаях им пользоваться, просто расскажу вкратце, как это реализовано в Fossil. Предположим, мы хотим создать новую ветку ver_2_0:

 fossil branch new ver_2_0 trunk
trunk — это та ветвь, от которой мы производим ответвление, trunk — это название главного «ствола» любого проекта. Если мы теперь выполним команду fossil branch list, то увидим, что у нас теперь 2 ветки — trunk и ver_2_0, причем знаком "*" помечена ветка trunk — это значит, что именно она — текущая. Создание новой ветки не изменяет статус рабочих файлов — мы все еще в старой ветке trunk, это можно проверить и командой fossil status. Чтобы перейти в новую ветку, выполняем

 fossil checkout ver_2_0
или fossil co ver_2_0, это одно и то же. Команда checkout заменяет рабочие файлы на те, что находятся в локальном репозитории ну и устанавливает указанную ветку ( если она указана ), как текущую. Теперь все наши последующие изменения будут относиться к ветке ver_2_0. Если нам понадобится произвести в ветке trunk те изменения, которые мы наработали в ver_2_0, переходим в trunk и производим объединение:

fossil co trunk
fossil merge ver_2_0

При объединении могут возникнуть конфликты — как с ними разбираться я рассказывал выше.

Существуют еще так называемые личные ветки, они остаются в локальном репозитории и не передаются на сервер, такие ветки образуются, если к команде branch добавить параметр --private.

Заключение.
Ну вот, вроде и все, что я планировал рассказать. Конечно, здесь были освещены не все вопросы, не все команды и не все параметры ( думаю, даже меньшая часть ) тех команд, о которых шла речь. Но на это есть документация. Есть еще интересная тема, о которой стоило упомянуть — это возможность создания своих скриптов на встроенном Tcl — подобном языке TH, но я и сам с этим еще не разбирался.

Напоследок хотелось бы ответить на вопрос, который чаще всего задавали в комментариях к первой части статьи: чем Fossil лучше Git или Mercurial.
Я никого не пытаюсь уговорить перейти на Fossil с другой VCS, более того, я считаю непродуктивным занятием менять VCS, которой вы пользуетесь, на другую, если вас все в ней устраивает и нет серьезных причин для миграции. Система контроля версий — всего лишь инструмент и он не должен быть на первом плане в вашей работе. Другое дело, если вы только подбираете VCS для себя — в этом случае стоит рассмотреть Fossil как вполне достойный вариант, который обеспечивает все необходимые возможности.
А лучше или хуже? Это, во многом, вопрос личных предпочтений. Мне нравится минимализм Fossil. Нравится, что для установки что клиента, что сервера надо просто скопировать один файл, что нет нужды поднимать отдельную службу для сервера, что я могу носить на флэшке два небольших исполняемых файла ( для Windows и для Linux ) и несколько файлов — репозиториев — и этого достаточно, чтобы использовать его в любом месте. Мне кажется удобным его web — интерфейс и представляется важным, что его можно использовать и локально. Мне нравится, что bug-tracking и wiki встроены в систему, их данные хранятся в том же репозитории и тесно интегрированы с остальным содержанием репозитория, что с ними можно работать offline, что не надо использовать услуги сторонних web — сервисов и беспокоиться о своих данных в случае закрытия этих сервисов или изменения их политики. Наверняка есть люди, которым это тоже понравится.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 8

    +1
    Прежде чем идти дальше, нам надо разобраться с параметром autosync. Если он включен ( «on», или 1 ), то при выполнении команды fossil update будет предпринята попытка получить сначала обновление из удаленного репозитория (pull), а при выполнении fossil commit — сначала pull и update, а сразу после commit — push ( передача изменений в удаленный репозиторий ), причем адресом удаленного репозитория будет указанный в последней выполненной команде clone, push, pull, sync, remote-url.
    Это прямо противоречит цели сохранения истории «как она есть». При включении autosync вы получаете автоматический rebase последних изменений, что очевидно является сменой контекста, в котором делались изменения. Особенно, если алгоритм слияния где‐то напортачил (потому что неидеален, или, скорее, от того, что не понимает суть изменений: к примеру, при проведении рефакторинга на одной стороне при автоматическом слиянии вы вполне можете воткнуть в середину блока код с несуществующей переменной из‐за того, что та была переименована на другой стороне, а близкое окружение вашего кода данной переменной не содержало).
      0
      А какова альтернатива?
        0
        Одну вы и сами написали: «нерекомендуемые» микро‐ветки. Вторая: сделать всё это самому, но перед commit просмотреть diff, просмотреть комментарии (а в особо запущенных случаях и diff’ы: актуально, если из пояснения к изменению ничего не понятно или из комментария понятно, что изменение, вероятно, затрагивает ваш код) новых изменений и прогнать тесты.
          0
          В принципе, вы правы, это, в общем случае, более безопасное решение. И развилка на Timeline в случае fork более адекватно описывает ситуацию, чем прямая. Но в реальных ситуациях участники проекта часто работают над малопересекающимися областями и делать каждый раз fork с последующим изучением нового кода и merge — лишние и бесполезные трудозатраты. К тому же при наличии элементарного порядка в организации труда ситуации, когда кто-то, допустим, изменил название переменной, используемой другими, предварительно это не обсудив ни с кем, и даже не предупредив об этом — такие ситуации практически исключены.
            0
            Обычно при организации параллельной работы над непересекающимися вещами используют отдельные ветки и проблема не возникает вообще. А «изменение названия переменной» — не единственная вещь, которая может сломать код, и учесть их все практически невозможно. Поэтому fetch, test, review, rebase или merge, а не что‐то ещё. При отдельных ветках для каждой из таких непересекающихся областей и с нормальными разработчиками (в т.ч. не забывающими обсудить потенциально проблемные изменения) review много времени занимать не должен.
      0
      Кстати, я правильно понял, что аналога git config remote.{name}.url {url}/echo $'[paths]\n{name} = {url}' >> .hg/hgrc для связывания URL с именем нет? Я что‐то в документации такого не вижу.
        0
        Прямого аналога нет. Но того же эффекта можно, наверное, добиться, если вызывать push, pull из bash-скрипта, где предварительно по нужным правилам устанавливать URL — fossil remote-url…
        Хотя я, честно говоря, не понял, для чего это нужно и как работает ваша конструкция.
          0
          Обе конструкции запоминают данный {url} под именем {name}, так что в дальнейшем можно будет писать имя везде, где требуется данный URL. Первая конструкция для git, вторая для mercurial.

          Нужно это, если у вас два remote репозитория: например, upstream, из которого вы берёте изменения (и который, скорее всего, вам не доступен на запись), и собственный форк.

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

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