[git] How to see the changes between two commits without commits in-between?

How do you make git diff only show the difference between two commits, excluding the other commits in-between?

This question is related to git diff

The answer is


Let's say you have this

A
|
B    A0
|    |
C    D
\   /
  |
 ...

And you want to make sure that A is the same as A0.

This will do the trick:

$ git diff B A > B-A.diff
$ git diff D A0 > D-A0.diff
$ diff B-A.diff D-A0.diff

To compare two git commits 12345 and abcdef as patches one can use the diff command as

diff <(git show 123456) <(git show abcdef)

git diff <a-commit> <another-commit> path

Example:

git diff commit1 commit2 config/routes.rb

It shows the difference on that file between those commits.


My alias settings in ~/.bashrc file for git diff:

alias gdca='git diff --cached' # diff between your staged file and the last commit
alias gdcc='git diff HEAD{,^}' # diff between your latest two commits

Let me introduce easy GUI/idiot proof approach that you can take in these situations.

  1. Clone another copy of your repo to new folder, for example myRepo_temp
  2. Checkout the commit/branch that you would like to compare with commit in your original repo (myRepo_original).
  3. Now you can use diff tools, (like Beyond Compare etc.) with these two folders (myRepo_temp and myRepo_original)

This is useful for example if you want partially reverse some changes as you can copy stuff from one to another folder.


My alias settings in ~/.zshrc file for git diff:

alias gdf='git diff HEAD{'^',}' # diff between your recent tow commits

Thanks @Jinmiao Luo


git diff HEAD~2 HEAD

complete change between latest 2nd commit and current.

HEAD is convenient


Since Git 2.19, you can simply use:

git range-diff rev1...rev2 - compare two commit trees, starting by their common ancestor

or git range-diff rev1~..rev1 rev2~..rev2 - compare of changes introduced by 2 given commits


Asking for the difference /between/ two commits without including the commits in-between makes little sense. Commits are just snapshots of the contents of the repository; asking for the difference between two necessarily includes them. So the question then is, what are you really looking for?

As William suggested, cherry-picking can give you the delta of a single commit rebased on top of another. That is:

$ git checkout 012345
$ git cherry-pick -n abcdef
$ git diff --cached

This takes commit 'abcdef', compares it to its immediate ancestor, then applies that difference on top of '012345'. This new difference is then shown - the only change is the context comes from '012345' rather than 'abcdef's immediate ancestor. Of course, you may get conflicts and etc, so it's not a very useful process in most cases.

If you're just interested in abcdef itself, you can do:

$ git log -u -1 abcdef

This compares abcdef to its immediate ancestor, alone, and is usually what you want.

And of course

$ git diff 012345..abcdef

gives you all differences between those two commits.

It would help to get a better idea of what you're trying to achieve - as I mentioned, asking for the difference between two commits without what's in between doesn't actually make sense.


you can simply pass the 2 commits to git diff like :

-> git diff 0da94be  59ff30c > my.patch
-> git apply my.patch

For checking complete changes:

  git diff <commit_Id_1> <commit_Id_2>

For checking only the changed/added/deleted files:

  git diff <commit_Id_1> <commit_Id_2> --name-only

NOTE: For checking diff without commit in between, you don't need to put the commit ids.


Suppose you want to see the difference between commits 012345 and abcdef. The following should do what you want:

$ git checkout 012345
$ git cherry-pick -n abcdef
$ git diff --cached

$git log

commit-1(new/latest/recent commit)
commit-2
commit-3
commit-4
*
*
commit-n(first commit)

$git diff commit-2 commit-1

display's all changes between commit-2 to commit-1 (patch of commit-1 alone & equivalent to git diff HEAD~1 HEAD)

similarly $git diff commit-4 commit-1

display's all changes between commit-4 to commit-1 (patch of commit-1, commit-2 & commit-3 together. Equivalent to git diff HEAD~3 HEAD)

$git diff commit-1 commit-2

By changing order commit ID's it is possible to get revert patch . ("$git diff commit-1 commit-2 > revert_patch_of_commit-1.diff")


What about this:

git diff abcdef 123456 | less

It's handy to just pipe it to less if you want to compare many different diffs on the fly.


I wrote a script which displays diff between two commits, works well on Ubuntu.

https://gist.github.com/jacobabrahamb4/a60624d6274ece7a0bd2d141b53407bc

#!/usr/bin/env python
import sys, subprocess, os

TOOLS = ['bcompare', 'meld']

def getTool():
    for tool in TOOLS:
        try:
            out = subprocess.check_output(['which', tool]).strip()
            if tool in out:
                return tool
        except subprocess.CalledProcessError:
            pass
    return None

def printUsageAndExit():
    print 'Usage: python bdiff.py <project> <commit_one> <commit_two>'
    print 'Example: python bdiff.py <project> 0 1'
    print 'Example: python bdiff.py <project> fhejk7fe d78ewg9we'
    print 'Example: python bdiff.py <project> 0 d78ewg9we'
    sys.exit(0)

def getCommitIds(name, first, second):
    commit1 = None
    commit2 = None
    try:
        first_index = int(first) - 1
        second_index = int(second) - 1
        if int(first) < 0 or int(second) < 0:
            print "Cannot handle negative values: "
            sys.exit(0)
        logs = subprocess.check_output(['git', '-C', name, 'log', '--oneline', '--reverse']).split('\n')
        if first_index >= 0:
            commit1 = logs[first_index].split(' ')[0]
        if second_index >= 0:
            commit2 = logs[second_index].split(' ')[0]
    except ValueError:
        if first != '0':
            commit1 = first
        if second != '0':
            commit2 = second
    return commit1, commit2

def validateCommitIds(name, commit1, commit2):
    if commit1 == None and commit2 == None:
        print "Nothing to do, exit!"
        return False
    try:
        if commit1 != None:
            subprocess.check_output(['git', '-C', name, 'cat-file', '-t', commit1]).strip()
        if commit2 != None:
            subprocess.check_output(['git', '-C', name, 'cat-file', '-t', commit2]).strip()
    except subprocess.CalledProcessError:
        return False
    return True

def cleanup(commit1, commit2):
        subprocess.check_output(['rm', '-rf', '/tmp/'+(commit1 if commit1 != None else '0'), '/tmp/'+(commit2 if commit2 != None else '0')])

def checkoutCommit(name, commit):
    if commit != None:
        subprocess.check_output(['git', 'clone', name, '/tmp/'+commit])
        subprocess.check_output(['git', '-C', '/tmp/'+commit, 'checkout', commit])
    else:
        subprocess.check_output(['mkdir', '/tmp/0'])

def compare(tool, commit1, commit2):
        subprocess.check_output([tool, '/tmp/'+(commit1 if commit1 != None else '0'), '/tmp/'+(commit2 if commit2 != None else '0')])

if __name__=='__main__':
    tool = getTool()
    if tool == None:
        print "No GUI diff tools"
        sys.exit(0)
    if len(sys.argv) != 4:
        printUsageAndExit()

    name, first, second = None, 0, 0
    try:
        name, first, second = sys.argv[1], sys.argv[2], sys.argv[3]
    except IndexError:
        printUsageAndExit()

    commit1, commit2 = getCommitIds(name, first, second)

    if not validateCommitIds(name, commit1, commit2):
        sys.exit(0)

    cleanup(commit1, commit2)
    checkoutCommit(name, commit1)
    checkoutCommit(name, commit2)

    try:
        compare(tool, commit1, commit2)
    except KeyboardInterrupt:
        pass
    finally:
        cleanup(commit1, commit2)
    sys.exit(0)