git log와 git diff의 ..과 ...: 같은 기호가 다르게 동작하는 이유
git log에서 A..B와 A...B가 고르는 커밋, git diff에서 두 기호가 비교하는 지점을 Git 공식 문서와 직접 실행한 예로 정리합니다. 풀 리퀘스트 diff가 어느 쪽인지도 다룹니다.
git log main..feature와 git log main...feature는 점 하나 차이지만 고르는 커밋이 다를 수 있습니다. 같은 표기를 git diff에 쓰면 뜻이 또 달라집니다. log는 점 표기를 커밋의 집합으로 읽고, diff는 비교할 두 끝점으로 읽기 때문입니다.
그래서 아래 예처럼 브랜치가 갈라진 히스토리에서는 log의 점 세 개가 두 브랜치의 커밋을 모두 고르는 반면, git diff main...feature는 main에서만 생긴 변경을 뺍니다. 이것은 이 히스토리에서 나온 결과일 뿐, 점 세 개가 언제나 더 많이 또는 더 적게 보여 준다는 규칙은 없습니다. 출력 양은 커밋 그래프와 파일 내용에 따라 달라집니다. 예제는 macOS 15.6.1의 git 2.55.0에서 실행했습니다.
예제 저장소
base 커밋에서 feature 브랜치를 만들고, 양쪽에 커밋을 하나씩 더했습니다.
feature:feature.txt추가main:main.txt추가
이 단순한 히스토리에서 두 브랜치 끝의 가장 좋은 공통 조상(best common ancestor)은 base입니다. Git은 이것을 merge base라고 부르고, git merge-base main feature로 확인할 수 있습니다.
그림: 이 예제 히스토리에서의 결과입니다. 히스토리 모양이 다르면 두 표기의 출력 차이도 달라집니다.
git log에서: 커밋의 집합
gitrevisions 문서에 따르면 r1..r2는 ^r1 r2의 줄임말입니다. r2에서 닿을 수 있는 커밋 중 r1에서 닿을 수 있는 커밋을 뺀 것입니다.
$ git log --format=%s main..feature
feature: add feature.txt
r1...r2는 대칭 차집합입니다. 둘 중 한쪽에서만 닿을 수 있는 커밋을 고르고, 양쪽에서 모두 닿는 커밋은 뺍니다. --left-right를 붙이면 왼쪽(r1)에서 온 커밋에는 <, 오른쪽(r2)에서 온 커밋에는 >가 붙습니다.
$ git log --left-right --format='%m %s' main...feature
< main: add main.txt
> feature: add feature.txt
두 표기 모두 한쪽을 생략할 수 있고, 생략한 자리는 HEAD가 됩니다. origin..은 origin..HEAD와 같습니다.
git diff에서: 두 지점의 비교
git-diff 문서는 diff가 두 끝점을 비교하는 명령이고, 여기서 쓰는 ..과 ...은 gitrevisions가 말하는 범위가 아니라고 따로 밝혀 둡니다.
git diff A..B는 git diff A B와 같습니다. 두 커밋의 스냅숏을 그대로 비교합니다.
$ git diff --stat main..feature
feature.txt | 1 +
main.txt | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
feature에서 main.txt를 지운 적은 없는데도 삭제로 나옵니다. 브랜치가 갈라진 뒤 main에 생긴 파일이라 feature에는 없고, 두 스냅숏을 나란히 놓으면 지워진 것처럼 보이기 때문입니다.
git diff A...B는 git diff $(git merge-base A B) B와 같습니다. 두 브랜치가 갈라진 지점부터 B까지, B 쪽에서 생긴 변경을 보여 줍니다.
$ git diff --stat main...feature
feature.txt | 1 +
1 file changed, 1 insertion(+)
GitHub 문서는 풀 리퀘스트 화면의 diff가 이 세 점 방식이라고 설명합니다(2026년 10월 3일 확인). “이 브랜치가 무엇을 바꿨나”가 궁금하다면 대개 세 점 쪽이 맞습니다.
정리
| 표기 | git log | git diff |
|---|---|---|
A..B |
B에서 닿지만 A에서는 닿지 않는 커밋 | A와 B 스냅숏을 직접 비교 |
A...B |
A와 B 중 한쪽에서만 닿는 커밋 | merge base에서 B까지의 변경 |
히스토리 모양에 따라서는 두 표기의 결과가 같아집니다. main이 feature의 조상이라면, 예를 들어 main에서 feature를 만든 뒤 main이 더 진행되지 않았다면, log에서 main..feature와 main...feature는 같은 커밋을 고르고 diff에서도 두 표기가 같은 두 스냅숏을 비교합니다.
merge base가 여러 개일 때
브랜치끼리 서로 병합한 히스토리(criss-cross merge)에서는 두 커밋의 가장 좋은 공통 조상이 여러 개일 수 있습니다. git diff A...B는 --all 없이 실행한 git merge-base A B가 돌려주는 merge base 하나를 씁니다. git-merge-base 문서에 따르면 후보가 여럿일 때 그중 어느 것이 나올지는 정해져 있지 않습니다.
git merge-base --all A B로 후보가 여럿인지 확인할 수는 있지만, 그렇다고 세 점 diff가 쓰는 merge base가 바뀌지는 않습니다.
diff 출력의 @@ 줄을 읽는 방법은 통합 diff 읽는 법에 정리해 두었습니다.
참고한 명세와 문서
링크와 내용은 2026년 10월 3일에 확인했습니다. GitHub의 화면 동작은 바뀔 수 있습니다.