【问题标题】:Committing Machine Specific Configuration Files提交机器特定的配置文件
【发布时间】:2010-11-26 15:59:15
【问题描述】:

我开发时的一个常见情况是代码库将有几个需要机器特定设置的配置文件。这些文件将被签入 Git,而其他开发人员总是会不小心将它们签入并破坏其他人的配置。

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

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

【问题讨论】:

  • 这听起来像是你的设计和你同事的大脑有问题。告诉他们确保他们知道他们在源代码控制系统中提交了什么,否则他们会检查你不想要的废话。另外:为什么不直接拆分文件,每个系统一个文件?
  • 我很确定这是一个相当普遍的情况?您如何跟踪机器的特定配置?为每个系统拆分文件似乎很混乱,有点违背分布式版本控制的目的:如果在新机器上签出,则不必签入新文件。
  • 您至少可以在您推送到的任何共享存储库上使用更新前挂钩来防止引入中断提交。它可以查找修改某些开发人员所做的配置文件的提交,或者它可以查找涉及该文件但在消息中未提及特殊关键字的提交。
  • +1,这常见问题。 @Pod:在 repo 中包含“Joe.conf”是不切实际的,但您仍然希望有时能够更新内容......有时由于代码的更改,配置必须进行更改。

标签: git configuration version-control


【解决方案1】:

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

【讨论】:

  • 是的,这是一个渐进式的改进,但它仍然不是最佳的,因为如果有意更改签入的版本,它不会反映在开发人员配置文件中。一个有用的例子是添加新属性时等。
  • 这可能是具有良好提交注释和描述性错误消息的地址,这些错误消息在未设置属性时会抱怨。此外,向您的团队发送有关更改的电子邮件也会有所帮助。
  • 有关此解决方案的更多信息和一个很好的示例,请参阅this answer
【解决方案2】:

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

首先,我基于 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 中。

最后,将 master 中的更改推送到上游后,我切换回 work 并 rebase:

git checkout work
git rebase master

Git 将重新应用 work 分支中的每个提交,通过挑选樱桃有效地跳过已经在 master 中应用的提交。您应该只剩下 NOCOMMIT 本地提交。

这种技术使推送过程更加耗时,但它为我解决了一个问题,所以我想分享一下。

【讨论】:

  • 您意识到您是在要求不知情的不提问者这样做吗?一个人在世界上毫无顾忌地跑git commit -a
  • 按照同样的策略,你可以在你设置本地配置文件的地方标记提交,并使用git rebase --ontogit fetch的组合来做同样的事情
【解决方案3】:

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

通过这种安排,新设置会出现在默认文件中,并在更新后立即生效。如果它们被覆盖,它们只会在特定系统上有所不同。

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

也可以see my answer 回答类似的问题。

【讨论】:

  • 我会给这个答案。这种方法达到了预期的效果,唯一的缺点是它需要应用程序的额外逻辑。
【解决方案4】:

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

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

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

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

【讨论】:

    【解决方案5】:

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

    但是,拥有.default.local 配置文件最终是最好的解决方案,因为这允许任何具有特定机器配置的人更改默认值,而不必破坏自己的配置。

    【讨论】:

    • 这不起作用 - 如果文件被跟踪并稍后添加到.gitignore,仍然会跟踪更改。
    【解决方案6】:

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

    【讨论】:

    【解决方案7】:

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

    通过这种方法,您可以跟踪本地配置文件,而无需将它们放入普通的源代码存储库中。

    【讨论】:

      【解决方案8】:

      我同意最佳答案,但也想添加一些内容。我使用 ANT 脚本从 GIT 存储库中剥离和修改文件,因此我确信没有生产文件被覆盖。 ANT 中有一个很好的选项来修改 java-property 文件。这意味着将您的本地测试变量放在 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>
      

      【讨论】:

        【解决方案9】:

        基于@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)不离开特征分支的快进master 3) 将 localchange 提交添加回功能分支的顶部,以便您可以继续处理它。您可以对要继续工作的任何其他分支执行此操作。 4) 将 localchange 标签重置为这个精心挑选的提交,这样我们就可以以同样的方式再次使用rebase --onto

        这并不是要取代公认的答案作为最佳通用解决方案,而是作为一种对问题进行开箱即用的思考方式。您基本上可以通过仅从 localchange 重新定位到 feature 并快速转发 master 来避免意外地将本地更改合并到 master。

        【讨论】:

          【解决方案10】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-09-23
            • 2016-11-13
            • 2018-12-07
            • 1970-01-01
            • 2015-08-03
            • 2016-02-29
            • 1970-01-01
            • 2011-08-12
            相关资源
            最近更新 更多