2013-04-25 59 views
13

我已经有以下.PO(gettext)文件的3路Git合并驱动程序在哪里?

[attr]POFILE merge=merge-po-files 

locale/*.po POFILE 
.gitattributes

,我想获得的分支合并时相同的本地化文件(例如locale/en.po)已在并联机器人分支被修改,以正常工作。我目前使用以下合并司机:

#!/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 

然而,msgcat是太愚蠢和这不是一个真正的三路合并。 举例来说,如果我有

  1. 基础版本

    msgid "foo" 
    msgstr "foo" 
    
  2. 本地版本

    msgid "foo" 
    msgstr "bar" 
    
  3. 远程版本

    msgid "foo" 
    msgstr "foo" 
    

我最终会遇到冲突。 然而,一个真正的三路合并驱动器将输出正确的合并

msgid "foo" 
msgstr "bar" 

请注意,我不能简单地添加到--use-firstmsgcat因为远程可能包含更新的翻译。另外,如果BASE,LOCAL和REMOTE都是独一无二的,我仍然需要冲突,因为那实际上是冲突。

我需要改变以做出这项工作?如果可能的话,比'# - # - # - # - #'更少的疯狂冲突标记的奖励分数。

+0

你可以使用另一种合并工具,像kdiff3(这是3路)任何机会呢? – VonC 2013-04-29 05:18:56

+0

您是否尝试修复冲突的.PO文件与kdiff3合并?我有,它不漂亮。 .PO文件的问题在于,实际上那些二进制数据库文件恰好看起来像文本文件。任何旨在合并文本文件的工具都将失败。 – 2013-04-29 05:21:31

回答

1

从Mikko的回答中获得了一些灵感,我们为git-whistles红宝石添加了一个全面的3向合并。

它不依赖于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

这对我的使用不够稳定。我同意这是正确的方向,但在某些情况下合并失败。我不能分享这个例子,我现在没有时间去创建最小的测试用例。当我有足够的时间时,我会尝试调试问题。我下面的复杂驱动程序能够成功合并,但该驱动程序是一个丑陋的黑客。 – 2016-06-30 06:31:54

3

下面是一个示例驱动程序,可以在正确的位置纠正带有冲突标记的基于文本的差异。但是,如果发生冲突,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来进行合并,合并后得到的单行格式被转换回常规的PO文件格式。 警告:此驱动程序将在.PO文件上使用msgcat --sort-output,所以如果您希望以某种特定顺序输入PO文件,则这可能不适合您。

5

下面是一个有点复杂的示例驱动程序,它似乎输出正确的合并,其中可能包含一些应该已被本地或远程版本删除的翻译。
没有东西应该丢失,所以这个驱动程序只是在某些情况下增加了一些额外的混乱。

此版本使用gettext原生冲突标记,看起来像#-#-#-#-#fuzzy标记组合,而不是正常的git冲突标记。
司机是个有点丑要解决的bug(或功能)在msgcatmsguniq

  • 它把常规PO文件格式,以单行:

    #!/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. 
    

    有关此驱动程序简短说明格式,每行是翻译条目。

  • 然后它使用常规的git merge-file --union来进行合并,合并后产生的单行格式被转换回常规的PO文件格式。
    实际的冲突解决后完成此使用msguniq
  • ,然后它最终合并与常规msgcat结合原始输入要恢复的文件可能丢失的元数据生成的模板生成的文件。

警告:此驱动程序将在.PO文件中使用msgcat --no-wrap和将迫使UTF-8编码,如果没有指定实际的编码。
如果要使用此合并驱动程序,但始终检查结果,请将最终的exit $MERGESTATUS更改为exit 1

获得此驱动程序合并冲突之后,在解决冲突的最好方法是打开冲突的文件,virtaal并选择Navigation: Incomplete
我发现这个用户界面是一个相当不错的工具来解决冲突。