2013-04-25 8 views
13

Ho già seguitoDov'è il driver di fusione Git a 3 vie per i file .PO (gettext)?

[attr]POFILE merge=merge-po-files 

locale/*.po POFILE 

nel .gitattributes e mi piacerebbe ottenere la fusione delle filiali per funzionare correttamente quando lo stesso file di localizzazione (ad esempio locale/en.po) è stato modificato nelle filiali paraller. Attualmente sto usando seguente driver unione:

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

Tuttavia, il msgcat è troppo stupido e questo non è un vero e proprio a tre vie fondersi. Per esempio, se ho

  1. versione base

    msgid "foo" 
    msgstr "foo" 
    
  2. versione locale

    msgid "foo" 
    msgstr "bar" 
    
  3. versione REMOTE

    msgid "foo" 
    msgstr "foo" 
    

Finirò con un conflitto. Tuttavia, un vero e proprio driver di fusione a tre vie sarebbe uscita corretta fusione:

msgid "foo" 
msgstr "bar" 

Nota che non posso aggiungere semplicemente --use-first-msgcat perché il telecomando potrebbe contenere la traduzione aggiornata. Inoltre, se BASE, LOCAL e REMOTE sono tutti unici, voglio ancora un conflitto, perché sarebbe davvero un conflitto.

Cosa devo cambiare per farlo funzionare? Punti bonus per segnalini conflitto meno folle di "# - # - # - # - #", se possibile.

+0

Qualsiasi possibilità che tu possa utilizzare un altro strumento di unione, come kdiff3 (che è a 3 vie)? – VonC

+0

Hai provato a correggere l'unione file .PO in conflitto con kdiff3? Ho e non è carino. Il problema con i file .PO è che in realtà si tratta di file di database binari che sembrano file di testo. Qualsiasi strumento progettato per unire file di testo fallirà. –

risposta

1

Prendendo ispirazione dalla risposta di Mikko, abbiamo aggiunto una vera fusione a 3 vie alla gemma git-whistles Ruby.

Non si basa su git-merge o riscrive la stringa con Perl e manipola solo i file PO con gli strumenti Gettext.

Ecco il codice (MIT licenza):

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

Questo non era abbastanza stabile per il mio uso. Sono d'accordo che questa è la direzione corretta da seguire, ma in alcuni casi l'unione fallisce. Non posso condividere il caso di esempio e al momento non ho tempo per creare un caso di test minimale. Proverò a eseguire il debug del problema quando avrò abbastanza tempo. Il mio driver complesso di seguito è in grado di unire con successo ma quel driver è un brutto trucco. –

3

Ecco un esempio di driver che corregge la differenza di testo basata sui contrassegni di conflitto nelle posizioni corrette. Tuttavia, in caso di conflitto, il git mergetool bloccherà sicuramente i risultati, quindi non è molto buono. Se si vuole risolvere in conflitto si fonde con un semplice editor di testo, allora questo dovrebbe andare bene:

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

breve spiegazione su questo driver: converte formato di file PO regolare in formato singola linea in cui ogni riga è una voce di traduzione. Quindi utilizza regolarmente git merge-file per eseguire l'unione e dopo l'unione il formato di riga singola risultante viene riconvertito in formato di file PO regolare. Avviso: questo driver utilizzerà msgcat --sort-output sul file .PO, quindi se si desidera che i file PO siano ordinati in un ordine specifico, questo potrebbe non essere lo strumento adatto a voi.

5

Ecco un esempio di driver un po 'complesso che sembra produrre un'unione corretta che potrebbe contenere alcune traduzioni che avrebbero dovuto essere cancellate dalla versione locale o remota.
Nulla dovrebbe mancare quindi questo driver aggiunge solo un po 'di confusione in alcuni casi.

Questa versione utilizza il contrassegno di conflitto nativo gettext che assomiglia a #-#-#-#-# combinato con il flag fuzzy anziché i normali indicatori di conflitto git.
Il driver è un po 'brutto Per risolvere i bug (o caratteristiche) in msgcat e msguniq:

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

breve spiegazione su questo driver:

  • Converte regolare formato di file PO a singola linea formato in cui ogni riga è una voce di traduzione.
  • Quindi utilizza regolare git merge-file --union per eseguire l'unione e dopo l'unione il formato di riga singola risultante viene riconvertito in formato di file PO regolare.
    La risoluzione effettiva dei conflitti viene fatto dopo questa operazione utilizzando msguniq,
  • e poi finalmente unisce il file risultante con il modello generato da regolare msgcat combinando i file di input originali per ripristinare i metadati forse perduta.

Attenzione: questo driver utilizzerà msgcat --no-wrap sul file .PO e costringerà UTF-8 codifica se la codifica attuale non è specificato.
Se si desidera utilizzare questo driver di unione ma ispezionare sempre i risultati, modificare l'exit $MERGESTATUS finale in modo che assomigli a exit 1.

Dopo aver ottenuto unire conflitto da questo driver, il metodo migliore per fissare il conflitto è quello di aprire il file in conflitto con virtaal e selezionare Navigation: Incomplete.
Trovo questa interfaccia utente uno strumento piuttosto carino per risolvere il conflitto.