localization - Where's the 3-way Git merge driver for .PO (gettext) files? -


i have following

[attr]pofile merge=merge-po-files  locale/*.po pofile 

in .gitattributes , i'd merging of branches work correctly when same localization file (e.g. locale/en.po) has been modified in paraller branches. i'm using following merge driver:

#!/bin/bash # git merge driver .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 bit more meaningful filenames better conflict results cp "${1}" "$local" cp "${2}" "$base" cp "${3}" "$remote"  # merge files , overwrite local file 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 here, merge successful exit 0 

however, msgcat dumb , this not true 3 way merge. example, if have

  1. base version

    msgid "foo" msgstr "foo" 
  2. local version

    msgid "foo" msgstr "bar" 
  3. remote version

    msgid "foo" msgstr "foo" 

i'll end conflict. however, a true 3 way merge driver output correct merge:

msgid "foo" msgstr "bar" 

note cannot add --use-first msgcat because remote contain updated translation. in addition, if base, local , remote unique, still want conflict, because conflict.

what need change make work? bonus points less insane conflict marker '#-#-#-#-#', if possible.

taking inspiration mikko's answer, we've added full-fledged 3-way merger git-whistles ruby gem.

it doesn't rely of git-merge or rewriting string perl, , manipulates po files gettext tools.

here's code (mit licensed):

#!/bin/sh # # three-way merge driver po files # set -e  # failure handler on_error() {   local parent_lineno="$1"   local message="$2"   local code="${3:-1}"   if [[ -n "$message" ]] ;     echo "error on or near line ${parent_lineno}: ${message}; exiting status ${code}"   else     echo "error on or near line ${parent_lineno}; exiting status ${code}"   fi   exit 255 } trap 'on_error ${lineno}' err  # given file, find path matches contents show_file() {   hash=`git hash-object "${1}"`   git ls-tree -r head | fgrep "$hash" | cut -b54- }  # wraps msgmerge default options function m_msgmerge() {   msgmerge --force-po --quiet --no-fuzzy-matching $@ }  # wraps msgcat default options function m_msgcat() {   msgcat --force-po $@ }   # removes "graveyard strings" input function strip_graveyard() {   sed -e '/^#~/d' }  # select messages conflict marker # pass -v inverse selection function grep_conflicts() {   msggrep $@ --msgstr -f -e '#-#-#' - }  # select messages $1 in $2 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 po header 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 , remote (conflicts) m_msgcat -o - ${temp}.remote-changes ${temp}.local-changes \   | grep_conflicts \   > ${temp}.conflicts  # messages changed on local, not on remote; , 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  # big merge m_msgcat -o ${temp}.merge1 ${temp}.unchanged ${temp}.conflicts ${temp}.local-only ${temp}.remote-only  # create template filter messages needed (those on local , 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 conflicts if grep '#-#' $output > /dev/null ;   echo "conflict(s) detected"   echo "   between ${temp}.local , ${temp}.remote"   exit 1 fi rm -f ${temp}* exit 0 

Popular posts from this blog

How to calculate SNR of signals in MATLAB? -

c# - Attempting to upload to FTP: System.Net.WebException: System error -

ios - UISlider customization: how to properly add shadow to custom knob image -