суббота, 5 июня 2010 г.

Вы не любите котов? Так Вы просто не умеете их готовить.

Многие сейчас переходят с Subversion на Git. Многие считают, что merge в Subversion практически невозможно использовать... и так далее.

Эта статья для тех, кто еще пользуется Subversion, но только планирует перейти на Git.

После прочтения статьи просто взвесьте  все за и против, которые Вы получите при переходе на Git.

Я ни в коей мере не наезжаю на Git, но для меня существует ряд проблем при переходе на новую систему контроля версий:
  1. Перенести весь код с svn-репозитария в git-репозитарий с сохранением всей истории изменений.
  2. Обучить всех сотрудников работе с новой системой контроля версий.
  3. Найти адекватные инструменты для работы с Git под Windows, MacOS, Linux. Например, я использую модуль Subclipse для Eclipse и достойной замены пока не вижу. 
  4. Принять новую схему работы с системой контроля версий. На каждый баг/таск создавать ветку...
  5. Изменить скрипты деплоинга и обновления проекта.
 Все, что требуется от Вас, это оценить стоит ли оно того.

 Готовить Subversion  мы будем в два этапа ( я буду больше рассказывать "как", чем "зачем" ):
  1. Работа со своими ветками
  2. Управление релизами
Сразу замечу, что все что здесь описано, требует Subversion >= 1.6.x :
  1. Умный мердж появился только в subversion 1.5 (До этого, я соглашусь, мерджинг был неадекватный)
  2. Сокращенные пути через "^" появились в subversion 1.6
Допустим у нас есть репозитарий по адресу  http://svn.myrepo.com/
И вся основная разработка ведется в http://svn.myrepo.com/trunk. (Trunk - это ствол (в переводи с англ.))
Все дополнительные ветки будут создаваться в http://svn.myrepo.com/branches/



РАБОТА СО СВОИМИ ВЕТКАМИ
На практике случаи, когда разработчик создает свою ветку, не так часты.
Отдельная ветка нужна если разработчик собирается делать изменения в коде, которые могут нарушить работоспособность основной ветки (^/trunk). Ему следуют доводить свой код до ума в дополнительной ветке.
Процедура:

1. Мы работаем с ^/trunk и решаем создать для определенной задачи новую ветку.

2. Создаем ветку. (Ветки создаются моментально поскольку Subversion использует Copy on Write).
Вариант 1:
svn copy http://svn.myrepo.com/trunk  http://svn.myrepo.com/branches/feature1 -m "Creating a private branch for feature1"
Вариант 2 (если мы сейчас находимся в нашей робочей копии):
svn copy ^/trunk  ^/branches/feature1 -m "Creating a private branch for feature1"

ОБРАТИТЕ ВНИМАНИЕ: Символ "^" - интерпретируется как путь к нашему текущему репозитарию.

3. Теперь нужно перейти в новосозданую ветку
Вариант 1:
Мы можем переключить нашу рабочую копию на новую ветку( я только так и делаю ):
svn switch ^/branches/feature1
ОБРАТИТЕ ВНИМАНИЕ : svn switch  - это по сути "svn update" только в пространстве и времени (между ветками и ревизиями), он не удаляет локальные изменения
Вариант 2:
Получение ветки в отдельную папку
svn checkout http://svn.myrepo.com/branches/feature1 /some/path

4. Работаем в новой ветке и периодически сливаем себе все изменения  сделанные в ^/trunk. (чем чаще - тем лучше).
Для слияния переходим в корень ветки и запускаем
svn merge ^/trunk #всегда мерджит в рабочий каталог


МЕГА ВАЖНО :
Всегда делайте merge полностью всей ветки
Ревизии которые были уже слиты запоминаются в специальных атрибутах(svn:mergeinfo).
Svn проверяет атрибут svn:mergeinfo только у каталога назначения (игнорируя родительские каталоги) и если Вы уже сливали код в родительский или дочерний каталог - у Вас возникнет конфликт.

ОБРАТИТЕ ВНИМАНИЕ :
Для просмотра ревизий которые уже были слиты с веткой feature1 можно использовать
svn mergeinfo ^/trunk
Для просмотра ревизий которые еще не были слиты с веткой feature1 можно использовать
svn mergeinfo ^/trunk --show-revs eligible
Для более детальной информации можно выполнить комнанду
svn merge ^/trunk --dry-run  #покажет только изменения которые буду произведены в случае мерджа

5. При возникновении конфликтов - решаем их. И комитим в нашу ветку
svn commit -m "Merged latest trunk changes to feature1 branch."

6. После завершения работы над новой функцией. Делаем финальный мердж с ^/trunk и комитим финальные изменения в нашу ветку feature1.
svn commit -m "Final merge of trunk changes to feature1 branch."

Тестируем и вливаем нашу ветку в trunk
svn switch ^/trunk
svn merge --reintegrate ^/branches/feature1 -m "Merging differences between repository URLs into"

ВАЖНО : не забываем про ключ --reintegrate. Этот ключ нужен для того чтобы смерджить только изменения уникальные для вашей ветки. После такого слияния ветка feature1 становиться "неработоспособной" - попытка залить в нее изменения ^/trunk приведет к множественным конфликтам (на самом деле, привести ветку в работоспособность можно очень просто).

7. Удаляем ветку feture1 поскольку она нам уже не нужна
svn delete ^/branches/feature1 -m "Remove feature1 branch."


ОБРАТИТЕ ВНИМАНИЕ : может быть полезно
1. Если вы начали работать над кодом в ^/trunk и после того, как сделали много изменений осознали, что нужно было создать отдельную ветку, то Вы можете это сделать в любой момент.
svn copy http://svn.myrepo.com/trunk  http://svn.myrepo.com/branches/newbranch -m "Create branch 'newbranch'."
svn switch ^/branches/newbranch #"svn switch" так же как и "svn update" сохраняет ваши локальные изменения.

2. Если вы случайно закомитили ненужные изменения в файл в ревизии 303
для отката  используйте
svn merge -c -303 ^/trunk
svn diff # проверьте, что ненужные изменения были удалены
svn commit -m "Undoing change committed in r303."

3. Восстановление удаленного фала (ВАЖНО: НЕ ИСПОЛЬЗОВАТЬ svn up real.c -r 807)
svn copy ^/trunk/real.c@807 ./real.c #точно так же можно и с папками
svn status  # A  +   real.c
svn commit -m "Resurrected real.c from revision 807, /calc/trunk/real.c."


4. Добавить как новый(без сохранения истории изменений)
svn cat ^/trunk/real.c@807 > ./real.c
svn add real.c
svn commit -m "Re-created real.c from revision 807."

УПРАВЛЕНИЕ РЕЛИЗАМИ
Система релизов подразумевает существование 3 веток:

^/trunk - основная ветка в которой происходит разработка. Весь новый функционал комитится только в ӕту ветку!!!
^/branches/test - стабилизиционная ветка(это снимок ^/trunk в который комитятся только багфиксы), все содержимое этой ветки всегда после этапа тестирования попадет в продакшен.
^/branches/prod - продакшен ветка(создается копированием стабилизиционной ветки после проведения тестирования).


Допустим мы выпускаем новую версию 1 раз в месяц, а время тестирования кода должно составлять 2 недели:
  1. За 2 недели до выпуска новой версии мы копируем ^/trunk в ^/test.
  2. После 2-х недельного тестирования и исправления багов(процедура ниже) ветки ^/branches/test мы копируем ee в ^/branches/prod  и обновляем сервера с продакшен ветки.

Возможен и другой подход: мы копируем ^/branches/test в ^/branches/prod после того, как не можем найти критический багов в ^/branches/test. И сразу же копируем ^/trunk в ^/branches/test для перехода к новому этапу тестирования.


Копирование ^/trunk в ^/branches/test делается так:
svn del http:/svn.myrepo.com/branches/test -m 'trunk2test: deleting test branch'
svn copy http:/svn.myrepo.com/trunk http:/svn.myrepo.com/branches/test -m 'trunk2test: copying

Копирование ^/branches/test в ^/branches/prod делается так:
svn del http:/svn.myrepo.com/branches/prod -m 'test2prod deleting prod branch'
svn copy http:/svn.myrepo.com/branches/test  http:/svn.myrepo.com/branches/prod -m 'test2prod: copying

Правила:
  1. Весь новый функционал комитится только в ^/trunk
  2. Баги найденные в ^/branches/test фиксятся в этой ветке и затем обязательно сливаются в ^/trunk
  3. Если фикс бага требует значительных изменений кода(может внести дополнительную нестабильность) и не является срочным, то баг разценивается как новый функционал и фиксится в  ^/trunk
  4. С веткой ^/branches/prod никто никогда не работает(кроме релизера)

У нас, например, два раза в сутки в 12:00 и 18:00 ветка ^/branches/test автоматически заливается на тестовый сервер, который тестирует тестер.

Процедура фикса бага:
ВАЖНО: Все команды svn выполняем в корне ветки!!!;
  1. svn switch ^/branches/test #переключаемся на стабилизационную ветку.
  2. Фиксим баг, тестируем и обязательно делаем коммит(сейча фикс есть только в стабилизиционной ветке)
  3. svn switch ^/trunk #переключаемся на на транк (фикса тут нет еще)
  4. Теперь необходимо перелить фикс, который мы закомитили, c  ^/branches/test в ^/trunk. Для этого делаем следующее:
    1. svn merge ^/branches/test  --dry-run #холостой запуск для того, чтобы оценить какие файлы будут мерджиться.
    2. svn merge ^/branches/test  #реальный мердж
    3. Разрешаем все конфилкты(если они возникли)
    4. svn ci -m 'commit to trunk' # комитим фикс в транк(теперь он есть и транке и в стабилизационной ветке).
  5. Указать в задаче, что фикс залит в тестовую ветку!


Если необходимо сделать мега критический фикс, который должен попасть на продакшн то:
  1. делаем фикс в ^/branches/prod ветке (тестим)
  2. сливаем фикс с ^/branches/prod в ^/branches/test ( не забываем закомитить)
  3. сливаем фикс с ^/branches/test в ^/trunk ( не забываем закомитить)
  4. обновляем сервера.

В продакшен ветку ^/branches/prod имеет право делать комиты только релизер(человек ответственный за выпуск новых версий)


БУДУЩЕЕ SUBVERSION
В Subversion 1.7 будет:
  1. Использование протокола HTTPv2 для ускорения работы
  2. Next Generation Working Copy (WC-NG) - будет централизированное хранилище метаданных на базе sqlite
  3. Поддержка локальных коммитов!!!
  4. Полезные функции для работы с бинарными файлами( возможность удалять файлы с репозитория )

Рекомендую всем прочитать:
  1. http://svnbook.red-bean.com/nightly/en/svn.branchmerge.html
  2. http://koorchik.blogspot.com/2010/06/svnexternals.html

    8 коммент.:

    rewlad комментирует...

    Замечательная статья.
    Как раз недавно мы перешли на подобную схему работы.

    Типовые поцедовательности svn-комманд обернул в свои скрипты с интегрцией рестартов и т. п.

    Большинство разработчиков пользуется этими скриптами,
    не заморачиваясь изучением самого svn,
    а в исключительных случаях зовут меня.

    koorchik комментирует...

    Спасибо. Ситуация у нас похожая, но в основном все работают непосредственно с SVN, в случае проблем, тоже дергают меня :). И есть несколько скриптов для комплексных или критических операций:
    1. для копирования trunk в test
    2. для копирования test в prod
    3. для апдейта системы(продакшен апдейтится с prod-ветки, а тестовый сервер с ветки test).

    Обновление тестового сервера происходит с ветки test автоматически несколько раз в день. Таким образом последние фиксы в тестовой ветки делаются доступными для тестирования тестерам.

    Slippery Jim комментирует...

    Виктор, хотелось бы отметить несколько моментов, мягко говоря "спорных"
    "На практике случаи, когда разработчик создает свою ветку, не так часты.
    Отдельная ветка нужна если разработчик собирается делать изменения в коде, которые могут нарушить работоспособность основной ветки"
    Охренеть стиль работы! И это утверждение несколько противоречит всему остальному тексту, по которому trunk - фронтир разработки, работоспособность которого вообще-то никто не гарантирует! Более стильный стиль - "Если вы думаете, делать кратковременный бранч или нет - не думайте, делайте! Много бранчей жизни не помеха, а лог - чище"
    Предложенный релиз-менеджмент - отвратителен и ошибочен методологически. Уничтожение и перезапись - саботаж и диверсия! После трех-пяти удалений-замен тестового и продакшен бранчей я читать лог и искать хвосты с радостью посажу лучшего врага.
    Более менее нормальная работа без искания приключений на задницу, мне кажется, будет примерно такой
    - Идет разработка в транке
    - когда начинаем готовить очередную релизную версию (пусть будет для примера 1.7) - бранчим транк в branches/1.7
    - тестим, правим, мержим trunk<->branches/1.7
    - когда релиз настал, признанная релизной ревизия едет в tags/1.7.0 и тэги - не трогаются вообще, коммиты в них отлавливаются и рубятся *коммит-хуками
    - пострелизные правки идут в branches/1.7 (с мержами), корректирующие миноры опять же едут в тэги 1.7.1, 1.7.2...
    Для следующей версии - просто сменить цифру у инструкции.

    Разницу в подходах наших надо объяснять?

    Slippery Jim комментирует...
    Этот комментарий был удален автором.
    koorchik комментирует...

    2Slippery Jim:
    Мне кажется, что все зависит от задач. Предложенный Вами вариант с тегами - это, по сути, классический вариант(описан даже в svnbook, если я не ошибаюсь). Но для нас удобнее оказался описанный в посте вариант. Могу сказать, что работаем уже достаточно длительное время по такой схеме и более чем довольны.

    По поводу создания веток разработчиками. Как я уже писал, trunk - это основная ветка для разработки(это не продакшен и даже не тестовая ветка), но она также всегда должна быть в работоспособном состоянии. Если коммит может нарушить ее работоспособность, то разработчик создает свою ветку. И я с Вами полностью согласен, что если стоит вопрос делать или не делать, то лучше делать. На нашей практике же, в основном задачи достаточно мелкие и независимые, и безболезненно реализуются в trunk.

    Slippery Jim комментирует...

    Мне кажется, что зависит все-таки не от задач, а от привычки ("плод ошибок трудных...") создавать себе минимум проблем в будущем. Надо помнить, что код мало написать, его ВСЕГДА надо сопровождать. В моей модели код для поиска тараканов в старой версии кода находится и получается сильно проще. Это отрицать не будем, да? Ведь в общем задача не только отыскать и исправить, но еще и знать, какой именно код поломал. В моей модели последнее - проще (модель, конечно, не лично моя, может она и "каноничная"... мы книжек не чтем)

    koorchik комментирует...

    > После трех-пяти удалений-замен тестового и продакшен бранчей я читать лог и искать хвосты с радостью посажу лучшего врага.
    > В моей модели код для поиска тараканов в старой версии кода находится и получается сильно проще. > Это отрицать не будем, да?

    Будем отрицать :).
    При удалении ветки prod и копировании на ее место новой копируютеся и весь svn log. Нет абсолютно никакой разницы, что смотреть на лог новосозданного тега/ветки в вашем примере и перезаписанной ветки в предложенной мной схеме.

    В сопроваждении никакой проблемы нет, даже сабж проще:
    1. Меньше путаницы с номерами версий. Разработчики не парятся с номерами версий и работают с одними и теми же ветками.
    2. Нет лишнего мусора в виде стабилизационных веток.

    Если вы хотите хранить код для разных версий, то никто ж не запрещает создавать использовать теги.
    Нужно просто перед удалением ветки "prod" затегировать ее.

    >Мне кажется, что зависит все-таки не от задач, а от привычки ("плод ошибок трудных...") создавать себе минимум проблем в будущем.

    Мы уже столько работаем с этой системой, что можем сказать, что будущее уже наступило и проблем это не вызвало :).

    Анонимный комментирует...

    2koorchik:
    Каким образом при merge-ах вы боретесь с Tree Conflict-ами?
    Например, если необходимо выполнить большой рефакторинг параллельно разработке новых фич. Если его выполнять в отдельной ветке и потом merge-ить ее в trunk - будет куча Tree Conflict-ов.
    Или если рефакторинг был выполнен между версиями 1.0 и 2.0 и в версии 2.0 есть фикс на какую-то проблему и вам надо этот фикс перенести в 1.0, создав 1.1. Фикс сидит в новой структуре, а переносим в старую. Tree Conflicts.
    Как вы с этим боретесь?

    Отправить комментарий

    Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)