Для некоторых современных программистов не существует систем контроля версий кроме Git, но на практике Subversion всё ещё востребован, а Mercurial имеет своих ярых сторонников. Быстрый поиск в подкрепление.
В результате DevOps'ы не монопроектных компаний встречаются с необходимостью автоматизировать работу с весьма разными системами. При этом у каждой есть свои нюансы и неизбежно появляются скрытые ошибки в сценариях, выстреливающие в самый неподходящий момент. Возникает потребность в предсказуемом поведении с минимальной "гибкостью", а не пёстрым букетом возможностей.
Интерфейс
В статье знакомства с FutoIn CID уже описывалась неявная работа с различными VCS. В версии v0.7 был добавлен явный командный интерфейс и новый функционал для автоматизации создания и слияния веток:
В общем-то сам интерфейс с комментариями ниже:
cid vcs checkout [<vcs_ref>] [--vcsRepo=<vcs_repo>] [--wcDir=<wc_dir>]
cid vcs commit <commit_msg> [<commit_files>...] [--wcDir=<wc_dir>]
cid vcs merge <vcs_ref> [--no-cleanup] [--wcDir=<wc_dir>]
cid vcs branch <vcs_ref> [--wcDir=<wc_dir>]
cid vcs delete <vcs_ref> [--vcsRepo=<vcs_repo>] [--cacheDir=<cache_dir>] [--wcDir=<wc_dir>]
cid vcs export <vcs_ref> <dst_dir> [--vcsRepo=<vcs_repo>] [--cacheDir=<cache_dir>] [--wcDir=<wc_dir>]
cid vcs tags [<tag_pattern>] [--vcsRepo=<vcs_repo>] [--cacheDir=<cache_dir>] [--wcDir=<wc_dir>]
cid vcs branches [<branch_pattern>] [--vcsRepo=<vcs_repo>] [--cacheDir=<cache_dir>] [--wcDir=<wc_dir>]
cid vcs reset [--wcDir=<wc_dir>]
cid vcs ismerged <vcs_ref> [--wcDir=<wc_dir>]
Если отстраниться от лозунгов об идейном превосходстве децентрализованных систем контроля версий, то в сухом остатке в разработке любого состоятельного проекта остаётся всё та же клиент-серверная модель с дополнительными плюшками на клиентской стороне. Вторым очевидным моментом является беспроблемная "эмуляция" клиент-серверной модели в распределённых системах и сложность реализации обратного. Этим и обусловлена логика работы — обязательная неявная синхронизацию с сервером.
Это подразумевает такую таблицу соответствия:
CID | Git | Mercurial | Subversion |
---|---|---|---|
cid vcs checkout |
git clone/fetch + git checkout |
hg pull + hg checkout |
svn checkout/switch |
cid vcs commit |
git commit + git push |
hg commit + hg push |
svn commit |
cid vcs merge |
git merge + git push |
hg merge + hg push |
svn merge + svn commit |
cid vcs branch |
git checkout -b + git push |
hg branch + hg push |
svn copy |
cid vcs delete |
git branch -D + git push -f --delete |
hg checkout + hg commit --close-branch + hg push |
svn remove |
cid vcs export |
git fetch/clone --mirror --depth=1 + git archive | tar |
hg archive --type files + .hg* cleanup |
svn export |
cid vcs tags |
git ls-remote |
hg tags |
svn ls {repo}/tags |
cid vcs branches |
git ls-remote |
hg branches |
svn ls {repo}/branches |
cid vcs reset |
git merge --abort + git reset --hard |
hg update --clean + hg purge -I **/*.orig |
svn revert -R . |
cid vcs ismerged |
git branch -r --merged HEAD |
hg merge --preview |
svn mergeinfo --show-revs eligible |
Вроде бы достаточно понятно при всей лаконичности. Стоит лишь отметить, что:
- SVN полагается на слияние без явных указаний ревизий — "знай что делаешь".
- В Hg нет удаления веток, а bookmarks больше напоминают костыль.
- Разумеется, хоть на этих трёх системах свет клином и сошёлся, есть возможность добавить поддержку любой другой через механизм плагинов.
Немного примеров
Все примеры без лишних слов: маленький комментарий, последовательность команд как есть и наглядный результат.
> Подготовительные работы
Снова используем чистый Debian Jessie. Сделаем Git репозиторий, добавим файл README.txt и LICENSE, сделаем вторую ветку develop.
sudo apt-get install -y python-pip
sudo pip install futoin-cid
VCS_REPO_DIR=${HOME}/repo
VCS_REPO=git:${VCS_REPO_DIR}
function prep_cache_dir() {
local repo=$1
local cache_dir=$(echo $repo | sed -e 's,[/:@],_,g')
[ -d $cache_dir ] && cid vcs reset --wcDir=$cache_dir 1>&2
echo $cache_dir
}
# set -x is too verbose
function cid() {
echo '$' cid "$@" 1>&2
$(which cid) "$@"
}
rm ${VCS_REPO_DIR} wc $(prep_cache_dir ${VCS_REPO}) -rf
cid tool exec git -- init --bare ${VCS_REPO_DIR}
cid vcs checkout --vcsRepo=${VCS_REPO} --wcDir=wc
# Commit All
echo 'Info' > wc/README.txt
cid vcs commit 'Initial commit' --wcDir=wc
# Commit specific file(s)
echo 'License' > wc/LICENSE
cid vcs commit 'Adding license' LICENSE --wcDir=wc
cid vcs branch develop --wcDir=wc
> Job Story №1: когда начинается работа над новой фичей, требуется автоматическое создание ветки, чтобы упорядочить наименование
Примечание: подразумевается, что разработчики не могут создавать ветки самостоятельно или это не приветствуется, а в трекере неким образом срабатывает триггер.
function on_feature_start() {
local repo="$1"
local feature="$2"
local cache_dir=$(prep_cache_dir $repo)
cid vcs checkout develop --vcsRepo=$repo --wcDir=$cache_dir
cid vcs branch feature-${feature} --wcDir=$cache_dir
}
function list_branches() {
# Remote case
cid vcs branches --vcsRepo=${VCS_REPO}
# Local case
cid vcs branches --wcDir=wc
}
on_feature_start "${VCS_REPO}" '123_one'
on_feature_start "${VCS_REPO}" '234_two'
list_branches
> Job Story №2: каждую ночь (каждый коммит), требуется сливать develop (release) ветку в ветку feature (develop), чтобы избежать расхождений и обеспечить соблюдение процесса
Примечание: такой рабочий процесс принято считать идеологически верным [и безопасным], хотя rebase делает более стерильную и понятную историю.
Подготовка веток:
cid vcs checkout develop --wcDir=wc
echo 'Info 2' > wc/README.txt
cid vcs commit 'Commit 2' --wcDir=wc
cid vcs checkout feature-234_two --wcDir=wc
echo 'Info 3' > wc/README.txt
cid vcs commit 'Conflict 3' --wcDir=wc
function sync_branches() {
local repo="$1"
local cache_dir=$(prep_cache_dir $repo)
local logfile=$(realpath $2)
cid vcs checkout develop --vcsRepo=$repo --wcDir=$cache_dir
pushd $cache_dir
# Release -> Develop
echo >$logfile
for b in $(cid vcs branches 'release*'); do
echo -n "Merging $b into develop: " >>$logfile
cid vcs merge $b && echo 'OK' >>$logfile || echo 'FAIL' >>$logfile
done
# Develop -> Feature
for b in $(cid vcs branches 'feature*'); do
echo -n "Merging develop into $b: " >>$logfile
cid vcs checkout $b && cid vcs merge develop && echo 'OK' >>$logfile || echo 'FAIL' >>$logfile
done
popd
}
sync_branches "${VCS_REPO}" merge.log
cat merge.log
Ошибка ожидаемая — мы специально создали конфликт, а вот опечатка в ней — нет. Будет исправлено в следующем релизе.
> Job Story №3: после релиза, удалять все включённые feature ветки, чтобы не засорять пространство имён
Примечание: для Hg и SVN вообще нет ничего страшного, а вот для Git есть риск потерять коммиты — поэтому желательно иметь read-only зеркало для истории.
function remove_merged() {
local repo="$1"
local cache_dir=$(prep_cache_dir $repo)
local logfile=$(realpath $2)
cid vcs checkout develop --vcsRepo=$repo --wcDir=$cache_dir
pushd $cache_dir
echo >$logfile
# Removed merged into develop
for b in $(cid vcs branches 'feature*'); do
if ! cid vcs ismerged $b; then
continue
fi
echo -n "Removing $b: " >>$logfile
cid vcs delete $b && echo 'OK' >>$logfile || echo 'FAIL' >>$logfile
done
popd
}
# Merge first branch
cid vcs checkout develop --wcDir=wc
cid vcs merge feature-123_one --wcDir=wc
# Try cleanup
remove_merged "${VCS_REPO}" delete.log
cat delete.log
> Job Story №4: с заданной периодичностью, генерировать файлы и отправлять в VCS, чтобы иметь целостную историю изменений
Примеры: а) загружаемые файлы в коммерческом блоге или новостном сайте имеют определённую ценность, для повышения целостности и эффективности архивирования крайне уместным становится Subversion б) пример постоянно обновляемых чёрных списков адресов активных атак, без участия FutoIn CID.
function update_file() {
local repo="$1"
local cache_dir=$(prep_cache_dir $repo)
cid vcs checkout develop --vcsRepo=$repo --wcDir=$cache_dir
pushd $cache_dir
date > Timestamp.txt
cid vcs commit 'Updated timestamp' Timestamp.txt
popd
}
update_file "${VCS_REPO}"
> Job Story №5: каждую ночь, требуется обновлять зависимости во всех проектах, чтобы всегда использовать актуальные версии
Примечание: может быть куча других вариаций автоматизированной обработки списка проектов.
function update_project_deps() {
local repo="$1"
local cache_dir=$(prep_cache_dir $repo)
cid vcs checkout develop --vcsRepo=$repo --wcDir=$cache_dir
pushd $cache_dir
date > Timestamp.txt
for t in $(cid tool detect); do
case $t in
npm*|composer*|bundler*) cid tool exec $t -- update ;;
esac
done
cid vcs commit 'Updated dependencies'
popd
}
update_project_deps "${VCS_REPO}"
В пример не были добавлены файлы npm/composer/bundler чтобы не загромождать.
Заключение
В целом FutoIn CID не навязывает какие-то технологические процессы, а лишь предоставляет единый легковесный интерфейс к VCS/SCM для простора творчества админа, DevOps'а и даже разработчика в рамках командной строки.
Разумеется интерфейс ещё полностью не устаканился, возможны обратно совместимые изменения. Любые найденные проблемы, предложения и пожелания приветствуются.