0011852: add git merge driver for po/pot files
authorCornelius Weiß <mail@corneliusweiss.de>
Tue, 25 Aug 2015 16:21:29 +0000 (18:21 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Mon, 11 Jul 2016 08:59:03 +0000 (10:59 +0200)
... to improve po file merging

add this to the end of .git/config file:
[merge "pofile"]
        name = merge po-files driver
        driver = ./scripts/merge-po-files %A %O %B
        recursive = binary

https://forge.tine20.org/view.php?id=11852

Change-Id: I94e2675206858b5c1140f50b62a31ca39e0b13b7
Reviewed-on: http://gerrit.tine20.com/customers/2975
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
.gitattributes [new file with mode: 0644]
scripts/merge-po-files [new file with mode: 0755]

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..93a2f63
--- /dev/null
@@ -0,0 +1,2 @@
+*.po merge=pofile
+*.pot merge=pofile
diff --git a/scripts/merge-po-files b/scripts/merge-po-files
new file mode 100755 (executable)
index 0000000..fd18031
--- /dev/null
@@ -0,0 +1,119 @@
+#!/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
+m_msgmerge() {
+  msgmerge --force-po --quiet --no-fuzzy-matching $@
+}
+
+# wraps msgcat with default options
+m_msgcat() {
+  msgcat --force-po $@
+}
+
+
+# removes the "graveyard strings" from the input
+strip_graveyard() {
+  sed -e '/^#~/d'
+}
+
+# removes the "graveyard strings" from the input
+replace_graveyard() {
+  sed -e 's/^#~ //g'
+}
+
+# select messages with a conflict marker
+# pass -v to inverse selection
+grep_conflicts() {
+  msggrep $@ --msgstr -F -e '#-#-#' -
+}
+
+# select messages from $1 that are also in $2 but whose contents have changed
+extract_changes() {
+  msgcat -o - $1 $2 \
+    | grep_conflicts \
+    | m_msgmerge -o - $1 - \
+    | strip_graveyard
+}
+
+
+BASE=$1
+LOCAL=$2
+REMOTE=$3
+OUTPUT=$BASE
+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 | replace_graveyard > $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
\ No newline at end of file