提交计算机专用配置文件

时间:2009-09-08 22:13:46

标签: git configuration version-control

我开发的一个常见场景是代码库将有几个配置文件需要特定于机器的设置。这些文件将被检入Git,其他开发人员将不小心将其重新检入并打破其他人的配置。

一个简单的解决方案是不将它们检入Git,甚至为它们另外添加.gitignore条目。但是,我发现在文件中有一些合理的默认值更加优雅,开发人员可以根据自己的需要进行修改。

有没有一种优雅的方式让Git很好地使用这些文件?我希望能够修改特定于机器的配置文件,然后能够在不检查该文件的情况下运行“git commit -a”。

10 个答案:

答案 0 :(得分:55)

让您的程序读取其设置的一对配置文件。首先,它应该读取将包含在存储库中的config.defaults文件。然后,它应该读取应该在config.local

中列出的.gitignore文件

通过这种安排,新设置会出现在默认文件中,并在更新后立即生效。只有在被覆盖的情况下,它们才会因特定系统而异。

作为对此的修改,您可以只使用版本控制中提供的常规config文件,并让它执行include config.local之类的操作以引入特定于计算机的值。这在您的代码中引入了更通用的机制(与策略相对),从而实现了更复杂的配置(如果您的应用程序需要这样)。在许多大型开源软件中看到的流行扩展是include conf.d,它从目录中的所有文件中读取配置。

see my answer同样问题。

答案 1 :(得分:16)

您可以尝试git update-index --skip-worktree filename。这将告诉git假装文件名的本地更改不存在,因此git commit -a将忽略它。它还具有抵制git reset --hard的额外优势,因此您不会意外丢失本地更改。此外,如果文件在上游更改,则自动合并将正常失败(除非工作目录副本与索引副本匹配,在这种情况下,它将自动更新)。缺点是必须在所有涉及的机器上运行命令,并且自动执行此操作很困难。另请参阅git update-index --assume-unchanged这个想法的略有不同的版本。有关这两者的详细信息,请参阅git help update-index

答案 2 :(得分:10)

另一种方法是在另一个专用分支中维护对公共配置文件的本地更改。我为一些需要多次本地更改的项目执行此操作。这种技术可能并不适用于所有情况,但在某些情况下它适用于我。

首先,我基于主分支创建一个新分支(在这个特殊情况下,我使用的是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

最后,在推送master上游的更改后,我切换回work并重新设置:

git checkout work
git rebase master

Git将重新应用work分支中的每个提交,有效地跳过已通过挑选master中已应用的那个提交。你应该留下的只是NOCOMMIT本地提交。

这种技术使推送过程更耗时,但它解决了我的问题,所以我想我会分享。

答案 3 :(得分:8)

一种可能性是在.gitignore中包含实际文件,但检查具有不同扩展名的默认配置。 Rails应用程序的典型示例是config / database.yml文件。我们将检入config / database.yml.sample,每个开发人员都创建自己的config / database.yml,它已经是.gitignored。

答案 4 :(得分:1)

使用不同的扩展名(例如.default)签入默认配置,使用符号链接将默认值符号链接到正确的位置,将正确的位置添加到.gitignore,并将与配置相关的所有其他内容添加到.gitignore(所以唯一可以检查的是config.default)。

此外,编写一个快速安装脚本,为您的应用程序范围设置符号链接。

我们在以前的公司使用过类似的方法。安装脚本自动检测您正在运行的环境(沙箱,开发,QA,生产),并自动执行正确的操作。如果您有一个config.sandbox文件,并且正在从沙箱运行,它将链接它(否则它只会链接.defaults文件)。常见的程序是复制.defaults并根据需要更改设置。

编写安装脚本比您想象的更容易,并且为您提供了很大的灵活性。

答案 5 :(得分: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>

答案 6 :(得分:0)

最简单的解决方案是将文件编辑为默认值,提交,然后将其添加到.gitignore。这样,开发人员在执行git commit -a时不会意外地提交它,但他们仍然可以在(可能是罕见的)情况下提交它,您希望使用git add --force更改默认值。

但是,拥有.default.local配置文件最终是最佳解决方案,因为这允许任何具有计算机专用配置的人更改默认设置,而无需中断自己的配置。 / p>

答案 7 :(得分:0)

我喜欢这里推荐使用默认和本地配置文件。 为了管理项目.gitignore中的本地配置文件,我制作了一个git repo ~/settings。在那里,我管理所有项目的所有本地设置。您可以在project1中创建文件夹~/settings,并将此项目的所有本地配置内容放入其中。之后,您可以将文件/文件夹符号链接到project1

使用这种方法,您可以跟踪本地配置文件,而不是将它们放入正常的源代码存储库。

答案 8 :(得分: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并快速转发master来将本地更改意外合并到master。

答案 9 :(得分:0)

现在(2019年),我在python / django中使用ENV vars,例如,您也可以为其添加默认值。 在docker的上下文中,我可以将ENV变量保存在docker-compose.yml文件或版本控制中忽略的其他文件中。

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