Параллельные вычисления в ИММ УрО РАН
|
|
Введение в CVSАлександр Владимирович Коновалов
Contents
Управление версиями -- зачем это надоНе требуется большого опыта программирования, чтобы понять -- наиболее чреваты ошибками окрестности недавно поправленных мест. Естественно, что любая сколько-нибудь осмысленная программистская деятельность предполагает: учёт правок, запоминание того, кто правил, комментирование смысла правки. Главное при такой деятельности -- порядок в голове разработчика. Если такой порядок есть, то остальное -- лишь вопрос инструментальных средств. Если нету -- любой инструментарий окажется бесполезен. Как обычно бывает, всех целей вполне можно добиться просто грамотным использованием текстового редактора. Достаточно окружать правку комментариями специального вида1, не выбрасывать старые варианты, писать дату в формате 020815 и пр. Естественно, что со временем наиболее интересные места исходников читать будет просто невозможно из-за изобилия старых вариантов, но вот тогда можно сделать генеральную уборку и выкинуть старую рухлядь. Всё можно делать руками, и никаких проблем! Ещё одна, схожая, задача -- слияние правок, сделанных независимо и поступивших из разных источников. Предположим, что вы получили нечто большое и страшное в исходных текстах, и моментально подправили это в соотвествии со своим виденем своих потребностей. Но оригинальный автор не успокоился, отдав вам исходные тексты, а продолжил развитие. Через некоторое время возникнет задача увязать то интересное2, что он понаписал, с вашими правками. Здесь, по-видимому, не обойдёшься просто текстовым редактором, лучше взять как минимум нечто вроде diff(1). В этой лекции мы обсудим инструментальные средства, которые автоматизируют многие из рутинных задач, возникающих в описаных выше ситуациях. Не боясь повторов, повторим, что эти средства лишь освобождают от рутинной работы при технологичной разработке, если технологии нет, то самые лучшие средства окажутся бессильны. Инструментальное средство, которое мы будем разглядывать -- система управления версиями cvs (Concurrent Versions System), ставшая стандартом при разработке свободного ПО3, широко используемая много где ещё, доступная для Win32 и разных Юниксов, и распространяемая под GPL.
РесурсыВ комплект поставки входит весьма неплохой CVS manual by Per Cederqvist. Его (пристойный) русский перевод можно обнаружить на http://alexm.here.ru/, вместе с ещё другими околоCVSными текстами. Гораздо больший, чем ключи командной строки, интерес представляют практика использования систем управления версиями в реальных проектах, отображения артефактов процесса разработки на сущности CVS и пр., но длинного связного текста обо всём этом мне, признаться, не попадалось. Какие-то кусочки есть здесь и там, но целостная картина возникнет, увы, после самостоятельного набивания шишек в не очень маленьком проекте. По-английски рассматриваемая область деятельности называется Software Configuration Management. Знание ключевых слов позволяет умеющим пользоваться Интернетом найти много разнообразных текстов по SCM.
Базовые представленияСистема устроена ``клиент-серверным'' образом: где-то далеко есть хранилище всей истории системы, называемое репозиторий, многочисленные разработчики паралллельно звонят в него, говорят ``дай мне текущую версию'' или ``я вот что написал, сделай ЭТО доступным всем желающим''. Способы звонения могут быть самыми разными, различие между ними важно с точки зрения безопасности, но по предоставляемым возможностям эквивалентно (кратко о доступе -- в разд. 10). Отметим по ходу дела, что CVS вполне себе работает по безобразным TCP-линиям. В простейшем варианте всё просто и понятно: cvs checkout pvfsдозвонится до репозитория, выберет оттуда свежую версию проекта pvfs, и создаст в текущем каталоге соответствующие файлики. Затем начинается самая интересная часть, которая нас в данном случае не интересует. Исправления Ужасных Ошибок нужно сделать доступным всем, для этого выполняем cvs commitоткуда-то изнутри проекта4. Теперь CVS запустит ваш любимый текстовый редактор5 и попросит объяснить смысл правок. Иногда, прежде чем объяснять, полезно вспомнить, что же вы накроили. Это можно сделать, выполнив cvs diff -uВ выдаче будет содержаться нечто наподобие Index: include/req.h =============================================================== RCS file: /home/u1305/cvsroot/pvfs/include/req.h,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 req.h --- include/req.h 12 Aug 2002 10:00:19 -0000 1.1.1.1 +++ include/req.h 18 Aug 2002 13:07:39 -0000 @@ -42,8 +42,9 @@ #define MGR_NOOP 21 #define MGR_LOOKUP 22 #define MGR_CTIME 23 +#define MGR_IOD_DIE 24 -#define MAX_MGR_REQ 23 +#define MAX_MGR_REQ 24 /* structure for request to manager */ typedef struct mreq mreq, *mreq_p;Формат совершнно понятный: - в первой позиции означает
``выбросить'', + -- добавить, а если там пусто -- строчка
есть и в старом, и в новом варианте.
Поразглядывать историю файла можно, выполнив ---------------------------- revision 1.3 date: 1999/08/13 23:26:23; author: rbross; state: Exp; lines: +23 -27 Major cosmetic changes resulting from -Wall output. ---------------------------- revision 1.2 date: 1999/08/13 16:04:52; author: rbross; state: Exp; lines: +6 -37 Reduces log output. ---------------------------- revision 1.1 date: 1999/08/10 17:11:29; author: rbross; state: Exp; branches: 1.1.1; Initial revision ---------------------------- revision 1.1.1.1 date: 1999/08/10 17:11:29; author: rbross; state: Exp; lines: +0 -0 Original 1.3.3 sources ---------------------------- revision 1.44.2.2 date: 2001/12/07 23:19:14; author: rbross; state: Exp; lines: +51 -10 Patch to fix incorrect stat values in the presence of holes in files. ---------------------------- revision 1.44.2.1 date: 2001/12/07 03:29:58; author: rbross; state: Exp; lines: +2 -1 Patch to set dsize to 0 on IOD_TRUNCATE messages. ======================================================================== Если вы правите локальную копию достаточно долго, другие разработчики, возможно, успеют всё поменять. Чтобы привести локальную копию в соответствие с текущим содержимым репозитория, используется cvs updateЛокальные правки при этом, конечно же, не потеряются, их судьба достаточно интересна и заслуживает отдельного (следующего) раздела. Довольно важный вопрос: как часто делать commit? Здесь встречаются подходы в диапазоне от ``Не компилируется -- и ладно, зато МЫСЛИ есть'' до ``Все тесты идут -- пора коммитить''. Сам я ориентируюсь на ощущение: захочу ли я увидеть этот вариант однажды в будущем? Необходимо понимать, что репозиторий -- это (идеализированно говоря) такое хранилище, куда добавить можно, а вот убрать оттуда ничего нельзя.
Параллельные разработчикиПроблема понятна: если мы хотим обеспечить одновременную работу нескольких разработчиков над одним проектом, нам нужно каким-то образом обработать случай, когда двое правят одно и то же. Самое простое решение здесь -- пока один правит, остальные тихо ждут своей очереди6. Ударный момент CVS -- как раз отказ от такого естественного решения, настолько ударный, что он и в название (concurrent) попал. А именно, конфликты не предупредаются, а разрешаются уже после возникновения.
Рассмотрим, во что это выливается в терминах командочек. Пусть 2
разработчика одновременно поправили одно и то же. Об этом они
узнают, когда попытаются выполнить commit. Точнее, поскольку
репозиторий-то один, один из разработчиков успеет первым, а
узнает о конфликте лишь оказавшийся вторым. Когда он в свою
очередь попытается зафиксировать изменения, ему объяснят, что
копия у него -- старая, и надо бы её обновить
( Merging differences between 1.1.1.1 and 1.2 into req.h rcsmerge: warning: conflicts during merge cvs update: conflicts found in include/req.h C include/req.h, поместив в исходник нечто наподобие #define MGR_NOOP 21 #define MGR_LOOKUP 22 #define MGR_CTIME 23 <<<<<<< req.h #define MGR_SUBTRACT 24 ======= #define MGR_IOD_DIE 24 >>>>>>> 1.2 #define MAX_MGR_REQ 24 /* structure for request to manager */ typedef struct mreq mreq, *mreq_p;Конечно же, отсутствие конфликтов с точки зрения CVS и даже транслятора не означает, что семантика не порушилась. Проверка этого, возможно нетривиальная, сваливается на 2-го разработчика. После того, как катаклизм (в каком-то приближении) ликвидирован, он вновь попытается сказать cvs commit (2-ой разработчик может не успеть и 2-ой раз, перед нами распределённые читатели-писатели...). Прелести версионирования перед блокировщиками можно осознать, например, привлекая следующие соображения:
Ревизии и тегиПовозившись с commit-ами, легко увидеть, что при коммитах изменяется (возрастает) некий параметр под названием ревизия (revision). Таким образом, если файл меняется часто, то его номер ``убежит вперед'' по сравнению со стабильными кусками. Это сугубо внутреннее дело CVS и никого не волнует, пока не возникает Внешний Мир. Предположим, что какой-то вариант системы мы передали счастливому заказчику. Через некоторое время заказчик сообщит нам, что релиз содержит Ужасные Ошибки. Проблема здесь в том, что мы знаем ревизии переданных файлов, и сами файлы у нас, естественно, есть, но вот обращаться к ним совершенно неудобно. Для решения проблемы в CVS введена возможность давать набору файлов символические имена -- теги. Это делается так: выполнивcvs tag REL-1-0 изнутри локальной копии, мы
присвоили тег REL-1-0 ревизиям файлов, которые там находились. В
сущности, для этой операции локальная копия не очень и нужна:
команда cvs rtag -D "1 hour ago" STABLE-1-1 c-project присвоит тег
STABLE-1-1 ревизиям, бывшим наиболее свежими час назад. Ключу -D
можно указывать и нормальные даты, например, 16 Sep. Если
таймзона не указана явно, она считается локальной7.
Выбрать файлы по тегу можно, выполнив
Опять же, самый интересный вопрос -- что украшать тегами, а что нет. Разумно звучит следующее: тегировать стоит группу целостных изменений, которая в качестве таковых представляет самостоятельный интерес. Сюда, естественно, попадает то, что вы на сторону отдали, либо наоборот, от независимого поставщика приняли, но и многое другое. Со слишком часто насаженными тегами проблема понятна -- они запутывают, и ни зачем не нужны, ведь из репозитория можно выбирать просто по дате. Ситуация с отсутствием нужного тега более забавна -- вся нужная информация в репозитории есть, но вот как её оттуда вытащить? В некоторых случаях проблему решают специально написанные скрипты. Я постоянно забываю, что именно символизирует конкретный тег, поэтому пишу памятку, содержащую расшифровку.
Ветви и слиянияПомимо параллелизма в работе над каждым файлом, CVS поддерживает параллелизм в работе над всем проектом. Обыденная ситуация, когда это нужно -- исправление катастрофических ошибок в стабильной версии, и активное развитие экспериментальной. Но ужасные ошибки, исправленные в стабильной ветви, неплохо бы исправить и в экспериментальной тоже. Так мы снова сталкиваемся со слиянием изменений. Ветвь можно создать на основе существующего тега, вот так cvs rtag -b -r STABLE-1-10 STABLE-1-10-PATCHES pvfs Другой способ -- на основе содержимого рабочего каталога, вот так: cvs tag -b. После этого репозиторий узнает про ветвь. Но текущая рабочая копия, из которой получена ветвь, отнюдь не окажется автоматический на ветви. Это -- весьма тонкий место, и его необходимо осознать: с точки зрения пользовательских файлов в начальный момент ветвь ничем не отличается от основного дерева, и если при checkout-е не указать явно (вот так: cvs checkout -r IAR-1 pvf), что мы хотим оказаться на ветви, мы там и не окажемся. Обычные теги, и теги, обозначающие ветви, ведут себя по-разному при редактировании файлов: обычный тег -- это постоянная веха где-то позади, а тег, образующий ветви, движется вместе с меняющимися файлами, что, собственно, и позволяет идентифицировть ветвь (для обращения к ветви IAR-1 неизменно нужно указывать этот тег, как бы она не ``уросла'' вперёд). Слияние выполняется так: в существующей рабочей копии выполняем cvs update -j IAR-1. Теперь рабочая копия будет содержать правки из IAR-1, но принадлежать по-прежнему к той ветви, что и до слияниия. Слияние опять-таки производится не в репозитории, и разработчик должен решить, когда получившееся созреет до commit-а.
|