2013-04-25 15 views
13

は、私はすでに.gitattributes.PO(gettext)ファイル用の3-way Gitマージドライバはどこにありますか?

[attr]POFILE merge=merge-po-files 

locale/*.po POFILE 

を以下持っていると私は同じローカライズファイル(例えばlocale/en.po)はparallerブランチに変更されたときに正しく動作するように枝のマージを取得したいのですが。しかし、msgcatはこれが本当の三方をマージしないで、あまりにもダムとある

#!/bin/bash 
# git merge driver for .PO files (gettext localizations) 
# Install: 
# git config merge.merge-po-files.driver "./bin/merge-po-files %A %O %B" 

LOCAL="${1}._LOCAL_" 
BASE="${2}._BASE_" 
REMOTE="${3}._REMOTE_" 

# rename to bit more meaningful filenames to get better conflict results 
cp "${1}" "$LOCAL" 
cp "${2}" "$BASE" 
cp "${3}" "$REMOTE" 

# merge files and overwrite local file with the result 
msgcat "$LOCAL" "$BASE" "$REMOTE" -o "${1}" || exit 1 

# cleanup 
rm -f "$LOCAL" "$BASE" "$REMOTE" 

# check if merge has conflicts 
fgrep -q '#-#-#-#-#' "${1}" && exit 1 

# if we get here, merge is successful 
exit 0 

:私は現在、ドライバーをマージ、次を使用しています。 たとえば、私は

  1. を持っている場合はBASEバージョン

    msgid "foo" 
    msgstr "foo" 
    
  2. LOCALバージョン

    msgid "foo" 
    msgstr "bar" 
    
  3. REMOTEバージョン

    msgid "foo" 
    msgstr "foo" 
    

私は紛争に終わるでしょう。 REMOTEが更新され、翻訳が含まれている可能性があるため、私は単にmsgcat--use-firstを追加することはできません

msgid "foo" 
msgstr "bar" 

注: しかし、真3つの方法は、ドライバの希望出力正しいマージをマージします。さらに、BASE、LOCAL、REMOTEがすべて一意であれば、私はまだ競合が欲しいです。

これを行うには何を変更する必要がありますか?可能であれば、「# - # - # - # - #」よりも狂気の少ない矛盾マーカーのボーナスポイント。

+0

あなたは(3ウェイである)KDiff3のと同様に、別のマージツールを使用することができます任意のチャンス? – VonC

+0

競合する.POファイルのマージをkdiff3に修正しようとしましたか?私は持っているし、それはかなりではありません。 .POファイルの問題は、実際にはテキストファイルのように見えるバイナリデータベースファイルであるということです。テキストファイルをマージするよう設計されたツールは失敗するでしょう。 –

答えて

1

私たちはMikkoの答えからインスピレーションを得て、git-whistles Ruby gemに本格的な3-way合併を加えました。

git-mergeに依存せず、Perlで文字列を書き直したり、GettextツールでPOファイルのみを操作したりすることはありません。

ここでは、コード(MITがライセンス)です:

#!/bin/sh 
# 
# Three-way merge driver for PO files 
# 
set -e 

# failure handler 
on_error() { 
    local parent_lineno="$1" 
    local message="$2" 
    local code="${3:-1}" 
    if [[ -n "$message" ]] ; then 
    echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}" 
    else 
    echo "Error on or near line ${parent_lineno}; exiting with status ${code}" 
    fi 
    exit 255 
} 
trap 'on_error ${LINENO}' ERR 

# given a file, find the path that matches its contents 
show_file() { 
    hash=`git hash-object "${1}"` 
    git ls-tree -r HEAD | fgrep "$hash" | cut -b54- 
} 

# wraps msgmerge with default options 
function m_msgmerge() { 
    msgmerge --force-po --quiet --no-fuzzy-matching [email protected] 
} 

# wraps msgcat with default options 
function m_msgcat() { 
    msgcat --force-po [email protected] 
} 


# removes the "graveyard strings" from the input 
function strip_graveyard() { 
    sed -e '/^#~/d' 
} 

# select messages with a conflict marker 
# pass -v to inverse selection 
function grep_conflicts() { 
    msggrep [email protected] --msgstr -F -e '#-#-#' - 
} 

# select messages from $1 that are also in $2 but whose contents have changed 
function extract_changes() { 
    msgcat -o - $1 $2 \ 
    | grep_conflicts \ 
    | m_msgmerge -o - $1 - \ 
    | strip_graveyard 
} 


BASE=$1 
LOCAL=$2 
REMOTE=$3 
OUTPUT=$LOCAL 
TEMP=`mktemp /tmp/merge-po.XXXX` 

echo "Using custom PO merge driver (`show_file ${LOCAL}`; $TEMP)" 

# Extract the PO header from the current branch (top of file until first empty line) 
sed -e '/^$/q' < $LOCAL > ${TEMP}.header 

# clean input files 
msguniq --force-po -o ${TEMP}.base --unique ${BASE} 
msguniq --force-po -o ${TEMP}.local --unique ${LOCAL} 
msguniq --force-po -o ${TEMP}.remote --unique ${REMOTE} 

# messages changed on local 
extract_changes ${TEMP}.local ${TEMP}.base > ${TEMP}.local-changes 

# messages changed on remote 
extract_changes ${TEMP}.remote ${TEMP}.base > ${TEMP}.remote-changes 

# unchanged messages 
m_msgcat -o - ${TEMP}.base ${TEMP}.local ${TEMP}.remote \ 
    | grep_conflicts -v \ 
    > ${TEMP}.unchanged 

# messages changed on both local and remote (conflicts) 
m_msgcat -o - ${TEMP}.remote-changes ${TEMP}.local-changes \ 
    | grep_conflicts \ 
    > ${TEMP}.conflicts 

# messages changed on local, not on remote; and vice-versa 
m_msgcat -o ${TEMP}.local-only --unique ${TEMP}.local-changes ${TEMP}.conflicts 
m_msgcat -o ${TEMP}.remote-only --unique ${TEMP}.remote-changes ${TEMP}.conflicts 

# the big merge 
m_msgcat -o ${TEMP}.merge1 ${TEMP}.unchanged ${TEMP}.conflicts ${TEMP}.local-only ${TEMP}.remote-only 

# create a template to filter messages actually needed (those on local and remote) 
m_msgcat -o - ${TEMP}.local ${TEMP}.remote \ 
    | m_msgmerge -o ${TEMP}.merge2 ${TEMP}.merge1 - 

# final merge, adds saved header 
m_msgcat -o ${TEMP}.merge3 --use-first ${TEMP}.header ${TEMP}.merge2 

# produce output file (overwrites input LOCAL file) 
cat ${TEMP}.merge3 > $OUTPUT 

# check for conflicts 
if grep '#-#' $OUTPUT > /dev/null ; then 
    echo "Conflict(s) detected" 
    echo " between ${TEMP}.local and ${TEMP}.remote" 
    exit 1 
fi 
rm -f ${TEMP}* 
exit 0 
+0

これは私の使用のために十分安定していませんでした。私はこれが正しい方向であることに同意しますが、場合によってはマージが失敗します。私はこの事例を分かち合うことができず、現在は最小限のテストケースを作成する時間がない。私は十分な時間があるときに問題をデバッグしようとします。下記の私の複雑なドライバは正常にマージできますが、そのドライバは醜いハックです。 –

3

正しい場所に競合マーカーがあるテキストベースのdiffを修正するドライバの例を以下に示します。しかし、矛盾が生じた場合はgit mergetoolが結果を混乱させてしまいますので、これは本当にうまくいかないでしょう。あなただけのテキストエディタを使用して、競合マージを修正したい場合は、これは問題ないはずです。このドライバに関する

#!/bin/bash 
# git merge driver for .PO files 
# Copyright (c) Mikko Rantalainen <[email protected]>, 2013 
# License: MIT 

LOCAL="${1}._LOCAL_" 
BASE="${2}._BASE_" 
REMOTE="${3}._REMOTE_" 
MERGED="${1}._MERGED_" 
OUTPUT="$LOCAL""OUTPUT_" 

LOCAL_ONELINE="$LOCAL""ONELINE_" 
BASE_ONELINE="$BASE""ONELINE_" 
REMOTE_ONELINE="$REMOTE""ONELINE_" 

# standardize the input files for regexping 
msgcat --no-wrap --strict --sort-output "${1}" > "$LOCAL" 
msgcat --no-wrap --strict --sort-output "${2}" > "$BASE" 
msgcat --no-wrap --strict --sort-output "${3}" > "$REMOTE" 

# convert each definition to single line presentation 
# extra fill is required to make sure that git separates each conflict 
perl -npe 'BEGIN {$/ = "#\n"}; s/#\n$/\n/s; s/#/##/sg; s/\n/#n/sg; s/#n$/\n/sg; s/#n$/\n/sg; $_.="#fill#\n" x 4' "$LOCAL" > "$LOCAL_ONELINE" 
perl -npe 'BEGIN {$/ = "#\n"}; s/#\n$/\n/s; s/#/##/sg; s/\n/#n/sg; s/#n$/\n/sg; s/#n$/\n/sg; $_.="#fill#\n" x 4' "$BASE" > "$BASE_ONELINE" 
perl -npe 'BEGIN {$/ = "#\n"}; s/#\n$/\n/s; s/#/##/sg; s/\n/#n/sg; s/#n$/\n/sg; s/#n$/\n/sg; $_.="#fill#\n" x 4' "$REMOTE" > "$REMOTE_ONELINE" 

# merge files using normal git merge machinery 
git merge-file -p -L "Current (working directory)" -L "Base (common ancestor)" -L "Incoming (another change)" "$LOCAL_ONELINE" "$BASE_ONELINE" "$REMOTE_ONELINE" > "$MERGED" 
MERGESTATUS=$? 

# convert back to normal PO file representation 
cat "$MERGED" | grep -v '^#fill#$' | perl -npe 's/#n/\n/g; s/##/#/g' > "$OUTPUT" 

# git merge driver must overwrite the first parameter with output 
mv "$OUTPUT" "${1}" 

# cleanup 
rm -f "$LOCAL" "$BASE" "$REMOTE" "$LOCAL_ONELINE" "$BASE_ONELINE" "$REMOTE_ONELINE" "$MERGED" 

exit $MERGESTATUS 

# Steps to install this driver: 
# (1) Edit ".git/config" in your repository directory 
# (2) Add following section: 
# 
# [merge "merge-po-files"] 
# name = merge po-files driver 
# driver = ./bin/merge-po-files %A %O %B 
# recursive = binary 
# 
# or 
# 
# git config merge.merge-po-files.driver "./bin/merge-po-files %A %O %B" 
# 
# The file ".gitattributes" will point git to use this merge driver. 

短い説明:それは、各行が変換エントリである単一行形式に通常のPOファイル形式に変換します。次に、通常のgit merge-fileを使用してマージを行い、マージ後に結果の1行形式が通常のPOファイル形式に変換されます。 警告:このドライバは.POファイルにmsgcat --sort-outputを使用します。そのため、特定の順序でPOファイルが必要な場合、これはツールではない可能性があります。

5

ここに、ローカルまたはリモートバージョンで削除されているはずの翻訳が含まれている可能性がある、正しいマージを出力すると思われる少し複雑なドライバがあります。
このドライバは、いくつかのケースでいくつか余分なクラッタを追加するだけで、欠けている必要はありません。

このバージョンでは、通常のgit競合マーカーの代わりにと組み合わせたgettextネイティブ競合マーカーが使用されています(fuzzyフラグ)。
ドライバがmsgcatmsguniqのバグ(または機能)を回避するには少し醜いです:

#!/bin/bash 
# git merge driver for .PO files 
# Copyright (c) Mikko Rantalainen <[email protected]>, 2013 
# License: MIT 

ORIG_HASH=$(git hash-object "${1}") 
WORKFILE=$(git ls-tree -r HEAD | fgrep "$ORIG_HASH" | cut -b54-) 
echo "Using custom merge driver for $WORKFILE..." 

LOCAL="${1}._LOCAL_" 
BASE="${2}._BASE_" 
REMOTE="${3}._REMOTE_" 

LOCAL_ONELINE="$LOCAL""ONELINE_" 
BASE_ONELINE="$BASE""ONELINE_" 
REMOTE_ONELINE="$REMOTE""ONELINE_" 

OUTPUT="$LOCAL""OUTPUT_" 
MERGED="$LOCAL""MERGED_" 
MERGED2="$LOCAL""MERGED2_" 

TEMPLATE1="$LOCAL""TEMPLATE1_" 
TEMPLATE2="$LOCAL""TEMPLATE2_" 
FALLBACK_OBSOLETE="$LOCAL""FALLBACK_OBSOLETE_" 

# standardize the input files for regexping 
# default to UTF-8 in case charset is still the placeholder "CHARSET" 
cat "${1}" | perl -npe 's!(^"Content-Type: text/plain; charset=)(CHARSET)(\\n"$)!$1UTF-8$3!' | msgcat --no-wrap --sort-output - > "$LOCAL" 
cat "${2}" | perl -npe 's!(^"Content-Type: text/plain; charset=)(CHARSET)(\\n"$)!$1UTF-8$3!' | msgcat --no-wrap --sort-output - > "$BASE" 
cat "${3}" | perl -npe 's!(^"Content-Type: text/plain; charset=)(CHARSET)(\\n"$)!$1UTF-8$3!' | msgcat --no-wrap --sort-output - > "$REMOTE" 

# convert each definition to single line presentation 
# extra fill is required to make sure that git separates each conflict 
perl -npe 'BEGIN {$/ = "\n\n"}; s/#\n$/\n/s; s/#/##/sg; s/\n/#n/sg; s/#n$/\n/sg; s/#n$/\n/sg; $_.="#fill#\n" x 4' "$LOCAL" > "$LOCAL_ONELINE" 
perl -npe 'BEGIN {$/ = "\n\n"}; s/#\n$/\n/s; s/#/##/sg; s/\n/#n/sg; s/#n$/\n/sg; s/#n$/\n/sg; $_.="#fill#\n" x 4' "$BASE" > "$BASE_ONELINE" 
perl -npe 'BEGIN {$/ = "\n\n"}; s/#\n$/\n/s; s/#/##/sg; s/\n/#n/sg; s/#n$/\n/sg; s/#n$/\n/sg; $_.="#fill#\n" x 4' "$REMOTE" > "$REMOTE_ONELINE" 

# merge files using normal git merge machinery 
git merge-file -p --union -L "Current (working directory)" -L "Base (common ancestor)" -L "Incoming (applied changeset)" "$LOCAL_ONELINE" "$BASE_ONELINE" "$REMOTE_ONELINE" > "$MERGED" 
MERGESTATUS=$? 

# remove possibly duplicated headers (workaround msguniq bug http://comments.gmane.org/gmane.comp.gnu.gettext.bugs/96) 
cat "$MERGED" | perl -npe 'BEGIN {$/ = "\n\n"}; s/^([^\n]+#nmsgid ""#nmsgstr ""#n.*?\n)([^\n]+#nmsgid ""#nmsgstr ""#n.*?\n)+/$1/gs' > "$MERGED2" 

# remove lines that have totally empty msgstr 
# and convert back to normal PO file representation 
cat "$MERGED2" | grep -v '#nmsgstr ""$' | grep -v '^#fill#$' | perl -npe 's/#n/\n/g; s/##/#/g' > "$MERGED" 

# run the output through msguniq to merge conflicts gettext style 
# msguniq seems to have a bug that causes empty output if zero msgids 
# are found after the header. Expected output would be the header... 
# Workaround the bug by adding an empty obsolete fallback msgid 
# that will be automatically removed by msguniq 

cat > "$FALLBACK_OBSOLETE" << 'EOF' 

#~ msgid "obsolete fallback" 
#~ msgstr "" 

EOF 
cat "$MERGED" "$FALLBACK_OBSOLETE" | msguniq --no-wrap --sort-output > "$MERGED2" 


# create a hacked template from default merge between 3 versions 
# we do this to try to preserve original file ordering 
msgcat --use-first "$LOCAL" "$REMOTE" "$BASE" > "$TEMPLATE1" 
msghack --empty "$TEMPLATE1" > "$TEMPLATE2" 
msgmerge --silent --no-wrap --no-fuzzy-matching "$MERGED2" "$TEMPLATE2" > "$OUTPUT" 

# show some results to stdout 
if grep -q '#-#-#-#-#' "$OUTPUT" 
then 
    FUZZY=$(cat "$OUTPUT" | msgattrib --only-fuzzy --no-obsolete --color | perl -npe 'BEGIN{ undef $/; }; s/^.*?msgid "".*?\n\n//s') 
    if test -n "$FUZZY" 
    then 
     echo "-------------------------------" 
     echo "Fuzzy translations after merge:" 
     echo "-------------------------------" 
     echo "$FUZZY" 
     echo "-------------------------------" 
    fi 
fi 

# git merge driver must overwrite the first parameter with output 
mv "$OUTPUT" "${1}" 

# cleanup 
rm -f "$LOCAL" "$BASE" "$REMOTE" "$LOCAL_ONELINE" "$BASE_ONELINE" "$REMOTE_ONELINE" "$MERGED" "$MERGED2" "$TEMPLATE1" "$TEMPLATE2" "$FALLBACK_OBSOLETE" 

# return conflict if merge has conflicts according to msgcat/msguniq 
grep -q '#-#-#-#-#' "${1}" && exit 1 

# otherwise, return git merge status 
exit $MERGESTATUS 

# Steps to install this driver: 
# (1) Edit ".git/config" in your repository directory 
# (2) Add following section: 
# 
# [merge "merge-po-files"] 
# name = merge po-files driver 
# driver = ./bin/merge-po-files %A %O %B 
# recursive = binary 
# 
# or 
# 
# git config merge.merge-po-files.driver "./bin/merge-po-files %A %O %B" 
# 
# The file ".gitattributes" will point git to use this merge driver. 

このドライバについての短い説明:

  • これは1行に通常のPOファイル形式を変換各行は翻訳エントリです。
  • 次に、通常のgit merge-file --unionを使用してマージを行い、マージ後に結果の1行形式が通常のPOファイル形式に変換されます。
    実際の紛争解決はmsguniq
  • を使用して、この後に行われ、それが最終的には可能性が失われたメタデータを復元するために元の入力ファイルを結合定期msgcatによって生成されたテンプレートと結果のファイルをマージしています。

警告:このドライバは.POファイルにmsgcat --no-wrapを使用し、実際のエンコーディングが指定されていない場合UTF-8エンコーディングを強制します。
このマージドライバを使用したいが結果を常に調べたい場合は、最後のexit $MERGESTATUSexit 1のように変更してください。

このドライバからの競合をマージ取得した後、競合を固定するための最良の方法は、virtaalとの競合ファイルを開き、Navigation: Incompleteを選択することです。
私はこのUIが競合を修正するための非常に素晴らしいツールであることを発見しました。

関連する問題