gitmergegit-diffmeldgit-difftool

Show a git merge commit in three-panel form in meld


This question shows many good ways to use git-mergetool to show the condition of files before a merge commit using meld:

Setting up and using Meld as your Git difftool and mergetool

I like the three-panel form with $BASE in the middle panel.

When I inspect the result of a merge after a commit I'd like to see about the same thing, but with the results of the merge ($MERGED of git-mergetool) in the middle pane.

Is there any existing way to coax git into showing this, or do I need to roll my own? Note that I'm not interested in the "combined" diff offered by e.g. git show.


Solution

  • Here's a script that does it for an individual file which is enough for me. It wouldn't be hard to generalize to dirs.

    #!/usr/bin/perl -w
    
    # Given a two-parent merge <commit> and a path to a non-dir file in
    # the repo, show the merge of that file in the commit in meld as a
    # 3-way diff (symmetric to what `get mergetool --tool meld' shows
    # before the merge, i.e. with the finished $MERGED result where $BASE
    # would be in the mergetool display).  This follows from this question:
    # https://stackoverflow.com/questions/34119866/setting-up-and-using-meld-as-your-git-difftool-and-mergetool
    #
    # The idea is to work around the fact that git mergetool is aware of
    # diff3-type features of e.g. meld but all the git-diff stuff (including
    # git-difftoot and git-show) is determinedly unaware and instead shows
    # various worse 2-pane renderings of what happened.
    
    @ARGV == 2 or die "wrong number of arguments";
    
    my ($commit, $path) = @ARGV;
    
    # Trees aren't supported yet
    `git cat-file -t $commit:$path` eq "blob\n"
        or die "git cat-file -t doesn't think $commit:$path is a blob";
    
    # Use our own subdir of /tmp to avoid littering /tmp too much
    my $tdd = "/tmp/show_merge_with_3_pane_meld.perl.scratch";
    -d $tdd or mkdir $tdd or die "mkdir $tdd failed: $!";
    
    # Unpack versions of file into temporary files
    my $cap = `git rev-list -n 1 --parents $commit`;   # Commit And Parents
    $? == 0 or die "git rev-list command failed";
    my @cap = map { chomp $_; $_ } split(' ', $cap);
    @cap == 3
        or die "unexpected number of words in rev-list output, maybe $commit is ".
               "not a two-parent merge commit?";
    my %cap = ( merged => $cap[0], p1 => $cap[1], p2 => $cap[2] );
    my %tf = ();   # Temp Files (final names in $tdd to be given to meld)
    while ( my ($key, $val) = each %cap ) {
        my $tfn = `git unpack-file $val:$path`;   # Temp File Name
        $? == 0 or die "git unpack-file command failed";
        chomp($tfn);
        # Verify temp file name form from git-unpack-file and save random part
        $tfn =~ m/^\.merge_file_(.*)$/
            or die "git unpack-file output doesn't match the expected pattern";
        my $tfn_rp = $1;   # Temp File Name Random Part
        my $otfn = "$key.$tfn_rp";   # Our Temp File Name
        # Move to our dir, rename
        not system("mv $tfn $tdd/$otfn") or die "mv command failed";
        $tf{$key} = $otfn;
    }
    
    # Display the merge
    not system("cd $tdd && meld $tf{p1} $tf{merged} $tf{p2}")
        or die "cd or meld command failed";
    
    # Uncomment if clean-up of temp files is desired
    #unlink(map { "$tdd/$_" } values(%tf)) == 3 or die;