git-mergetool.sh 8.7 KB
Newer Older
1 2 3 4 5 6 7
#!/bin/sh
#
# This program resolves merge conflicts in git
#
# Copyright (c) 2006 Theodore Y. Ts'o
#
# This file is licensed under the GPL v2, or a later version
J
Josh Triplett 已提交
8
# at the discretion of Junio C Hamano.
9 10
#

11
USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...'
12
SUBDIRECTORY_OK=Yes
13
OPTIONS_SPEC=
14
TOOL_MODE=merge
15
. git-sh-setup
16
. git-mergetool--lib
17 18 19
require_work_tree

# Returns true if the mode reflects a symlink
20
is_symlink () {
21 22 23
    test "$1" = 120000
}

J
Jonathon Mah 已提交
24 25 26 27
is_submodule () {
    test "$1" = 160000
}

28
local_present () {
29 30 31
    test -n "$local_mode"
}

32
remote_present () {
33 34 35
    test -n "$remote_mode"
}

36
base_present () {
37 38 39 40 41
    test -n "$base_mode"
}

cleanup_temp_files () {
    if test "$1" = --save-backup ; then
J
Jonathon Mah 已提交
42 43
	rm -rf -- "$MERGED.orig"
	test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
44 45 46 47 48 49
	rm -f -- "$LOCAL" "$REMOTE" "$BASE"
    else
	rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
    fi
}

50
describe_file () {
51 52 53 54
    mode="$1"
    branch="$2"
    file="$3"

55
    printf "  {%s}: " "$branch"
56
    if test -z "$mode"; then
57
	echo "deleted"
58
    elif is_symlink "$mode" ; then
59
	echo "a symbolic link -> '$(cat "$file")'"
J
Jonathon Mah 已提交
60 61
    elif is_submodule "$mode" ; then
	echo "submodule commit $file"
62 63
    else
	if base_present; then
J
Jonathon Mah 已提交
64
	    echo "modified file"
65
	else
J
Jonathon Mah 已提交
66
	    echo "created file"
67 68 69 70 71 72
	fi
    fi
}


resolve_symlink_merge () {
73
    while true; do
74
	printf "Use (l)ocal or (r)emote, or (a)bort? "
75
	read ans || return 1
76 77
	case "$ans" in
	    [lL]*)
78 79
		git checkout-index -f --stage=2 -- "$MERGED"
		git add -- "$MERGED"
80
		cleanup_temp_files --save-backup
81
		return 0
82
		;;
83
	    [rR]*)
84 85
		git checkout-index -f --stage=3 -- "$MERGED"
		git add -- "$MERGED"
86
		cleanup_temp_files --save-backup
87
		return 0
88
		;;
89
	    [aA]*)
90
		return 1
91 92 93 94 95 96
		;;
	    esac
	done
}

resolve_deleted_merge () {
97
    while true; do
98 99 100 101 102
	if base_present; then
	    printf "Use (m)odified or (d)eleted file, or (a)bort? "
	else
	    printf "Use (c)reated or (d)eleted file, or (a)bort? "
	fi
103
	read ans || return 1
104
	case "$ans" in
105
	    [mMcC]*)
106
		git add -- "$MERGED"
107
		cleanup_temp_files --save-backup
108
		return 0
109
		;;
110
	    [dD]*)
111
		git rm -- "$MERGED" > /dev/null
112
		cleanup_temp_files
113
		return 0
114
		;;
115
	    [aA]*)
116
		return 1
117 118 119 120 121
		;;
	    esac
	done
}

J
Jonathon Mah 已提交
122 123 124
resolve_submodule_merge () {
    while true; do
	printf "Use (l)ocal or (r)emote, or (a)bort? "
125
	read ans || return 1
J
Jonathon Mah 已提交
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
	case "$ans" in
	    [lL]*)
		if ! local_present; then
		    if test -n "$(git ls-tree HEAD -- "$MERGED")"; then
			# Local isn't present, but it's a subdirectory
			git ls-tree --full-name -r HEAD -- "$MERGED" | git update-index --index-info || exit $?
		    else
			test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
			git update-index --force-remove "$MERGED"
			cleanup_temp_files --save-backup
		    fi
		elif is_submodule "$local_mode"; then
		    stage_submodule "$MERGED" "$local_sha1"
		else
		    git checkout-index -f --stage=2 -- "$MERGED"
		    git add -- "$MERGED"
		fi
		return 0
		;;
	    [rR]*)
		if ! remote_present; then
		    if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"; then
			# Remote isn't present, but it's a subdirectory
			git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" | git update-index --index-info || exit $?
		    else
			test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
			git update-index --force-remove "$MERGED"
		    fi
		elif is_submodule "$remote_mode"; then
		    ! is_submodule "$local_mode" && test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
		    stage_submodule "$MERGED" "$remote_sha1"
		else
		    test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
		    git checkout-index -f --stage=3 -- "$MERGED"
		    git add -- "$MERGED"
		fi
		cleanup_temp_files --save-backup
		return 0
		;;
	    [aA]*)
		return 1
		;;
	    esac
	done
}

stage_submodule () {
    path="$1"
    submodule_sha1="$2"
    mkdir -p "$path" || die "fatal: unable to create directory for module at $path"
    # Find $path relative to work tree
    work_tree_root=$(cd_to_toplevel && pwd)
    work_rel_path=$(cd "$path" && GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix)
    test -n "$work_rel_path" || die "fatal: unable to get path of module $path relative to work tree"
    git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
}

183 184 185 186
checkout_staged_file () {
    tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^	]*\)	')

    if test $? -eq 0 -a -n "$tmpfile" ; then
187
	mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
188 189 190
    fi
}

191
merge_file () {
192
    MERGED="$1"
193

194
    f=$(git ls-files -u -- "$MERGED")
195
    if test -z "$f" ; then
196 197
	if test ! -f "$MERGED" ; then
	    echo "$MERGED: file not found"
198
	else
199
	    echo "$MERGED: file does not need merging"
200
	fi
201
	return 1
202 203
    fi

204
    ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
205 206 207 208
    BACKUP="./$MERGED.BACKUP.$ext"
    LOCAL="./$MERGED.LOCAL.$ext"
    REMOTE="./$MERGED.REMOTE.$ext"
    BASE="./$MERGED.BASE.$ext"
209

210 211 212
    base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
    local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
    remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
213

J
Jonathon Mah 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226
    if is_submodule "$local_mode" || is_submodule "$remote_mode"; then
	echo "Submodule merge conflict for '$MERGED':"
	local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
	remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
	describe_file "$local_mode" "local" "$local_sha1"
	describe_file "$remote_mode" "remote" "$remote_sha1"
	resolve_submodule_merge
	return
    fi

    mv -- "$MERGED" "$BACKUP"
    cp -- "$BACKUP" "$MERGED"

227 228 229
    base_present   && checkout_staged_file 1 "$MERGED" "$BASE"
    local_present  && checkout_staged_file 2 "$MERGED" "$LOCAL"
    remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
230 231

    if test -z "$local_mode" -o -z "$remote_mode"; then
232
	echo "Deleted merge conflict for '$MERGED':"
233 234 235 236 237 238 239
	describe_file "$local_mode" "local" "$LOCAL"
	describe_file "$remote_mode" "remote" "$REMOTE"
	resolve_deleted_merge
	return
    fi

    if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
240
	echo "Symbolic link merge conflict for '$MERGED':"
241 242 243 244 245 246
	describe_file "$local_mode" "local" "$LOCAL"
	describe_file "$remote_mode" "remote" "$REMOTE"
	resolve_symlink_merge
	return
    fi

247
    echo "Normal merge conflict for '$MERGED':"
248 249
    describe_file "$local_mode" "local" "$LOCAL"
    describe_file "$remote_mode" "remote" "$REMOTE"
250 251
    if "$prompt" = true; then
	printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
252
	read ans || return 1
253
    fi
254

255 256 257 258 259
    if base_present; then
	    present=true
    else
	    present=false
    fi
260 261

    if ! run_merge_tool "$merge_tool" "$present"; then
262 263
	echo "merge of $MERGED failed" 1>&2
	mv -- "$BACKUP" "$MERGED"
264 265 266 267 268

	if test "$merge_keep_temporaries" = "false"; then
	    cleanup_temp_files
	fi

269
	return 1
270
    fi
271 272

    if test "$merge_keep_backup" = "true"; then
273
	mv -- "$BACKUP" "$MERGED.orig"
274 275 276 277
    else
	rm -- "$BACKUP"
    fi

278
    git add -- "$MERGED"
279
    cleanup_temp_files
280
    return 0
281 282
}

283 284
prompt=$(git config --bool mergetool.prompt || echo true)

285
while test $# != 0
286 287 288 289 290
do
    case "$1" in
	-t|--tool*)
	    case "$#,$1" in
		*,*=*)
291
		    merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
292 293 294 295 296 297 298 299
		    ;;
		1,*)
		    usage ;;
		*)
		    merge_tool="$2"
		    shift ;;
	    esac
	    ;;
300 301 302 303 304 305
	-y|--no-prompt)
	    prompt=false
	    ;;
	--prompt)
	    prompt=true
	    ;;
306
	--)
307
	    shift
308 309 310 311 312 313 314 315 316 317 318 319
	    break
	    ;;
	-*)
	    usage
	    ;;
	*)
	    break
	    ;;
    esac
    shift
done

320 321 322
prompt_after_failed_merge() {
    while true; do
	printf "Continue merging other unresolved paths (y/n) ? "
323
	read ans || return 1
324 325 326 327 328 329 330 331 332 333 334 335
	case "$ans" in

	    [yY]*)
		return 0
		;;

	    [nN]*)
		return 1
		;;
	esac
    done
}
336

337 338 339
if test -z "$merge_tool"; then
    merge_tool=$(get_merge_tool "$merge_tool") || exit
fi
340
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
341
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
342

343 344
last_status=0
rollup_status=0
345 346 347 348 349
rerere=false

files_to_merge() {
    if test "$rerere" = true
    then
350
	git rerere remaining
351 352 353 354 355
    else
	git ls-files -u | sed -e 's/^[^	]*	//' | sort -u
    fi
}

356 357

if test $# -eq 0 ; then
358 359 360 361 362 363 364 365
    cd_to_toplevel

    if test -e "$GIT_DIR/MERGE_RR"
    then
	rerere=true
    fi

    files=$(files_to_merge)
366 367 368 369
    if test -z "$files" ; then
	echo "No files need merging"
	exit 0
    fi
370 371 372 373

    # Save original stdin
    exec 3<&0

374 375 376 377
    printf "Merging:\n"
    printf "$files\n"

    files_to_merge |
378 379
    while IFS= read i
    do
380
	if test $last_status -ne 0; then
381
	    prompt_after_failed_merge <&3 || exit 1
382
	fi
383
	printf "\n"
384
	merge_file "$i" <&3
385 386 387 388
	last_status=$?
	if test $last_status -ne 0; then
	    rollup_status=1
	fi
389
    done
390
else
391
    while test $# -gt 0; do
392 393 394
	if test $last_status -ne 0; then
	    prompt_after_failed_merge || exit 1
	fi
395 396
	printf "\n"
	merge_file "$1"
397 398 399 400
	last_status=$?
	if test $last_status -ne 0; then
	    rollup_status=1
	fi
401 402
	shift
    done
403
fi
404 405

exit $rollup_status