xx77aBs
xx77aBs

Reputation: 4778

Diff only changed parts of lines

Is it possible to show only differences between old and new lines using git diff?

Here is content of file a.txt:

123456789

And here is content of file b.txt

123455789

Here is normal git diff output:

normal output

And here is what I'm trying to accomplish: wanted output

I'd prefer to do this using git diff, but if it can't be done I'm ok with any other app.

Upvotes: 22

Views: 5270

Answers (1)

Etan Reisner
Etan Reisner

Reputation: 81052

This is what the --word-diff option to git diff does. Though not to that level of granularity by default.

To get single character diffs like that would require a custom --word-diff-regex as well. (I have '([^[:alnum:]]|[^[:space:]])' in a git alias here which seems to do that though. Though that wasn't the point when I wrote it.)

You can abbreviate --word-diff=color --word-diff-regex='...' to --color-words='...' too by the way.

See if this does what you want:

$ cat worddiff.awk
BEGIN {
    RS="\n?~\n"
    FS="\n"
}

# Special case the diff header/chunk header lines.
/^diff --git/ {
    print
    next
}

{
    delete outs
    for (i=1; i<=NF; i++) {
        if ($i ~ /^[-+]/) {
            mode = substr($i, 1, 1)
            $i = ((mode=="-")?red:green) substr($i, 2) reset
            outs[mode] = outs[mode] $i reset
            outs["set" mode]++
        } else {
            gsub(/^ /, "", $i)
            outs["-"] = outs["-"] $i
            outs["+"] = outs["+"] $i
        }
    }

    # If we didn't have any changes then this is a context line and we need to
    # print it out.
    if (!outs["set-"] && !outs["set+"]) {
        print " " outs["-"]
        next
    }

    if (outs["set-"]) {
        print red "-" reset outs["-"]
    }

    if (outs["set+"]) {
        print green "+" reset outs["+"]
    }
}

Used as:

git diff --word-diff=porcelain | awk -v red="$(tput setaf 1)" -v green="$(tput setaf 2)" -v reset="$(tput sgr0)" -f worddiff.awk

or

git diff --word-diff-regex='([^[:alnum:]]|[^[:space:]])' --word-diff=porcelain | awk -v red="$(tput setaf 1)" -v green="$(tput setaf 2)" -v reset="$(tput sgr0)" -f worddiff.awk

That awk could probably be cleaner and there's a non-GNU awk split idiom to clear an array that I can't recall offhand if delete a is a problem.

Edit: Updated code above to match newest revision of gist. The original special case pattern was overly permissive. The original code did not handle added blank lines correctly. The updated code doesn't either but does the best that I believe is possible given the known limitations of --word-diff=porcelain.

Upvotes: 24

Related Questions