버전 관리 친화적인 파일 변환
개발 팀이 문서, 디자인 자산, 데이터 파일을 소스 코드와 함께 저장할 때, 파일 형식 선택은 버전 관리 시스템의 사용성을 좌우합니다. 부적절한 변환은 저장소 크기를 부풀게 하고, diff 출력을 흐리게 하며, 자동 빌드를 취약하게 만들 수 있습니다. 이 글에서는 Git이 제공하는 깔끔한 히스토리와 재현성을 희생하지 않고 파일을 변환하는 기술적 고려사항을 살펴봅니다. 안내 내용은 실제 워크플로우에 기반하며, 빠르고 프라이버시를 고려한 변환이 필요할 때 convertise.app 같은 클라우드 기반 변환기를 사용한다는 가정을 두고 있습니다.
기존 변환이 Git과 충돌하는 이유
Git은 텍스트 파일의 줄 단위 변화를 추적하는 데 뛰어납니다. 반면 바이너리 블롭은 불투명한 스냅샷으로 저장되며, 변경이 발생할 때마다 전체 파일을 다시 업로드하게 되어 저장소가 급격히 커집니다. 또한 많은 변환 파이프라인은 타임스탬프, GUID, 임베드된 메타데이터 등 비결정적인 출력을 생성합니다. 이 때문에 git diff에서 거짓 양성 결과가 나오고, 병합 충돌 해결이 어려워집니다. 대용량 바이너리와 비결정성이 결합되면 단일 진실 소스(Single Source of Truth)의 장점이 빠르게 사라집니다.
버전 관리 친화적인 변환 워크플로우는 다음 세 가지 핵심 문제를 해결합니다.
- 용량 팽창 – 저장소에 메가바이트 단위의 생성 자산을 저장하지 않음.
- Diff 불투명성 – Git이 의미 있는 차이를 보여줄 수 있는 형식으로 출력 유지.
- 재현성 – 동일한 소스가 언제나 동일한 출력을 만들도록 보장해 CI 파이프라인을 결정론적으로 유지.
초기 단계에서 변환‑가능 형식 선택하기
가장 효과적인 완화책은 Git의 강점에 맞는 대상 형식을 선택하는 것입니다. 아래는 가장 흔한 소스‑대‑대상 쌍과 그 이유입니다.
- Markdown → HTML / PDF – Markdown은 순수 텍스트이며, HTML도 텍스트 기반이므로 diff가 가능합니다. PDF가 필요할 경우, 타임스탬프를 제거한 결정론적인 LaTeX 파이프라인을 사용해 생성합니다.
- SVG → PNG – SVG는 벡터이며 diff가 가능합니다. PNG는 최종 배포용으로만 변환하고, SVG는 히스토리를 남기기 위해 저장소에 유지합니다.
- CSV → Parquet – 인간이 검토할 수 있도록 CSV를 저장하고, 분석용으로 Parquet을 자동으로 생성합니다. Parquet 파일은 바이너리이므로 데이터 레이크 버킷에 두고 저장소에는 두지 않습니다.
- 디자인 소스(Figma, Sketch) → PNG / PDF – 원본 디자인 파일(보통 바이너리)을 버전‑관리 프로젝트에 포함하고, 배포 시에만 PNG/PDF로 내보냅니다. 내보낸 파일은 별도의 아티팩트 저장소에 보관합니다.
변환 과정에서 어쩔 수 없이 바이너리가 생성되는 경우(예: 컴파일된 PDF)에는 소스(LaTeX, Markdown, SVG)를 Git에 보관하고, 바이너리는 파생 아티팩트로 취급합니다. 이렇게 하면 크기와 diff 문제를 동시에 해결할 수 있습니다.
결정론적 변환: 숨겨진 변동성 제거하기
바이너리를 저장소에 두어야 할 때도 변환을 재현 가능하게 만들 수 있습니다. 다음 절차를 따르세요.
- 타임스탬프 제거 – 대부분의 변환기는 현재 날짜를 삽입합니다.
exiftool -AllDates= …같은 후처리 스크립트로 삭제합니다. - 메타데이터 순서 정규화 – 일부 도구는 사전(entry) 순서를 비결정적으로 기록합니다. 변환기가 순서를 지정하는 플래그를 제공한다면 사용하고, 없으면 안정적인 직렬화 도구(
jq -Sfor JSON,xsltprocfor XML)로 파이프합니다. - 압축 설정 고정 – 무손실·결정론적 압축 알고리즘을 선택합니다(예: 고정 시드가 있는
zlib). 무작위 시드를 포함하는 설정은 피하세요. - 줄 바꿈 제어 – 전체 파일에 LF(
\n)를 강제합니다. Windows 스타일(\r\n)은 diff를 깨뜨립니다. - 재현 가능한 환경 사용 – 모든 라이브러리 버전을 고정한 Docker 컨테이너 안에서 변환을 실행합니다. 이렇게 하면 “내 환경에서는 동작한다”는 문제를 없앨 수 있습니다.
변환 파이프라인을 순수 함수처럼 만들면, 동일한 소스에 대해 언제 실행해도 동일한 해시 값을 가진 아티팩트를 얻을 수 있어 git diff --binary와 CI 캐시가 안정적으로 동작합니다.
Git 워크플로우에 변환 통합하기
변환 단계를 통합하는 일반적인 패턴은 두 가지입니다.
1. Pre‑commit 훅을 이용한 생성
Pre‑commit 훅은 커밋 전에 스테이징된 파일을 변환하고, 파생 아티팩트를 인덱스에 다시 추가합니다. 예시 Bash 스크립트:
#!/usr/bin/env bash
# Pre‑commit 훅: Markdown에서 PDF 생성
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.md$')
for f in $files; do
out=${f%.md}.pdf
curl -X POST -F "file=@$f" https://api.convertise.app/convert -F "target=pdf" -o "$out"
# 타임스탬프 제거로 파일을 결정론적으로 만듦
exiftool -AllDates= "$out" -overwrite_original
git add "$out"
done
이 훅은 변환을 자동화하고, 모든 커밋에 일관된 바이너리가 포함되도록 보장합니다.
2. CI‑전용 빌드 아티팩트
바이너리가 큰 경우, CI 서버에서 생성하고 아티팩트 저장소(GitHub Packages, Artifactory 등)에 푸시하는 것이 좋습니다. 소스는 Git에 남고, 릴리스 단계에서 아티팩트 저장소에서 파일을 받아 사용합니다. 이렇게 하면 저장소 부피를 줄이면서도 최종 사용자에게 준비된 자산을 제공할 수 있습니다.
Git LFS로 대용량 바이너리 관리하기
고해상도 이미지, 책 전체 PDF, 3D 모델 미리보기 등 큰 자산을 버전 관리해야 할 경우 Git LFS( Large File Storage )가 표준 솔루션입니다. 성공 포인트는:
- 필수 바이너리만 추적합니다. 변환 가능한 소스 파일은 일반 저장소에 두고, LFS에는 최종 출력만 저장합니다.
- 명명 규칙을 강제(
*.pdf.lfs,*.png.lfs등)해 개발자가 어떤 파일이 LFS에 의해 관리되는지 알 수 있게 합니다. .gitattributes에 크기 제한을 설정(*.pdf filter=lfs diff=lfs merge=lfs -text)해 실수로 대용량 파일을 직접 커밋하는 일을 방지합니다.
결정론적 변환과 결합하면 Git LFS는 버전당 한 사본만 보관하고, 동일한 출력이 여러 브랜치에 걸쳐 공유되므로 대역폭을 절감합니다.
Pre‑commit·Pre‑push 훅으로 자동화하기
기본 생성 훅 외에도 검증 단계를 추가해 회귀를 초기에 잡아낼 수 있습니다.
- 체크섬 검증 – 변환 후 SHA‑256 해시를 계산해
.checksums파일에 저장된 해시와 비교합니다. 차이가 있으면 변환이 비결정적임을 알 수 있습니다. - 스키마 검증 – 데이터 파일(CSV → Parquet)의 경우 JSON Schema 또는 Avro 정의를 사용해 열 타입이 기대한 대로인지 확인합니다.
- 접근성 검사 – 생성된 PDF·HTML에 자동 a11y 도구를 실행해 alt‑text와 헤딩 구조가 보존됐는지 검증합니다.
이러한 검증은 로컬에서 실행되므로 코드를 중앙 저장소에 푸시하기 전에 즉시 피드백을 제공합니다.
메타데이터·출처 보존하기
바이너리가 diff 불가능하더라도 중요한 출처 정보를 사이드카 파일에 담을 수 있습니다. 각 생성 자산 옆에 JSON 매니페스트를 두세요.
{
"source": "docs/chapter1.md",
"converter": "convertise.app",
"timestamp": "2026-05-24T12:34:56Z",
"options": {
"pdfVersion": "1.7",
"embedFonts": true
},
"hash": "a3f5c2..."
}
매니페스트는 순수 텍스트이므로 완전히 버전 관리되며, CI 파이프라인이 바이너리가 선언된 출처와 일치하는지 확인하는 데 활용될 수 있습니다.
변환 정확도 테스트하기
견고한 워크플로우에는 회귀 테스트가 포함됩니다. 새로 생성된 바이너스를 알려진 베이스라인과 비교합니다. 바이너리 diff가 잡음이 많으므로 다음 방법을 조합합니다.
- 픽셀‑단위 이미지 비교 – 허용 오차(
compare -metric RMSE)를 지정해 비교합니다. - PDF 구조 비교 –
diff-pdf --output-diff로 시각적 차이를 강조합니다. - 텍스트 추출 검사 – PDF에 OCR을 적용해 추출된 텍스트를 원본과 비교합니다.
이 검증을 GitHub Actions 작업에 자동화하여, 허용 범위를 초과하는 편차가 있으면 PR을 실패하도록 합니다.
미니 케이스 스터디: 기술 문서 사이트
한 소프트웨어 팀은 Hugo 로 만든 공개 문서 사이트를 운영합니다. 소스 문서는 Markdown으로 작성하고, 사이트에서는 다운로드용 PDF 핸드북도 제공합니다. 초기 워크플로우는 PDF를 직접 저장소에 넣었고, 결과적으로 저장소가 1.5 GB까지 부풀었으며, 개발자들은 PDF 머지 충돌에 불만을 토로했습니다.
해결 단계
- 저장소에는
.md파일만 남김. - Pre‑commit 훅을 추가해
convertise.app으로 각 Markdown에서 PDF를 생성하고, 타임스탬프를 제거한 뒤 SHA‑256 해시를.md5파일에 기록. - Git LFS를 설정해 PDF를 관리(
*.pdf filter=lfs). - 동일 변환을 CI 작업에서도 실행해, 커밋된
.md5와 일치하는지 검증하고, PDF를 S3 버킷에 배포. - 웹사이트는 빌드 시 S3에서 PDF를 가져옴.
결과: 저장소 크기가 78 % 감소했고, diff가 다시 의미 있게 작동했으며, PDF 생성이 완전히 재현 가능해져 브랜치간 “PDF 드리프트”가 사라졌습니다.
모범 사례 요약
- 소스 친화적 형식(Markdown, SVG, CSV)을 Git에 보관하고, 바이너리는 파생 아티팩트로 다룸.
- 변환을 결정론적으로 만들기 위해 타임스탬프 제거, 압축 고정, 컨테이너 기반 환경 사용.
- 작은 자산은 pre‑commit 훅, 큰 자산은 CI 파이프라인으로 자동 생성.
- Git LFS는 필수 바이너리만 대상으로 하고, 명확한 네이밍 규칙을 적용.
- 사이드카 JSON 매니페스트에 출처·옵션·해시를 기록해 감사를 용이하게 함.
- 정기 검증: 체크섬, 스키마, 시각적 회귀 테스트를 통해 품질을 유지.
변환 선택을 버전 관리의 강점에 맞추면, 저장소를 슬림하게 유지하면서 명확한 히스토리를 보존하고, 필요 시 고품질 바이너리 자산도 제공할 수 있습니다. 이 접근법은 코드 중심 프로젝트뿐 아니라 콘텐츠‑중심 문서 사이트에도 동일하게 적용 가능하며, 신뢰할 수 있는 온‑디맨드 변환이 필요할 때 convertise.app 같은 프라이버시‑우선 클라우드 변환기와도 매끄럽게 통합됩니다.