← 개발 로그 목록

LearningCollector: dev/PR draft 구조적 중복 자동 제외

/ 5분 분량 / 개발 로그

website 레포의 feature→dev→main 승격 워크플로에서, 같은 작업이 dev draft와 PR draft로 한 배치 안에서 두 번 생성되는 문제를 커밋 메시지 원문 일치로 잡아서 자동으로 걸러내도록 했다.

LearningCollector는 여러 레포의 커밋, PR, 스터디 기록 같은 걸 모아서 블로그 draft를 자동 생성하고 포스팅하는 파이프라인이다. website 레포는 feature 브랜치에서 작업하고 dev로 머지한 다음, dev를 다시 main으로 승격하는 구조를 쓴다. 문제는 이 구조 자체가 draft 생성 로직 입장에서는 같은 작업을 두 번 보게 만든다는 점이었다.

패턴이 두 가지였다. 하나는 "Merge pull request #N" 형태의 머지 커밋이 dev draft로 하나 생기고, 그 PR#N도 별도로 pr draft가 생기는 경우 — 둘 다 결국 같은 병합 이벤트를 설명하는 글이다. 다른 하나는 dev→main 승격 PR처럼 여러 커밋을 한 번에 묶는 draft가, 이미 개별 dev draft로 따로 올라간 커밋들을 다시 롤업해서 요약하는 경우였다. 이건 사실 처음 겪는 문제는 아니었다. 이미 게시된 381개 포스트를 정리하면서 같은 패턴의 중복을 확인하고 걸러낸 적이 있었는데, 그때 방법론이 제목 유사도를 추측하는 게 아니라 커밋 메시지 원문이 정확히 일치하는지를 구조적으로 확인하는 것이었다. 이번 PR은 그 방법론을 사후 정리가 아니라 draft 생성 시점에, 포스팅 직전 단계에서 자동으로 적용하도록 만드는 작업이었다.

구현은 _dedupe_dev_pr_drafts() 하나로 정리했다. 먼저 draft 파일 경로에서 draft_saver가 저장할 때 쓴 이름 규칙을 거꾸로 풀어 원본 소스 JSON을 다시 찾아오는 _source_json_for_draft()가 필요했다. draft 타입(dev/pr/study/algorithm)에 따라 원본 JSON이 저장된 서브디렉토리가 다르길래 _DRAFT_SOURCE_SUBDIR 매핑을 만들어서 처리했다.

패턴 A는 dev draft의 커밋 메시지 첫 줄이 Merge pull request #N from ... 형태인지 정규식으로 확인하고, 같은 배치에 그 PR#N draft가 있으면 둘을 짝짓는 방식이다. 여기서 Merge branch와 Merge pull request를 구분해야 했는데, dev/main 사이의 순수 브랜치 병합("Merge branch")은 짝지을 PR 번호가 없어서 패턴 A로는 처리할 수 없고 패턴 B(커밋 메시지 비교)에서 노이즈를 걸러내는 용도로만 쓰인다. 이 구분을 안 하면 머지 커밋 자체를 개별 작업 커밋처럼 취급해서 엉뚱하게 매칭될 수 있었다.

패턴 B는 조금 더 조심스럽게 접근했다. PR draft가 담고 있는 커밋_메시지_목록을 순회하면서, 같은 배치의 dev draft 중에 첫 줄이 완전히 일치하는 게 있는지 찾는다. 겹치는 게 하나라도 있으면 롤업 중복 후보로 보는데, 여기서 바로 지우지 않고 조건을 하나 더 걸었다. PR draft의 본문 길이가 겹치는 개별 dev draft들 중 가장 긴 것보다 짧거나 같을 때만 제외하도록 했다. PR draft가 더 길다면 그건 개별 커밋들보다 더 넓은 맥락(예: 여러 기능을 아우르는 설명)을 담고 있다는 뜻이니 자동으로 지우지 않고 사람이 판단하게 남겨두는 쪽을 택했다. 중복 제거가 과도해서 필요한 글까지 날리는 것보다는, 애매한 경우 둘 다 남기는 게 안전하다고 생각했다.

또 하나 신경 쓴 부분은 draft 파일 자체는 지우지 않는다는 점이다. all_timed_drafts에서 걸러서 포스팅 목록에만 안 들어가게 했다. 원본 JSON은 이미 succeeded_jsons에 들어가서 posted 처리가 되기 때문에, 다음 실행에서 같은 항목을 다시 draft로 만들어 재시도하는 일은 없다.

검증은 실제 website 레포의 draft 파일 두 케이스(#158 BUILDPLATFORM, #81 OCI Email Delivery)로 _dedupe_dev_pr_drafts()를 단독으로 돌려서, 예상대로 더 짧은 쪽이 제외되는지 확인했다. 커밋은 한 번, PR도 생성부터 병합까지 3분 만에 끝났다 — 로직 자체가 크지 않고(파일 하나, 138줄 추가) 실제 데이터로 바로 검증할 수 있는 종류의 변경이라 굳이 여러 커밋으로 나눌 필요가 없었다.

이번 작업을 하면서 다시 확인한 건, "제목이 비슷하다"는 판단은 결국 추측이라 오탐이 나올 수밖에 없고, 커밋 메시지 원문 같은 구조적 근거가 있을 때만 자동화를 신뢰할 수 있다는 점이다. 애매한 케이스(PR draft가 더 긴 경우)를 자동으로 정리하지 않고 남겨둔 것도 같은 맥락인데, 나중에 이 롤업 draft들이 어떤 패턴으로 쌓이는지 좀 더 보고 나서 필요하면 조건을 추가하는 게 나을 것 같다.