1P by GN⁺ | ★ favorite | 댓글 1개
  • Conventional Commits<type>[optional scope]: <description> 형식으로 커밋 메시지에 의미를 부여하려 하지만, 변경 유형을 앞세우고 범위를 선택사항으로 둬 실제 탐색에 필요한 정보를 뒤로 미룸
  • 기여자·디버거·장애 대응자는 커밋 로그에서 변경이 닿은 코드 영역을 찾으며, 버그는 어떤 유형의 변경에서도 생길 수 있어 범위(scope) 가 유형보다 중요함
  • fix(compiler): prevent namespaced SVG <style> elements from being stripped처럼 설명만으로도 버그 수정 성격을 알 수 있고, refactor(core): Update webmcp support to use document.modelContext처럼 한 커밋이 수정·리팩터링·기능 추가에 걸칠 수 있어 type이 중복적이고 제한적임
  • 자동 CHANGELOG 생성과 시맨틱 버전 증가 판단은 커밋 로그와 변경 로그의 독자가 다르고, 되돌리기·우발적 하위 호환성 깨짐·나중의 깨짐 해소 때문에 결과가 어긋날 수 있음
  • 범위 접두사 커밋 메시지는 변경 주체를 먼저 보여 주며, 빌드·배포 조건도 제목 유형보다 git diff로 바뀐 파일을 기준으로 삼는 편이 낫음

잘못된 우선순위

  • Conventional Commits는 커밋 메시지에 의미를 부여해 개발자와 최종 사용자가 변경을 이해하도록 돕겠다는 목표를 가짐
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
  • 제목 줄은 fix, feat, chore, docs, refactor 같은 <type>, 선택적 scope, description으로 구성됨
  • 핵심 결함은 변경의 주체인 scope보다 변경 종류인 type을 우선하는 구조임
  • scope가 선택사항인 구조는 커밋에서 가장 중요한 정보가 빠질 수 있게 만들고, type을 제목 맨 앞에 둬 우선순위를 뒤집음

scope가 type보다 중요한 이유

  • 기여자는 마지막 기여 이후의 변화, 프로젝트의 전체 흐름, pull이나 rebase 때 진행 중인 작업과 충돌할 수 있는 커밋을 찾기 위해 커밋 로그를 읽음
  • 디버거는 버그가 드러난 컴포넌트와 관련된 영역을 건드린 변경을 찾으며, 버그는 어떤 type의 변경에서도 생길 수 있어 type 정보가 도움이 되지 않음
  • 장애 대응자는 장애 시점 주변의 커밋 로그를 훑어 문제를 일으킨 영역을 찾으며, 인바운드 API 오류 급증 지점에 auth scope 커밋이 있으면 유력한 원인 후보가 됨
  • 커밋 로그를 읽는 사람에게 중요한 정보는 변경이 어떤 종류였는지가 아니라 어떤 영역을 건드렸는지임

type의 중복성과 제한

자동화 약속의 한계

  • git-cliffconventional-changelog 같은 도구로 커밋에서 CHANGELOG를 자동 생성하는 발상은 커밋 로그와 변경 로그의 독자가 다르다는 문제를 가짐
  • CHANGELOG는 사용자 대상이며 버전 간 기능적·비즈니스적 차이를 이해하는 데 초점이 있음
  • 커밋 로그는 개발자 대상이며 코드베이스가 시간에 따라 어떻게 바뀌었는지와 scope 관점의 흐름을 읽는 데 초점이 있음
  • 중간 이상의 복잡도를 가진 프로젝트에서는 의미 있는 기능 하나가 여러 커밋으로 들어가며, 개발자에게는 구현 과정이 유용하지만 최종 사용자에게는 새 기능 자체만 중요함
  • 되돌리기(revert) 커밋은 개발자에게 커밋 로그의 흐름상 중요하지만, 최종 사용자에게는 되돌려진 변경이 만들어지지 않은 변경과 같음
  • 커밋 type에 기반한 시맨틱 버전 증가는 하위 호환성을 깨는 변경이 되돌려졌는데도 major 버전을 올리거나, 나중에야 깨짐을 알아차려 minor/patch로 잘못 올리거나, 후속 커밋과 합쳐져 깨짐이 사라졌는데도 깨짐으로 판단하는 문제를 만들 수 있음
  • 이런 상황에서 rebase로 히스토리를 고칠 수는 있지만, 워크플로가 이를 막거나 깨뜨릴 수 있고 커밋 로그가 전하는 흐름의 신뢰성을 낮춤
  • 커밋 제목 type으로 빌드·배포 프로세스를 트리거하면 docs: fix typos라는 제목의 커밋이 인증 서브시스템에 취약점을 넣는 식으로 자동 도구를 우회할 수 있음
  • 빌드·배포 조건은 커밋 제목보다 git diff로 변경 파일을 식별해 정하는 편이 낫음

적용 문제와 대안

  • Conventional Commits는 프로젝트별 type 집합을 정의하게 하지만, 많은 프로젝트가 commitlint의 기본 type을 그대로 가져가며 개별 프로젝트 특성과 잘 맞지 않을 수 있음
  • Conventional Commits 명세는 기술적으로 fixfeat만 정의하고 추가 type은 프로젝트에 맡김
  • 기업 환경에서는 변경 관리와 감사 요구로 모든 커밋 메시지에 티켓 번호를 넣어야 하는 경우가 있으며, <scope>가 티켓 번호 자리로 쓰이면 유용한 metadata가 사라짐
  • Linux, FreeBSD, Git, Go, NixOS, Node.js는 프로젝트에 맞는 scope 접두사 커밋 메시지를 사용함
  • Linux 커널에서는 subsystem, Go 프로젝트에서는 package path, 마이크로서비스 아키텍처에서는 microservice 이름이 자연스러운 scope가 됨
  • scopedcommits.com은 커밋 메시지에서 scope 중심 형식으로 돌아가고, CHANGELOG 생성과 커밋 로그 관리를 분리하자는 방향을 다룸
  • Conventional Commits의 장점은 실제 이점으로 이어지지 않았고, 오픈소스 프로젝트에서의 인기와 AI의 기본 선택 경향이 안티패턴이 섞인 커밋 메시지 확산을 낳음

댓글과 토론

Lobste.rs 의견들
  • conventional commits에 대한 반박을 본능적 거부감 말고 논리로 정리한 글이라 반갑다
    왜 싫은지 깊게 생각해보진 않았고, LLM이 생성한 코드와 연관 짓게 된 탓인가 싶었음. 특히 chore:가 제일 싫은데, 헝가리안 표기법을 다시 발명하지 말았으면 함. 애초에 만들어지지 말았어야 함

    • 특히 chore:는 더 이상 Angular 커밋 스타일 가이드에도 없고, 너무 모호하다는 걸 깨달았는지 build:에 흡수됨
      Angular 스타일에 있던 시절에도 chore: 설명은 꽤 구체적인 용도를 제시했는데, 일부 오픈소스 프로젝트에서는 말 그대로 하기 귀찮은 일처럼 느껴지는 작업에 분위기로 붙이는 듯함
  • conventional commits를 좋아하진 않지만, 제안된 대안은 scope가 선택 사항인 이유를 놓치는 것 같음
    뚜렷한 모듈이 많지 않은 작은 프로젝트에서는 “scope”라는 개념이 별로 유용하지 않음. 둘 다 빠뜨린 유용한 관행으로, 커밋 제목에 이슈나 티켓 번호를 넣으면 변경의 추가 맥락을 파악하기 쉬워지고 코드 리뷰 때 특히 도움이 됨. 다만 티켓 번호를 필수로 만들면 사소한 변경에도 쓸모없는 티켓이 양산되니 싫고, 특정 버그나 작업을 처리하는 변경이라면 해당 버그나 작업으로 연결돼야 함

    • scope가 필요 없으면 그냥 생략하면 됨
      제목 줄만 봐도 드러나야 할 중복된 커밋 “type”보다 여전히 낫다
    • 이상적으로는 어떤 규정된 커밋 스타일도 없이, 특정 커밋에 맞는 표현을 쓰면 된다고 봄
      변경이 티켓과 명확히 대응되면 “티켓 번호” 커밋을 쓰고, 그렇지 않으면 다른 방식을 쓰면 됨. 어떤 변경은 type에는 잘 맞지만 scope에는 덜 맞고, 반대도 있으니 scoped commits와 conventional commits를 섞어 쓸 수도 있음
  • “문단 텍스트에는 고정폭 글꼴을 쓰지 말라”고 말하고 싶음
    그래도 글의 전제에는 대체로 동의함

  • 커밋 메시지가 별로여도 변경 범위를 감잡으려면 git log --name-onlygit log --stat를 자주 써보는 걸 추천함
    파일명을 보면 각 커밋을 전부 열어보지 않아도 무엇이 바뀌었는지 아는 데 꽤 도움이 됨

  • 정말 마음에 드는 방식은 PR 제목에 conventional commit 스타일을 강제하는 것임
    PR 제목은 병합 후에도 maintainer가 수정할 수 있고, 커밋 기록을 다시 쓸 필요도 없으며, release-drafter 같은 도구와 함께 쓰면 GitHub 릴리스에서 의미 있는 변경 로그를 자동화할 수 있음. 작성자가 말한 이해관계자에게 맞는 적절한 입도, 즉 기능·수정·호환성 깨짐을 나눠 보여주고 다음 GitHub 릴리스 초안의 합리적인 semver도 자동으로 처리해 줌
    parse-lib 같은 컴포넌트가 선택 사항이어서는 안 된다는 글의 지적은 맞고, conventional commits를 강제하면 새 기여가 위축된다는 데도 동의함. 하지만 대안들이 딱히 더 낫진 않음
    그래도 호환성 깨짐 식별자인 fix!(parse-lib): Don't leave sparse holes when parsing JSON arrays는 꽤 많은 정보를 줌. 특정 컴포넌트의 버그 수정이고, 그 수정에 불가피하게 따라온 호환성 깨짐이며, minor semver 증가 같은 의미를 담고 있음. 이런 건 PR 제목에 쓸 수 있음

  • 커밋 규율을 장려하는 방법으로 conventional commits에 너무 빠졌고, 결국 습관으로 굳어졌음을 인정함
    지금은 종종 제한적이고 임의적이라고 느낌. 몇몇 프로젝트에서는 그게 실제 관례인지도 모르고 Linux/Go/Node 스타일에 가까워졌는데, 다양한 설정이 있는 monorepo에서는 type을 억지로 만들기보다 [service]: [what changed]라고 쓰는 편이 자연스러웠음. 앞으로는 엄격한 관례에 맞추기보다 무엇이 유용해 보이는지 기준으로 개인 커밋 스타일을 더 실험해볼 생각이고, scoped commits는 좋은 출발점처럼 느껴짐

  • chore(lobsters): add my 2 cents on conventionals commits [JIRA-69420]
    거의 전부 동의하지만, “커밋 로그가 말하는 이야기의 신뢰성을 낮추는 수정주의적 기록을 기여자에게 보여준다”는 부분은 하나 다르게 봄. 작성자는 주로 공개 브랜치를 말하는 것 같은데, 공개 브랜치라면 합리적인 조언임. 하지만 비공개 브랜치에는 적용되지 않아야 함. 최종 변경을 리뷰하는 사람, 즉 maintainer나 10년 뒤의 내가 이해하기 쉽게 만들면 되지, 앞뒤 안 맞는 사고 흐름이나 더 나쁘게는 address review 커밋 묶음을 남길 필요는 없음

  • “왜 scope가 선택 사항인가?”에 대한 답은 작은 프로젝트에는 그냥 프로젝트 전체가 scope이기 때문임
    커밋의 “type”이 그다지 유용하지 않다는 데는 동의하지만, scoped commits와 conventional commits 사이에 큰 차이가 있는지도 잘 모르겠음. scoped는 “type”이 빠진 conventional일 뿐이고, fix·feat·refactor·chore 구분은 있어도 괜찮은 구분임
    다들 commitlint 기본값을 그대로 가져다 쓴다면, 사람들이 더 잘 다루게 만들면 되는 일 아닐까 싶음