Фіксація конкретних файлів конфігурації машини


82

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

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

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


1
Це схоже на те, що у вас є проблеми у дизайні та мозку колеги. Скажіть їм, щоб вони переконались, що вони знають, що вони роблять у системі керування джерелами, інакше вони будуть перевіряти лайно, що вони вам не потрібні. Також: Чому б просто не розділити файл, по одному файлу для кожної системи?
Под

11
Я майже впевнений, що це досить поширений сценарій? Як ви відстежуєте конкретну конфігурацію машини? Розбиття файлу на кожну систему здається досить безладною і начебто перемагає мету розподіленого контролю версій: якщо його виписують на новій машині, не повинно реєструватися новий файл.
ghempton

1
Можливо, ви зможете принаймні запобігти введенню коммітуючих злому за допомогою гачка попереднього оновлення на будь-якому спільному сховищі, до якого ви всі натискаєте. Він може шукати коміти, які модифікують файл конфігурації, зроблені певними розробниками, або може шукати коміти, що стосуються цього файлу, які не згадуються за спеціальним ключовим словом у повідомленні.
Phil Miller,

2
+1, це є спільною проблемою. @Pod: Нереально мати "Joe.conf" у репо, але ви все одно хочете мати можливість інколи оновлювати речі ... іноді конфігурації повинні зазнати змін через зміни коду.
Танатос

Відповіді:


59

Нехай ваша програма прочитає пару конфігураційних файлів для своїх налаштувань. По-перше, він повинен прочитати config.defaultsфайл, який буде включено до сховища. Потім він повинен прочитати config.localфайл, який повинен бути в списку.gitignore

За допомогою цієї домовленості нові налаштування з’являються у файлі за замовчуванням і набирають чинності, щойно оновиться. Вони змінюватимуться лише в певних системах, якщо їх перевизначити.

Як варіацію цього, ви можете мати лише загальний configфайл, який ви передаєте в систему керування версіями, і зробити так, include config.localщоб він вводив значення, характерні для машини. Це вводить більш загальний механізм (проти політики) у ваш код і, отже, забезпечує більш складні конфігурації (якщо це бажано для вашої програми). Популярне розширення з цього, яке можна побачити у багатьох масштабних програмах з відкритим кодом, - до 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.


Ви можете знайти більш детальну інформацію про ці команди в питанні різниці між «припустить, без змін» і «пропуск worktree» . З верхнього відповіді , схоже , що ви хочете --skip-worktreeв цьому випадку.
Почутливий

10

Інший підхід полягає у підтримці локальних змін загальних файлів конфігурації в іншій приватній гілці. Я роблю це для деяких проектів, які вимагають декількох локальних змін. Цей прийом може застосовуватися не у всіх ситуаціях, але в деяких випадках він працює для мене.

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

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робити те саме
Danilo Souza Morães

8

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


Так, це додаткове вдосконалення, але воно все ще не є оптимальним, оскільки, якщо зареєстрована версія навмисно змінена, це не відображається на конфігураційних файлах розробника. Прикладом того, коли це було б корисно, є додавання нової властивості тощо
ghempton

Це може бути адреса з хорошими примітками про коміти та описовими повідомленнями про помилки, які скаржаться, коли властивість не встановлено. Також допоможе електронний лист, що повідомляється вашій команді про зміни.
Brian Kelly

Для отримання додаткової інформації про це рішення та чудового прикладу див. Цю відповідь .
Почутливий

1

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

Крім того, напишіть скрипт швидкої інсталяції, який встановлює символічні посилання для вашої програми.

Ми застосовували подібний підхід у попередній компанії. Сценарій встановлення автоматично визначив, в якому середовищі ви працювали (пісочниця, розробка, контроль якості, виробництво), і автоматично зробив би правильно. Якщо у вас був файл config.sandbox, і ви працювали з пісочниці, він би зв’язав це (інакше він просто зв’язав би файл .defaults). Загальноприйнятою процедурою було копіювання .default та зміна налаштувань за необхідності.

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


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 vars, наприклад, в 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')

0

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

Однак наявність файлу .defaultі .localconfig є, зрештою, найкращим рішенням, оскільки це дозволяє кожному, хто має машинну конфігурацію, змінювати за замовчуванням, не порушуючи власну конфігурацію.


Це не працює - якщо файли відстежуються та додаються до .gitignoreпізніших, зміни все одно відстежуються.
Zeemee

0

Я роблю це так, як це рекомендується тут із типовими та локальними файлами конфігурації. Для управління своїми локальними конфігураційними файлами, які є в проектах .gitignore, я зробив git-репо ~/settings. Там я керую всіма своїми локальними налаштуваннями з усіх проектів. Ви можете створити, наприклад , папку project1в ~/settingsі помістити все місцеві конфігураційні файли для цього проекту в нього. Після цього ви можете символічно зв'язати ці файли / папки зі своїм project1.

Завдяки такому підходу ви можете відстежувати свої локальні конфігураційні файли і не розміщувати їх у звичайному сховищі вихідного коду.


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і швидко майстри переадресації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.