Быстрое развертывание небольших web-приложений на сервере посредством git push

    Контекст


    Предположим мы поддерживаем небольшой web-проект. У нас есть песочница для разработки с git'ом, отладчиками и прочими полезными вещами. Сайт уже запущен, и код скопирован из песочницы на удаленный сервер. Код приходится иногда (а возможно и частенько) обновлять и дорабатывать. Любые изменения естественно обкатать в песочнице. И тут возникает вопрос: как максимально просто и удобно обновить код на сервере?

    Первым же решением, пришедшим в голову, оказывается простая команда git push: мы пушаем в удаленный репозиторий и получаем обновленную версию кода на сервере. Но не все так просто.


    В порыве энтузиазма настраиваем репозиторий на сервере, аккуратно прячем папочку .git от web-сервера. Однако не все так просто: после первого же изменения в песочнице заветный git push server master вернет нам что-то наподобие:

    remote: error: refusing to update checked out branch: refs/heads/master
    remote: error: By default, updating the current branch in a non-bare repository
    remote: error: is denied, because it will make the index and work tree inconsistent
    remote: error: with what you pushed, and will require 'git reset --hard' to match
    remote: error: the work tree to HEAD.
    remote: error: 
    remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
    remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
    remote: error: its current branch; however, this is not recommended unless you
    remote: error: arranged to update its work tree to match what you pushed in some
    remote: error: other way.
    remote: error: 
    remote: error: To squelch this message and still keep the default behaviour, set
    remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.


    Общий смысл данного сообщения: «нельзя пушать в текущую ветку non-bare репозитория, так как рабочее дерево не будет соответствовать состоянию ветки».

    Стоит отметить что для bare-репозиториев такой ошибки не возникнет. Но подобный тип репозитория для наших целей совершенно не подходит.

    Также ошибок не будет если мы пушаем не в текущую ветку. Результат данной операции еще дальше от нужного нам.

    Концепция


    Для решения возникшей проблемы воспользуемся возможностью git'а реагировать на производимые над репозиторием манипуляции посредством хуков (триггеров).

    Для начала логинимся обратно на сервер и создаем продакшн ветку
    git checkout -b production
    


    Правим файл .git/hooks/post-receive. Содержимое должно быть таким:
    #!/bin/sh
    cd ..
    env -i git merge --ff-only master
    


    Такая настройка позволяет оставить удаленный репозиторий с текущей веткой production. А каждый push в этот репозиторий будет вызывать merge ветки master в production, что будет фактически обновлять файлы в рабочем каталоге.

    Не забываем выставить разрешение на запуск скрипта:
    chmod +x .git/hooks/post-receive
    


    Радуемся и возвращаемся в песочницу. Теперь можно обновлять продакшн простой командой git push server master.

    Приятные дополнения


    В конец файла .git/hooks/post-receive можно добавить вызов какого либо скрипта, выполнение которого необходимо для полного развертывания кода. Например, очистка кэша, сборка файлов локали, обновление БД и т.д…

    Источники вдохновения


    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

      +4
      Почему бы не посмотреть в строну нармального деплоя, обычно в добавок к коду еще может измениться и БД, а так же потребоваться перезапуск сервера приложений
      P.S.
      я использую capistranorb.com/
        +1
        Есть еще такой способ: Git и публикация сайта
          0
          Спасибо, схема довольно удобная.
          +1
          Первым же решением, пришедшим в голову, оказывается совсем не git, а rsync. Технология примерно такая: на девелоперской машине собираем копию (более-менее точную) того, что получится на сервере, и rsync'ом обновляем сервер (ну или лучше обновить на сервере копию, а с этой копии уже обновить сам сервер — чтобы уменьшить время простоя). После этого запускаем скрипты, настраивающие что-то на сервере — права доступа, директории, базу… что угодно, и перезапускаем серверную часть — что может потребовать рутовых прав.

          Почему не git: причин может быть много, например отсутствие git на сервере, наличие больших бинарных кусков (которые с git'ом не дружат), нежелание выкладывать на сервер что-то кроме текущей версии, наличие в git файлов не для выкладывания на сервер, сложность отладки хуков по сравнению с обычными скриптами.
            0
            rsync, не спорю, достаточно гибок, но не всегда удобен: в случае когда рядом с кодом появляется контент, производимый сайтом, нужно либо действительно держать на девелоперской машине практически полную копию продакшна (не только код), либо настраивать rsync не совсем тривиальным путем.
            Насчет бинарных кусков не совсем понял — зачем их переливать? Вряд ли код имеет большие бинарные блоки. Бинарные файлы это картинки в основном.
            Отсутствие гита конечно ограничивает применение рецепта, но если гит уже есть — решение довольно просто внедряется.
            0
            Мне кажется, вы усложняете. Если вы работаете над проектом один, не имеет смысла разводить много репозиториев: можно прямо в локальном репозитории добавить post-commit хук, выливающий rsync'ом изменения и перезапускающий что надо. Если работает несколько человек, в любом случае желательно иметь центральный репозиторий (лучше — bare, с обвязкой типа gitolite), и оттуда по post-update хуку выливать изменения.

            Такая схема, в частности, даст возможность иметь несколько версий запущенного проекта (production, testing...)

            Если что, такой командой можно получить ветку в не-bare репозитории.
            BRANCH=`git rev-parse --abbrev-ref HEAD`
            Такой — в bare:
            BRANCH=`git rev-parse --symbolic --abbrev-ref $1`
            И такой командой можно вынуть данные из репозитория:
            git archive --format=tar HEAD | (cd ${DIR} && tar -xf)
              0
              А если не смержится? Если --ff-only не сработает.

              Можно делать настроить 'receive.denyCurrentBranch' configuration variable to 'refuse' пушить в текущую ветку. Тогда в хуке нужно сделать только git checkout -f.

              Но ещё правильнее пушить не бранчи, а теги — вы же не будете на сервере ничего коммитить, значит и бранч вам не нужен. Создаём тег, пушим его на сервер. А на сервере хук git checkout -f tagname. После этого все бранчи на сервере даже можно удалить.
                0
                Если не смержится значит что-то меняли/коммитили на проде, что не есть гут. --ff-only какраз для того чтобы в случае отклонения от основного принципа работы с проектом все сломалось. На всякий случай, так сказать.

                насчет тега спасибо — попробую.
                  0
                  Ну это зависит от процесса разработки и выкладки. У нас иногда переключались ветки и это было вполне валидно.

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

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

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