私が開発するときの一般的なシナリオは、コードベースにマシン固有の設定を必要とするいくつかの構成ファイルがあることです。これらのファイルはGitにチェックインされ、他の開発者は常に誤ってチェックインし直し、他の誰かの構成を壊してしまいます。
これに対する簡単な解決策は、Gitにチェックインしないか、さらに.gitignoreエントリを追加することです。ただし、開発者が自分のニーズに合わせて変更できるいくつかの賢明なデフォルトをファイルに含める方がはるかにエレガントであることがわかりました。
Gitをそのようなファイルでうまく再生させるためのエレガントな方法はありますか?マシン固有の構成ファイルを変更して、そのファイルをチェックインせずに「gitcommit-a」を実行できるようにしたいと思います。
プログラムに、設定用の構成ファイルのペアを読み取らせます。まず、リポジトリに含まれるconfig.defaults
ファイルを読み取る必要があります。次に、config.local
にリストされている必要がある.gitignore
ファイルを読み取る必要があります。
この配置では、新しい設定がデフォルトファイルに表示され、更新されるとすぐに有効になります。それらがオーバーライドされた場合にのみ、特定のシステムで異なります。
これのバリエーションとして、バージョン管理で出荷する一般的なconfig
ファイルだけを作成し、include config.local
のような処理を実行してマシン固有の値を取り込むことができます。これにより、コードに(ポリシーではなく)より一般的なメカニズムが導入され、その結果、より複雑な構成が可能になります(アプリケーションにとって望ましい場合)。多くの大規模なオープンソースソフトウェアで見られる、これからの一般的な拡張機能は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ローカルコミットだけです。
この手法を使用すると、プッシュプロセスに少し時間がかかりますが、問題が解決したので、共有したいと思いました。
1つの可能性は、実際のファイルを.gitignoreに含めることですが、拡張子が異なるデフォルト構成をチェックインします。 Railsアプリの典型的な例は、config /database.ymlファイルです。config/ database.yml.sampleをチェックインすると、各開発者が独自のconfig /database.ymlを作成します。すでに.gitignoredされています。
別の拡張子(たとえば.default)でデフォルト構成をチェックインし、シンボリックリンクを使用してデフォルトを正しい場所にシンボリックリンクし、正しい場所を.gitignoreに追加し、構成に関連する他のすべてを.gitignoreに追加します(したがって、チェックインされるのはconfig.defaultです)。
さらに、アプリケーション全体のシンボリックリンクを設定するクイックインストールスクリプトを記述します。
以前の会社でも同様のアプローチを使用しました。インストールスクリプトは、実行している環境(サンドボックス、開発、QA、本番)を自動検出し、自動的に正しい処理を実行します。 config.sandboxファイルがあり、サンドボックスから実行している場合は、それをリンクします(そうでない場合は、.defaultsファイルをリンクするだけです)。一般的な手順は、.defaultsをコピーし、必要に応じて設定を変更することでした。
インストールスクリプトの記述は想像以上に簡単で、柔軟性があります。
私はベストアンサーに同意しますが、何かを追加したいと思います。 ANTスクリプトを使用してGITリポジトリからファイルを削除および変更しているため、本番ファイルが上書きされることはありません。 ANTには、Javaプロパティファイルを変更するためのNiceオプションがあります。これは、ローカルテスト変数を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>
私はそれがデフォルトとローカルの設定ファイルでここで推奨されているようにそれをします。プロジェクト.gitignore
にあるローカル構成ファイルを管理するために、gitリポジトリ~/settings
を作成しました。そこで、すべてのプロジェクトのすべてのローカル設定を管理します。たとえば、project1
に~/settings
フォルダーを作成し、このプロジェクトのすべてのローカル構成をその中に入れます。その後、そのファイル/フォルダをproject1
にシンボリックリンクできます。
このアプローチを使用すると、ローカル構成ファイルを追跡でき、通常のソースコードリポジトリに配置しないでください。
@Greg Hewgillの回答に基づいて、ローカルの変更を使用して特定のコミットを追加し、localchangeとしてタグ付けすることができます。
git checkout -b feature master
vim config.local
git add -A && git commit -m "local commit" && git tag localchange
次に、機能のコミットの追加に進みます。作業が終了したら、次のようにして、localchangeをコミットせずにこのブランチをマスターにマージして戻すことができます。
git rebase --onto master localchange feature
git fetch . feature:master
git cherry-pick localchange
git tag localchange -f
これらのコマンドは次のようになります。
1)localchangeコミットを無視して、機能ブランチをマスターにリベースします。 2)機能ブランチを離れずにマスターを早送りします。3)ローカル変更コミットを機能ブランチの先頭に追加して作業を続行できるようにします。これは、作業を継続したい他のブランチに対して行うことができます。 4)localchangeタグをこの厳選されたコミットにリセットして、同じ方法でrebase --onto
を再度使用できるようにします。
これは、受け入れられた回答を最良の一般的な解決策として置き換えることを意味するのではなく、問題について箱から出して考える方法として意図されています。基本的に、localchange
からfeature
にリベースし、マスターを早送りするだけで、ローカルの変更を誤ってマスターにマージすることを回避できます。
最も簡単な解決策は、ファイルをデフォルトに編集し、コミットしてから、.gitignore
に追加することです。このように、開発者はgit commit -a
を実行するときに誤ってコミットすることはありませんが、デフォルトをgit add --force
で変更したい場合(おそらくまれ)でもコミットできます。
ただし、.default
および.local
構成ファイルを用意することが、最終的には最善の解決策です。これにより、マシン固有の構成を持っている人なら誰でも、独自の構成を壊すことなくデフォルトを変更できます。
最近(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')