- 1. Atomic Commit (원자적 커밋)
- 2. Conventional Commits
- 3. Semantic Versioning (버전이 만들어지는 원리)
- 4. 실전 예제 (Do & Don’t)
전편 에서 전반적인 git의 원리 기능에대해 파봤으니까 이제 본격적인 협업을 위한 규칙과 좋은 commit 습관을 파헤쳐 본다.
1. Atomic Commit (원자적 커밋)
git 기능들을 보면 왔다갔다 뺐다 꼽았다, 수정/변경이 핵심인듯하다. 기존꺼 뭐 고치면서 추가로 필요할꺼같아서 다른것도 만지고 3~4 파일 한번에 커밋해버리고 이러면 사실 되돌아가고 빼오고 이러기 힘들어진다.
이론적으로 완벽한 단위는 분할 불가능한 최소한의 논리적 작업 단위 로 봐야한다.
이 커밋 하나만 떼어냈을 때, 논리적으로 말이 되며 빌드가 깨지지 않는가?
1.1 규칙 (Rules)
- Single Responsibility Principle (SRP): 하나의 커밋은 하나의 목적만 가져야 한다.
- Bad: “버그 수정하고, 코드 정리하고, 주석 달았음” (3가지 섞임)
- Good:
fix: resolve null pointer exception in user authstyle: remove trailing spaces-
docs: add comments for auth logic - Pass The Build: 모든 커밋은 체크아웃했을 때 빌드가 성공하고 테스트가 통과해야함.
- “작업 중(saving work)”이라며 컴파일 에러 나는 코드를 커밋하지 마라. 그건 git stash를 쓰거나 로컬에서만 두자.
1.2 Why? (Rationale & Counterfactual)
이유 1: git bisect (이진 탐색 디버깅)
- 상황: 어제까진 잘 돌던 코드가 오늘 갑자기 뒤짐. 언제부터 죽었는지 모르는데 커밋은 100개가 쌓임ㅋ.
- 해결: Git은 bisect라는 명령어를 제공하여, 100개의 커밋 중 문제가 발생한 최초의 커밋을 $O(\log N)$ 시간 복잡도로 찾아준다.
- Counterfactual (안 지켰을 때): 빌드가 안 되는 “중간 저장용 커밋”을 잔뜩 섞어놨다면?
- bisect가 돌다가 “빌드 실패”로 멈춤. 범인을 잡을 수 없게 되어 100개 커밋을 눈으로 다 뒤져야 함.
이유 2: git revert (롤백)
- 상황: “로그인 기능 수정” 커밋에 치명적인 버그가 있어서 롤백하려고함.
- Counterfactual (안 지켰을 때): 귀찮다고 “로그인 수정”과 “전체 코드 포맷팅(들여쓰기 정리)”을 한 커밋에 섞어버림.
- 이 커밋을 revert 하면? 전체 코드 포맷팅까지 롤백되어 코드 스타일이 엉망이 되거나 충돌(Conflict)이 발생.
결국 롤백 못 하고 밤새 버그를 수정하거나 죽통 돌아감.
작업을 하다보면 “아 이것도 해야겠노, 아 저것도 해야하노” 하는 식으로 자꾸 딸린 식구들을 만들게 된다. 나도 모르게 관성적으로 이것저것 손대고 만지고 근데 commit 은 안했음.
앞으로는 무지성 작업을 하는게 아니고 오늘 뭐할지 정하고, 어떻게 구성할지 뭔 파일 만들고 뭔 함수 어떻게, 어디에 쑤셔박을지 생각하고 작업을 해야겠다.
- 작업 할 내용 큰 그림 구성 (ex. multi-process dataloader shuffle 제대로 되는지 확인, 안되면 고쳐야함)
- 파일 뭐뭐 만들지 구성, 파일명, 폴더명은 뭘로할지 어디에 박아놓고 가져다 쓸지
- Atomic 단위로 나눠서 하나씩 작업 후 commit 하기.
- 이거하다 저거하다 절대 안됨. 왜냐면 Antigravity 커밋 메세지 딸깍을 못함.
2. Conventional Commits
커밋 메시지에 대한 표준 규약.
사람뿐만 아니라 스크립트(기계) 가 읽을 수 있도록 정해진 형식을 따라줘야함.
2.1 구조 (Syntax)
<type>(<scope>): <subject>
<BLANK LINE> <-- 여기 반드시 비워야 함 (치명적)
<body>
<BLANK LINE>
<footer>
2.1.1 Header (필수): type(scope): subject
- 50자 이내 권장.
- 끝에 마침표(.) 찍지 않음.
- 과거형 말고 명령조(Imperative mood) 사용 (Fixed X -> Fix O).
2.1.2 Body (선택, 하지만 권장): 구체적인 설명.
- Rule 1: 반드시 Header와 빈 줄로 분리되어야 함. 안 그러면 Git 로그 툴(Sourcetree 등)에서 제목이랑 본문이 한 줄로 떡져서 보임.
- Rule 2: 내용은 “What(무엇을 했나)”이 아니라 “Why(왜 했나)”를 적습니다. “코드를 수정했다”는 diff 보면 앎.
- “왜 이 방식이 최선이었는지”, “이전 방식의 문제는 뭐였는지”를 적으면됨.
2.1.3 Footer 심화: 이슈 자동 종료 (Magic Words)
동작 원리: GitHub은 특정 키워드(Magic Word) 뒤에 이슈 번호가 오면, 해당 커밋이 default branch(보통 main)에 머지될 때 이슈를 자동으로 닫음(Closed).
- Magic Words (대소문자 구분 없음):
- Close, Closes, Closed
- Fix, Fixes, Fixed
- Resolve, Resolves, Resolved
- 작성 위치: Body 끝나고 한 줄 띄우고 맨 마지막(Footer)
예시
Closes #123(가장 일반적)Fixes #123, #124(여러 개 닫을 때)Resolves my-org/other-repo#99(다른 저장소 이슈 닫을 때)
주의사항
- 그냥 #123이라고만 쓰면? -> 이슈에 링크는 걸리지만, 자동으로 닫히지는 않음. (단순 참조용)
2.2 주요 Types (Strick Rules)
| Type | 의미 | Semantic Versioning 대응 |
|---|---|---|
| feat | 새로운 기능 추가 | Minor ( 1.5.5 -> 1.6.5 ) |
| fix | 버그 수정 | Patch ( 1.0.2 -> 1.0.3 ) |
| docs | 문서 수정 (README, 주석) | 버전 영향 없음 |
| style | 코드 포맷팅 (로직 변경 없음) | 버전 영향 없음 |
| refactor | 리팩토링 (기능 변경 없음) | 버전 영향 없음 |
| test | 테스트 코드 추가/수정 | 버전 영향 없음 |
| chore | 빌드 설정, 패키지 매니저 등 | 버전 영향 없음 |
3. Semantic Versioning (버전이 만들어지는 원리)
롤 패치 노트, ComfyUI 보면 릴리즈 노트 하면서 버전을 깔쌈하게 정리하면서 올리는데 나는이게 사람이 하는건줄 알았음. 근데 이게 규칙/원칙대로 하는거였음;
3.1 Semantic Versioning (SemVer)
소프트웨어 버전은 vMajor.Minor.Patch (예: v1.4.2) 형태를 띠며, 각각은 의미를 가짐.
Major(주 버전): 하위 호환성이 깨짐. (기존 코드가 이 버전을 쓰면 에러 날 수 있음)- 발동 조건: 커밋 Footer에
BREAKING CHANGE가 있을 때.
- 발동 조건: 커밋 Footer에
Minor(부 버전): 새로운 기능 추가, 하위 호환성 유지.- 발동 조건: 커밋 Type이
feat일 때.
- 발동 조건: 커밋 Type이
Patch(수패치 버전): 버그 수정, 하위 호환성 유지.- 발동 조건: 커밋 Type이
fix일 때.
- 발동 조건: 커밋 Type이
개발자들 진짜 씹새끼들인듯. 존나 꼼꼼함ㅇㅇ.
3.1.1 Breaking Change (Major 버전을 부르는 주문)
feat, fix 는 자주봐서 알겠는데 Major 올리는 얘는 처음봄.
이건 기존 사용자가(pull 받은 팀원, clone 뜬 사람들) 내 코드를 업데이트했을 때 에러가 나는 경우(하위 호환성 파괴)에만 씀.
case 1. Forward Return Type 변경 (Tensor -> Dict)
model(x)의 반환값이 torch.Tensor였는데, auxiliary loss를 추가한답시고 {"logits": tensor, "loss": ...} 형태의 Dict나 Object로 바꾼 경우.
-> 기존의 loss = criterion(model(x), target) 코드는 전부 AttributeError로 터짐.
refactor(model)!: return OutputClass instead of raw tensor in forward
<-- 빈 줄 -->
To support auxiliary losses in the diffusion process, the forward method now returns a
`DiffusionOutput` dataclass containing `sample` and `aux_loss`.
<-- 빈 줄 -->
BREAKING CHANGE: The `forward` method no longer returns a `torch.Tensor`.
Access the sample via `output.sample`.
Example: `pred = model(x)` -> `pred = model(x).sample`
case 2. Input Shape Format 강제 변경 (BCHW -> BHWC)
메모리 접근 패턴 최적화(Channels Last)를 위해 입력 텐서 포맷을 강제한 경우.
-> 기존 [B, C, H, W] 데이터를 넣던 추론 코드는 전부 RuntimeError: shape mismatch로 터짐.
perf(attention)!: enforce BHWC input layout for flash-attention
<-- 빈 줄 -->
Switching to FlashAttention v2 requires inputs to be in channel-last format.
This avoids permute operations inside the attention block, reducing latency by 15%.
<-- 빈 줄 -->
BREAKING CHANGE: `forward(x)` now expects input shape `[Batch, Height, Width, Channel]`.
Passing `[Batch, Channel, Height, Width]` will raise a RuntimeError.
!를 붙여서 “이거 호환성 깨먹는 위험한 짓이다”라고 경고해주자.
그리고 Footer에 BREAKING CHANGE:를 대문자로 박아야 Major 버전이 올라가짐.
3.2 버전 관리는 어디서 어떻게 보노?
보통 pyproject.toml (최신 표준), setup.py (레거시), 또는 __init__.py 안에 __version__ = "1.0.0" 변수로 관리
오픈소스 애플리케이션 (ComfyUI 등) 명시적인 패키징 파일보다는 Git Tag 그 자체를 버전으로 쓰는 경우도 있음.
3.2.1 Git Tag의 실체
git tag v1.0.0을 쳐도, 내 눈앞의 파일 탐색기에는 아무 변화는 없음- 태그는
.git/refs/tags/v1.0.0경로에 저장된 40자리 해시값(Pointer)으로 관리됨. - 어디서 봄?
- 터미널:
git tag -lorgit show v1.0.0 - Github 웹: 우측 사이드바의 “Releases” or “Tags” 섹션
- 터미널:

정리 진짜 존ㄴ나 깔끔함. 옆에 붙은 #11212은 PR임.
3.2.2 자동화
git tag 명령어만으로는 파일(pyproject.toml) 안의 숫자가 안바뀜.

이렇게 Bump 어쩌고 되어있는게 pyproject.toml 을 자동으로 관리하는거임.
- 작동 매커니즘 (The Workflow)
- 내가
feat: add new scheduler커밋을 Push 했음. - GitHub Actions(Bot): 코드를 쓱 스캔 -> “어? feat 있네? 버전 올려드림”
- GitHub Actions(Bot):
pyproject.toml파일의version = "1.0.0"을version = "1.1.0"으로 직접 수정 - GitHub Actions(Bot):
chore(release):bump version to 1.1.0이라는 제목으로 새로운 PR을 생성(또는 커밋).
- 내가
- 내가 할 일
BumpPR을 보고 “음, 버전 잘 올라갔네. CHANGELOG도 잘 했군쓰” 하고Merge버튼만 누르면 됨.
- 주의사항
- 이 기능은 GitHub에 기본 내장이 아님
Release Drafter나Changesets,Standard Version같은 GitHub Action 워크플로우 파일(.github/workflows/release.yml) 을 레포지토리에 심어놔야 작동함.
3.3 버전 생성 자동화 매커니즘 (How it works)
semantic-release나 standard-version 같은 CI 도구가 이 과정을 수행해준다.
- 스캔: 도구가 마지막 버전(v1.0.0) 이후에 쌓인 커밋 로그들을 전부 체크.
- 계산: 커밋 헤더 파싱.
fix: … 가 있다 ->Patch +1feat: … 가 있다 ->Minor +1(Patch는 0으로 초기화)feat!: … 또는BREAKING CHANGE->Major +1(Minor,Patch0으로 초기화)
- 적용 (Tagging):
- 계산 결과가
v1.1.0이라면, 도구는 package.json 등의 버전 명시 파일 숫자를 바꿈. - 핵심:
git tag v1.1.0명령어를 실행하여 해당 커밋 해시(Hash)에 꼬리표(Tag)를 달아줌.
- 계산 결과가
4. 실전 예제 (Do & Don’t)
❌ Bad Case (내가 하는 짓)
Commit 1: 모델 수정 (빌드 안됨)
Commit 2: 아 시발 섹스 안됨
Commit 3: fix bug
Commit 4: ㅋㅋㅋ 이제 됨
✅ Good Case (Conventional + Atomic)
feat(model): add cross-attention to unet bottleneck
<-- (여기에 빈 줄 필수) -->
Integrated standard CrossAttention mechanism into the bottleneck layer of UNet2DModel.
This improves conditioning on text embeddings.
<-- (여기에 빈 줄 필수) -->
Resolves: #102
fix(train): resolve nan loss in mixed precision training
<-- (여기에 빈 줄 필수) -->
Gradient scaling was not properly applied before the optimizer step in fp16 mode.
Added `scaler.unscale_(optimizer)` to prevent underflow.
<-- (여기에 빈 줄 필수) -->
Closes #45, Fixes #48
진짜 이거 본 것 만으로 일단 3만원짜리 강의 꽁으로 들은거다ㅇㅇ
이제 다음편은 PR을 통해 남코드 훈수 + 내코드 피드백을 받아보도록하다.