Как увидеть изменения между двумя коммитами без промежуточных коммитов?


643

Как вы можете показать git diffтолько разницу между двумя коммитами, исключая другие коммиты между ними?


15
«git diff» всегда показывает разницу между двумя коммитами (или коммитом, и рабочим каталогом и т. д.).
Якуб Наребски

21
@ JakubNarębski, он спрашивает, как увидеть разницу между изменениями, внесенными одной командой, и изменениями, внесенными другим коммитом. Другими словами, diff из diffs или interdiff.
Псуси

1
и если вы добавите параметр --dirstat = files к команде diff, вы получите очень хороший скриншот с точными проектами и файлами, которые были изменены, вместе с процентом изменений. Например: git diff [номер-коммита] [номер-коммита] --dirstat = files
Оскар Ибаньес Фернандес,

Ответы:


606

вы можете просто передать 2 коммита в git diff:

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

1
Это сработало для меня, но теперь, как я могу обратиться my.patchв другую ветку?
nacho4d

2
@ nacho4d: git checkout other-branch && git apply my.patch && git add. && git commit -am "Message"
Феликс Рабе

1
Преимущество использования git apply и patch заключается в том, что вы можете включать переименования и некоторые другие изменения, относящиеся к git. Мне нравится использовать git format-patch и git am.
Рассел

58
Этот ответ совершенно не отвечает на вопрос, поэтому я понятия не имею, почему у него так много голосов. ОП конкретно спрашивает, как НЕ получить первую команду, которую вы даете, а вторая не имеет никакого отношения к чему-либо.
Псуси

3
Этот ответ не в состоянии ничего не ответить. Работает отлично. Если вы разветвите последний из двух коммитов, о котором идет речь, то примените этот diff к этой новой ветке, вы увидите изменения между двумя коммитами без головной боли прерывистых коммитов.
Крейг Лабенс

142

Запрашивать разницу / между / двумя коммитами без учета промежуточных коммитов не имеет большого смысла. Коммиты - это просто снимки содержимого хранилища; просить разницу между двумя обязательно включает их. Итак, вопрос в том, что вы действительно ищете?

Как предположил Уильям, сбор вишни может дать вам дельту одного комбита, перебазированного поверх другого. Это:

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

Это принимает коммит 'abcdef', сравнивает его с его непосредственным предком, затем применяет эту разницу поверх '012345'. Затем показывается это новое отличие - единственное изменение заключается в том, что контекст происходит от «012345», а не от «непосредственного предка» abcdef. Конечно, вы можете столкнуться с конфликтами и т. Д., Поэтому в большинстве случаев это не очень полезный процесс.

Если вы просто заинтересованы в самом abcdef, вы можете сделать:

$ git log -u -1 abcdef

Это сравнивает abcdef с его непосредственным предком, в одиночку, и обычно это то, что вы хотите.

И конечно

$ git diff 012345..abcdef

дает вам все различия между этими двумя коммитами.

Это помогло бы получить лучшее представление о том, чего вы пытаетесь достичь - как я уже говорил, просить разницу между двумя коммитами без того, что между ними, на самом деле не имеет смысла.


41
Я согласен, что в общем случае не имеет смысла сравнивать два коммита. Но Git действительно не умеет говорить, как вы должны думать. Предположим, у вас есть две ветви, каждая с разными фиксациями, которые выглядят так, как будто они вносят одинаковые изменения в одни и те же наборы файлов. Я хотел бы иметь возможность использовать git, чтобы сказать мне, являются ли эти два исправления одинаковыми, не доверяя моим глазам. Я думаю, что есть полезность в этом.
Крис Кливленд

9
@ChrisCleeland, утилита interdiff может пригодиться в этом случае. Используйте git diff, чтобы получить diff каждого коммита против его непосредственного родителя, затем используйте interdiff, чтобы сравнить diff.
bdonlan

3
@ChrisCleeland, git не хранит патчи. Хранит содержимое файла. У него действительно есть схема сжатия, которая использует дельты, но источники дельты не обязательно связаны с фактической историей файлов.
bdonlan

11
Разница между двумя коммитами, исключая другие коммиты на их соответствующих ветвях, имеет смысл: один коммит был выбран из другого, но может иметь некоторые тонкие различия. Вы хотите увидеть, что они из себя представляют, не будучи загромождены всем другим несвязанным дерьмом, которое отличается между двумя ветвями.
Псуси

2
Или, скажем, вы перебазируете master на ветку функций и должны разрешать конфликты. Впоследствии сравнение origin/featurebranch#HEADс local/featurebranch#HEADможет помочь вам убедиться, что вы ничего не испортили во время разрешения конфликта.
Lefnire

91

Для сравнения двух коммитов git 12345 и abcdef как патчей можно использовать команду diff:

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

8
Почему вы используете GNU diff с git?
OneOfOne

7
@OneOfOne git diff <(git show 123456) <(git show abcdef)не работает; diff <(...) <(...)делает. (Я только что попробовал).
Менахем

@Menachem git diff 123456 abcdef.
OneOfOne

15
@OneOfOne Это не делает то же самое. То, что вы предложили, будет сравнивать деревья каждого коммита, показывая один патч . То, что я (и @plexoos) делаю, сравнивает два патча , каждый из которых был введен отдельными коммитами - другими словами, diffвыводит результат из двух diffs. Это включает в себя чтение и сравнение двух входных потоков. diff(GNU или Unix diff) может это сделать, а git diffне может. Некоторые могут задаться вопросом, почему кто-то хотел бы сделать это. Я сейчас занимаюсь этим, убирая слияние, которое пошло плохо.
Менахем

1
не будет ли это включать в себя разницу между всеми метаданными в git diff?
Джоэлб

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

Пример:

git diff commit1 commit2 config/routes.rb

Он показывает разницу в этом файле между этими коммитами.


24

Для проверки полных изменений:

  git diff <commit_Id_1> <commit_Id_2>

Для проверки только измененных / добавленных / удаленных файлов:

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

ПРИМЕЧАНИЕ . Для проверки diff без фиксации между ними вам не нужно указывать идентификаторы коммитов.


20

Допустим, у вас есть это

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

И вы хотите убедиться, что Aэто так же, как A0.

Это сделает свое дело:

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

3
Также может быть сокращен как однострочный как ответ @plexoos : diff <(git diff B A) <(git diff D A0)(тот же результат, что и с git show)
pogosama

14

Предположим, вы хотите увидеть разницу между коммитами 012345 и abcdef. Следующее должно делать то, что вы хотите:

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

Спасибо, это хорошая идея, чтобы проверить ваш результат после фиксации. Например, вы можете оформить свою ветвь коммитами без сдавливания, а вишня выбрать свой сдавленный коммит, чтобы увидеть, все ли гладко с интерактивной перебазировкой. Кроме того, когда мастер вышел впереди ветви.
Акостадинов

10

Что насчет этого:

git diff abcdef 123456 | less

Это удобно, если вы хотите сравнить множество различных различий на лету.


6

Начиная с Git 2.19, вы можете просто использовать:

git range-diff rev1...rev2 - сравнить два дерева коммитов, начиная с их общего предка

или git range-diff rev1~..rev1 rev2~..rev2 - сравнить изменения, внесенные двумя данными коммитами


4

Мои aliasнастройки в ~/.bashrcфайле для 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

2

Мои aliasнастройки в ~/.zshrcфайле для git diff:

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

Спасибо @Jinmiao Ло


git diff HEAD~2 HEAD

полное изменение между последним 2-м коммитом и текущим.

HEAD это удобно


1

Я написал скрипт, который отображает diff между двумя коммитами, хорошо работает на 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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.