How to perform a three-way diff in Git without merging?
I want to be able to perform this diff before merging, including on files without merge conflicts.
You just have to set up the index as you like, you never have to commit results. The way to set up for exactly what was asked, straight diffs-since-base with no merge prep, is
git merge -s ours --no-ff --no-commit $your_other_tip
a complete handroll in which git only sets up parents for whatever you eventually decide to commit as the a result, but it's probably better to do this with a normal merge while still being able to get in there and examine everything,
git merge --no-ff --no-commit $your_other_tip
Pick your starting point, and then
force a merge visit for all entries that show any changes in either tip:
#!/bin/sh git checkout -m . # identify paths that show changes in either tip but were automerged scratch=`mktemp -t` sort <<EOD | uniq -u >"$scratch" $( # paths that show changes at either tip: ( git diff --name-only ...MERGE_HEAD git diff --name-only MERGE_HEAD... ) | sort -u ) $( # paths that already show a conflict: git ls-files -u | cut -f2- ) EOD # un-automerge them: strip the resolved-content entry and explicitly # add the base/ours/theirs content entries git update-index --force-remove --stdin <"$scratch" stage_paths_from () { xargs -a "$1" -d\\n git ls-tree -r $2 | sed "s/ [^ ]*//;s/\t/ $3\t/" | git update-index --index-info } stage_paths_from "$scratch" $(git merge-base @ MERGE_HEAD) 1 stage_paths_from "$scratch" @ 2 stage_paths_from "$scratch" MERGE_HEAD 3
... if you were using vimdiff, step 2 would be just
git mergetool
. vimdiff starts from what's in the worktree and doesn't do its own automerge. It looks like kdiff3 wants to ignore the worktree. Anyhoo, setting it up to run without --auto doesn't look too too hacky:# one-time setup: wip=~/libexec/my-git-mergetools mkdir -p "$wip" cp -a "$(git --exec-path)/mergetools/kdiff3" "$wip" sed -si 's/--auto //g' "$wip"/kdiff3
and then you can
MERGE_TOOLS_DIR=~/libexec/my-git-mergetools git mergetool
Backout from this is just the usual git merge --abort
or git reset --hard
.
I don't think it is possible.
The merge logic is actually quite complex. The merge base is not necessarily unique and the merge code goes to great length to deal with such situation reasonably, but this is not duplicated in any diff code.
Git makes it easy to go back to previous state. So stash your changes if you have any, try the merge and then
--abort
it orreset
when you've looked enough and don't need the result any more.