2016-03-21 17:09:42

Промышленная модель разработки средствами GIT

Про работу Linux Git

Итак, у нас для начала уже есть сервер на котором настроен gitolite по адресу git.example.com, на нем расположен репозиторий myrepo в котором есть две основные ветки master и develop.

Общий принцип разработки такой:

  1. При необходимости что-то доработать разработчик создает новую ветку из актуального состояния master
  2. Делает в ней доработку и вливает ветку в develop
  3. Проводится тестирование и если все хорошо, то ветка с доработкой вливается в master

Задача заключается в том, чтоб при изменениях в ветке develop они автоматически переносились в рабочую копию на хост develop.example.com, а изменения сделанные в master - на хост production.example.com.


Автоматика на develop и production хостах

Для начала на develop.example.com создаем пользователя gitbot и генерим для него ssh ключи.

adduser gitbot
su gitbot
ssh-keygen -t rsa

Добавляем gitbot и его ключ в конфигурацию gitolite и разрешаем доступ к репозиторию myrepo, подробно описывать не буду тк это уже описано много раз, в том числе тут.

Далее на develop.example.com создаем место под репозиторий, например /usr/local/www/myrepo и делаем его владельцем пользователя gitbot:

mkdir /usr/local/www/myrepo
chown gitbot /usr/local/www/myrepo

Создаем папку для логов, не обязательно, но желательно, если этого не сделать, то надо убрать код логов из скрипта ниже

mkdir /var/log/gitbot
chown gitbot /var/log/gitbot

Клонируем ветку develop из myrepo, операции выполняем от имени gitbot

su gitbot
git clone --single-branch -b develop git@git.example.com:myrepo /usr/local/www/myrepo

Эта операция также занесет fingerprint хоста, чтоб потом автомату ничего не мешало работать.

Создаем служебный скрипт, который будет пуллить указанный репозиторий, опять же все делаем от имени gitbot, скрипт размещаем в домашней папке

su gitbot
cd ~
echo > pull-repo.sh
chmod +x ./pull-repo.sh

Содержимое скрипта pull-repo.sh примерно такое

#!/bin/sh

# Usage:
# ./pull-repo.sh /path/to/reponame [branchname =master]
#
# Logs:
# /var/log/gitbot/reponame.log

if [ ! -d "$1" ]; then
        echo "'$1' is not a directory!"
        echo "Usage:"
        echo "./pull-repo.sh /path/to/reponame [branchname =master]"
        exit 1
fi

if [ -z $2 ]; then
        BRANCH=master
else
        BRANCH=$2
fi

REPO_DIR=${1%/}
REPO_NAME=${REPO_DIR##*/}
LOG=/var/log/gitbot/$REPO_NAME.log
DATE=`date +%Y-%m-%d.%H:%M:%S`

echo "$DATE $REPO_NAME [$BRANCH] $REPO_DIR" >> $LOG

cd $REPO_DIR
git pull origin $BRANCH 2>&1 >> $LOG

И теперь скрипт который будет пуллить изменения в ветке develop нашего репозитория, опять же все делаем от имени gitbot, скрипт размещаем в домашней папке

su gitbot
cd ~
echo > myrepo.sh
chmod +x ./myrepo.sh

Содержимое скрипта myrepo.sh такое

#!/bin/sh
DIR=/usr/local/www/myrepo
./pull-repo.sh $DIR develop

Обязательно проверяем работоспособность myrepo.sh, что оно не только работает без ошибок, но и ничего не спаршивает.

Теперь на production.example.com создаем такого же пользователя gitbot и переносим его ключи с develop.example.com, важно чтоб ключи были одинаковые на обоих серверах.

Точно также клонируем репозиторий как описано выше, но с поправкой на ветку master; и создаем скрипты для автоматизации, так же с поправкой на ветку master. Не забываем все проверить.

Промежуточный результат достигнут - у нас есть автоматика, которая может пуллить изменения в master и develop на нужных хостах.

Автоматика на git.example.com

Идем на наш gitolite сервер git.example.com и проводим настройки в репозитории myrepo, естественно от имени пользователя git

su git
vim ~/repositories/myrepo.git/hooks/post-receive

Нам надо создать хук, который сработает после получения данных в репозиторий и отправит их "куда надо". Хорошим тоном в git считается, что нельзя пушить в рабочую копию, а наоборот - рабочая копия должна пуллить с bare. В post-receive пишем

#!/bin/sh

while read oldrev newrev refname
do
    if [ "$oldrev" != "$newrev"  ]; then
        branch=$(git rev-parse --symbolic --abbrev-ref $refname)
        if [ "master" == "$branch" ]; then              
                echo Deploying $branch branch to production
                ssh -i ~/.ssh/gitbot.id_rsa gitbot@production.example.com "~/myrepo.sh"
        elif [ "develop" == "$branch" ]; then
                echo Deploying $branch branch to develop
                ssh -i ~/.ssh/gitbot.id_rsa gitbot@develop.example.com "~/myrepo.sh"
        else
                echo "Branch $branch is not deployable"
        fi
    fi;
done

Взаимодействие с develop.example.com и production.example.com производится с помощью ключей, поэтому приватный ключ от пользователя gitbot с этих серверов нужно поместить куда-то на git.example.com, я разместил его в /home/git/.ssh/gitbot.id_rsa, в случае иного размещения, правьте скрипт выше.

Не забудьте добавить публичный ключ пользователя gitbot в /home/gitbot/.ssh/authorized_keys на develop.example.com и production.example.com, иначе подключиться к ним с использованием приватного ключа не получится.

Тестируем взаимодействие с серверами из консоли (это также обеспечит fingerprint хостов)

su git
ssh -i ~/.ssh/gitbot.id_rsa gitbot@production.example.com "~/myrepo.sh"
ssh -i ~/.ssh/gitbot.id_rsa gitbot@develop.example.com "~/myrepo.sh"

Убедившись, что все работает, проверяем полную схему взаимодействия: делаем коммит + пуш в ветку develop и master, и убеждаемся, что изменения попали на нужные серверы. Результат достигнут.

Автоматизация git reset и подмодули

Если требуется откатить ветку назад через git reset + git push --force, то такого рода изменения автоматически не применятся, и придется аналогичные действия производить на конечных хостах, чтоб этого не делать попробуем автомтизировать.

А если в вашем проекте используются подмодули, то можно их также автоматически обновлять командой git submodule update --init --remote, ориентируясь на наличие файла .gitmodules в репозитории.

Для этого модернизируем скрипт pull-repo.sh, будем искать следы форсированного пуша в выводе git fetch и проверять наличие файла подмодулей:

#!/bin/sh

# Usage:
# ./pull-repo.sh /path/to/reponame [branchname =master]
#
# Logs:
# /var/log/gitbot/reponame.log

if [ ! -d "$1" ]; then
        echo "'$1' is not a directory!"
        echo "Usage:"
        echo "./pull-repo.sh /path/to/reponame [branchname =master]"
        exit 1
fi

if [ -z $2 ]; then
        BRANCH=master
else
        BRANCH=$2
fi

REPO_DIR=${1%/}
REPO_NAME=${REPO_DIR##*/}
LOG=/var/log/gitbot/$REPO_NAME.log
DATE=`date +%Y-%m-%d.%H:%M:%S`

echo "$DATE $REPO_NAME [$BRANCH] $REPO_DIR" >> $LOG

cd $REPO_DIR
#git pull origin $BRANCH 2>&1 >> $LOG
git fetch origin 2>&1 | grep $BRANCH |  grep "(forced update)" >> $LOG 2>&1
if [[ $? -eq 0 ]]; then
        git reset --hard origin/$BRANCH >> $LOG 2>&1
        echo Resetting brahch $BRANCH
else
        git pull origin $BRANCH >> $LOG 2>&1
        echo Normal pull branch $BRANCH
fi
[ -f ./.gitmodules ] && git submodule update --init --remote >> $LOG 2>&1

Здесь заменен git pull на git fetch и поиск в его выводе force update в нужной ветке. Если был форсированный пуш, то мы просто ресетим наш репозиторий в состояние удаленного, а если нет - то делаем обычный пулл.

Полезные скрипты