2013-04-25 27 views
13

Tôi đã có sauTrình điều khiển hợp nhất Git 3 chiều cho tệp .PO (gettext) ở đâu?

[attr]POFILE merge=merge-po-files 

locale/*.po POFILE 

trong .gitattributes và tôi muốn để có được sáp nhập chi nhánh để hoạt động chính xác khi các tập tin định vị tương tự (ví dụ locale/en.po) đã được sửa đổi tại các chi nhánh paraller. Tôi hiện đang sử dụng trình điều khiển kết hợp sau đây:

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

Tuy nhiên, msgcat là quá ngu ngốc và đây không phải là ba cách đúng merge. Ví dụ, nếu tôi có

  1. phiên bản CƠ SỞ

    msgid "foo" 
    msgstr "foo" 
    
  2. phiên bản ĐỊA PHƯƠNG

    msgid "foo" 
    msgstr "bar" 
    
  3. REMOTE phiên bản

    msgid "foo" 
    msgstr "foo" 
    

Tôi sẽ kết thúc với một cuộc xung đột. Tuy nhiên, một ba cách điều khiển kết hợp đúng sẽ ra đúng merge:

msgid "foo" 
msgstr "bar" 

Lưu ý rằng tôi không thể chỉ cần thêm --use-first-msgcat vì REMOTE thể chứa bản dịch được cập nhật. Ngoài ra, nếu BASE, LOCAL và REMOTE là tất cả duy nhất, tôi vẫn muốn có xung đột, bởi vì điều đó thực sự sẽ là một xung đột.

Tôi cần thay đổi gì để thực hiện công việc này? Điểm thưởng cho điểm đánh dấu xung đột kém hơn '# - # - # - # - #', nếu có thể.

+0

Bất kỳ cơ hội bạn có thể sử dụng một công cụ hợp nhất, như KDiff3 (đó là 3 chiều)? – VonC

+0

Bạn đã cố gắng sửa chữa tệp .PO xung đột với kdiff3 chưa? Tôi có và nó không đẹp. Vấn đề với các tệp .PO là trong thực tế đó là các tệp cơ sở dữ liệu nhị phân chỉ xảy ra giống như các tệp văn bản. Bất kỳ công cụ nào được thiết kế để hợp nhất các tệp văn bản sẽ bị lỗi. –

Trả lời

1

Lấy cảm hứng từ câu trả lời của Mikko, chúng tôi đã thêm một cách sáp nhập 3 chiều chính thức vào đá quý Ruby git-whistles.

Nó không phụ thuộc vào git-merge hoặc viết lại chuỗi bằng Perl và chỉ thao tác các tệp PO bằng công cụ Gettext.

Dưới đây là đoạn code (MIT cấp phép):

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

Điều này không đủ ổn định để tôi sử dụng. Tôi đồng ý rằng đây là hướng đi chính xác nhưng trong một số trường hợp hợp nhất không thành công. Tôi không thể chia sẻ các trường hợp ví dụ và tôi hiện không có thời gian để tạo ra trường hợp thử nghiệm tối thiểu. Tôi sẽ cố gắng gỡ lỗi vấn đề khi tôi có đủ thời gian. Trình điều khiển phức tạp của tôi dưới đây có thể hợp nhất thành công nhưng trình điều khiển đó là một hack xấu xí. –

3

Dưới đây là một trình điều khiển ví dụ thực hiện sửa lỗi dựa trên văn bản với các điểm đánh dấu xung đột ở những vị trí chính xác. Tuy nhiên, trong trường hợp xung đột, git mergetool chắc chắn sẽ làm hỏng kết quả vì vậy điều này không thực sự tốt. Nếu bạn muốn sửa chữa mâu thuẫn hòa trộn chỉ sử dụng một trình soạn thảo văn bản, thì đây nên được tốt:

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

lời giải thích ngắn về trình điều khiển này: nó chuyển đổi định dạng tập tin PO thông thường thành dạng dòng đơn nơi mỗi dòng là một mục dịch. Sau đó, nó sử dụng thường xuyên git merge-file để làm việc hợp nhất và sau khi hợp nhất định dạng dòng đơn kết quả được chuyển đổi trở lại định dạng tệp PO thường xuyên. Cảnh báo: trình điều khiển này sẽ sử dụng msgcat --sort-output trên tệp .PO vì vậy nếu bạn muốn tệp PO của mình theo một số thứ tự cụ thể, đây có thể không phải là công cụ dành cho bạn.

5

Dưới đây là một trình điều khiển ví dụ phức tạp có vẻ giống như kết quả hợp nhất chính xác có thể chứa một số bản dịch đã bị xóa bởi phiên bản cục bộ hoặc từ xa.
Không có gì bị thiếu nên trình điều khiển này chỉ thêm một số sự lộn xộn phụ trong một số trường hợp.

Phiên bản này sử dụng gettext điểm đánh dấu xung đột gốc trông giống như #-#-#-#-# kết hợp với cờ fuzzy thay vì các dấu xung đột git thông thường.
Người tài xế là một chút xấu xí để workaround lỗi (hoặc tính năng) trong 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. 

lời giải thích ngắn về trình điều khiển này:

  • Nó chuyển đổi định dạng tập tin PO thường xuyên để dòng đơn định dạng trong đó mỗi dòng là một mục nhập dịch.
  • Sau đó, nó sử dụng thông thường git merge-file --union để thực hiện hợp nhất và sau khi hợp nhất định dạng đơn dòng kết quả được chuyển đổi về định dạng tệp PO thông thường.
    Độ phân giải xung đột thực tế được thực hiện sau khi sử dụng msguniq,
  • và sau đó kết hợp tệp kết quả với mẫu được tạo bằng cách thường xuyên msgcat kết hợp tệp nhập gốc để khôi phục siêu dữ liệu bị mất.

Cảnh báo: trình điều khiển này sẽ sử dụng msgcat --no-wrap vào file .PO và sẽ buộc UTF-8 mã hóa nếu mã hóa thực tế không được xác định.
Nếu bạn muốn sử dụng trình điều khiển hợp nhất này nhưng kiểm tra kết quả luôn, thay đổi exit $MERGESTATUS cuối cùng để trông giống như exit 1.

Sau khi nhận được xung đột hợp nhất từ ​​trình điều khiển này, phương pháp tốt nhất để khắc phục xung đột là mở tệp xung đột với virtaal và chọn Navigation: Incomplete.
Tôi thấy giao diện người dùng này là một công cụ khá đẹp để sửa chữa xung đột.

Các vấn đề liên quan