Фиксация Файлов Конфигурации Конкретной Машины


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

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

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

9 73

9 ответов:

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

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

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

и посмотреть мои ответ на подобный вопрос.

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

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

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

git checkout -b work master

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

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

#!/bin/sh

BRANCH=`git branch | grep ^\* | cut -d' ' -f2`
if [ $BRANCH != "master" ]; then
  echo ": 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.

наконец, после нажатия изменений в master upstream, я переключаюсь обратно на work и rebase:

git checkout work
git rebase master

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

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

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

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

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

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

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

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

    <?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_");

вашего сайта.по умолчанию.свойства будут выглядеть так:

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

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

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>

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

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

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

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

основываясь на ответе @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 опять таким же образом.

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