Skip to content

CHANGELOG merge=union은 GitHub에 적용되지 않는다 — 유령 충돌 15건 + 릴리스된 섹션 조용한 오염(재현됨) #3929

Description

@yazzang-homelab

요약

.gitattributespackages/*/CHANGELOG.md merge=union로컬 git에서만 동작하고 GitHub의 병합 판정에는 적용되지 않는다. 그 결과 두 가지가 동시에 일어난다.

  1. 실제로는 깨끗이 붙는 PR 15개가 GitHub에서 dirty로 표시되어 머지 버튼이 막힌다.
  2. 로컬에서 union이 조용히 성공할 때, 이미 릴리스된 섹션에 항목을 잘못 집어넣는다. 충돌 마커도, 경고도, CI 신호도 없다.

근거 1 — GitHub는 union을 무시한다 (A/B, 통제됨)

같은 base(1f735b7b), 같은 head(#3892 f18ae6ff), .gitattributes의 union 한 줄만 차이:

조건 충돌 파일
union 적용 (저장소 설정 그대로) 없음
union 줄 제거 packages/ai/CHANGELOG.md, packages/coding-agent/CHANGELOG.md

GitHub REST는 같은 head에 대해 mergeable: false, mergeable_state: "dirty"를 반환한다. 즉 GitHub는 union 없는 쪽 규칙으로 계산한다.

열린 PR 24개 전수 대조 — 예외 0건, 상관 100%:

base.sha가 낡아서 생긴 캐시 문제가 아니다. #3892의 head를 base.sha(4bc67a8d)에 붙여도, 현재 dev에 붙여도 union 하에서는 둘 다 깨끗하다.

근거 2 — union이 릴리스된 섹션을 오염시킨다 (재현됨)

릴리스 커밋은 ## [Unreleased] 바로 아래에 ## [0.12.12] - 2026-08-05삽입한다(0e9e9447d). [Unreleased] 헤딩 자체는 그대로 남는다. 그래서 릴리스 이전 base를 가진 PR의 CHANGELOG 추가분은 union 병합 시 두 삽입 지점이 겹친다.

#3697(base가 0.12.12 릴리스 이전)을 현재 dev에 union 병합한 결과:

28: ## [0.12.12] - 2026-08-05
...
61: - Deferred composer shell results now commit to the session transcript before ...

아직 머지되지도 않은 #3697의 항목이 이미 배포된 0.12.12 릴리스 노트 안에 들어갔다. 위쪽 ## [Unreleased]가 아니다. 충돌 없음, CI 통과, 리뷰 diff에도 안 보인다 — 리베이스 결과물에만 나타난다.

부수 손상도 있다. 같은 병합 결과에서 ### Fixed 다음 빈 줄이 사라지고 목록 중간에 빈 줄이 끼어든다. union이 양쪽 청크를 그대로 이어붙이기 때문이다.

지금 치르고 있는 비용

저장소에 exact-head 게이트(git merge-base --is-ancestor "$GITHUB_BASE_SHA" HEAD)가 있어서 모든 PR은 머지 전 리베이스가 강제된다. 여기에 "GitHub에서만 충돌로 보이는 CHANGELOG"가 겹치면:

  • dev가 한 번 움직일 때마다 열린 PR 전부가 dirty로 뒤집힌다.
  • 기여자는 유령 충돌을 보고 리베이스·force-push한다 → verdict 무효화, CI 전면 재실행.
  • 그 리베이스 중 일부가 위의 오염을 조용히 만들어낸다.

#3928(docs-index.generated.ts)과 합치면 열린 PR 40개가 안 줄어드는 메커니즘이 설명된다. 저쪽은 병합 불가능한 생성 파일, 이쪽은 GitHub가 안 보는 병합 규칙이다.

제안

A. CHANGELOG 조각 파일로 전환 (권장). PR은 changelog.d/<pr-번호>.md 같은 조각만 추가하고, 릴리스 스크립트가 릴리스 시점에 CHANGELOG.md로 합친다. 두 PR이 같은 파일을 건드리지 않으니 충돌 자체가 사라지고, git과 GitHub의 판정이 일치하며, 잘못된 섹션에 끼어들 자리도 없어진다. union 줄은 그때 삭제한다. 비용은 스크립트 + CI 검사 + PR 템플릿 한 줄.

B. 최소 조치 — union은 두되 오염을 막는다. CI에 "## [Unreleased] 위쪽이 아닌 이미 릴리스된 ## [X.Y.Z] 섹션이 이 PR에서 수정되면 실패" 검사를 추가한다. 유령 충돌과 리베이스 churn은 그대로 남지만 조용한 오염은 막는다.

C. union 줄 삭제. 로컬과 GitHub 판정이 일치해 정직해지지만 충돌은 더 늘어난다. A와 묶을 때만 의미 있다.

A를 권한다. B는 A를 하기 전까지의 임시 방벽으로도 유효하다.

관련: #3928

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions