SELECT 1
Entry 0232026.11.02 월
One launch a day. Picked, run, explained.

2026.11.02 월

OmniDiff: 파일 종류에 맞춰 변경을 비교하기

오늘의 픽: OmniDiff

OmniDiff 는 파일의 종류에 맞춰 변경 내용을 비교해 보여 주는 프로덕트다. 코드는 문법 구조로 비교한다. 이미지는 픽셀로, PDF 는 페이지로 비교한다.

오늘의 픽으로 선정한 이유는 이미지 같은 바이너리 파일의 변경도 리뷰할 수 있게 하기 때문이다.

exit 0실행함작동한다. 확장자를 바꾼 PNG 에서도 바뀐 영역의 위치와 크기를 정확히 찾았다.
출처
Show HN
만든 방식
Rust, tree-sitter, Ratatui

무엇이 문제인가

코드 리뷰에서는 diff 로 변경을 읽는다. 변경이 라인의 일부에만 있거나 파일이 텍스트가 아니면, 라인 단위 diff 로는 무엇이 바뀌었는지 알기 어렵다.

제작자는 README 에서 같은 코드 변경을 GNU diff 와 OmniDiff 로 나란히 보여 준다. GNU diff 는 라인 안의 일부만 바뀌어도 그 라인 전체를 지우고 새로 추가한 것으로 표시한다. Git 공식 문서에 따르면 git diff 의 --word-diff 옵션도 라인 단위 diff 의 결과 안에서 단어 단위 변경을 계산한다. git diff 는 바이너리로 판단한 파일에는 "Binary files differ" 만 출력한다. 내용을 비교하려면 사용자가 파일을 텍스트로 바꾸는 프로그램을 textconv 로 지정해야 한다. 같은 문서에 따르면 이 변환에서는 정보 일부가 사라진다.

어떻게 해결했나

OmniDiff 는 2026년 11월 1일 Hacker News 에 Show HN 글로 올라왔다. OmniDiff 는 파일의 종류를 먼저 판별하고, 그 종류에 적합한 방법으로 비교한다. 코드는 확장자로 언어를 판별하고, 지원하는 25개 언어의 코드는 AST소스 코드를 문법 구조에 따라 트리로 나타낸 자료 구조. 컴파일러와 분석 도구가 코드를 읽을 때 만든다. 로 바꿔 구조끼리 비교한다. 텍스트가 아닌 파일은 확장자가 아니라 내용의 바이트로 종류를 판별한다. 이미지는 픽셀 단위로, 압축 파일은 안에 든 파일 단위로, 폰트는 글자 모양 단위로, PDF 는 페이지 단위로 비교한다.

결과는 키보드로 조작하는 터미널 화면, 일반 텍스트, JSON 으로 볼 수 있다. 환경 변수 GIT_EXTERNAL_DIFF 로 Git 에 연결하면 git diff 의 출력도 OmniDiff 가 만든다. git difftool 로 연결하면 파일마다 OmniDiff 의 터미널 화면이 열린다.

무엇이 다른가

첫째, 코드의 변경을 표시하는 단위가 다르다. OmniDiff 는 코드를 AST 로 비교해 라인 전체가 아니라 라인 안에서 변경된 부분만 표시하고, 옮긴 코드는 옮겼다고 표시한다. 제작자는 직접 표시한 정답과 바이트 단위로 대조해 정확도를 측정했다. 다만 README 에 따르면 정답이 있는 1,419건 가운데 OmniDiff 의 표시가 정답과 모두 같은 경우는 --full 설정에서 60.0%, 강조 범위가 좁은 --minimal 에서 69.6% 다. 목표는 90% 다.

둘째, 텍스트가 아닌 파일을 다루는 방식이 다르다. OmniDiff 는 이미지에서 바뀐 픽셀의 비율과 영역을, 폰트에서 바뀐 글자 모양을 보고한다. 트레이드오프는 압축을 풀거나 렌더링해서 비교하기 때문에 시간이 더 걸린다는 점이다. README 에 따르면 PDF 비교는 57% 가 100 ms 안에 끝난다. 압축 파일은 p99측정값을 작은 순서로 늘어놓았을 때 99% 지점의 값이다. 요청 100개 가운데 가장 느린 1개를 빼면 나머지는 모두 이 값보다 빠르다. 가 1,938 ms 로 목표인 1초를 넘는다.

어떻게 만들었나

  • 커밋 1,514개는 2026년 2월 11일부터 11월 1일까지 작성됐다. 그중 795개에 Claude 와 함께 작성했다는 Co-authored-by 트레일러가 있다.
  • 제작자는 README 의 AI 정책에서 개발에 Claude Code 의 도움을 많이 받는다고 밝혔다.
  • 제작자는 Gentoo Linux 의 패키지 목록에서 오픈소스 저장소 약 7,500개를 골라, 비교가 실패 없이 끝나는지 시험했다. README 에 따르면 코드 파일의 변경 442,530건 가운데 99.95% 가 건당 120초, 메모리 6 GB 안에서 끝났다. 나머지는 시간이나 메모리 한도를 넘었다.

실행해 보니

Rust 1.97 로 저장소를 빌드하고, 4코어 가상 머신에서 결과를 텍스트로 출력하는 --headless 모드로 실행했다.

  1. 빌드. cargo build --release --locked 는 6분 1초가 걸렸다. 실행 파일은 54 MB 였다.
  2. 이미지. 200×120 PNG 두 장에서 사각형 하나를 바꿔 비교했다. OmniDiff 는 픽셀의 12.96% 가 바뀌었다고 보고하고, 바뀐 영역의 크기(61×51)와 위치를 출력했다. 실제로 바꾼 영역과 같았다. 두 PNG 파일의 확장자를 .txt 로 바꿔도 결과가 같았다.
  3. Git. 새 저장소에 PNG 와 Python 파일을 커밋하고 바꿨다. git diff 는 PNG 에 "Binary files … differ" 만 출력했다. GIT_EXTERNAL_DIFF 로 OmniDiff 를 지정하자 PNG 에는 바뀐 픽셀의 비율과 영역이 출력됐다. Python 파일에서는 OmniDiff 가 total 이 sum(numbers) 로 바뀐 부분을 표시하고, return 라인은 5번째 라인에서 2번째 라인으로 옮겼다고 표시했다.
  4. 종료 코드. --exit-code 를 붙이자 같은 파일은 0, 다른 파일은 1, 없는 파일은 2 를 반환했다.
  5. 속도. 저장소에 든 테스트 파일 쌍 2,302건을 하나씩 비교했다. 비교 시간의 중앙값은 26.5 ms, p99 는 858 ms 였고, 1초를 넘은 쌍이 16건이었다. README 에 따르면 제작자의 노트북에서는 같은 2,302건의 중앙값이 4.2 ms, p99 가 177 ms 였고 1초를 넘은 쌍이 없었다. 제작자의 Xeon 서버에서는 노트북보다 약 2.5배 오래 걸린다. 오늘은 쌍마다 프로세스를 새로 실행했다. 실행 파일을 시작하는 데는 약 3 ms 가 걸렸다. README 의 측정이 시작 시간을 포함하는지는 확인하지 못했다.
Picture logo.png: PNG 200x120 RGB8, 419 bytes -> PNG 200x120 RGB8, 453 bytes
  12.96% of pixels changed (3111 of 24000), in 1 region:
    61x51 at (120, 30)
=== after: stats.py ===
1    def mean(numbers):
Moved from line 5
2 |~     return sum(numbers) / len(numbers)

확인하지 못한 것

  • 터미널 화면. kitty 처럼 그래픽을 지원하는 터미널에서 이미지를 그림으로 보여 주는지 확인하자.
  • PDF 와 폰트. README 의 예처럼 PDF 다이어그램에서 이름 하나를 바꿔 비교하고, 폰트도 글자 하나의 모양을 바꿔 비교해 보자.

판정

작동한다. 확장자를 바꾼 PNG 에서도 바뀐 영역의 위치와 크기를 정확히 찾았다. 다만 4코어 가상 머신에서 측정한 비교 시간의 중앙값은 README 에 적힌 제작자 노트북의 값보다 약 6배 길었다. 이미지 같은 바이너리 파일의 변경도 리뷰해야 하는 경우에 써 볼 만하다.

시작하기

  1. cargo install --locked omnidiff
  2. omnidiff --headless before.py after.py
  3. git config difftool.omnidiff.cmd 'omnidiff "$LOCAL" "$REMOTE"'
필요한 것
Rust 1.92 이상과 C 컴파일러. 첫 빌드는 4코어 가상 머신에서 6분이 걸렸다
비용
AGPL-3.0, 무료. 제작자는 AGPL 의 소스 공개 의무를 지지 않고 회사 안에서 사용하려면 GitHub Sponsors 에서 월 구독으로 상용 라이선스를 받으라고 안내한다.

제작자에 따르면 지금은 외부 기여를 받지 않는다.

실행해서 확인한 절차다.

진행

  1. 3일예정
  2. 7일예정
  3. 15일예정
  4. 30일예정
  5. 90일예정
  6. 180일예정
  7. 365일예정