✈️ Go Up

Git 히스토리 기반의 프론트매터 자동화 시스템 구축

Go to Nav

Git 히스토리 기반의 프론트매터 자동화 시스템 구축

이 문서는 마크다운 기반의 블로그에서 프론트매터를 깃 기반으로 자동 관리하는 방법을 다룹니다.

문제 인식: 반복되는 메타데이터 관리의 비효율성

개발 문서나 블로그 포스트를 관리하다 보면 누구나 경험하는 문제가 있습니다. 내용을 수정하고 커밋했지만, 정작 문서의 메타데이터는 업데이트하지 않는 실수입니다. Git 히스토리에는 정확한 수정 시간이 기록되어 있지만, 프론트매터의 날짜는 여전히 과거에 머물러 있습니다.

# 실제 커밋은 2025-01-13인데...
dates:
  - '2025-01-01T10:00:00+09:00'  # 까먹고 안 바꿈

이는 단순한 실수를 넘어 개발 프로세스의 효율성 문제로 이어집니다. Git이 이미 모든 변경 이력을 추적하고 있음에도 불구하고, 우리는 같은 정보를 수동으로 중복 관리하고 있는 셈입니다. 이는 DRY(Don't Repeat Yourself) 원칙에 위배되며, Single Source of Truth 개념에도 어긋납니다.

초기 접근: Git Hooks를 활용한 자동화 시도

문제 해결을 위한 첫 번째 접근은 Git hooks를 활용하는 것이었습니다. 커밋 시점에 자동으로 프론트매터를 업데이트하는 간단한 스크립트를 작성하면 될 것으로 예상했습니다.

그러나 실제 구현 과정에서 예상치 못한 기술적 난관들이 연쇄적으로 발생했습니다:

  1. Git hook 실행 시점과 커밋 데이터 접근 가능 시점의 불일치
  2. Hook 내에서의 커밋 수정이 야기하는 재귀 호출 문제
  3. YAML 파서의 유니코드 처리 비일관성
  4. 팀 환경에서의 Git hooks 공유 제약

각 문제는 단순해 보였지만, 해결 과정에서 Git의 내부 동작과 여러 도구들의 세부 사항을 깊이 이해해야 했습니다.

기술적 과제 1: Git Hook 실행 시점의 이해

Git hooks는 Git의 특정 이벤트가 발생할 때 자동으로 실행되는 스크립트입니다. 가장 먼저 시도한 것은 pre-commit hook이었습니다.

# .git/hooks/pre-commit
#!/bin/sh
echo "프론트매터 업데이트 중..."
node update-frontmatter.js

실행 시점을 테스트해본 결과, pre-commit hook은 커밋 객체가 생성되기 이전에 실행되므로 커밋 메시지나 해시에 접근할 수 없다는 것을 확인했습니다. 이는 Git의 커밋 프로세스를 이해하는 중요한 계기가 되었습니다.

Git의 커밋 프로세스는 다음과 같은 순서로 진행됩니다:

  1. pre-commit hook 실행
  2. 커밋 메시지 입력
  3. commit-msg hook 실행
  4. 커밋 객체 생성
  5. post-commit hook 실행

이러한 실행 시점의 차이를 정확히 이해하지 못한 채 구현을 시작했던 것이 첫 번째 실수였습니다. 기술 문서의 세부사항을 충분히 검토하지 않고 직관에만 의존한 결과였습니다.

Post-commit Hook과 커밋 수정 전략

Post-commit hook으로 전환한 후, 커밋 정보에 접근할 수 있게 되었습니다. 그러나 새로운 문제가 발생했습니다.

git log --oneline
f3d2a1b 프론트매터 자동 업데이트
a8c9e2f 🚀 새로운 기능 추가
7b5c3d2 프론트매터 자동 업데이트  
9d1e4f6 🐛 버그 수정

프론트매터를 업데이트하고 새로운 커밋을 생성하면 히스토리가 불필요하게 복잡해지는 문제가 있었습니다. 각 커밋마다 메타데이터 업데이트를 위한 추가 커밋이 생성되어 프로젝트 히스토리의 가독성을 해치게 됩니다.

이를 해결하기 위해 git commit --amend 명령을 활용하기로 했습니다. 이 명령은 가장 최근 커밋을 수정하여 새로운 커밋을 생성하지 않고도 변경사항을 반영할 수 있게 해줍니다.

# Post-commit hook 내부
git add -u
git commit --amend --no-edit --no-verify

이 접근법을 통해 클린한 커밋 히스토리를 유지하면서도 메타데이터를 자동으로 업데이트할 수 있었습니다.

기술적 과제 2: 팀 환경에서의 Git Hooks 공유

.git/hooks 디렉토리는 Git이 추적하지 않는 로컬 설정입니다. 이는 보안상의 이유로 설계된 것이지만, 팀 전체가 동일한 hooks를 사용해야 하는 상황에서는 제약사항이 됩니다.

각 팀원이 수동으로 hooks 파일을 복사해야 한다면, 이는 자동화의 의미를 퇴색시킵니다. 또한 hooks가 업데이트될 때마다 모든 팀원이 동기화해야 하는 관리 부담도 발생합니다.

Husky: 공유 가능한 Git Hooks 관리 도구

Husky는 Git hooks를 프로젝트의 일부로 관리할 수 있게 해주는 도구입니다. 2010년대 중반에 등장한 이 도구는 JavaScript 프로젝트에서 Git hooks를 npm 패키지처럼 관리할 수 있게 해줍니다.

npm install --save-dev husky
npx husky install
npx husky add .husky/post-commit "node scripts/update-frontmatter.js"

Husky를 통해 hooks는 프로젝트 저장소의 일부가 되며, npm install 시 자동으로 설정됩니다. 이로써 팀 전체가 일관된 개발 환경을 유지할 수 있게 되었습니다.

{
  "scripts": {
    "prepare": "husky install"
  }
}

이 설정을 package.json에 추가하면, 프로젝트를 클론한 후 npm install만으로도 Husky가 자동으로 설정됩니다.

기술적 과제 3: 재귀 호출로 인한 무한 루프

모든 설정이 완료되었다고 판단하고 테스트를 진행했습니다. 그러나 예상치 못한 상황이 발생했습니다.

post-commit hook이 무한히 반복 실행되는 현상이 발생했습니다. 분석 결과, 다음과 같은 순환 구조가 형성되고 있었습니다:

  1. 커밋 생성 → post-commit hook 실행
  2. Hook 내에서 git commit --amend 실행
  3. Amend로 인한 새로운 커밋 생성 → post-commit hook 재실행
  4. 2-3 과정의 무한 반복

이는 Git hooks의 재귀적 실행을 제어하는 메커니즘이 없다는 것을 보여주는 사례였습니다.

--no-verify 플래그의 한계

첫 번째 시도는 --no-verify 플래그를 사용하는 것이었습니다. 이 플래그는 Git hooks를 우회하는 옵션으로 알려져 있습니다.

그러나 Git 문서를 자세히 확인한 결과, --no-verify 플래그는 pre-commit과 commit-msg hooks만 우회하며, post-commit hook에는 영향을 주지 않는다는 것을 발견했습니다. 이는 Git의 설계상 post-commit은 이미 커밋이 완료된 후의 알림 목적으로 사용되기 때문입니다.

환경 변수를 활용한 재귀 방지 전략

다양한 해결책을 검토한 끝에, 환경 변수를 활용한 간단하면서도 효과적인 방법을 구현했습니다.

#!/bin/sh
# 이미 실행 중이면 그냥 끝내기
if [ "$FRONTMATTER_UPDATE_RUNNING" = "1" ]; then
  echo "🔄 Already running, skip"
  exit 0
fi

export FRONTMATTER_UPDATE_RUNNING=1

# 실제 작업
node scripts/update-frontmatter-postcommit.js

# 정리 (사실 필요 없음, 프로세스 끝나면 환경변수도 사라짐)
unset FRONTMATTER_UPDATE_RUNNING

이 접근법은 단순하지만 확실한 해결책입니다. 프로세스 수준에서 상태를 관리함으로써 재귀 호출을 효과적으로 방지할 수 있었습니다. 복잡한 플래그 조합이나 Git 내부 메커니즘에 의존하지 않고도 문제를 해결할 수 있다는 점에서 실용적인 선택이었습니다.

기술적 과제 4: 유니코드 처리의 일관성 문제

모든 기능이 정상적으로 작동하는 것처럼 보였지만, 생성된 프론트매터를 확인했을 때 또 다른 문제가 발견되었습니다.

messages:
  - "\U0001F680 새 기능 추가"
  - "\U0001F41B 버그 수정"

커밋 메시지의 이모지가 유니코드 이스케이프 시퀀스로 변환되어 저장되고 있었습니다.

YAML 파서 간의 동작 차이 분석

gray-matter는 내부적으로 js-yaml을 사용하지만, stringify 과정에서 자체적인 처리를 추가합니다. 디버깅 결과, gray-matter의 stringify 메서드가 유니코드 문자를 이스케이프하는 것을 확인했습니다.

이는 YAML 스펙상 유효한 표현이지만, 가독성과 사용자 경험 측면에서는 바람직하지 않았습니다. GitHub 이슈와 관련 문서를 조사한 결과, 다음과 같은 해결책을 도출했습니다:

import yaml from 'js-yaml';
import matter from 'gray-matter';

// 파싱은 gray-matter로 (이건 문제없음)
const { data, content } = matter(fileContent);

// 저장은 js-yaml로 직접!
const yamlStr = yaml.dump(data, {
  lineWidth: -1,
  noRefs: true,
  sortKeys: false
});

// 프론트매터 재조립
const updatedContent = `---\n${yamlStr}---\n${content}`;

이 접근법은 각 라이브러리의 장점을 활용하는 실용적인 해결책입니다. gray-matter의 편리한 파싱 기능은 유지하면서, js-yaml의 더 유연한 stringify 옵션을 활용하여 원하는 출력 형식을 얻을 수 있었습니다.

기술적 과제 5: 기존 메타데이터 보존 전략

시스템이 완성된 후, 중요한 설계 결정을 내려야 했습니다. 자동화 시스템이 사용자가 수동으로 설정한 메타데이터를 덮어쓰지 않도록 하는 것이었습니다.

---
title: 내 포스트
category: blog        # 이거 사라지면 안 되는데...
tags: [git, automation]  # 이것도...
featured: true        # 이것도 중요한데...
---

프론트매터에는 다양한 메타데이터가 포함될 수 있으며, 이들 중 일부는 자동화할 수 있지만 일부는 사용자의 명시적인 의도를 반영해야 합니다.

메타데이터 관리 정책 수립

다음과 같은 명확한 정책을 수립했습니다:

자동 관리 대상:

  • Git 히스토리에서 추출 가능한 정보
  • 문서 내용에서 파생 가능한 정보

수동 관리 대상:

  • 사용자의 의도적 선택이 필요한 정보
  • 외부 시스템과의 연동을 위한 식별자
const AUTO_MANAGED_FIELDS = [
  'title',        // H1에서 자동 추출
  'description',  // 첫 단락에서 자동 추출
  'authors',      // git log에서
  'dates',        // git log에서
  'messages'      // git log에서
];

// 기존 데이터에서 자동 필드만 지우고
AUTO_MANAGED_FIELDS.forEach(field => delete data[field]);

// 새로 추출한 데이터 합치기
Object.assign(data, newData);

이러한 선택적 업데이트 전략을 통해 자동화의 편의성과 사용자 제어권 사이의 균형을 달성할 수 있었습니다.

구현 결과 및 성과

이전 워크플로우

---
title: 수동으로 복사-붙여넣기
description: 이것도 수동으로 작성
authors:
  - 어... 내 GitHub 아이디가 뭐였지?
dates:
  - '2025-01-... 몇 일이더라?'
---

개선된 워크플로우

git add posts/my-post.md  
git commit -m "📝 블로그 포스트 작성"
# 끝. 진짜 끝.

프론트매터는 자동으로:

---
title: Git 히스토리 기반의 프론트매터 자동화 시스템 구축
description: "또 날짜 업데이트 깜빡했네..." 금요일 오후 4시...
authors:
  - xiyo
dates:
  - '2025-07-13T19:24:00+09:00'
messages:
  - 📝 블로그 포스트 작성
category: blog  # 이건 내가 설정한 거니까 그대로 유지
---

결론: 투자 대비 효과 분석

초기 예상과 달리 전체 구현에는 약 6시간이 소요되었습니다:

  • Git hooks 실행 시점 이해 및 구현: 1시간
  • 무한 루프 문제 해결: 2시간
  • 유니코드 처리 문제: 1시간
  • Husky 도입 및 설정: 30분
  • 메타데이터 보존 로직: 30분
  • 테스트 및 디버깅: 1시간

그러나 이 투자는 장기적으로 상당한 시간 절약 효과를 가져왔습니다. 매 커밋마다 30초-1분 정도 소요되던 메타데이터 업데이트 작업이 완전히 자동화되었으며, 인적 오류의 가능성도 제거되었습니다.

시스템의 실질적 가치

이 자동화 시스템의 핵심 가치는 단순한 시간 절약을 넘어섭니다. 개발자가 메타데이터 관리라는 부수적인 작업에서 해방되어 본질적인 콘텐츠 작성에 집중할 수 있게 되었다는 점이 가장 중요합니다.

반복적인 수작업은 단순히 시간을 소비할 뿐만 아니라 창의적인 작업의 흐름을 방해합니다. 이러한 자동화를 통해 개발자는 더 가치 있는 작업에 시간과 에너지를 투자할 수 있게 됩니다.

예제 코드

/scripts/update-frontmatter-postcommit.js /.husky/post-commit