標準のgitツールによって実行されるマージ操作は、プログラムのソースコードを含むテキストファイルに適しています。 ただし、厳密に構造化されたデータ(特にJSON)を含むテキストファイルをマージすることは大きな頭痛の種です。
この問題を解決するには、JSONファイル用の別個のマージツールをgitに接続します。これは、行ごとには機能しませんが、JSONオブジェクトの構造を考慮します。
このためにJavaScriptスクリプトを使用することをお勧めします。このスクリプトは、マージされたJSONファイルを分析し、JSONオブジェクトの構造とネストに基づいてマージします。
それは何を与えますか?
たとえば、アプリケーション設定をJSONファイルに保持します。 同時に2人の開発者が新しいパラメーターをファイルの末尾に追加すると(それぞれ独自の名前が付けられます)、gitにマージすると競合が発生します。 セマンティックマージスクリプトは、2つの異なるパラメーターが追加されていることを把握し、結果ファイル内の両方のパラメーターを含め、競合なしに自動的にマージします。
また、同じプロパティセットを持つオブジェクトのリストがJSONに保存されている場合、セマンティックマージが保存されます。 そのようなリストの中央に新しいオブジェクトが追加され、プロパティ値が周囲のものと異なる場合、標準のgitマージ、および行ごとの比較ツールは混乱する可能性が高くなります。 テキストを注意深く見て、JSON構造を損なう危険性があるため、手動で競合を解決する必要があります。
JSONのオブジェクトの順序を変更する場合も同様です。
セマンティックマージの機能
ファイル内の名前付きオブジェクトの順序は無視されます。
名前のないオブジェクト(配列要素)の場合、行の順序が重要です。 配列の比較は、両方のファイルの配列要素の順序です。 配列の中央に要素を追加すると、この状況は認識されず、後続のすべての要素で競合が発生します。 マージされた両方のファイルで配列の最後に要素が追加された場合にも競合が発生します。
対立
競合が発生した場合(マージされた両方のバージョンのファイルで同じオブジェクトが変更された場合)、競合の本質を示す情報が結果のマージファイルに追加されます。
{ "CONFLICT": "<<<<<<<<>>>>>>>>",
スクリプトバージョン
Windowsアプリケーションを開発し、Windows用のgitを使用しているので、スクリプトを完成させて、「そのまま」Windowsで、つまりWindows Scripting Hostを介して動作するようにしました。 WSHはJSONをサポートしていないため、JSON解析ライブラリはスクリプトに直接含まれています。 node.jsを使用する準備ができている人のために、スクリプトのよりコンパクトなバージョンがあります。
Gitでマージドライバーを有効にする手順
1. jsonmerge.jsスクリプトを
git \ libフォルダー、たとえば
%Program Files(x86)%\ Git \ lib \に配置します2. gitで新しいmerge-driverを接続します。 これを行うには、git構成ファイルに変更を加えます。
1つのリポジトリに対してのみドライバーを有効にするには、ファイル
<project folder> \。Git \ confgを変更します
または、すべてのローカルリポジトリに対してドライバーを有効にするには、ユーザー設定フォルダー(Windowsでは%userprofile%)にあるグローバル設定ファイル
.gitconfigを変更します(例
:C:\ Users \ <username> \)。 [merge "json_merge"] name = A custom merge driver for json files driver = cscript //B //Nologo 'C:/Program Files (x86)/Git/lib/jsonmerge.js' %O %A %B recursive = binary
node.jsの場合、ドライバー行は次のようになります。
driver = node 'C:/Program Files (x86)/Git/lib/jsonmerge.js' %O %A %B
jsonmerge.jsファイルへのパスを独自のものに変更することを忘れないでください。
3.
.gitattributesファイルで、このドライバーを使用するファイル拡張子を示します。 効果を下位レベルのフォルダーに拡張するために、任意のプロジェクトフォルダーに配置できます。 通常、プロジェクトのルートフォルダにあります。
*.json merge=json_merge
最初に、coffeescriptスクリプトは
ここから取得
され 、純粋なjavacriptに転送され、標準のWindows Script Hostを介してWindowsで実行できるように改善されました。 エラー処理も追加されました。マージされたファイルのいずれかのJSON構造が正しくない場合、元のスクリプトは競合の兆候を返さず、gitはマージが成功したと見なします。 これは私のバージョンで修正されています。
サンプルソースデータと結果
たとえば、デバッグプロパティの競合する変更、および競合を引き起こさない2つの類似オブジェクトの追加。
共通の祖先 { "widget": { "debug": "off", "window": { "title": "This is title", "name": "main_window", "width": 500, "height": 500 } } }
私たちのバージョン { "widget": { "debug": "on", "window": { "title": "This is title", "name": "main_window", "width": 500, "height": 500 }, "imageMoon": { "src": "Images/Moon.png", "name": "moon", "hOffset": 250, "vOffset": 250, "alignment": "center" } } }
彼らのバージョン { "widget": { "debug": "full", "window": { "title": "This is title", "name": "main_window", "width": 500, "height": 500 }, "imageSaturn": { "src": "Images/Saturn.png", "name": "saturn", "hOffset": 250, "vOffset": 250, "alignment": "center" } } }
結果をマージ { "widget": { "debug": { "CONFLICT": "<<<<<<<<>>>>>>>>", "OURS": "on", "THEIRS": "full", "ANCESTOR": "off", "PATH": "widget.debug" }, "window": { "title": "This is title", "name": "main_window", "width": 500, "height": 500 }, "imageMoon": { "src": "Images/Moon.png", "name": "moon", "hOffset": 250, "vOffset": 250, "alignment": "center" }, "imageSaturn": { "src": "Images/Saturn.png", "name": "saturn", "hOffset": 250, "vOffset": 250, "alignment": "center" } } }
実際、スクリプト自体
node.jsの下のJsonmerge.jsバージョン var ancestor, conflicts, fs, make_conflict_node, merge, ours, theirs; fs = require('fs'); try { ancestor = JSON.parse(fs.readFileSync(process.argv[2])); } catch(e) { console.log('Incorrect JSON in ancestor file '+process.argv[2]+ ' '+e.message); process.exit(1); } try { ours = JSON.parse(fs.readFileSync(process.argv[3])); } catch(e) { console.log('Incorrect JSON in ours file '+process.argv[3]+ ' '+e.message); process.exit(1); } try { theirs = JSON.parse(fs.readFileSync(process.argv[4])); } catch(e) { console.log('Incorrect JSON in theirs file '+process.argv[4]+ ' '+e.message); process.exit(1); } conflicts = false; make_conflict_node = function(ancestor_value, our_value, their_value, path) { var res; res = {}; res['CONFLICT'] = '<<<<<<<<>>>>>>>>'; res['OURS'] = our_value != null ? our_value : null; res['THEIRS'] = their_value != null ? their_value : null; res['ANCESTOR'] = ancestor_value != null ? ancestor_value : null; res['PATH'] = path.join('.'); return res; }; merge = function(ancestor_node, our_node, their_node, path) { var ancestor_value, key, keys, our_value, sub_path, their_value, _, _results; if (path == null) { path = []; } keys = {}; for (key in our_node) { _ = our_node[key]; keys[key] = true; } for (key in their_node) { _ = their_node[key]; keys[key] = true; } _results = []; for (key in keys) { _ = keys[key]; ancestor_value = ancestor_node != null ? ancestor_node[key] : void 0; our_value = our_node != null ? our_node[key] : void 0; their_value = their_node != null ? their_node[key] : void 0; sub_path = path.concat(key); if (our_value !== their_value) { if (JSON.stringify(their_value) === JSON.stringify(ancestor_value)) { continue; } else if (JSON.stringify(our_value) === JSON.stringify(ancestor_value)) { _results.push(our_node[key] = their_value); } else if (our_value && their_value && typeof our_value === 'object' && typeof their_value === 'object') { _results.push(merge(ancestor_value, our_value, their_value, sub_path)); } else { conflicts = true; _results.push(our_node[key] = make_conflict_node(ancestor_value, our_value, their_value, sub_path)); } } else { _results.push(void 0); } } return _results; }; merge(ancestor, ours, theirs); fs.writeFileSync(process.argv[3], JSON.stringify(ours, null, 4)); process.exit(conflicts ? 1 : 0);