Файлы конфигурации конкретной машины

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

Простым решением этого было бы просто не проверить их на Git или даже добавить к ним запись .gitignore. Тем не менее, я считаю, что гораздо более элегантно иметь некоторые разумные значения по умолчанию в файле, который разработчик может изменить в соответствии с его потребностями.

Есть ли элегантный способ сделать Git хорошо играть с такими файлами? Я хотел бы иметь возможность изменять конфигурационный файл, специфичный для машины, а затем иметь возможность запускать "git commit -a" без проверки этого файла.

Ответ 1

Попросите вашу программу прочитать пару конфигурационных файлов для своих настроек. Во-первых, он должен прочитать файл config.defaults, который будет включен в репозиторий. Затем он должен прочитать файл config.local, который должен быть указан в .gitignore

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

Как вариант, вы можете иметь только общий файл config, который вы отправляете в управлении версиями, и попросите его сделать что-то вроде include config.local, чтобы ввести конкретные значения для машины. Это вводит более общий механизм (по сравнению с политикой) в вашем коде и, следовательно, позволяет более сложные конфигурации (если это желательно для вашего приложения). Популярное расширение от этого, видимое во многих крупномасштабных программах с открытым исходным кодом, - это include conf.d, который считывает конфигурацию из всех файлов в каталоге.

Также см. мой ответ на аналогичный вопрос.

Ответ 2

Вы можете попробовать git update-index --skip-worktree filename. Это сообщит git, чтобы притвориться, что локальные изменения в имени файла не существуют, поэтому git commit -a игнорирует его. Он имеет дополнительное преимущество, также сопротивляясь git reset --hard, поэтому вы не будете случайно потерять локальные изменения. Кроме того, автоматические слияния будут изящно сбой, если файл будет изменен вверх по течению (если копия рабочего каталога не будет соответствовать индексной копии, и в этом случае она будет автоматически обновлена). Недостатком является то, что команда должна выполняться на всех задействованных машинах, и это сложно сделать автоматически. См. Также git update-index --assume-unchanged для немного отличающейся версии этой идеи. Подробности обоим можно найти с помощью git help update-index.

Ответ 3

Другой подход - поддерживать локальные изменения в общих файлах конфигурации в другой частной ветке. Я делаю это для некоторых проектов, требующих нескольких локальных изменений. Этот метод может быть неприменим ко всем ситуациям, но в некоторых случаях он работает для меня.

Сначала я создаю новую ветку, основанную на главной ветке (в этом конкретном случае я использую git -svn, поэтому мне нужно выполнить команду master, но это не очень важно здесь):

git checkout -b work master

Теперь измените конфигурационный файл по мере необходимости и зафиксируйте. Я обычно добавляю что-то своеобразное в сообщении фиксации типа "NOCOMMIT" или "PRIVATE" (это будет полезно позже). На этом этапе вы можете работать в своем частном филиале с помощью собственного файла конфигурации.

Когда вы хотите подтолкнуть свою работу назад, вишни выберите каждое изменение из ветки work на хозяина. У меня есть script, чтобы сделать это, что выглядит примерно так:

#!/bin/sh

BRANCH=`git branch | grep ^\\* | cut -d' ' -f2`
if [ $BRANCH != "master" ]; then
  echo "$0: Current branch is not master"
  exit 1
fi

git log --pretty=oneline work...master | grep -v NOCOMMIT: | cut -d' ' -f1 | tac | xargs -l git cherry-pick

Это сначала проверяет, чтобы я был на ветке master (проверка работоспособности). Затем он перечисляет каждую фиксацию в work, отфильтровывает те, которые упоминают ключевое слово NOCOMMIT, меняет порядок и, наконец, вишня выбирает каждую фиксацию (теперь с самого старого) в master.

Наконец, после нажатия изменений в главном потоке вверх, я вернусь к work и rebase:

git checkout work
git rebase master

Git будет повторно применять каждый из коммитов в ветке work, эффективно пропуская те, которые уже были применены в master, через выбор вишни. То, что вам нужно оставить, - это только локальные коммиты NOCOMMIT.

Этот метод делает процесс push более трудоемким, но он решил проблему для меня, поэтому я думал, что буду делиться.

Ответ 4

Одна возможность состоит в том, чтобы иметь фактические файлы в вашем .gitignore, но проверить конфигурации по умолчанию с другим расширением. Типичным примером для приложения Rails будет файл config/database.yml. Мы проверили бы config/database.yml.sample, и каждый разработчик создаст свой собственный config/database.yml, который уже является .gitignored.

Ответ 5

Проверьте конфигурацию по умолчанию с другим расширением (например,.default), используйте символическую ссылку для ссылки по умолчанию на правильное местоположение, добавьте правильное местоположение в .gitignore и добавьте все остальное, связанное с конфигурацией, в .gitignore (поэтому единственное, что проверяется, это config.default).

Кроме того, напишите быструю установку script, которая устанавливает символические ссылки для вашей прикладной программы.

Мы использовали аналогичный подход в предыдущей компании. Установкой script автоматически определяется, в какой среде вы работали (песочница, разработка, QA, производство) и автоматически выполняли бы правильные действия. Если у вас был файл config.sandbox и запущен из песочницы, он свяжет это (в противном случае он просто свяжет файл .defaults). Общая процедура заключалась в том, чтобы скопировать .defaults и изменить настройки по мере необходимости.

Написание установки script проще, чем вы могли себе представить, и дает вам большую гибкость.

Ответ 6

Я согласен с лучшим ответом, но хочу добавить что-то. Я использую ANT script для удаления и изменения файлов из репозитория GIT, поэтому я уверен, что никакие производственные файлы не будут перезаписаны. В ANT есть хороший вариант для изменения файлов java-property. Это означает, что ваши локальные тестовые переменные в файле свойств в стиле Java и добавлении некоторого кода для его обработки, но это дает вам возможность автоматизировать создание вашего сайта до того, как вы его подключите через Интернет. Как правило, вы отправляете свою производственную информацию в файл site.default.properties и позволяете ANT управлять настройками. Ваши локальные настройки будут находиться в site.local.properties.

    <?php
/**
 * This class will read one or two files with JAVA style property files. For instance site.local.properties & site.default.properties
 * This will enable developers to make config files for their personal development environment, while maintaining a config file for 
 * the production site. 
 * Hint: use ANT to build the site and use the ANT <propertyfile> command to change some parameters while building.
 * @author martin
 *
 */
class javaPropertyFileReader {

    private $_properties;
    private $_validFile;

    /**
     * Constructor
     * @return javaPropertyFileReader
     */
    public function   __construct(){
        $this->_validFile = false;
        return $this;
    }//__construct

    /**
     * Reads one or both Java style property files
     * @param String $filenameDefaults
     * @param String $filenameLocal
     * @throws Exception
     * @return javaPropertyFileReader
     */
    public function readFile($filenameDefaults, $filenameLocal = ""){

        $this->handleFile($filenameDefaults);
        if ($filenameLocal != "") $this->handleFile($filenameLocal);
    }//readFile

    /**
     * This private function will do all the work of reading the file and  setting up the properties
     * @param String $filename
     * @throws Exception
     * @return javaPropertyFileReader
     */
    private function handleFile($filename){

    $file = @file_get_contents($filename);

    if ($file === false) {
         throw (New Exception("Cannot open property file: " . $filename, "01"));
    }
    else {
        # indicate a valid file was opened
        $this->_validFile = true;

        // if file is Windows style, remove the carriage returns
        $file = str_replace("\r", "", $file);

        // split file into array : one line for each record
        $lines = explode("\n", $file);

        // cycle lines from file
        foreach ($lines as $line){
            $line = trim($line);

            if (substr($line, 0,1) == "#" || $line == "") {
                #skip comment line
            }
            else{
                // create a property via an associative array
                $parts   = explode("=", $line);
                $varName = trim($parts[0]);
                $value   = trim($parts[1]);

                // assign property
                $this->_properties[$varName] = $value;
            }
        }// for each line in a file
    }
    return $this;
    }//readFile

    /**
     * This function will retrieve the value of a property from the property list.
     * @param String $propertyName
     * @throws Exception
     * @return NULL or value of requested property
     */
    function getProperty($propertyName){
        if (!$this->_validFile) throw (new Exception("No file opened", "03"));

        if (key_exists($propertyName, $this->_properties)){
            return $this->_properties[$propertyName];
        }
        else{
          return NULL;
        }
    }//getProperty

    /**
     * This function will retreive an array of properties beginning with a certain prefix.
     * @param String $propertyPrefix
     * @param Boolean $caseSensitive
     * @throws Exception
     * @return Array
     */
    function getPropertyArray($propertyPrefix, $caseSensitive = true){
        if (!$this->_validFile) throw (new Exception("No file opened", "03"));

        $res = array();

        if (! $caseSensitive) $propertyPrefix= strtolower($propertyPrefix);

        foreach ($this->_properties as $key => $prop){
            $l = strlen($propertyPrefix);

            if (! $caseSensitive) $key = strtolower($key);

            if (substr($key, 0, $l ) == $propertyPrefix) $res[$key] = $prop;
        }//for each proprty

        return $res;
    }//getPropertyArray

    function createDefineFromProperty($propertyName){
        $propValue = $this->getProperty($propertyName);
        define($propertyName, $propValue);
    }//createDefineFromProperty


    /**
     * This will create a number of 'constants' (DEFINE) from an array of properties that have a certain prefix.
     * An exception is thrown if 
     * @param  String $propertyPrefix
     * @throws Exception
     * @return Array The array of found properties is returned.
     */
    function createDefinesFromProperties($propertyPrefix){
        // find properties
        $props = $this->getPropertyArray($propertyPrefix);

        // cycle all properties 
        foreach($props as $key => $prop){

            // check for a valid define name
            if (preg_match("'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'", $key)) {
                define($key, $prop);
            }   
            else{
                throw (new Exception("Invalid entry in property file: cannot create define for {" . $key . "}", "04"));
            }   
        }// for each property found

        return $props;
    }//createDefineFromProperty

}//class javaPropertyFileReader

затем используйте его:

  $props = new javaPropertyFileReader();
  $props->readFile($_SERVER["DOCUMENT_ROOT"] . "/lib/site.default.properties",$_SERVER["DOCUMENT_ROOT"] . "/lib/site.local.properties");

  #create one DEFINE
  $props->createDefineFromProperty("picture-path");

  # create a number of DEFINEs for enabled modules
  $modules = $props->createDefinesFromProperties("mod_enabled_");

Ваш файл site.default.properties будет выглядеть так:

release-date=x
environment=PROD
picture-path=/images/

SITE_VERSION_PRODUCTION=PROD
SITE_VERSION_TEST=TEST
SITE_VERSION_DEVELOP=DEV

# Available Modules
mod_enabled_x=false
mod_enabled_y=true
mod_enabled_z=true

и ваш сайт .local.properties будет выглядеть (обратите внимание на среду различий и включенные модули):

release-date=x
environment=TEST
picture-path=/images/

SITE_VERSION_PRODUCTION=PROD
SITE_VERSION_TEST=TEST
SITE_VERSION_DEVELOP=DEV

# Available Modules
mod_enabled_x=true
mod_enabled_y=true
mod_enabled_z=true

И ваши инструкции ANT: ($ d {deploy} является вашим целевым каталогом развертывания)

<propertyfile
    file="${deploy}/lib/site.properties"
    comment="Site properties">
    <entry  key="environment" value="PROD"/>
    <entry  key="release-date" type="date" value="now" pattern="yyyyMMddHHmm"/>
</propertyfile>

Ответ 7

Самое простое решение - отредактировать файл по умолчанию, зафиксировать его, а затем добавить в свой .gitignore. Таким образом, разработчики не будут случайно совершать это при выполнении git commit -a, но они все равно могут зафиксировать его в (предположительно редком) случае, когда вы хотите изменить свои значения по умолчанию с помощью git add --force.

Однако наличие конфигурационного файла .default и .local в конечном итоге является лучшим решением, поскольку это позволяет любому, у кого есть конкретная конфигурация машины, изменять значения по умолчанию, не нарушая их собственной конфигурации.

Ответ 8

Я делаю так, как это рекомендуется здесь, с файлами по умолчанию и локальными конфигурациями. Чтобы управлять локальными конфигурационными файлами, которые находятся в проектах .gitignore, я создал git repo ~/settings. Там я управляю всеми своими локальными настройками из всех проектов. Вы создаете, например, папку project1 в ~/settings и помещаете в нее все файлы локальной конфигурации для этого проекта. После этого вы можете привязать эти файлы/папку к вашему project1.

С помощью этого подхода вы можете отслеживать локальные файлы конфигурации и не помещать их в обычный репозиторий исходного кода.