Festschreiben maschinenspezifischer Konfigurationsdateien

82

Ein häufiges Szenario bei der Entwicklung ist, dass die Codebasis mehrere Konfigurationsdateien enthält, für die maschinenspezifische Einstellungen erforderlich sind. Diese Dateien werden in Git eingecheckt und andere Entwickler checken sie immer versehentlich wieder ein und brechen die Konfiguration eines anderen.

Eine einfache Lösung hierfür wäre, sie einfach nicht in Git einzuchecken oder zusätzlich einen .gitignore-Eintrag für sie hinzuzufügen. Ich finde es jedoch viel eleganter, einige sinnvolle Standardeinstellungen in der Datei zu haben, die der Entwickler an seine Bedürfnisse anpassen kann.

Gibt es eine elegante Möglichkeit, Git mit solchen Dateien gut spielen zu lassen? Ich möchte in der Lage sein, eine maschinenspezifische Konfigurationsdatei zu ändern und dann "git commit -a" ausführen zu können, ohne diese Datei einzuchecken.

Ghempton
quelle
1
Das klingt so, als hätten Sie ein Problem in Ihrem Design und im Gehirn Ihres Kollegen. Sagen Sie ihnen, sie sollen sicherstellen, dass sie wissen, was sie in ein Versionsverwaltungssystem einbinden, sonst überprüfen sie den Mist, den sie nicht wollen. Außerdem: Warum nicht einfach die Datei teilen, eine Datei für jedes System?
Pod
11
Ich bin mir ziemlich sicher, dass dies ein ziemlich häufiges Szenario ist. Wie verfolgen Sie die maschinenspezifische Konfiguration? Das Aufteilen der Datei für jedes System scheint ziemlich chaotisch zu sein und den Zweck einer verteilten Versionskontrolle irgendwie zu vereiteln: Wenn sie auf einem neuen Computer
ausgecheckt wird,
1
Möglicherweise können Sie zumindest verhindern, dass die fehlerhaften Commits mithilfe eines Pre-Update-Hooks für das gemeinsam genutzte Repository eingegeben werden, an das Sie alle senden. Es kann nach Commits suchen, die die von bestimmten Entwicklern erstellte Konfigurationsdatei ändern, oder nach Commits, die diese Datei berühren und in der Nachricht kein spezielles Schlüsselwort enthalten.
Phil Miller
2
+1, das ist ein häufiges Problem. @Pod: Es ist nicht praktisch, "Joe.conf" im Repo zu haben, aber Sie möchten trotzdem in der Lage sein, Dinge manchmal zu aktualisieren ... manchmal müssen die Konfigurationen aufgrund von Änderungen im Code geändert werden.
Thanatos

Antworten:

59

Lassen Sie Ihr Programm zwei Konfigurationsdateien für die Einstellungen lesen. Zunächst sollte eine config.defaultsDatei gelesen werden, die im Repository enthalten sein würde. Dann sollte es eine config.localDatei lesen , die in aufgelistet sein sollte.gitignore

Mit dieser Anordnung werden neue Einstellungen in der Standarddatei angezeigt und wirksam, sobald sie aktualisiert werden. Sie variieren nur auf bestimmten Systemen, wenn sie überschrieben werden.

Als Variation davon könnten Sie nur eine allgemeine configDatei haben, die Sie in der Versionskontrolle versenden, und sie so etwas wie include config.localdie maschinenspezifischen Werte einbringen lassen. Dies führt einen allgemeineren Mechanismus (im Vergleich zu Richtlinien) in Ihren Code ein und ermöglicht folglich kompliziertere Konfigurationen (falls dies für Ihre Anwendung wünschenswert ist). Die beliebte Erweiterung davon, die in vielen großen Open-Source-Programmen zu finden ist, ist to include conf.d, die die Konfiguration aus allen Dateien in einem Verzeichnis liest.

Auch sieht meine Antwort auf eine ähnliche Frage.

Phil Miller
quelle
Ich werde dem die Antwort geben. Diese Methode erzielt den gewünschten Effekt mit dem einzigen Nachteil, dass zusätzliche Logik seitens der Anwendung erforderlich ist.
Ghempton
18

Sie können es versuchen git update-index --skip-worktree filename. Dadurch wird git angewiesen, so zu tun, als ob lokale Änderungen am Dateinamen nicht vorhanden wären, und git commit -awird daher ignoriert. Es hat den zusätzlichen Vorteil git reset --hard, dass Sie auch Widerstand leisten können , damit Sie Ihre lokalen Änderungen nicht versehentlich verlieren. Außerdem schlagen automatische Zusammenführungen ordnungsgemäß fehl, wenn die Datei vorgelagert geändert wird (es sei denn, die Arbeitsverzeichniskopie stimmt mit der Indexkopie überein. In diesem Fall wird sie automatisch aktualisiert). Der Nachteil ist, dass der Befehl auf allen beteiligten Computern ausgeführt werden muss, und es ist schwierig, dies automatisch zu tun. Siehe auch git update-index --assume-unchangedfür eine subtil andere Version dieser Idee. Details zu beiden finden Sie mit git help update-index.

bhuber
quelle
Weitere Informationen zu diesen Befehlen finden Sie in der Frage Unterschied zwischen 'unverändert annehmen' und 'Arbeitsbaum überspringen' . Aus der oberen Antwort geht hervor , dass Sie --skip-worktreein diesem Fall möchten .
Sinnvoll
10

Ein anderer Ansatz besteht darin, lokale Änderungen an allgemeinen Konfigurationsdateien in einem anderen privaten Zweig beizubehalten. Ich mache dies für einige Projekte, die mehrere lokale Änderungen erfordern. Diese Technik ist möglicherweise nicht in allen Situationen anwendbar, funktioniert jedoch in einigen Fällen bei mir.

Zuerst erstelle ich einen neuen Zweig basierend auf dem Master-Zweig (in diesem speziellen Fall verwende ich git-svn, also muss ich mich vom Master verpflichten, aber das ist hier nicht besonders wichtig):

git checkout -b work master

Ändern Sie nun die Konfigurationsdatei (en) nach Bedarf und schreiben Sie sie fest. Normalerweise füge ich der Commit-Nachricht etwas Besonderes hinzu, wie "NOCOMMIT" oder "PRIVATE" (dies wird später nützlich sein). Zu diesem Zeitpunkt können Sie mit Ihrer eigenen Konfigurationsdatei an Ihrem privaten Zweig arbeiten.

Wenn Sie Ihre Arbeit wieder in den Vordergrund stellen möchten, wählen Sie jede Änderung von Ihrem workZweig zum Master aus. Ich habe ein Skript, das dabei hilft und ungefähr so ​​aussieht:

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

Dies überprüft zuerst, ob ich in der masterFiliale bin (Sanity Check). Anschließend werden alle Commits workaufgelistet, diejenigen herausgefiltert, die das Schlüsselwort NOCOMMIT erwähnen, die Reihenfolge umgekehrt und schließlich jedes Commit (jetzt vom ältesten zuerst) ausgewählt master.

Nachdem ich die Änderungen im Master in den Upstream workverschoben habe, wechsle ich zurück zu :

git checkout work
git rebase master

Git wendet jedes Commit in der workVerzweigung erneut an und überspringt effektiv die Commits , die bereits masterdurch das Kirschpflücken angewendet wurden . Was Ihnen übrig bleiben sollte, sind nur die lokalen NOCOMMIT-Commits.

Diese Technik macht den Push-Prozess etwas zeitaufwändiger, hat aber ein Problem für mich gelöst, sodass ich dachte, ich würde es teilen.

Greg Hewgill
quelle
2
Sie erkennen, dass Sie den ahnungslosen Nicht-Fragesteller bitten, dies zu tun? Die Person, die nur git commit -asorglos in der Welt läuft ?
Phil Miller
Nach der gleichen Strategie können Sie das Commit markieren, in dem Sie Ihre lokalen Konfigurationsdateien festlegen, und eine Kombination aus git rebase --ontound verwenden git fetch, um dasselbe zu tun
Danilo Souza Morães
8

Eine Möglichkeit besteht darin, die tatsächlichen Dateien in Ihrem .gitignore zu haben, aber Standardkonfigurationen mit einer anderen Erweiterung einzuchecken. Ein typisches Beispiel für eine Rails-App ist die Datei config / database.yml. Wir würden config / database.yml.sample einchecken und jeder Entwickler erstellt seine eigene config / database.yml, die bereits .gitignored ist.

hgmnz
quelle
Ja, dies ist eine inkrementelle Verbesserung, aber immer noch nicht optimal, da die eingecheckte Version, wenn sie absichtlich geändert wird, nicht in den Entwicklerkonfigurationsdateien berücksichtigt wird. Ein Beispiel dafür, wann dies nützlich wäre, ist das Hinzufügen einer neuen Eigenschaft usw.
Ghempton
Dies kann eine Adresse mit guten Festschreibungsnotizen und beschreibenden Fehlermeldungen sein, die sich beschweren, wenn eine Eigenschaft nicht festgelegt ist. Auch eine E-Mail, die Ihrem Team über die Änderung mitteilt, hilft.
Brian Kelly
Weitere Informationen zu dieser Lösung und ein gutes Beispiel finden Sie in dieser Antwort .
Sinnvoller
1

Checken Sie eine Standardkonfiguration mit einer anderen Erweiterung ein (z. B. .default), verwenden Sie einen Symlink, um den Standard mit dem richtigen Speicherort zu verknüpfen, fügen Sie den richtigen Speicherort zu .gitignore hinzu und fügen Sie alles andere, was mit der Konfiguration zu tun hat, zu .gitignore hinzu (also den einzigen Das, was eingecheckt wird, ist config.default.

Schreiben Sie außerdem ein Schnellinstallationsskript, mit dem die Symlinks für Ihre gesamte Anwendung eingerichtet werden.

Bei einem früheren Unternehmen haben wir einen ähnlichen Ansatz gewählt. Das Installationsskript erkannte automatisch, in welcher Umgebung Sie ausgeführt wurden (Sandbox, Entwicklung, Qualitätssicherung, Produktion) und tat automatisch das Richtige. Wenn Sie eine config.sandbox-Datei hätten und von der Sandbox aus ausgeführt würden, würde dies verknüpft (andernfalls würde nur die .defaults-Datei verknüpft). Das übliche Verfahren bestand darin, .defaults zu kopieren und die Einstellungen nach Bedarf zu ändern.

Das Schreiben des Installationsskripts ist einfacher als Sie sich vorstellen können und bietet Ihnen viel Flexibilität.

Bryan Alves
quelle
1

Ich stimme der besten Antwort zu, möchte aber auch etwas hinzufügen. Ich verwende ein ANT-Skript, um Dateien aus dem GIT-Repo zu entfernen und zu ändern, sodass ich sicher bin, dass keine Produktionsdateien überschrieben werden. In ANT gibt es eine nette Option, um Java-Eigenschaftendateien zu ändern. Dies bedeutet, dass Sie Ihre lokalen Testvariablen in eine Eigenschaftendatei im Java-Stil einfügen und Code hinzufügen, um sie zu verarbeiten. Sie haben jedoch die Möglichkeit, die Erstellung Ihrer Site zu automatisieren, bevor Sie sie online per FTP übertragen. Normalerweise fügen Sie Ihre Produktionsinformationen in die Datei site.default.properties ein und lassen ANT die Einstellungen verwalten. Ihre lokalen Einstellungen befinden sich in den 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

dann benutze es:

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

Ihre site.default.properties würde folgendermaßen aussehen:

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

und Ihre site.local.properties würden folgendermaßen aussehen (beachten Sie die unterschiedliche Umgebung und die aktivierten Module):

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

Und Ihre ANT-Anweisungen: ($ d {deploy} ist Ihr Bereitstellungszielverzeichnis)

<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
quelle
1

Heutzutage (2019) verwende ich ENV-Vars zum Beispiel in Python / Django. Sie können ihnen auch Standardeinstellungen hinzufügen. Im Kontext von Docker kann ich die ENV-Variablen in einer Datei docker-compose.yml oder einer zusätzlichen Datei speichern, die in der Versionskontrolle ignoriert wird.

# settings.py
import os
DEBUG = os.getenv('DJANGO_DEBUG') == 'True'
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')
yvess
quelle
0

Die einfachste Lösung besteht darin, die Datei auf die Standardeinstellungen zu bearbeiten, sie festzuschreiben und dann zu Ihrer Datei hinzuzufügen .gitignore. Auf diese Weise werden Entwickler es nicht versehentlich festschreiben git commit -a, aber sie können es dennoch in dem (vermutlich seltenen) Fall festschreiben, mit dem Sie Ihre Standardeinstellungen ändern möchten git add --force.

Allerdings ist eine mit .defaultund .localDatei config ist letztlich die beste Lösung, da dies jemand mit einer maschinenspezifische Konfiguration ermöglicht die Standardeinstellung zu ändern, ohne dass sie ihre eigene Konfiguration zu brechen.

crazy2be
quelle
Dies funktioniert nicht. Wenn die Dateien nachverfolgt und später hinzugefügt werden, werden die .gitignoreÄnderungen weiterhin nachverfolgt.
Zeemee
0

Ich mache es so, wie es hier mit Standard- und lokalen Konfigurationsdateien empfohlen wird. Um meine lokalen Konfigurationsdateien zu verwalten, die sich in den Projekten befinden .gitignore, habe ich ein Git-Repo erstellt ~/settings. Dort verwalte ich alle meine lokalen Einstellungen aus allen Projekten. Sie erstellen beispielsweise einen Ordner project1in ~/settingsund legen alle lokalen Konfigurationsdaten für dieses Projekt darin ab. Danach können Sie diese Dateien / Ordner mit Ihrem Symlink verknüpfen project1.

Mit diesem Ansatz können Sie Ihre lokalen Konfigurationsdateien verfolgen und nicht in das normale Quellcode-Repository stellen.

yvess
quelle
0

Aufbauend auf der Antwort von @Greg Hewgill können Sie Ihren lokalen Änderungen ein bestimmtes Commit hinzufügen und es als localchange kennzeichnen:

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

Fügen Sie dann die Commits Ihrer Funktion hinzu. Nach Abschluss der Arbeit können Sie diesen Zweig ohne das lokale Änderungs-Commit wieder zum Master zusammenführen, indem Sie Folgendes tun:

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

Diese Befehle werden:

1) Setzen Sie Ihren Feature-Zweig auf Master um und ignorieren Sie das Localchange-Commit. 2) Schneller Vorlauf des Masters, ohne den Feature-Zweig zu verlassen. 3) Fügen Sie das Localchange-Commit wieder oben im Feature-Zweig hinzu, damit Sie weiter daran arbeiten können. Sie können dies für jeden anderen Zweig tun, an dem Sie weiterarbeiten möchten. 4) Setzen Sie das localchange-Tag auf dieses von Kirschen ausgewählte Commit zurück, damit wir es rebase --ontoauf die gleiche Weise wieder verwenden können.

Dies soll nicht die akzeptierte Antwort als beste allgemeine Lösung ersetzen, sondern als eine Möglichkeit, über das Problem hinauszudenken. Sie vermeiden grundsätzlich versehentlich lokale Änderungen an Master Verschmelzung von nur von Rebasing localchangezu featureund schnellen Vorlauf - Master.

Danilo Souza Morães
quelle