Фиксация файлов конфигурации для конкретного компьютера

82

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

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

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

Ghempton
источник
1
Похоже, у вас проблемы с дизайном и с мозгами вашего коллеги. Скажите им, чтобы они знали, что они передают в систему управления версиями, иначе они будут проверять дерьмо, которое им не нужно. Также: почему бы просто не разделить файл, по одному файлу для каждой системы?
Pod,
11
Я почти уверен, что это довольно распространенный сценарий? Как вы отслеживаете конфигурацию конкретной машины? Разделение файла для каждой системы кажется довольно запутанным и в
некотором роде противоречит
1
Вы могли бы хотя бы предотвратить внесение критических коммитов, используя ловушку предварительного обновления в любом общем репозитории, в который вы все нажимаете. Он может искать коммиты, которые изменяют файл конфигурации, сделанный определенными разработчиками, или он может искать коммиты, касающиеся этого файла, которые не упоминаются для специального ключевого слова в сообщении.
Фил Миллер,
2
+1, это является общей проблемой. @Pod: Нецелесообразно иметь "Joe.conf" в репо, но вы все равно хотите иметь возможность обновлять что-то время от времени ... иногда конфигурации должны претерпевать изменения из-за изменений в коде.
Thanatos

Ответы:

59

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

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

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

Также см мой ответ на подобный вопрос.

Фил Миллер
источник
Я дам ответ. Этот метод позволяет достичь желаемого эффекта с единственным недостатком: он требует дополнительной логики со стороны приложения.
ghempton,
17

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

Bhuber
источник
Вы можете найти дополнительную информацию об этих командах в вопросе « Разница между« предположить без изменений »и« пропустить-рабочее дерево » . Судя по верхнему ответу , --skip-worktreeв этом случае похоже, что вы хотите .
Senseful
10

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

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

git checkout -b work master

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

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

#!/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и перебазирую:

git checkout work
git rebase master

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

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

Грег Хьюгилл
источник
2
Вы понимаете, что просите об этом забывчивого человека, не задающего вопросы? Человек, который просто беззаботно бегает git commit -aпо миру?
Фил Миллер,
Следуя той же стратегии, вы можете пометить фиксацию, в которой вы устанавливаете свои локальные файлы конфигурации, и использовать комбинацию git rebase --ontoи git fetchделать то же самое
Данило Соуза Морайнс
8

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

hgmnz
источник
Да, это постепенное улучшение, но оно все еще не оптимально, так как если проверенная версия намеренно изменена, это не отражается в файлах конфигурации разработчика. Пример того, когда это было бы полезно, - добавление нового свойства и т. Д.
ghempton
Это может быть адрес с хорошими примечаниями к фиксации и описательными сообщениями об ошибках, которые жалуются, когда свойство не установлено. Также помогает сообщение электронной почты вашей команде об изменении.
Брайан Келли,
Дополнительные сведения об этом решении и отличный пример см. В этом ответе .
Senseful 03
1

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

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

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

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

Брайан Алвес
источник
1

Я согласен с лучшим ответом, но также хотел бы кое-что добавить. Я использую сценарий ANT для удаления и изменения файлов из репозитория GIT, поэтому я уверен, что производственные файлы не будут перезаписаны. В ANT есть удобная опция для изменения файлов свойств java. Это означает помещение ваших локальных тестовых переменных в файл свойств в стиле Java и добавление некоторого кода для его обработки, но это дает вам возможность автоматизировать создание вашего сайта, прежде чем вы отправите его через FTP. Обычно вы помещаете свою производственную информацию в файл 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

и ваш site.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>
Пианино
источник
1

В настоящее время (2019 г.) я использую переменные ENV, например, в python / django, вы также можете добавить к ним значения по умолчанию. В контексте docker я могу сохранить переменные ENV в файле docker-compose.yml или в дополнительном файле, который игнорируется в системе контроля версий.

# settings.py
import os
DEBUG = os.getenv('DJANGO_DEBUG') == 'True'
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')
Yvess
источник
0

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

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

crazy2be
источник
Это не работает - если файлы отслеживаются и добавляются в более .gitignoreпоздние версии , изменения все равно отслеживаются.
Zeemee
0

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

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

Yvess
источник
0

Основываясь на ответе @Greg Hewgill, вы можете добавить конкретный коммит с вашими локальными изменениями и пометить его как localchange:

git checkout -b feature master
vim config.local
git add -A && git commit -m "local commit" && git tag localchange

Затем приступайте к добавлению коммитов вашей функции. После завершения работы вы можете объединить эту ветку обратно в master без фиксации localchange, выполнив следующие действия:

git rebase --onto master localchange feature
git fetch . feature:master
git cherry-pick localchange
git tag localchange -f

Эти команды будут:

1) Переустановите свою функциональную ветку на master, игнорируя фиксацию localchange. 2) Мастер быстрой перемотки вперед, не покидая функциональную ветку. 3) Добавьте фиксацию localchange обратно в верхнюю часть функциональной ветки, чтобы вы могли продолжить работу над ней. Вы можете сделать это с любой другой веткой, над которой хотите продолжить работу. 4) Сбросьте тег localchange на эту выбранную фиксацию, чтобы мы могли использовать rebase --ontoснова таким же образом.

Это не означает замену принятого ответа как лучшего общего решения, а как способ нестандартного мышления о проблеме. Вы в основном избежать случайного объединения локальных изменений в мастер лишь перебазирование от localchangeдо featureи быстро мастера переадресации.

Данило Соуза Морайнш
источник