在我进行开发时,一个常见的情况是代码库将有几个配置文件需要机器特定的设置。这些文件将被提交到Git中,其他开发人员会不小心再次将它们提交,从而破坏其他人的配置。
一个简单的解决方案是不要将它们提交到Git中,甚至为它们添加 .gitignore 条目。但是,我发现更加优雅的方式是在文件中设置一些合理的默认值,开发人员可以修改以适应自己的需求。
是否有一种优雅的方法使Git与这些文件友好相处?我想能够修改特定于机器的配置文件,然后运行 "git commit -a" 而无需将该文件检入。
在我进行开发时,一个常见的情况是代码库将有几个配置文件需要机器特定的设置。这些文件将被提交到Git中,其他开发人员会不小心再次将它们提交,从而破坏其他人的配置。
一个简单的解决方案是不要将它们提交到Git中,甚至为它们添加 .gitignore 条目。但是,我发现更加优雅的方式是在文件中设置一些合理的默认值,开发人员可以修改以适应自己的需求。
是否有一种优雅的方法使Git与这些文件友好相处?我想能够修改特定于机器的配置文件,然后运行 "git commit -a" 而无需将该文件检入。
config.defaults
的文件,该文件应该包含在代码库中。然后,它应该读取一个名为config.local
的文件,该文件应该列在.gitignore
中。config
文件,将其放在版本控制中,并使用include config.local
的方法引入特定机器的值。这引入了更通用的机制(而不是策略)到你的代码中,因此可以实现更复杂的配置(如果你的应用需要)。从这个方面来看,许多大型开源软件都采用了一种流行的扩展,即include conf.d
,该扩展可以从目录中的所有文件中读取配置。git update-index --skip-worktree 文件名
。这会告诉git假装本地对文件名的更改不存在,因此git commit -a
会忽略它。它还有一个额外的好处,即可以抵御git reset --hard
,因此您不会意外丢失本地更改。如果文件在上游发生更改,自动合并也会优雅地失败(除非工作目录副本与索引副本匹配,在这种情况下,它将被自动更新)。缺点是必须在涉及的所有计算机上运行该命令,而且难以自动化执行。有关此想法的微妙不同版本,请参阅git update-index --assume-unchanged
。可以在git help update-index
中找到这两个命令的详细信息。--skip-worktree
。 - Senseful另一种方法是在另一个私有分支中维护对共同配置文件的本地更改。我在需要多个本地更改的一些项目中使用这种技术。这种技术可能不适用于所有情况,但在某些情况下对我很有效。
首先,我基于主分支创建一个新的分支(在这种特殊情况下,我正在使用git-svn,因此需要从主分支提交,但这里并不是非常重要):
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关键字的提交,倒序排列,最后将每个提交(现在从最老的开始)cherry-pick到master
中。
最后,在将更改推送到上游的master
后,我切换回work
并进行变基:
git checkout work
git rebase master
Git会重新应用在work
分支中的每个提交,有效地跳过那些已经通过cherry-picking应用在master
中的提交。你最终只剩下NOCOMMIT本地提交。
这种技术使得推送过程变得有点耗时,但它解决了我的问题,所以我想分享一下。
git commit -a
的人? - Phil Millergit rebase --onto
和 git fetch
的组合来完成相同的操作。 - Danilo Souza Morães一种可能的方法是将实际文件放在.gitignore中,但使用不同的扩展名检查默认配置。Rails应用程序的典型示例是config/database.yml文件。我们会检查config/database.yml.sample,并且每个开发人员都创建自己的config/database.yml,该文件已经.gitignored。
<?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_");
Your site.default.properties would look like:
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>
# settings.py
import os
DEBUG = os.getenv('DJANGO_DEBUG') == 'True'
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')
使用不同的扩展名(例如 .default)将默认配置检入,并使用符号链接将默认配置符号链接到正确位置,将正确位置添加到 .gitignore 中,并将与配置相关的所有其他内容添加到 .gitignore 中(因此唯一要检查的是 config.default)。
另外,编写一个快速安装脚本,为您的应用程序范围设置符号链接。
我们在以前的公司中使用了类似的方法。该安装脚本可以自动检测您运行的环境(沙箱、开发、QA、生产),并自动执行正确的操作。如果您有一个 config.sandbox 文件,并且正在从沙箱运行,则会链接该文件(否则它只会链接 .defaults 文件)。常见的过程是复制 .defaults 文件并根据需要更改设置。
编写安装脚本比你想象的要容易,而且还给予您很大的灵活性。
最简单的解决方案是编辑文件到默认值,提交它,然后将其添加到您的.gitignore
。这样,开发人员在执行git commit -a
时不会意外提交它,但在您想要使用git add --force
更改默认值的(假定很少出现的)情况下,他们仍然可以提交它。
然而,拥有一个.default
和.local
配置文件最终是最好的解决方案,因为这允许具有特定于机器的配置的任何人更改默认值,而无需破坏自己的配置。
.gitignore
中,更改仍将被跟踪。 - Zeemeegit checkout -b feature master
vim config.local
git add -A && git commit -m "local commit" && git tag localchange
然后继续添加您的功能提交。完成工作后,您可以通过执行以下操作将此分支合并回主分支而不包括本地更改提交:
git rebase --onto master localchange feature
git fetch . feature:master
git cherry-pick localchange
git tag localchange -f
这些命令将会:
1)将你的特性分支rebase到master,忽略localchange提交。
2)在不离开特性分支的情况下快进master。
3)将localchange提交添加回特性分支的顶部,以便您可以继续在其上工作。您可以对任何其他要继续工作的分支执行此操作。
4)将localchange标签重置为此cherry-picked提交,以便我们可以以相同的方式再次使用rebase --onto
。
这并不意味着要取代最佳通用解决方案的被接受答案,而是一种思考问题的新方法。你基本上通过只从localchange
到feature
进行rebase和快进master来避免意外地将本地更改合并到master。
我按照这里推荐的方式使用默认和本地配置文件。
为了管理我的本地配置文件,它们在项目的.gitignore
中,我创建了一个git仓库~/settings
。在那里,我管理所有项目的本地设置。例如,您可以在~/settings
中创建一个名为project1
的文件夹,并将该项目的所有本地配置文件放入其中。之后,您可以将这些文件/文件夹的符号链接到您的project1
中。
通过这种方法,您可以跟踪您的本地配置文件,并且不需要将它们放入正常的源代码存储库中。