0011852: add git merge driver for po/pot files
[tine20] / scripts / merge-po-files
1 #!/bin/sh
2 #
3 # Three-way merge driver for PO files
4 #
5 set -e
6
7 # failure handler
8 on_error() {
9   local parent_lineno="$1"
10   local message="$2"
11   local code="${3:-1}"
12   if [[ -n "$message" ]] ; then
13     echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
14   else
15     echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
16   fi
17   exit 255
18 }
19 # trap 'on_error ${LINENO}' ERR
20
21 # given a file, find the path that matches its contents
22 show_file() {
23   hash=`git hash-object "${1}"`
24   git ls-tree -r HEAD | fgrep "$hash" | cut -b54-
25 }
26
27 # wraps msgmerge with default options
28 m_msgmerge() {
29   msgmerge --force-po --quiet --no-fuzzy-matching $@
30 }
31
32 # wraps msgcat with default options
33 m_msgcat() {
34   msgcat --force-po $@
35 }
36
37
38 # removes the "graveyard strings" from the input
39 strip_graveyard() {
40   sed -e '/^#~/d'
41 }
42
43 # removes the "graveyard strings" from the input
44 replace_graveyard() {
45   sed -e 's/^#~ //g'
46 }
47
48 # select messages with a conflict marker
49 # pass -v to inverse selection
50 grep_conflicts() {
51   msggrep $@ --msgstr -F -e '#-#-#' -
52 }
53
54 # select messages from $1 that are also in $2 but whose contents have changed
55 extract_changes() {
56   msgcat -o - $1 $2 \
57     | grep_conflicts \
58     | m_msgmerge -o - $1 - \
59     | strip_graveyard
60 }
61
62
63 BASE=$1
64 LOCAL=$2
65 REMOTE=$3
66 OUTPUT=$BASE
67 TEMP=`mktemp /tmp/merge-po.XXXX`
68
69 echo "Using custom PO merge driver (`show_file ${LOCAL}`; $TEMP)"
70
71 # Extract the PO header from the current branch (top of file until first empty line)
72 sed -e '/^$/q' < $LOCAL > ${TEMP}.header
73
74 # clean input files
75 msguniq --force-po -o ${TEMP}.base   --unique ${BASE}
76 msguniq --force-po -o ${TEMP}.local  --unique ${LOCAL}
77 msguniq --force-po -o ${TEMP}.remote --unique ${REMOTE}
78
79 # messages changed on local
80 extract_changes ${TEMP}.local ${TEMP}.base > ${TEMP}.local-changes
81
82 # messages changed on remote
83 extract_changes ${TEMP}.remote ${TEMP}.base > ${TEMP}.remote-changes
84
85 # unchanged messages
86 m_msgcat -o - ${TEMP}.base ${TEMP}.local ${TEMP}.remote \
87   | grep_conflicts -v \
88   > ${TEMP}.unchanged
89
90 # messages changed on both local and remote (conflicts)
91 m_msgcat -o - ${TEMP}.remote-changes ${TEMP}.local-changes \
92   | grep_conflicts \
93   > ${TEMP}.conflicts
94
95 # messages changed on local, not on remote; and vice-versa
96 m_msgcat -o ${TEMP}.local-only  --unique ${TEMP}.local-changes  ${TEMP}.conflicts
97 m_msgcat -o ${TEMP}.remote-only --unique ${TEMP}.remote-changes ${TEMP}.conflicts
98
99 # the big merge
100 m_msgcat -o ${TEMP}.merge1 ${TEMP}.unchanged ${TEMP}.conflicts ${TEMP}.local-only ${TEMP}.remote-only
101
102 # create a template to filter messages actually needed (those on local and remote)
103 m_msgcat -o - ${TEMP}.local ${TEMP}.remote \
104   | m_msgmerge -o ${TEMP}.merge2 ${TEMP}.merge1 -
105
106 # final merge, adds saved header
107 m_msgcat -o ${TEMP}.merge3 --use-first ${TEMP}.header ${TEMP}.merge2
108
109 # produce output file (overwrites input LOCAL file)
110 cat ${TEMP}.merge3 | replace_graveyard > $OUTPUT
111
112 # check for conflicts
113 if grep '#-#' $OUTPUT > /dev/null ; then
114   echo "Conflict(s) detected"
115   echo "   between ${TEMP}.local and ${TEMP}.remote"
116   exit 1
117 fi
118 rm -f ${TEMP}*
119 exit 0