텍스트 비교
입력한 내용은 저장, 기록, 공유되지 않습니다.
두 텍스트를 붙여넣으면 줄 단위와 단어 단위로 무엇이 바뀌었는지 정확히 확인할 수 있습니다. 모든 처리가 브라우저에서 이루어지므로 어느 쪽도 업로드되지 않습니다.
사용 방법
- 왼쪽에 원본, 오른쪽에 새 버전을 붙여넣습니다.
- 차이가 아래에 나란히 보기 또는 통합 보기로 표시됩니다.
- 공백이나 대소문자를 무시하려면 옵션을 사용하세요.
비교를 계산하는 방식
두 텍스트를 모두 줄 단위로 나눈 뒤, 공통된 줄을 최장 공통 부분 수열로 찾습니다. Unix diff 명령의 바탕이 된 것과 같은 개념입니다. 부분 수열에 속한 줄은 제자리를 유지하고, 나머지는 왼쪽에서 삭제되었거나 오른쪽에 추가된 것으로 보고됩니다. 삭제와 추가가 나란히 붙게 되면 서로 무관한 두 사건이 아니라 하나의 수정으로 짝지어 보여 줍니다.
수정된 짝은 단어 수준에서 한 번 더 비교되므로, 긴 줄에서 숫자 하나만 바뀌었다면 줄 전체를 빨갛게 칠하지 않고 그 숫자만 강조합니다. 이것이 읽을 수 있는 diff와 눈을 찌푸리며 봐야 하는 diff의 차이이며, 줄 단위 도구가 문장이나 설정 파일에서 도움이 안 된다고 느껴지는 이유입니다.
그에 앞서 앞뒤로 똑같은 줄은 잘라 냅니다. 그래서 한 곳만 다른 천 줄짜리 파일 두 개는 몇 줄만 비교하는 셈이 되어, 더 빠르고 더 명확합니다.
똑같아 보이는 두 줄이 다르다고 표시될 때
가장 흔한 놀라움이며, 원인은 언제나 눈에 보이지 않는 문자입니다. 가장 흔한 범인은 줄 끝 공백이고, 그래서 이를 무시하는 옵션이 기본으로 켜져 있습니다. 다음은 줄바꿈 문자입니다. Windows에서 작성한 파일은 줄 끝이 CR+LF이고 macOS나 Linux에서 작성한 파일은 LF만 쓰므로, 원시 바이트를 비교하는 diff는 모든 줄이 바뀌었다고 판단합니다.
그다음은 탭과 연속된 공백의 차이(공백 무시 옵션으로 합칠 수 있습니다), 워드프로세서나 일부 웹 페이지가 일반 공백 대신 넣는 줄 바꿈 없는 공백, 자동 고침이 바꿔 넣은 둥근 따옴표와 엔 대시, 그리고 첫 줄 맨 앞에 보이지 않게 자리 잡은 BOM입니다. 이 중 무엇이든 화면에서는 똑같아 보이는 두 줄을 파일 안에서는 실제로 다르게 만듭니다.
크기 한도
최장 공통 부분 수열은 제곱 복잡도입니다. m줄과 n줄을 비교하면 m×n 표를 채워야 합니다. 수백 줄이면 사소하지만, 만 줄 대 만 줄이면 1억 칸이 되고, 표를 채우는 동안 페이지가 응답을 멈추게 됩니다.
그래서 공통된 앞뒤를 잘라 낸 뒤의 비교에 400만 칸이라는 한도를 두고, 이를 넘는 비교는 시도하지 않고 메시지와 함께 거절합니다. 실제로는 대체로 비슷하기만 하면 수천 줄짜리 파일도 즉시 비교되며, 한도에 걸리는 것은 정말로 서로 무관한 큰 문서 두 개뿐입니다. 그럴 때는 스트리밍으로 처리할 수 있는 명령줄 diff가 알맞은 도구입니다.
나란히 보기와 통합 보기
나란히 보기는 두 버전을 각자의 열에 두므로, 문장이나 번역처럼 두 텍스트를 글로서 읽고 싶을 때 더 좋습니다. 통합 보기는 표시를 붙여 번갈아 배치하는 형식으로, 패치와 코드 리뷰에서 쓰이며, 긴 파일 곳곳에 작은 변경이 흩어져 있을 때 따라가기 쉽습니다.
어느 쪽도 계산 결과를 바꾸지 않습니다. 같은 결과를 두 가지 방식으로 보여 줄 뿐입니다.
diff 뒤에 있는 알고리즘들
1986년 Eugene Myers가 발표한 고전적인 방식은 두 시퀀스 사이의 최단 편집 스크립트, 즉 한쪽을 다른 쪽으로 바꾸는 데 필요한 삽입과 삭제의 최소 집합을 찾습니다. Unix의 diff 명령과 Git의 기본 구현이 쓰는 방식이고, 변경 개수가 가장 적다는 의미에서는 최적입니다.
그러나 변경이 가장 적다고 해서 가장 읽기 좋은 것은 아닙니다. Myers 방식은 우연히 같은 내용이라는 이유로 서로 무관한 줄들을, 이를테면 여기 있는 닫는 중괄호와 저기 있는 빈 줄을 짝지어 버릴 수 있습니다. 그러면 함수 하나가 지워지고 다른 하나가 추가된 것으로 보이는 대신, 두 함수가 뒤섞인 diff가 나옵니다. Git의 patience 알고리즘은 양쪽 버전에 정확히 한 번씩만 나타나는 줄을 앵커로 삼고 그 사이를 재귀적으로 처리해 이 문제를 다루는데, 대개 읽는 사람이 진짜 대응 관계라고 여기는 것과 일치합니다.
지금은 Git이 권장하는 대안이 된 histogram 알고리즘은 patience에 빈도 휴리스틱을 더한 것으로, 큰 파일에서 더 빠릅니다. 코드 리뷰의 diff가 도무지 말이 안 되게 보인다면, diff.algorithm을 histogram으로 바꾸는 것만으로 똑같은 두 파일에서 훨씬 명확한 결과가 나오는 경우가 많습니다.
양방향 비교와 3-way 병합
두 파일을 비교하면 무엇이 바뀌었는지는 알 수 있습니다. 병합에는 세 번째 입력, 즉 공통 조상이 필요합니다. 그것이 없으면 어느 쪽이 줄을 바꿨고 어느 쪽이 원본 그대로인지 알아낼 방법이 없기 때문입니다. 버전 관리 시스템이 병합 기준점을 보관하는 이유도, 3-way 병합 도구가 창을 세 개 보여 주는 이유도 여기에 있습니다.
충돌은 그 조상을 기준으로 양쪽이 같은 영역을 바꿨을 때 생깁니다. 그 밖의 모든 것은 자동으로 병합되며, 서로 붙어 있지만 겹치지는 않는 변경도 마찬가지입니다. 두 사람이 하루 종일 같은 파일을 고쳐도 충돌 없이 지나가다가 어느 한 줄에서 부딪히는 이유가 바로 이것입니다.
이 점을 이해하면 충돌을 푸는 방식이 달라집니다. 충돌이 난 파일의 마커는 내 버전과 상대 버전을 보여 주지만, 정작 쓸모 있는 질문은 기준 버전을 놓고 볼 때 각 쪽이 무엇을 하려 했느냐입니다. git checkout --conflict=diff3는 마커에 기준 버전까지 넣어 주는데, 그러면 두 선택지 사이에서 찍는 일이 대개 답이 뻔히 보이는 일로 바뀝니다.
텍스트 줄이 아닌 것들을 비교하기
줄 단위 비교는 줄 경계에 의미가 있다고 전제합니다. 압축된 JavaScript, 한 줄짜리 JSON 페이로드, 긴 산문 문단은 모두 그 전제를 깨뜨립니다. 한 글자만 바뀌어도 그 줄 전체가 다르다고 보고되니까요. 양쪽을 먼저 보기 좋게 정렬해 출력하면 diff가 필요로 하는 구조가 되살아나고, JSON이라면 키를 정렬하고 들여쓰기를 통일한 정규형으로 만들어야 비교가 우연이 아니라 의미 있는 것이 됩니다.
CSV는 정반대 문제를 안고 있습니다. 줄 단위 diff가 동작하기는 하지만, 열 순서만 바뀌어도 모든 행이 변경된 것으로 보고합니다. 먼저 키가 되는 열로 정렬하거나 CSV를 이해하는 도구를 쓰면, 정작 중요한 변경을 잡음에서 떼어낼 수 있습니다.
바이너리이거나 그에 가까운 형식에는 저마다의 변환기가 필요합니다. .docx는 XML을 모아 압축한 zip이고, 노트북은 출력이 박혀 있는 JSON이며, PDF는 대체로 속을 들여다볼 수 없습니다. Git은 비교하기 전에 파일을 텍스트로 바꿔 주는 textconv 필터를 지원하는데, 팀들이 사본을 하나 더 보관하지 않고도 문서와 노트북의 읽을 만한 diff를 얻는 방법이 이것입니다.
남이 만든 diff 읽기
리뷰하기 좋은 diff는 그 안에 무엇이 없느냐로 대부분 결정됩니다. 서식으로 인한 잡음, 즉 파일 전체에 포매터를 돌린 것, 줄바꿈 문자가 바뀐 것, import 정렬기를 적용한 것 같은 변경은 중요한 세 줄을 중요하지 않은 삼백 줄 밑에 파묻어 버립니다. 보통의 해법은 기계적인 커밋과 실질적인 커밋을 분리해 각각을 알맞은 속도로 읽을 수 있게 하는 것입니다.
그래도 뒤섞인 diff를 받았다면, 공백을 무시하는 비교가 가장 빠른 통로입니다. git diff -w를 쓰거나 여기 있는 무시 옵션을 켜면 됩니다. 다만 승인하기 전에는 다시 꺼야 합니다. 어떤 파일에서는 공백이 곧 내용이기 때문입니다. Python의 들여쓰기, YAML의 구조, Makefile의 탭, 그리고 출력이 정확히 일치하는지 검사하는 테스트가 그렇습니다.
의도적으로 다뤄 둘 만한 또 하나는 대규모 이름 변경입니다. 옮겨지면서 수정까지 된 파일은 도구가 이름 변경을 감지하지 못하면 삭제 하나에 추가 하나로 나타납니다. Git의 -M 플래그나 이름 변경을 인식하는 리뷰 화면을 쓰면, 천 줄짜리 diff가 실제로 바뀐 열 줄로 되돌아옵니다.
편집 작업으로서의 텍스트 비교
모든 diff가 코드인 것은 아닙니다. 계약서 초안 두 부, 번역문 두 편, 또는 편집 전후의 문서를 비교하는 일은 흔한 용도이고, 좌우로 나란히 보여 주는 화면은 바로 그것을 위해 있습니다. 패치가 아니라 글로서 양쪽을 읽고 싶기 때문입니다.
산문에서는 단어 단위 강조가 가치의 대부분을 담당합니다. 다시 쓴 문장이 삭제된 줄 하나와 추가된 줄 하나가 아니라 다시 쓴 문장으로 보이기 때문입니다. 한 줄 안에서 이 일을 잘해 내는 도구라야 사람들이 편집 작업에 쓸 만하다고 느끼며, 여기서의 비교가 바뀐 줄 쌍마다 단어 단위로 두 번째 패스를 돌리는 이유도 그것입니다.
문서와 관련해 실용적인 메모가 둘 있습니다. 워드프로세서에서 붙여 넣은 글에는 다른 사본에는 없는 둥근 따옴표, 엔 대시, 줄바꿈 없는 공백이 섞여 들어오는 일이 잦아서, 보기에는 똑같은데 차이가 생깁니다. 또 같은 원본에서 두 번 내보낸 문서가 줄바꿈 위치만 다를 수도 있으니, 비교하기 전에 줄바꿈 문자를 정규화해 두면 혼란을 크게 줄일 수 있습니다.
자주 묻는 질문
- 차이는 어떻게 계산되나요?
- 줄의 최장 공통 부분 수열을 찾는 방식으로, diff 명령과 같은 원리입니다. 양쪽에 모두 있는 줄은 자리를 지키고 나머지는 추가 또는 삭제로 표시되며, 삭제와 추가가 나란히 오면 하나의 변경으로 짝지어집니다.
- 변경된 줄 안의 단어까지 강조하는 이유는?
- 줄이 바뀌었다는 사실만으로는 부족한 경우가 많기 때문입니다. 변경된 각 쌍에 대해 단어 단위로 다시 비교하므로 긴 줄에서 한 군데만 달라져도 바로 눈에 띕니다.
- 무시 옵션은 무엇을 하나요?
- 대소문자 무시는 대문자와 소문자를 같게 봅니다. 공백 무시는 연속된 공백과 탭을 합치므로 들여쓰기만 바뀐 경우에 유용합니다. 줄 끝 공백 무시는 기본으로 켜져 있는데, 보이지 않는 줄 끝 공백이 실제 차이인 경우는 거의 없기 때문입니다.
- 크기 제한이 있나요?
- 비교 비용은 다른 줄 수의 제곱에 비례하므로 앞뒤의 동일한 부분을 먼저 건너뛰고, 지나치게 큰 비교는 화면이 멈추기 전에 거부합니다. 실제로 수천 줄 파일은 즉시 비교됩니다.
- 텍스트가 어딘가로 업로드되나요?
- 아닙니다. 두 버전 모두 브라우저에만 남으므로 계약서, 로그, 미공개 코드도 안전하게 비교할 수 있습니다.
마지막 업데이트:
진행 단계, 예상 결과, 실제 결과를 적어 주세요. 비밀 정보와 개인 정보는 삭제해 주세요.
