git 提交机器特定的配置文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1396617/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Committing Machine Specific Configuration Files
提问by ghempton
A common scenario when I develop is that the codebase will have several config files which require machine specific settings. These files will be checked into Git and other developers will always accidentally check them back in and break someone else's configuration.
我开发时的一个常见场景是代码库将有几个需要机器特定设置的配置文件。这些文件将被检入 Git,其他开发人员总是会不小心将它们检入并破坏其他人的配置。
A simple solution to this would be to just not check them in to Git, or even to additionally add a .gitignore entry for them. However, I find that it is much more elegant to have some sensible defaults in the file which the developer can modify to suit his needs.
对此的一个简单解决方案是不将它们签入 Git,或者甚至为它们另外添加一个 .gitignore 条目。但是,我发现在文件中包含一些合理的默认值更优雅,开发人员可以修改这些默认值以满足他的需要。
Is there an elegant way to make Git play nicely with such files? I would like to be able to modify a machine-specific configuration file and then be able to run "git commit -a" without checking that file in.
有没有一种优雅的方法可以让 Git 很好地处理这些文件?我希望能够修改特定于机器的配置文件,然后能够在不签入该文件的情况下运行“git commit -a”。
采纳答案by Phil Miller
Have your program read a pair of configuration files for its settings. First, it should read a config.defaults
file that would be included in the repository. Then, it should read a config.local
file that should be listed in .gitignore
让您的程序读取其设置的一对配置文件。首先,它应该读取一个config.defaults
将包含在存储库中的文件。然后,它应该读取一个config.local
应该列在.gitignore
With this arrangement, new settings appear in the defaults file and take effect as soon as it's updated. They will only vary on particular systems if they're overridden.
通过这种安排,新设置会出现在默认文件中,并在更新后立即生效。如果它们被覆盖,它们只会在特定系统上有所不同。
As a variation on this, you could have just a general config
file that you ship in version control, and have it do something like include config.local
to bring in the machine-specific values. This introduces a more general mechanism (versus policy) in you code, and consequently enables more complicated configurations (if that's desirable for your application). The popular extension from this, seen in many large-scale open-source software, is to include conf.d
, which reads configuration from all the files in a directory.
作为对此的一种变体,您可以只config
在版本控制中提供一个通用文件,并让它执行诸如include config.local
引入特定于机器的值之类的操作。这在您的代码中引入了更通用的机制(相对于策略),因此可以启用更复杂的配置(如果您的应用程序需要的话)。在很多大型开源软件中,流行的扩展名是 to include conf.d
,它从目录中的所有文件中读取配置。
Also see my answerto a similar question.
另请参阅我对类似问题的回答。
回答by bhuber
You can try git update-index --skip-worktree filename
. This will tell git to pretend that local changes to filename don't exist, so git commit -a
will ignore it. It has the added advantage of also resisting git reset --hard
, so you won't accidentally lose your local changes. Also, automatic merges will fail gracefully if the file is changed upstream (unless the working directory copy matches the index copy, in which case it will be automatically updated). The downside is the command has to be run on all machines involved, and it's difficult to do this automatically. See also git update-index --assume-unchanged
for a subtly different version of this idea. Details on both can be found with git help update-index
.
你可以试试git update-index --skip-worktree filename
。这将告诉 git 假装对文件名的本地更改不存在,因此git commit -a
将忽略它。它还具有抵抗 的额外优势git reset --hard
,因此您不会意外丢失本地更改。此外,如果文件在上游更改,自动合并将正常失败(除非工作目录副本与索引副本匹配,在这种情况下它将自动更新)。缺点是命令必须在所有涉及的机器上运行,而且很难自动执行此操作。另见git update-index --assume-unchanged
这个想法的一个微妙的不同版本。两者的详细信息都可以通过 找到git help update-index
。
回答by Greg Hewgill
Another approach is to maintain local changes to common configuration files in another private branch. I do this for some projects that require several local changes. This technique may not be applicable to all situations, but it works for me in some cases.
另一种方法是在另一个私有分支中维护对公共配置文件的本地更改。我为一些需要多次本地更改的项目执行此操作。这种技术可能不适用于所有情况,但在某些情况下对我有用。
First I create a new branch based on the master branch (in this particular case I'm using git-svn so I need to commit from master but that's not terribly important here):
首先,我基于 master 分支创建一个新分支(在这种特殊情况下,我使用的是 git-svn,所以我需要从 master 提交,但这在这里并不是非常重要):
git checkout -b work master
Now modify the configuration file(s) as necessary and commit. I usually put something distinctive in the commit message like "NOCOMMIT" or "PRIVATE" (this will be useful later). At this point, you can work away on your private branch using your own config file.
现在根据需要修改配置文件并提交。我通常会在提交消息中添加一些与众不同的东西,例如“NOCOMMIT”或“PRIVATE”(这将在以后有用)。此时,您可以使用您自己的配置文件在您的私有分支上工作。
When you want to push your work back upstream, cherry-pick each change from your work
branch to the master. I have a script to help do this, which looks something like this:
当你想把你的工作推回上游时,从你的work
分支到 master挑选每个更改。我有一个脚本来帮助做到这一点,它看起来像这样:
#!/bin/sh
BRANCH=`git branch | grep ^\* | cut -d' ' -f2`
if [ $BRANCH != "master" ]; then
echo "git checkout work
git rebase master
: 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
This first checks to make sure I'm on the master
branch (sanity check). Then, it lists each commit in work
, filters out the ones that mention the NOCOMMIT keyword, reverses the order, and finally cherry-picks each commit (now from the oldest first) into master
.
这首先检查以确保我在master
分支上(健全性检查)。然后,它列出 中的每个提交work
,过滤掉提到 NOCOMMIT 关键字的提交,颠倒顺序,最后将每个提交(现在从最旧的第一个)挑选到master
.
Finally, after pushing the changes in master upstream, I switch back to work
and rebase:
最后,在将 master 中的更改推送到上游后,我切换回work
并 rebase:
<?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
Git will reapply each of the commits in the work
branch, effectively skipping over the one(s) that have already been applied in master
through the cherry-picking. What you should be left with is only the NOCOMMIT local commits.
Git 将重新应用work
分支中的每个提交,有效地跳过已经master
通过挑选的提交。您应该只剩下 NOCOMMIT 本地提交。
This technique makes the push process a bit more time-consuming, but it solved a problem for me so I thought I'd share.
这种技术使推送过程更加耗时,但它为我解决了一个问题,所以我想我会分享。
回答by hgmnz
One possibility is to have the actual files in your .gitignore, but check in default configurations with a different extension. A typical example for a Rails app would be the config/database.yml file. We would check in config/database.yml.sample, and each developer creates their own config/database.yml which is already .gitignored.
一种可能性是在 .gitignore 中包含实际文件,但检查具有不同扩展名的默认配置。Rails 应用程序的典型示例是 config/database.yml 文件。我们将检查 config/database.yml.sample,每个开发人员都会创建自己的 config/database.yml,它已经是 .gitignored。
回答by Bryan Alves
Check in a default configuration with a different extension (say .default), use a symlink to symlink the default to the correct location, add the correct location to .gitignore, and add everything else related to the configuration to .gitignore (so the only thing that gets checked in is config.default).
检查具有不同扩展名(例如 .default)的默认配置,使用符号链接将默认值符号链接到正确的位置,将正确的位置添加到 .gitignore,并将与配置相关的所有其他内容添加到 .gitignore(所以唯一的被检入的是 config.default)。
Additionally, write a quick install script that sets up the symlinks for your application-wide.
此外,编写一个快速安装脚本,为您的应用程序设置符号链接。
We used a similar approach at a previous company. The install script autodetected what environment you were running in (sandbox, development, QA, production), and would automatically do the right thing. If you had a config.sandbox file, and were running from the sandbox, it would link that (otherwise it would just link the .defaults file). Common procedure was to copy .defaults and change settings as necessary.
我们在之前的一家公司使用了类似的方法。安装脚本会自动检测您正在运行的环境(沙箱、开发、QA、生产),并会自动执行正确的操作。如果您有一个 config.sandbox 文件,并且正在从沙箱运行,它会链接它(否则它只会链接 .defaults 文件)。常见的过程是复制 .defaults 并根据需要更改设置。
Writing the install script is easier than you might imagine, and gives you a lot of flexibility.
编写安装脚本比您想象的要容易,并且为您提供了很大的灵活性。
回答by Pianoman
I agree with the best answer but also would like add something. I use an ANT script to strip & modify files from the GIT repo so I'm sure no production files get overwritten. There is a nice option in ANT to modify java-property files. This means putting your local test variables in a java-style property file and adding some code to process it, but it gives you the opportunity to automate building your site before you FTP it online. Typically you would put your production information in the site.default.properties file, and let ANT manage the settings. Your local settings would be in the site.local.properties.
我同意最佳答案,但也想添加一些内容。我使用 ANT 脚本从 GIT 存储库中删除和修改文件,因此我确定没有生产文件被覆盖。ANT 中有一个不错的选项可以修改 java-property 文件。这意味着将您的本地测试变量放在一个 java 样式的属性文件中并添加一些代码来处理它,但它使您有机会在在线 FTP 之前自动构建您的站点。通常,您会将生产信息放在 site.default.properties 文件中,并让 ANT 管理这些设置。您的本地设置将在 site.local.properties 中。
$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_");
then use it:
然后使用它:
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
Your site.default.properties would look like:
您的 site.default.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
and your site.local.properties would look like (notice the difference environment and enabled modules):
和您的 site.local.properties 看起来像(注意不同的环境和启用的模块):
<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>
And your ANT instructions: ($d{deploy} being your deployment target directory)
还有你的 ANT 指令:($d{deploy} 是你的部署目标目录)
# settings.py
import os
DEBUG = os.getenv('DJANGO_DEBUG') == 'True'
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')
回答by yvess
Nowadays (2019) I use ENV vars for example in python/django, you can also add defaults to them. In the context of docker I can save the ENV vars in a docker-compose.yml file or an extra file which is ignored in version control.
现在(2019 年)我在 python/django 中使用 ENV 变量,你也可以为它们添加默认值。在 docker 的上下文中,我可以将 ENV 变量保存在 docker-compose.yml 文件或在版本控制中被忽略的额外文件中。
git checkout -b feature master
vim config.local
git add -A && git commit -m "local commit" && git tag localchange
回答by Danilo Souza Mor?es
Building on @Greg Hewgill's answer, you could add a specific commit with your local changes and tag it as localchange:
基于@Greg Hewgill 的回答,您可以使用本地更改添加特定提交并将其标记为 localchange:
git rebase --onto master localchange feature
git fetch . feature:master
git cherry-pick localchange
git tag localchange -f
Then proceed to add your feature's commits. After finishing the work, you can merge this branch back to master without the localchange commit by doing this:
然后继续添加您的功能提交。完成工作后,您可以通过执行以下操作将此分支合并回 master 而不进行 localchange 提交:
##代码##These commands will:
这些命令将:
1) Rebase your feature branch to master, ignoring the localchange commit.
2) Fast forward master without leaving feature branch
3) Add localchange commit back to the top of the feature branch so you can continue working on it. You can do this to any other branch you want to continue working on.
4) Reset localchange tag to this cherry-picked commit so we can use rebase --onto
again in the same way.
1) 将您的功能分支重新设置为 master,忽略 localchange 提交。2) 在不离开功能分支的情况下快进 master 3) 将 localchange 提交添加回功能分支的顶部,以便您可以继续处理它。您可以对要继续工作的任何其他分支执行此操作。4) 将 localchange 标签重置为这个精心挑选的提交,以便我们可以rebase --onto
以相同的方式再次使用。
This isn't meant to replace the accepted answer as the best general solution, but as a way of thinking out of the box about the problem. You basically avoid accidentally merging local changes to master by only rebasing from localchange
to feature
and fast forwarding master.
这并不意味着将公认的答案替换为最佳通用解决方案,而是作为一种对问题开箱即用的思考方式。您基本上可以避免意外地将本地更改合并到 master,只需从localchange
tofeature
和 fast forwarding master 变基即可。
回答by yvess
I do it like it's recommended here with default and local config files.
To manage my local config files wich are in the projects .gitignore
, I made a git repo ~/settings
. There I manage all my local settings from all projects. You create, for example a folder project1
in ~/settings
and put all the local config stuff for this project into it. After that you can symlink that files/folder to your project1
.
我这样做就像在这里推荐使用默认和本地配置文件一样。为了管理项目中的本地配置文件.gitignore
,我创建了一个 git repo ~/settings
。在那里,我管理所有项目的所有本地设置。您创建,例如一个文件夹project1
中~/settings
,并把所有的本地配置的东西这个项目了进去。之后,您可以将该文件/文件夹符号链接到您的project1
.
With that approach you can track your local config files, and don't put them into to the normal source code repository.
通过这种方法,您可以跟踪本地配置文件,而不要将它们放入普通的源代码存储库中。
回答by crazy2be
The simplest solution is to edit the file to defaults, commit it, then add it to your .gitignore
. This way, developers will not accidentally commit it when doing git commit -a
, but they can still commit it in the (presumably rare) case where you want to change your defaults with git add --force
.
最简单的解决方案是将文件编辑为默认值,提交它,然后将其添加到您的.gitignore
. 这样一来,开发商不会意外做时提交它git commit -a
,但他们仍然可以提交它在你想改变你的默认值(大概是罕见的)情况git add --force
。
However, having a .default
and .local
config file is ultimately the best solution, since this allows anyone with a machine-specific configuration to change the defaults, without having to break their own configuration.
然而,拥有一个.default
和.local
配置文件最终是最好的解决方案,因为这允许任何具有特定机器配置的人更改默认值,而不必破坏他们自己的配置。