Имеем следующую ситуацию:

В проекте присутствует файл super.config. В нем содержатся много разных настроек проекта. Например, конфигурация взаимодействия со сторонним сервисом и уровень логирования. Система контроля версий — git.

В чем проблема:

  1. Этот файл должен находиться под версионным контролем, так как в нем содержатся «общие» настройки. Ну и сама структура файла ценна.
  2. Каждый разработчик меняет уровень логирования под свои нужды и не хочет, чтобы эти изменения попадали в коммиты, и тем более в интеграционный репозиторий.
  3. В этом файле периодически меняются общие настройки, и разработчик хочет их получать (pull) и публиковать (commit)


Возможные решения:

  1. Хранить исходные тексты приложения и его настройку отдельно. Но жизнь боль, и не всегда это можно сделать. Причины на это разные: от технических до (некомпетентных людей) административных.
  2. Пошаманить с загрузкой конфигурации. Например, под версионным контролем хранить default.super.config, а super.config исключить. Приложение должно сначала грузить свойства из default.super.config, а потом, если он есть, из super.config. Причем значения из последнего перекрывают значения из дефолтного.
  3. Просто не добавлять изменения локального конфига в индекс.
    Плюсы:
    • Простота.
    • Если нужно закоммитить изменения в конфиге, то это делается по фрагментно (git add -p).

    Минусы:
    • Не сделаешь git add.
    • Постоянно «маячат» какие-то изменения. Разработчик каждый раз их анализирует, проверяя все ли закоммитил.
    • Переключение (checkout) на ветку с другим конфигом возможно только с ключом -m или через stash (ключ -f убивает изменения в рабочей копии).
    • Pull изменений конфига приводит к ошибке. Прежде чем получить изменения, локальные придется убрать на полку.

  4. Исключить конфиг из-под версионного контроля.
    Не сработает. Только untracked файлы можно исключать из-под версионного контроля.
  5. Убедить git, что файл конфига не меняется. Как это сделать:

    git update-index --assume-unchanged super.conf

    Плюсы:
    • git add . не добавит лишнего.
    • «Чистый» статус.

    Минусы:
    • Git не сможет переключиться на ветку с другим конфигом, получить или закоммитить изменения конфига. При этом сообщения в консоли могут вводить в заблуждение: git status пишет «nothing to commit, working directory clean», а git pull«Your local changes to the following files would…».
      Для того чтобы разрешить эту ситуацию, придется открыть git глаза обратной командой git update-index --no-assume-unchanged super.conf. И все равно, далее придется воспользоваться полкой.
    • Посмотреть, на что git закрывает глаза можно командой git ls-files -v | grep -e "^[hsmrck]" (не, ну все же понятно).


Как бы поступил я:

Если конфиг меняется крайне редко, то --assume-unchanged выглядит подходящим решением. Иначе просто не обращал бы внимания на изменения.

А вообще, надо хранить настройки отдельно.

Ресурсы по теме


Комментарии (10)


  1. Starche
    29.09.2015 14:37
    +7

    Я обычно решаю эту проблему несколькими конфиг файлами.
    1) Глобальный общий конфиг, который трогают только тогда, когда надо внести изменения глобально. Например, там могут храниться глобальные константы, вычисляемые пути к файлам, и т.п. (global.conf)
    2) По конфигу на каждый сервер/среду где разворачивается приложение (dev.conf, prod.conf...). Обычно как раз тут живут уровни логирования, уровни кэширования, наличие/отсутствие минификации файлов (если речь о веб-проекте), и прочие радости.
    3) Конфиг локального компьютера. Добавлен в гитигнор. В Readme проекта записаны правила его создания. (local.conf). Можно также добавить файл-пример, который на самом деле конфигом не является (local-sample.conf). В локальном определяется подключаемый конфиг среды, а также вносятся локальные настройки (например доступ к базе данных, хранилищу кэша, и т.п.)

    При этом конфиги «наследуются». Т.е. конфиг среды частично перезаписывает глобальный конфиг, а конфиг локального компьютера перезаписывает конфиг среды. (global->dev->local). Так обеспечивается естественное желение разработчика потестить под разными уровнями логирования/кэширования и т.п.


    1. JIghtuse
      29.09.2015 16:12

      Самый адекватный подход, я считаю. Гибкий и понятный.
      Подобными слоями устроены настройки в дистрибутивах Linux; в том же git.


    1. AShushunov
      29.09.2015 16:19

      К сожалению, дело не всегда ограничивается конфигами. Я работал на проекте, в котором для сборки на локальной машине надо было менять pom-файл (описание сборки проекта для maven). Это и было одной из предпосылок для этого поста.


  1. nitso
    29.09.2015 14:59
    -1

    А добавить в git нужный конфиг, а потом прописать его в .gitignore не решает вашу проблему?


    1. poxu
      29.09.2015 15:41
      +3

      Если файл уже добавлен в гит, то гит проигнорирует запись в гитигноре.


  1. poxu
    29.09.2015 15:42

    Я не совсем понял, чем вас не устраивает второй вариант.


    1. AShushunov
      29.09.2015 16:29

      Как я писал выше (http://habrahabr.ru/post/267927/#comment_8595311), не всегда дело ограничивается конфигами.
      Есть еще случаи, когда нельзя просто так взять и изменить способ загрузки конфигов на живом проекте, которому > 10 лет.


  1. dMetrius
    29.09.2015 16:50

    >>Не сработает. Только untracked файлы можно исключать из-под версионного контроля.
    Ваша неправда:

    git rm --cached filename
    и после этого добавляем его в .gitignore


    1. AShushunov
      29.09.2015 17:00

      Так git rm убирает файлы из-под версионного контроля.
      И как после этого быть с требованием:
      «В этом файле периодически меняются общие настройки, и разработчик хочет их получать (pull) и публиковать (commit)»


  1. viatoriche
    29.09.2015 18:48
    +1

    Мы храним все конфиги в системе управления конфигурацией (у нас ansible). Конфигурация (куча yaml файлов и jinja шаблонов) версионируется гитом, настроены разные хосты, роли, плейбуки, разная конфигурация для vagrant, production, test-server, local-development, remote-development и так далее. Нам нравится.