在我进行开发时,常见的情况是该代码库将包含几个需要计算机特定设置的配置文件。这些文件将被检入Git,其他开发人员将总是不小心将它们重新检入并破坏其他人的配置。
一个简单的解决方案是不将其签入Git,甚至不为它们添加.gitignore条目。但是,我发现在文件中包含一些合理的默认值(这很明智),开发人员可以修改这些默认值以满足他的需要。
有没有一种优雅的方法可以使Git很好地处理此类文件?我希望能够修改特定于计算机的配置文件,然后能够在不检入该文件的情况下运行“ git commit -a”。
在我进行开发时,常见的情况是该代码库将包含几个需要计算机特定设置的配置文件。这些文件将被检入Git,其他开发人员将总是不小心将它们重新检入并破坏其他人的配置。
一个简单的解决方案是不将其签入Git,甚至不为它们添加.gitignore条目。但是,我发现在文件中包含一些合理的默认值(这很明智),开发人员可以修改这些默认值以满足他的需要。
有没有一种优雅的方法可以使Git很好地处理此类文件?我希望能够修改特定于计算机的配置文件,然后能够在不检入该文件的情况下运行“ git commit -a”。
Answers:
让您的程序读取一对配置文件以获取其设置。首先,它应该读取config.defaults将包含在存储库中的文件。然后,它应该读取一个config.local文件,该文件应列在.gitignore
通过这种安排,新设置将显示在默认文件中,并在更新后立即生效。如果它们被覆盖,它们只会在特定系统上有所不同。
作为对此的一种变体,您可以config在版本控制中仅提供一个常规文件,并使其进行一些操作,例如include config.local引入机器特定的值。这在您的代码中引入了更通用的机制(相对于策略),因此启用了更复杂的配置(如果您的应用程序需要这样做)。在许多大型开源软件中都可以看到,从这里流行的扩展是to include conf.d,它从目录中的所有文件读取配置。
另请参阅我对类似问题的回答。
你可以试试看git update-index --skip-worktree filename。这将告诉git假装对文件名的本地更改不存在,因此git commit -a将其忽略。它还具有抵抗的额外优势git reset --hard,因此您不会意外丢失本地更改。另外,如果文件在上游更改,自动合并将自动失败(除非工作目录副本与索引副本匹配,在这种情况下它将自动更新)。缺点是该命令必须在所有涉及的计算机上运行,并且很难自动执行此操作。另请参阅有关git update-index --assume-unchanged此想法的一个微妙不同的版本。有关这两者的详细信息,请参见git help update-index。
另一种方法是在另一个专用分支中维护对公共配置文件的本地更改。对于某些需要进行一些本地更改的项目,我会这样做。这项技术可能不适用于所有情况,但在某些情况下对我有用。
首先,我基于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。
最后,在将主服务器中的更改推送到上游之后,我切换回work并重新设置基准:
git checkout work
git rebase master
Git将重新应用work分支中的每个提交,从而有效地跳过master通过摘樱桃已应用的提交。您应该只剩下NOCOMMIT本地提交。
这种技术使推送过程更加耗时,但是它为我解决了一个问题,因此我想与大家分享。
git commit -a没有在世界上的照顾?
git rebase --onto和git fetch进行组合
一种可能是将实际文件包含在.gitignore中,但请使用其他扩展名签入默认配置。Rails应用程序的一个典型示例是config / database.yml文件。我们将检入config / database.yml.sample,每个开发人员都会创建自己的已经被.gitignored的config / database.yml。
检入具有不同扩展名的默认配置(例如.default),使用符号链接将默认设置符号链接到正确的位置,将正确的位置添加到.gitignore,并将与配置相关的其他所有内容添加到.gitignore(因此唯一签入的内容是config.default)。
此外,编写一个快速安装脚本,为您的整个应用程序设置符号链接。
我们在以前的公司中使用了类似的方法。安装脚本会自动检测您在哪个环境中运行(沙盒,开发,质量检查,生产),并会自动执行正确的操作。如果您有一个config.sandbox文件,并且是从沙箱运行的,它将对其进行链接(否则将仅链接.defaults文件)。常见的过程是复制.defaults并根据需要更改设置。
编写安装脚本比您想像的要容易,并且给您很大的灵活性。
我同意最佳答案,但也想补充一点。我使用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>
以@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以相同的方式再次。
这并不是要取代已接受的答案作为最佳的通用解决方案,而是要以一种开阔的方式思考问题。您基本上可以避免仅将fromlocalchange迁移到masterfeature并快速转发master ,从而避免将本地更改意外合并到master。