Confirmación de archivos de configuración específicos de la máquina

82

Un escenario común cuando desarrollo es que el código base tendrá varios archivos de configuración que requieren configuraciones específicas de la máquina. Estos archivos se registrarán en Git y otros desarrolladores siempre los verificarán accidentalmente y romperán la configuración de otra persona.

Una solución simple para esto sería simplemente no registrarlos en Git, o incluso agregarles una entrada .gitignore. Sin embargo, encuentro que es mucho más elegante tener algunos valores predeterminados sensibles en el archivo que el desarrollador puede modificar para satisfacer sus necesidades.

¿Existe una forma elegante de hacer que Git funcione bien con estos archivos? Me gustaría poder modificar un archivo de configuración específico de la máquina y luego poder ejecutar "git commit -a" sin registrar ese archivo.

ghempton
fuente
1
Parece que tiene un problema en su diseño y en el cerebro de su colega. Dígales que se aseguren de que sepan lo que están comprometiendo en un sistema de control de fuente, de lo contrario estarán revisando basura que usted no quiere. Además: ¿Por qué no dividir el archivo, un archivo para cada sistema?
Pod
11
Estoy bastante seguro de que este es un escenario bastante común. ¿Cómo se realiza un seguimiento de la configuración específica de la máquina? Dividir el archivo para cada sistema parece bastante complicado y frustra el propósito de tener un control de versiones distribuido: si se extrae en una nueva máquina, no debería tener que haber un nuevo archivo registrado.
ghempton
1
Es posible que al menos pueda evitar la incorporación de las confirmaciones de ruptura utilizando un enlace de actualización previa en cualquier repositorio compartido al que todos empujen. Puede buscar confirmaciones que modifiquen el archivo de configuración realizado por ciertos desarrolladores, o puede buscar confirmaciones que toquen ese archivo que no mencionen una palabra clave especial en el mensaje.
Phil Miller
2
+1, este es un problema común. @Pod: No es práctico tener "Joe.conf" en el repositorio, pero aún desea poder actualizar cosas a veces ... a veces las configuraciones deben sufrir cambios debido a cambios en el código.
Thanatos

Respuestas:

59

Haga que su programa lea un par de archivos de configuración para su configuración. Primero, debería leer un config.defaultsarchivo que se incluiría en el repositorio. Luego, debería leer un config.localarchivo que debería estar listado en.gitignore

Con esta disposición, las nuevas configuraciones aparecen en el archivo de valores predeterminados y entran en vigencia tan pronto como se actualice. Solo variarán en sistemas particulares si se anulan.

Como una variación de esto, podría tener solo un configarchivo general que envíe en control de versiones y hacer que haga algo como include config.localtraer los valores específicos de la máquina. Esto introduce un mecanismo más general (versus una política) en su código y, en consecuencia, permite configuraciones más complicadas (si eso es deseable para su aplicación). La extensión popular de esto, que se ve en muchos software de código abierto a gran escala, es to include conf.d, que lee la configuración de todos los archivos en un directorio.

Vea también mi respuesta a una pregunta similar.

Phil Miller
fuente
Le daré a esta la respuesta. Este método logra el efecto deseado, con el único inconveniente de que requiere una lógica adicional por parte de la aplicación.
ghempton
17

Puedes intentarlo git update-index --skip-worktree filename. Esto le dirá a git que finja que los cambios locales en el nombre del archivo no existen, por lo que git commit -alo ignorará. Tiene la ventaja adicional de resistir git reset --hard, por lo que no perderá accidentalmente sus cambios locales. Además, las fusiones automáticas fallarán sin problemas si el archivo se cambia en sentido ascendente (a menos que la copia del directorio de trabajo coincida con la copia del índice, en cuyo caso se actualizará automáticamente). La desventaja es que el comando debe ejecutarse en todas las máquinas involucradas, y es difícil hacerlo automáticamente. Consulte también git update-index --assume-unchangeduna versión sutilmente diferente de esta idea. Los detalles de ambos se pueden encontrar con git help update-index.

bhuber
fuente
Puede encontrar más información sobre estos comandos en la pregunta Diferencia entre 'asumir sin cambios' y 'saltar-árbol de trabajo' . De la respuesta superior , parece que quieres --skip-worktreeen este caso.
Sensible
10

Otro enfoque es mantener los cambios locales en los archivos de configuración comunes en otra sucursal privada. Hago esto para algunos proyectos que requieren varios cambios locales. Es posible que esta técnica no sea aplicable a todas las situaciones, pero me funciona en algunos casos.

Primero creo una nueva rama basada en la rama maestra (en este caso particular estoy usando git-svn, así que necesito confirmar desde la maestra, pero eso no es muy importante aquí):

git checkout -b work master

Ahora modifique los archivos de configuración según sea necesario y confirme. Normalmente pongo algo distintivo en el mensaje de confirmación como "NOCOMMIT" o "PRIVATE" (esto será útil más adelante). En este punto, puede trabajar en su rama privada usando su propio archivo de configuración.

Cuando desee impulsar su trabajo de nuevo en sentido ascendente, seleccione cada cambio de su workrama al maestro. Tengo un script para ayudar a hacer esto, que se parece a esto:

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

Esto primero comprueba para asegurarse de que estoy en la masterrama (comprobación de cordura). Luego, enumera cada confirmación work, filtra las que mencionan la palabra clave NOCOMMIT, invierte el orden y finalmente selecciona cada confirmación (ahora desde la más antigua primero) en master.

Finalmente, después de impulsar los cambios en el maestro aguas arriba, vuelvo a cambiar de workbase:

git checkout work
git rebase master

Git volverá a aplicar cada una de las confirmaciones en la workrama, omitiendo efectivamente las que ya se han aplicado a mastertravés de la selección. Lo que debería quedarse es solo las confirmaciones locales de NOCOMMIT.

Esta técnica hace que el proceso de inserción requiera un poco más de tiempo, pero me resolvió un problema, así que pensé en compartirlo.

Greg Hewgill
fuente
2
¿Te das cuenta de que le estás pidiendo al ajeno que no hace preguntas que haga esto? ¿La persona que simplemente corre git commit -asin preocupaciones en el mundo?
Phil Miller
Siguiendo esa misma estrategia, puede etiquetar la confirmación donde establece sus archivos de configuración locales y usar una combinación de git rebase --ontoy git fetchpara hacer lo mismo
Danilo Souza Morães
8

Una posibilidad es tener los archivos reales en su .gitignore, pero verifique las configuraciones predeterminadas con una extensión diferente. Un ejemplo típico de una aplicación Rails sería el archivo config / database.yml. Verificaríamos config / database.yml.sample, y cada desarrollador crea su propia config / database.yml que ya es .gitignored.

hgmnz
fuente
Sí, esta es una mejora incremental, pero aún no es óptima, ya que si la versión comprobada se cambia intencionalmente, no se refleja en los archivos de configuración del desarrollador. Un ejemplo de cuándo sería útil es cuando se agrega una nueva propiedad, etc.
ghempton
Esa podría ser una dirección con buenas notas de confirmación y mensajes de error descriptivos que se quejan cuando no se establece una propiedad. También ayuda un correo electrónico comunicándole a su equipo sobre el cambio.
Brian Kelly
Para obtener más información sobre esta solución y un gran ejemplo, consulte esta respuesta .
Sensible
1

Verifique una configuración predeterminada con una extensión diferente (por ejemplo. lo que se registra es config.default).

Además, escriba un script de instalación rápida que configure los enlaces simbólicos para toda su aplicación.

Usamos un enfoque similar en una empresa anterior. El script de instalación detectó automáticamente el entorno en el que estaba ejecutando (sandbox, desarrollo, control de calidad, producción) y automáticamente haría lo correcto. Si tuviera un archivo config.sandbox y estuviera ejecutando desde el sandbox, lo vincularía (de lo contrario, solo vincularía el archivo .defaults). El procedimiento común era copiar .defaults y cambiar la configuración según fuera necesario.

Escribir el script de instalación es más fácil de lo que imagina y le brinda mucha flexibilidad.

Bryan Alves
fuente
1

Estoy de acuerdo con la mejor respuesta pero también me gustaría agregar algo. Utilizo un script ANT para eliminar y modificar archivos del repositorio GIT, así que estoy seguro de que no se sobrescriben archivos de producción. Hay una buena opción en ANT para modificar los archivos de propiedades de Java. Esto significa poner sus variables de prueba locales en un archivo de propiedades de estilo java y agregar código para procesarlo, pero le brinda la oportunidad de automatizar la construcción de su sitio antes de enviarlo por FTP en línea. Normalmente, pondría su información de producción en el archivo site.default.properties y dejaría que ANT administre la configuración. Su configuración local estaría en 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

luego úsalo:

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

Su site.default.properties se vería así:

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

y su site.local.properties se vería así (observe la diferencia entre el entorno y los módulos habilitados):

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

Y sus instrucciones ANT: ($ d {deploy} es su directorio de destino de implementación)

<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>
Pianista
fuente
1

Hoy en día (2019) utilizo ENV vars, por ejemplo, en python / django, también puede agregarles valores predeterminados. En el contexto de la ventana acoplable, puedo guardar las vars ENV en un archivo docker-compose.yml o en un archivo adicional que se ignora en el control de versiones.

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

La solución más simple es editar el archivo a los valores predeterminados, confirmarlo y luego agregarlo a su archivo .gitignore. De esta manera, los desarrolladores no lo confirmarán accidentalmente cuando lo hagan git commit -a, pero aún pueden hacerlo en el caso (presumiblemente raro) en el que desee cambiar sus valores predeterminados git add --force.

Sin embargo, tener un .default.local archivo y config es en última instancia la mejor solución, ya que esto permite que cualquier persona con una configuración específica de la máquina cambie los valores predeterminados, sin tener que romper su propia configuración.

crazy2be
fuente
Esto no funciona: si se realiza un seguimiento de los archivos y se agregan .gitignoreposteriormente, los cambios aún se controlan.
Zeemee
0

Lo hago como se recomienda aquí con archivos de configuración locales y predeterminados. Para administrar mis archivos de configuración locales que están en los proyectos .gitignore, hice un repositorio de git ~/settings. Allí administro todas mis configuraciones locales de todos los proyectos. Se crea, por ejemplo, una carpeta project1en ~/settingsy poner todo el paquete de configuración local para este proyecto en él. Después de eso, puede enlazar simbólicamente esos archivos / carpeta a su project1.

Con ese enfoque, puede realizar un seguimiento de sus archivos de configuración locales y no ponerlos en el repositorio de código fuente normal.

Yvess
fuente
0

Sobre la base de la respuesta de @Greg Hewgill, puede agregar una confirmación específica con sus cambios locales y etiquetarla como localchange:

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

Luego proceda a agregar las confirmaciones de su función. Después de terminar el trabajo, puede fusionar esta rama de nuevo a master sin el compromiso localchange haciendo esto:

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

Estos comandos:

1) Vuelva a basar su rama de características en maestra, ignorando la confirmación de cambio local. 2) Avance rápido del maestro sin salir de la rama de características 3) Agregue el compromiso de cambio local en la parte superior de la rama de características para que pueda continuar trabajando en ella. Puede hacer esto en cualquier otra rama en la que desee seguir trabajando. 4) Restablezca la etiqueta localchange a esta confirmación elegida con precisión para que podamos usarla rebase --ontonuevamente de la misma manera.

Esto no pretende reemplazar la respuesta aceptada como la mejor solución general, sino como una forma de pensar fuera de la caja sobre el problema. Básicamente, evita fusionar accidentalmente los cambios locales con el maestro simplemente reajustando de localchangeafeature y el maestro de avance rápido.

Danilo Souza Morães
fuente