pack-bitmap: improve grammar of "xor chain" error message
[git/debian.git] / mergetools / vimdiff
blobf770b8fe244c8ddd9dc349125a8e8e0ac0325574
1 # This script can be run in two different contexts:
3 #   - From git, when the user invokes the "vimdiff" merge tool. In this context
4 #     this script expects the following environment variables (among others) to
5 #     be defined (which is something "git" takes care of):
7 #       - $BASE
8 #       - $LOCAL
9 #       - $REMOTE
10 #       - $MERGED
12 #     In this mode, all this script does is to run the next command:
14 #         vim -f -c ... $LOCAL $BASE $REMOTE $MERGED
16 #     ...where the "..." string depends on the value of the
17 #     "mergetool.vimdiff.layout" configuration variable and is used to open vim
18 #     with a certain layout of buffers, windows and tabs.
20 #   - From a script inside the unit tests framework folder ("t" folder) by
21 #     sourcing this script and then manually calling "run_unit_tests", which
22 #     will run a battery of unit tests to make sure nothing breaks.
23 #     In this context this script does not expect any particular environment
24 #     variable to be set.
27 ################################################################################
28 ## Internal functions (not meant to be used outside this script)
29 ################################################################################
31 debug_print () {
32         # Send message to stderr if global variable GIT_MERGETOOL_VIMDIFF is set
33         # to "true"
35         if test -n "$GIT_MERGETOOL_VIMDIFF_DEBUG"
36         then
37                 >&2 echo "$@"
38         fi
41 substring () {
42         # Return a substring of $1 containing $3 characters starting at
43         # zero-based offset $2.
44         #
45         # Examples:
46         #
47         #   substring "Hello world" 0 4  --> "Hell"
48         #   substring "Hello world" 3 4  --> "lo w"
49         #   substring "Hello world" 3 10 --> "lo world"
51         STRING=$1
52         START=$2
53         LEN=$3
55         echo "$STRING" | cut -c$(( START + 1 ))-$(( START + $LEN ))
58 gen_cmd_aux () {
59         # Auxiliary function used from "gen_cmd()".
60         # Read that other function documentation for more details.
62         LAYOUT=$1
63         CMD=$2  # This is a second (hidden) argument used for recursion
65         debug_print
66         debug_print "LAYOUT    : $LAYOUT"
67         debug_print "CMD       : $CMD"
69         if test -z "$CMD"
70         then
71                 CMD="echo" # vim "nop" operator
72         fi
74         start=0
75         end=${#LAYOUT}
77         nested=0
78         nested_min=100
81         # Step 1:
82         #
83         # Increase/decrease "start"/"end" indices respectively to get rid of
84         # outer parenthesis.
85         #
86         # Example:
87         #
88         #   - BEFORE: (( LOCAL , BASE ) / MERGED )
89         #   - AFTER :  ( LOCAL , BASE ) / MERGED
91         oldIFS=$IFS
92         IFS=#
93         for c in $(echo "$LAYOUT" | sed 's:.:&#:g')
94         do
95                 if test "$c" = " "
96                 then
97                         continue
98                 fi
100                 if test "$c" = "("
101                 then
102                         nested=$(( nested + 1 ))
103                         continue
104                 fi
106                 if test "$c" = ")"
107                 then
108                         nested=$(( nested - 1 ))
109                         continue
110                 fi
112                 if test "$nested" -lt "$nested_min"
113                 then
114                         nested_min=$nested
115                 fi
116         done
117         IFS=$oldIFS
119         debug_print "NESTED MIN: $nested_min"
121         while test "$nested_min" -gt "0"
122         do
123                 start=$(( start + 1 ))
124                 end=$(( end - 1 ))
126                 start_minus_one=$(( start - 1 ))
128                 while ! test "$(substring "$LAYOUT" "$start_minus_one" 1)" = "("
129                 do
130                         start=$(( start + 1 ))
131                         start_minus_one=$(( start_minus_one + 1 ))
132                 done
134                 while ! test "$(substring "$LAYOUT" "$end" 1)" = ")"
135                 do
136                         end=$(( end - 1 ))
137                 done
139                 nested_min=$(( nested_min - 1 ))
140         done
142         debug_print "CLEAN     : $(substring "$LAYOUT" "$start" "$(( end - start ))")"
145         # Step 2:
146         #
147         # Search for all valid separators ("+", "/" or ",") which are *not*
148         # inside parenthesis. Save the index at which each of them makes the
149         # first appearance.
151         index_new_tab=""
152         index_horizontal_split=""
153         index_vertical_split=""
155         nested=0
156         i=$(( start - 1 ))
158         oldIFS=$IFS
159         IFS=#
160         for c in $(substring "$LAYOUT" "$start" "$(( end - start ))" | sed 's:.:&#:g');
161         do
162                 i=$(( i + 1 ))
164                 if test "$c" = " "
165                 then
166                         continue
167                 fi
169                 if test "$c" = "("
170                 then
171                         nested=$(( nested + 1 ))
172                         continue
173                 fi
175                 if test "$c" = ")"
176                 then
177                         nested=$(( nested - 1 ))
178                         continue
179                 fi
181                 if test "$nested" = 0
182                 then
183                         current=$c
185                         if test "$current" = "+"
186                         then
187                                 if test -z "$index_new_tab"
188                                 then
189                                         index_new_tab=$i
190                                 fi
192                         elif test "$current" = "/"
193                         then
194                                 if test -z "$index_horizontal_split"
195                                 then
196                                         index_horizontal_split=$i
197                                 fi
199                         elif test "$current" = ","
200                         then
201                                 if test -z "$index_vertical_split"
202                                 then
203                                         index_vertical_split=$i
204                                 fi
205                         fi
206                 fi
207         done
208         IFS=$oldIFS
211         # Step 3:
212         #
213         # Process the separator with the highest order of precedence
214         # (";" has the highest precedence and "|" the lowest one).
215         #
216         # By "process" I mean recursively call this function twice: the first
217         # one with the substring at the left of the separator and the second one
218         # with the one at its right.
220         terminate="false"
222         if ! test -z "$index_new_tab"
223         then
224                 before="-tabnew"
225                 after="tabnext"
226                 index=$index_new_tab
227                 terminate="true"
229         elif ! test -z "$index_horizontal_split"
230         then
231                 before="leftabove split"
232                 after="wincmd j"
233                 index=$index_horizontal_split
234                 terminate="true"
236         elif ! test -z "$index_vertical_split"
237         then
238                 before="leftabove vertical split"
239                 after="wincmd l"
240                 index=$index_vertical_split
241                 terminate="true"
242         fi
244         if  test "$terminate" = "true"
245         then
246                 CMD="$CMD | $before"
247                 CMD=$(gen_cmd_aux "$(substring "$LAYOUT" "$start" "$(( index - start ))")" "$CMD")
248                 CMD="$CMD | $after"
249                 CMD=$(gen_cmd_aux "$(substring "$LAYOUT" "$(( index + 1 ))" "$(( ${#LAYOUT} - index ))")" "$CMD")
250                 echo "$CMD"
251                 return
252         fi
255         # Step 4:
256         #
257         # If we reach this point, it means there are no separators and we just
258         # need to print the command to display the specified buffer
260         target=$(substring "$LAYOUT" "$start" "$(( end - start ))" | sed 's:[ @();|-]::g')
262         if test "$target" = "LOCAL"
263         then
264                 CMD="$CMD | 1b"
266         elif test "$target" = "BASE"
267         then
268                 CMD="$CMD | 2b"
270         elif test "$target" = "REMOTE"
271         then
272                 CMD="$CMD | 3b"
274         elif test "$target" = "MERGED"
275         then
276                 CMD="$CMD | 4b"
278         else
279                 CMD="$CMD | ERROR: >$target<"
280         fi
282         echo "$CMD"
283         return
287 gen_cmd () {
288         # This function returns (in global variable FINAL_CMD) the string that
289         # you can use when invoking "vim" (as shown next) to obtain a given
290         # layout:
291         #
292         #   $ vim -f $FINAL_CMD "$LOCAL" "$BASE" "$REMOTE" "$MERGED"
293         #
294         # It takes one single argument: a string containing the desired layout
295         # definition.
296         #
297         # The syntax of the "layout definitions" is explained in "Documentation/
298         # mergetools/vimdiff.txt" but you can already intuitively understand how
299         # it works by knowing that...
300         #
301         #   * "+" means "a new vim tab"
302         #   * "/" means "a new vim horizontal split"
303         #   * "," means "a new vim vertical split"
304         #
305         # It also returns (in global variable FINAL_TARGET) the name ("LOCAL",
306         # "BASE", "REMOTE" or "MERGED") of the file that is marked with an "@",
307         # or "MERGED" if none of them is.
308         #
309         # Example:
310         #
311         #     gen_cmd "@LOCAL , REMOTE"
312         #     |
313         #     `-> FINAL_CMD    == "-c \"echo | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
314         #         FINAL_TARGET == "LOCAL"
316         LAYOUT=$1
319         # Search for a "@" in one of the files identifiers ("LOCAL", "BASE",
320         # "REMOTE", "MERGED"). If not found, use "MERGE" as the default file
321         # where changes will be saved.
323         if echo "$LAYOUT" | grep @LOCAL >/dev/null
324         then
325                 FINAL_TARGET="LOCAL"
326         elif echo "$LAYOUT" | grep @BASE >/dev/null
327         then
328                 FINAL_TARGET="BASE"
329         else
330                 FINAL_TARGET="MERGED"
331         fi
334         # Obtain the first part of vim "-c" option to obtain the desired layout
336         CMD=$(gen_cmd_aux "$LAYOUT")
339         # Adjust the just obtained script depending on whether more than one
340         # windows are visible or not
342         if echo "$LAYOUT" | grep ",\|/" >/dev/null
343         then
344                 CMD="$CMD | tabdo windo diffthis"
345         else
346                 CMD="$CMD | bufdo diffthis"
347         fi
350         # Add an extra "-c" option to move to the first tab (notice that we
351         # can't simply append the command to the previous "-c" string as
352         # explained here: https://github.com/vim/vim/issues/9076
354         FINAL_CMD="-c \"$CMD\" -c \"tabfirst\""
358 ################################################################################
359 ## API functions (called from "git-mergetool--lib.sh")
360 ################################################################################
362 diff_cmd () {
363         "$merge_tool_path" -R -f -d \
364                 -c 'wincmd l' -c 'cd $GIT_PREFIX' "$LOCAL" "$REMOTE"
368 diff_cmd_help () {
369         TOOL=$1
371         case "$TOOL" in
372         nvimdiff*)
373                 printf "Use Neovim"
374                 ;;
375         gvimdiff*)
376                 printf "Use gVim (requires a graphical session)"
377                 ;;
378         vimdiff*)
379                 printf "Use Vim"
380                 ;;
381         esac
383         return 0
387 merge_cmd () {
388         layout=$(git config mergetool.vimdiff.layout)
390         case "$1" in
391         *vimdiff)
392                 if test -z "$layout"
393                 then
394                         # Default layout when none is specified
395                         layout="(LOCAL,BASE,REMOTE)/MERGED"
396                 fi
397                 ;;
398         *vimdiff1)
399                 layout="@LOCAL,REMOTE"
400                 ;;
401         *vimdiff2)
402                 layout="LOCAL,MERGED,REMOTE"
403                 ;;
404         *vimdiff3)
405                 layout="MERGED"
406                 ;;
407         esac
409         gen_cmd "$layout"
411         debug_print ""
412         debug_print "FINAL CMD : $FINAL_CMD"
413         debug_print "FINAL TAR : $FINAL_TARGET"
415         if $base_present
416         then
417                 eval '"$merge_tool_path"' \
418                         -f "$FINAL_CMD" '"$LOCAL"' '"$BASE"' '"$REMOTE"' '"$MERGED"'
419         else
420                 # If there is no BASE (example: a merge conflict in a new file
421                 # with the same name created in both braches which didn't exist
422                 # before), close all BASE windows using vim's "quit" command
424                 FINAL_CMD=$(echo "$FINAL_CMD" | \
425                         sed -e 's:2b:quit:g' -e 's:3b:2b:g' -e 's:4b:3b:g')
427                 eval '"$merge_tool_path"' \
428                         -f "$FINAL_CMD" '"$LOCAL"' '"$REMOTE"' '"$MERGED"'
429         fi
431         ret="$?"
433         if test "$ret" -eq 0
434         then
435                 case "$FINAL_TARGET" in
436                 LOCAL)
437                         source_path="$LOCAL"
438                         ;;
439                 REMOTE)
440                         source_path="$REMOTE"
441                         ;;
442                 MERGED|*)
443                         # Do nothing
444                         source_path=
445                         ;;
446                 esac
448                 if test -n "$source_path"
449                 then
450                         cp "$source_path" "$MERGED"
451                 fi
452         fi
454         return "$ret"
458 merge_cmd_help () {
459         TOOL=$1
461         case "$TOOL" in
462         nvimdiff*)
463                 printf "Use Neovim "
464                 ;;
465         gvimdiff*)
466                 printf "Use gVim (requires a graphical session) "
467                 ;;
468         vimdiff*)
469                 printf "Use Vim "
470                 ;;
471         esac
473         case "$TOOL" in
474         *1)
475                 echo "with a 2 panes layout (LOCAL and REMOTE)"
476                 ;;
477         *2)
478                 echo "with a 3 panes layout (LOCAL, MERGED and REMOTE)"
479                 ;;
480         *3)
481                 echo "where only the MERGED file is shown"
482                 ;;
483         *)
484                 echo "with a custom layout (see \`git help mergetool\`'s \`BACKEND SPECIFIC HINTS\` section)"
485                 ;;
486         esac
488         return 0
492 translate_merge_tool_path () {
493         case "$1" in
494         nvimdiff*)
495                 echo nvim
496                 ;;
497         gvimdiff*)
498                 echo gvim
499                 ;;
500         vimdiff*)
501                 echo vim
502                 ;;
503         esac
507 exit_code_trustable () {
508         true
512 list_tool_variants () {
513         if test "$TOOL_MODE" = "diff"
514         then
515                 for prefix in '' g n
516                 do
517                         echo "${prefix}vimdiff"
518                 done
519         else
520                 for prefix in '' g n
521                 do
522                         for suffix in '' 1 2 3
523                         do
524                                 echo "${prefix}vimdiff${suffix}"
525                         done
526                 done
527         fi
531 ################################################################################
532 ## Unit tests (called from scripts inside the "t" folder)
533 ################################################################################
535 run_unit_tests () {
536         # Function to make sure that we don't break anything when modifying this
537         # script.
539         NUMBER_OF_TEST_CASES=16
541         TEST_CASE_01="(LOCAL,BASE,REMOTE)/MERGED"   # default behaviour
542         TEST_CASE_02="@LOCAL,REMOTE"                # when using vimdiff1
543         TEST_CASE_03="LOCAL,MERGED,REMOTE"          # when using vimdiff2
544         TEST_CASE_04="MERGED"                       # when using vimdiff3
545         TEST_CASE_05="LOCAL/MERGED/REMOTE"
546         TEST_CASE_06="(LOCAL/REMOTE),MERGED"
547         TEST_CASE_07="MERGED,(LOCAL/REMOTE)"
548         TEST_CASE_08="(LOCAL,REMOTE)/MERGED"
549         TEST_CASE_09="MERGED/(LOCAL,REMOTE)"
550         TEST_CASE_10="(LOCAL/BASE/REMOTE),MERGED"
551         TEST_CASE_11="(LOCAL,BASE,REMOTE)/MERGED+BASE,LOCAL+BASE,REMOTE+(LOCAL/BASE/REMOTE),MERGED"
552         TEST_CASE_12="((LOCAL,REMOTE)/BASE),MERGED"
553         TEST_CASE_13="((LOCAL,REMOTE)/BASE),((LOCAL/REMOTE),MERGED)"
554         TEST_CASE_14="BASE,REMOTE+BASE,LOCAL"
555         TEST_CASE_15="  ((  (LOCAL , BASE , REMOTE) / MERGED))   +(BASE)   , LOCAL+ BASE , REMOTE+ (((LOCAL / BASE / REMOTE)) ,    MERGED   )  "
556         TEST_CASE_16="LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL / BASE / REMOTE),MERGED"
558         EXPECTED_CMD_01="-c \"echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
559         EXPECTED_CMD_02="-c \"echo | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
560         EXPECTED_CMD_03="-c \"echo | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 4b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
561         EXPECTED_CMD_04="-c \"echo | 4b | bufdo diffthis\" -c \"tabfirst\""
562         EXPECTED_CMD_05="-c \"echo | leftabove split | 1b | wincmd j | leftabove split | 4b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
563         EXPECTED_CMD_06="-c \"echo | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
564         EXPECTED_CMD_07="-c \"echo | leftabove vertical split | 4b | wincmd l | leftabove split | 1b | wincmd j | 3b | tabdo windo diffthis\" -c \"tabfirst\""
565         EXPECTED_CMD_08="-c \"echo | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 4b | tabdo windo diffthis\" -c \"tabfirst\""
566         EXPECTED_CMD_09="-c \"echo | leftabove split | 4b | wincmd j | leftabove vertical split | 1b | wincmd l | 3b | tabdo windo diffthis\" -c \"tabfirst\""
567         EXPECTED_CMD_10="-c \"echo | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
568         EXPECTED_CMD_11="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
569         EXPECTED_CMD_12="-c \"echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
570         EXPECTED_CMD_13="-c \"echo | leftabove vertical split | leftabove split | leftabove vertical split | 1b | wincmd l | 3b | wincmd j | 2b | wincmd l | leftabove vertical split | leftabove split | 1b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
571         EXPECTED_CMD_14="-c \"echo | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | 2b | wincmd l | 1b | tabdo windo diffthis\" -c \"tabfirst\""
572         EXPECTED_CMD_15="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
573         EXPECTED_CMD_16="-c \"echo | -tabnew | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnext | -tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnext | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | tabdo windo diffthis\" -c \"tabfirst\""
575         EXPECTED_TARGET_01="MERGED"
576         EXPECTED_TARGET_02="LOCAL"
577         EXPECTED_TARGET_03="MERGED"
578         EXPECTED_TARGET_04="MERGED"
579         EXPECTED_TARGET_05="MERGED"
580         EXPECTED_TARGET_06="MERGED"
581         EXPECTED_TARGET_07="MERGED"
582         EXPECTED_TARGET_08="MERGED"
583         EXPECTED_TARGET_09="MERGED"
584         EXPECTED_TARGET_10="MERGED"
585         EXPECTED_TARGET_11="MERGED"
586         EXPECTED_TARGET_12="MERGED"
587         EXPECTED_TARGET_13="MERGED"
588         EXPECTED_TARGET_14="MERGED"
589         EXPECTED_TARGET_15="MERGED"
590         EXPECTED_TARGET_16="MERGED"
592         at_least_one_ko="false"
594         for i in $(seq -w 1 99)
595         do
596                 if test "$i" -gt $NUMBER_OF_TEST_CASES
597                 then
598                         break
599                 fi
601                 gen_cmd "$(eval echo \${TEST_CASE_"$i"})"
603                 if test "$FINAL_CMD" = "$(eval echo \${EXPECTED_CMD_"$i"})" \
604                         && test "$FINAL_TARGET" = "$(eval echo \${EXPECTED_TARGET_"$i"})"
605                 then
606                         printf "Test Case #%02d: OK\n" "$(echo "$i" | sed 's/^0*//')"
607                 else
608                         printf "Test Case #%02d: KO !!!!\n" "$(echo "$i" | sed 's/^0*//')"
609                         echo "  FINAL_CMD              : $FINAL_CMD"
610                         echo "  FINAL_CMD (expected)   : $(eval echo \${EXPECTED_CMD_"$i"})"
611                         echo "  FINAL_TARGET           : $FINAL_TARGET"
612                         echo "  FINAL_TARGET (expected): $(eval echo \${EXPECTED_TARGET_"$i"})"
613                         at_least_one_ko="true"
614                 fi
615         done
617         # verify that `merge_cmd` handles paths with spaces
618         record_parameters () {
619                 >actual
620                 for arg
621                 do
622                         echo "$arg" >>actual
623                 done
624         }
626         base_present=false
627         LOCAL='lo cal'
628         BASE='ba se'
629         REMOTE="' '"
630         MERGED='mer ged'
631         merge_tool_path=record_parameters
633         merge_cmd vimdiff || at_least_one_ko=true
635         cat >expect <<-\EOF
636         -f
637         -c
638         echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | quit | wincmd l | 2b | wincmd j | 3b | tabdo windo diffthis
639         -c
640         tabfirst
641         lo cal
642         ' '
643         mer ged
644         EOF
646         diff -u expect actual || at_least_one_ko=true
648         if test "$at_least_one_ko" = "true"
649         then
650                 return 255
651         else
652                 return 0
653         fi