简体   繁体   中英

Allow different behaviour for external diff tool if git command is piped

I'm using difftastic for better visualisation of my diffs in my terminal. Which means my config is

git config --global diff.external difft

However, when the output is not a tty, I'd rather have git use it's internal diff tool (eg the same as doing git diff --no-ext-diff but without me having to type that). So basically

$ git diff # please use difft
# git diff | cat # please use no external diff tool

However, I'm struggling with that because I cannot find a way to bubble up this information to my diff tool. Let's say I have some pseudo diff tool that actually just shows content of argv, first line of argc and check if its stdout is a tty:

#!/usr/bin/env ruby

require "io/console"
require "logger"
require "pp"

f = File.open("mygitdiff.log", "a")
f.puts("\n---- #{Time.now}")
PP.pp(ARGV, f)
PP.pp(gets, f)
f.puts("STDOUT.tty? #{STDOUT.tty?}")

This script prints the same log whether I am piping or not:

---- 2022-08-02 18:57:30 +0200
["bin/new",
 "/var/folders/v5/1p20ylgn0db5621r8wtdg5v80000gn/T//37FrIz_new",
 "c2a6c5aafc3485755f774cb3efcae4a2819a777f",
 "100755",
 "bin/new",
 "0000000000000000000000000000000000000000",
 "100755"]
"#!/usr/bin/env ruby\n"
STDOUT.tty? false

---- 2022-08-02 18:57:34 +0200
["bin/new",
 "/var/folders/v5/1p20ylgn0db5621r8wtdg5v80000gn/T//jaEmlN_new",
 "c2a6c5aafc3485755f774cb3efcae4a2819a777f",
 "100755",
 "bin/new",
 "0000000000000000000000000000000000000000",
 "100755"]
"#!/usr/bin/env ruby\n"
STDOUT.tty? false

As you can see there is no major difference there. Do you have any other idea how to achieve that goal?

Also note that the first line of argv, I don't understand at all what it means

A solution is to create a new git bin file, and put it in your path somewhere before the original git path (for me /usr/bin ). The wrapper will decide whether an external diff tool should be used depending on the stdout state:

#!/usr/bin/env ruby

require "io/console"

if STDOUT.tty?
  system({ "GIT_EXTERNAL_DIFF" => "difft" }, "/usr/bin/git", *ARGV)
else
  system("/usr/bin/git", *ARGV)
end

exit($?.exitstatus)

C version for usage speed

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAX(x, y) (((x) > (y)) ? (x) : (y))

char* shellescape(const char* str);

int main(int argc, char *argv[]) {
    char *base;
    int size = 64;
    char *cmd = calloc(size, sizeof(char));
    int ret;

    if (isatty(fileno(stdout)) && isatty(fileno(stderr))) {
        base = "GIT_EXTERNAL_DIFF=difft /usr/bin/git";
    } else {
        base = "/usr/bin/git";
    }

    size -= strlen(base);
    strcat(cmd, base);

    for (int i = 1; i < argc; i++) {
        char *escaped = shellescape(argv[i]);
        int arg_size = strlen(escaped) + 1;
        if (arg_size > size) {
            cmd = realloc(cmd, (MAX(arg_size, 64) + strlen(cmd)) * sizeof(char));
            size += MAX(arg_size, 64);
        }

        strcat(cmd, " ");
        strcat(cmd, escaped);
        size -= arg_size;
        free(escaped);
    }

    ret = system(cmd);
    return WEXITSTATUS(ret);
}

// https://stackoverflow.com/a/11854550/6320039
char* shellescape(const char* str) {
    char *rv;
    int i,
        count = strlen(str),
            ptr_size = count+3;

    rv = (char *) calloc(ptr_size, sizeof(char));
    if (rv == NULL) {
        return NULL;
    }
    sprintf(rv, "'");

    for(i=0; i<count; i++) {
        if (str[i] == '\'') {
                    ptr_size += 3;
            rv = (char *) realloc(rv,ptr_size * sizeof(char));
            if (rv == NULL) {
                return NULL;
            }
            sprintf(rv, "%s'\\''", rv);
        } else {
            sprintf(rv, "%s%c", rv, str[i]);
        }
    }

    sprintf(rv, "%s%c", rv, '\'');
    return rv;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM