Zatwierdzanie plików konfiguracyjnych specyficznych dla komputera

82

Typowy scenariusz, kiedy tworzę, jest taki, że baza kodu będzie zawierała kilka plików konfiguracyjnych, które wymagają ustawień specyficznych dla komputera. Te pliki zostaną wpisane do Gita, a inni programiści zawsze przypadkowo sprawdzą je z powrotem i zepsują konfigurację innej osoby.

Prostym rozwiązaniem byłoby po prostu nie wpisywanie ich do Gita, a nawet dodanie dla nich wpisu .gitignore. Jednak uważam, że o wiele bardziej eleganckie jest posiadanie pewnych rozsądnych ustawień domyślnych w pliku, które programista może zmodyfikować zgodnie ze swoimi potrzebami.

Czy istnieje elegancki sposób, aby Git ładnie współpracował z takimi plikami? Chciałbym móc zmodyfikować plik konfiguracyjny specyficzny dla komputera, a następnie uruchomić „git commit -a” bez wpisywania tego pliku.

ghempton
źródło
1
Wygląda na to, że masz problem w swoim projekcie i mózgach kolegi. Powiedz im, aby się upewnili, że wiedzą, co wprowadzają do systemu kontroli źródła, w przeciwnym razie będą sprawdzać bzdury, których nie chcesz. Ponadto: dlaczego nie podzielić pliku, po jednym pliku dla każdego systemu?
Pod
11
Jestem prawie pewien, że to dość powszechny scenariusz? Jak można śledzić konfigurację konkretnej maszyny? Dzielenie pliku dla każdego systemu wydaje się dość kłopotliwe i w pewnym sensie udaremnia cel posiadania rozproszonej kontroli wersji: jeśli jest on wyewidencjonowany na nowym komputerze, nie powinno być
zaewidencjonowanego
1
Możesz przynajmniej zapobiec wprowadzaniu zrywających zatwierdzeń za pomocą haka przed aktualizacją na jakimkolwiek współdzielonym repozytorium, do którego wszyscy pchasz. Może szukać zatwierdzeń, które modyfikują plik konfiguracyjny utworzony przez niektórych programistów, lub może szukać zatwierdzeń dotyczących tego pliku, które nie wspominają o specjalnym słowie kluczowym w wiadomości.
Phil Miller
2
+1, to jest to wspólny problem. @Pod: Niepraktyczne jest posiadanie „Joe.conf” w repozytorium, ale nadal chcesz mieć możliwość czasami aktualizowania rzeczy ... czasami konfiguracje muszą ulec zmianie z powodu zmian w kodzie.
Thanatos

Odpowiedzi:

59

Niech twój program przeczyta parę plików konfiguracyjnych dla swoich ustawień. Najpierw powinien odczytać config.defaultsplik, który miałby znaleźć się w repozytorium. Następnie powinien odczytać config.localplik, który powinien być wymieniony w.gitignore

W takim układzie nowe ustawienia pojawiają się w pliku ustawień domyślnych i zaczynają obowiązywać zaraz po ich zaktualizowaniu. Będą się różnić w poszczególnych systemach tylko wtedy, gdy zostaną zastąpione.

Jako odmianę tego, możesz mieć tylko ogólny configplik, który wysyłasz w ramach kontroli wersji i sprawi, że będzie wykonywał coś w rodzaju include config.localwprowadzenia wartości specyficznych dla maszyny. Wprowadza to bardziej ogólny mechanizm (w przeciwieństwie do zasad) w kodzie, a tym samym umożliwia bardziej skomplikowane konfiguracje (jeśli jest to pożądane w przypadku aplikacji). Popularnym rozszerzeniem tego, widzianym w wielu programach open source na dużą skalę, jest to include conf.d, które odczytuje konfigurację ze wszystkich plików w katalogu.

Zobacz także moją odpowiedź na podobne pytanie.

Phil Miller
źródło
Dam na to odpowiedź. Ta metoda daje pożądany efekt, a jedyną wadą jest to, że wymaga dodatkowej logiki ze strony aplikacji.
ghempton
17

Możesz spróbować git update-index --skip-worktree filename. To każe gitowi udawać, że lokalne zmiany nazwy pliku nie istnieją, więc git commit -aje zignoruje. Ma tę dodatkową zaletę, że jest również odporny git reset --hard, więc przypadkowo nie utracisz lokalnych zmian. Ponadto automatyczne scalanie zakończy się niepowodzeniem, jeśli plik zostanie zmieniony nadrzędnie (chyba że kopia katalogu roboczego pasuje do kopii indeksu, w którym to przypadku zostanie automatycznie zaktualizowana). Wadą jest to, że polecenie musi być uruchamiane na wszystkich zaangażowanych komputerach i trudno jest to zrobić automatycznie. Zobacz także, git update-index --assume-unchangedaby uzyskać nieco inną wersję tego pomysłu. Szczegóły dotyczące obu można znaleźć pod adresem git help update-index.

bhuber
źródło
Więcej informacji na temat tych poleceń można znaleźć w pytaniu Różnica między „założenie niezmienione” a „pominięcie drzewa pracy” . Z górnej odpowiedzi wygląda na to, że --skip-worktreew tym przypadku chcesz .
Senseful
10

Innym podejściem jest zachowanie lokalnych zmian we wspólnych plikach konfiguracyjnych w innej gałęzi prywatnej. Robię to dla niektórych projektów, które wymagają kilku lokalnych zmian. Ta technika może nie mieć zastosowania we wszystkich sytuacjach, ale w niektórych przypadkach działa.

Najpierw tworzę nową gałąź na podstawie gałęzi master (w tym konkretnym przypadku używam git-svn, więc muszę zatwierdzić od mastera, ale nie jest to szczególnie ważne):

git checkout -b work master

Teraz zmodyfikuj pliki konfiguracyjne zgodnie z potrzebami i zatwierdź. Zwykle umieszczam w komunikacie o zmianach coś charakterystycznego, na przykład „NOCOMMIT” lub „PRIVATE” (będzie to przydatne później). W tym momencie możesz pracować na swojej prywatnej gałęzi, używając własnego pliku konfiguracyjnego.

Jeśli chcesz popchnąć swoją pracę z powrotem w górę, wybierz każdą zmianę ze swojej workgałęzi do mastera. Mam skrypt, który pomoże mi to zrobić, który wygląda mniej więcej tak:

#!/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

To pierwsze sprawdzenie, czy jestem w masteroddziale (kontrola poczytalności). Następnie wyświetla listę wszystkich zatwierdzeń work, odfiltrowuje te, które wspominają o słowie kluczowym NOCOMMIT, odwraca kolejność i ostatecznie wybiera każde zatwierdzenie (teraz od najstarszego) do master.

Wreszcie, po wprowadzeniu zmian w master upstream, przełączam się z powrotem na worki rebase:

git checkout work
git rebase master

Git ponownie zastosuje każde z zatwierdzeń w workgałęzi, skutecznie pomijając te, które zostały już zastosowane podczas masterwybierania wiśniowego. Powinieneś pozostać tylko z lokalnymi zatwierdzeniami NOCOMMIT.

Ta technika sprawia, że ​​proces wypychania jest nieco bardziej czasochłonny, ale rozwiązała problem, więc pomyślałem, że się nim podzielę.

Greg Hewgill
źródło
2
Zdajesz sobie sprawę, że prosisz o to nieświadomego nie zadającego pytań? Osoba, która po prostu biegnie git commit -abez troski po świecie?
Phil Miller
Postępując zgodnie z tą samą strategią, możesz oznaczyć zatwierdzenie, w którym ustawiasz lokalne pliki konfiguracyjne i użyć kombinacji git rebase --ontoi git fetchzrobić to samo
Danilo Souza Morães
8

Jedną z możliwości jest posiadanie rzeczywistych plików w swoim .gitignore, ale wpisanie domyślnych konfiguracji z innym rozszerzeniem. Typowym przykładem aplikacji Railsowej byłby plik config / database.yml. Sprawdzilibyśmy w config / database.yml.sample, a każdy programista utworzył swój własny plik config / database.yml, który jest już .gitignored.

hgmnz
źródło
Tak, jest to stopniowe ulepszenie, ale nadal nie jest optymalne, ponieważ jeśli wpisana wersja zostanie celowo zmieniona, nie zostanie to odzwierciedlone w plikach konfiguracyjnych programisty. Przykładem sytuacji, w których byłoby to przydatne, jest dodanie nowej właściwości itp.
ghempton
Może to być adres z dobrymi notatkami do zatwierdzenia i opisowymi komunikatami o błędach, które narzekają, gdy właściwość nie jest ustawiona. Pomocny jest również e-mail informujący Twój zespół o zmianie.
Brian Kelly
Aby uzyskać więcej informacji na temat tego rozwiązania i świetny przykład, zobacz tę odpowiedź .
Senseful
1

Sprawdź domyślną konfigurację z innym rozszerzeniem (powiedzmy .default), użyj dowiązania symbolicznego do domyślnego dowiązania symbolicznego do właściwej lokalizacji, dodaj poprawną lokalizację do .gitignore i dodaj wszystko inne związane z konfiguracją do .gitignore (więc jedyne rzeczą, która jest wpisywana, jest config.default).

Dodatkowo napisz skrypt szybkiej instalacji, który ustawi dowiązania symboliczne dla całej aplikacji.

Podobne podejście zastosowaliśmy w poprzedniej firmie. Skrypt instalacyjny automatycznie wykrył, w jakim środowisku działasz (piaskownica, programowanie, kontrola jakości, produkcja) i automatycznie zrobiłby właściwą rzecz. Gdybyś miał plik config.sandbox i był uruchomiony z piaskownicy, połączyłby go (w przeciwnym razie po prostu połączyłby plik .defaults). Typową procedurą było skopiowanie .defaults i zmiana ustawień w razie potrzeby.

Pisanie skryptu instalacyjnego jest łatwiejsze, niż można by sobie wyobrazić, i zapewnia dużą elastyczność.

Bryan Alves
źródło
1

Zgadzam się z najlepszą odpowiedzią, ale chciałbym też coś dodać. Używam skryptu ANT do usuwania i modyfikowania plików z repozytorium GIT, więc jestem pewien, że żadne pliki produkcyjne nie zostaną nadpisane. W ANT jest fajna opcja modyfikacji plików właściwości java. Oznacza to umieszczenie lokalnych zmiennych testowych w pliku właściwości w stylu Java i dodanie kodu do jego przetworzenia, ale daje to możliwość zautomatyzowania budowania witryny przed wysłaniem jej na serwer FTP. Zwykle informacje o produkcji należy umieścić w pliku site.default.properties i pozwolić ANT zarządzać ustawieniami. Twoje ustawienia lokalne będą znajdować się w 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

następnie użyj go:

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

Twoja site.default.properties wyglądałaby następująco:

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

a Twoja witryna site.local.properties będzie wyglądać (zwróć uwagę na różnice w środowisku i włączonych modułach):

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

Oraz instrukcje ANT: ($ d {deploy} to katalog docelowy wdrożenia)

<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>
Pianoman
źródło
1

Obecnie (2019) używam zmiennych ENV np. W pythonie / django, można też dodawać do nich wartości domyślne. W kontekście dockera mogę zapisać zmienne ENV w pliku docker-compose.yml lub w dodatkowym pliku, który jest ignorowany w kontroli wersji.

# settings.py
import os
DEBUG = os.getenv('DJANGO_DEBUG') == 'True'
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')
tak
źródło
0

Najprostszym rozwiązaniem jest edycja pliku do wartości domyślnych, zatwierdzenie go, a następnie dodanie do pliku .gitignore. W ten sposób programiści nie popełnią tego przypadkowo podczas wykonywania git commit -a, ale nadal mogą to zrobić w (prawdopodobnie rzadkim) przypadku, w którym chcesz zmienić ustawienia domyślne za pomocą git add --force.

Jednak posiadanie pliku konfiguracyjnego .defaulti .localjest ostatecznie najlepszym rozwiązaniem, ponieważ umożliwia to każdemu, kto ma konfigurację specyficzną dla komputera, zmianę ustawień domyślnych bez konieczności przerywania własnej konfiguracji.

crazy2be
źródło
To nie działa - jeśli pliki są śledzone i dodawane do .gitignorepóźniejszych, zmiany są nadal śledzone.
Zeemee
0

Robię to tak, jak jest to zalecane tutaj z domyślnymi i lokalnymi plikami konfiguracyjnymi. Aby zarządzać moimi lokalnymi plikami konfiguracyjnymi, które są w projektach .gitignore, utworzyłem repozytorium git ~/settings. Tam zarządzam wszystkimi moimi ustawieniami lokalnymi ze wszystkich projektów. Utworzyć, na przykład folder project1w ~/settingsi umieścić wszystkie rzeczy lokalną config dla tego projektu do niego. Następnie możesz dowiązać symbolicznie te pliki / folder do swojego project1.

Dzięki takiemu podejściu możesz śledzić lokalne pliki konfiguracyjne i nie umieszczać ich w normalnym repozytorium kodu źródłowego.

tak
źródło
0

Opierając się na odpowiedzi @Greg Hewgill, możesz dodać określone zatwierdzenie z lokalnymi zmianami i oznaczyć je jako zmianę lokalną:

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

Następnie przejdź do dodawania zatwierdzeń funkcji. Po zakończeniu pracy możesz scalić tę gałąź z powrotem do mastera bez zatwierdzania zmiany lokalnej, wykonując następujące czynności:

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

Te polecenia:

1) Przebuduj gałąź funkcji do mastera, ignorując zatwierdzenie zmiany lokalnej. 2) Przewiń do przodu master bez opuszczania gałęzi funkcji 3) Dodaj zatwierdzenie zmiany lokalnej z powrotem na górę gałęzi funkcji, abyś mógł kontynuować pracę nad nią. Możesz to zrobić w dowolnej innej branży, w której chcesz kontynuować pracę. 4) Zresetuj tag zmiany lokalnej do tego wybranego zatwierdzenia, abyśmy mogli użyć go rebase --ontoponownie w ten sam sposób.

Nie ma to na celu zastąpienia zaakceptowanej odpowiedzi jako najlepszego rozwiązania ogólnego, ale jako sposób nieszablonowego myślenia o problemie. Zasadniczo unikasz przypadkowego łączenia lokalnych zmian z wzorcem, zmieniając tylko bazę od localchangedo featurei przewijając do przodu .

Danilo Souza Morães
źródło