의도적인 커밋: 좋은 커밋의 예술

발행: (2026년 3월 15일 PM 05:26 GMT+9)
14 분 소요
원문: Dev.to

Source: Dev.to

번역할 텍스트를 제공해 주시면 한국어로 번역해 드리겠습니다.

Part 2 of the Git Mastery Series

Part 1: How Git Actually Thinks | Part 3: Branching Without Fear →

The Problem

Six months into a project, you’re hunting a bug. You run git log and the history looks like this:

a3f8c9d fix
1b4e7a2 update
c5d2f8a wip
9d3e1f4 asdf
7a2b5c8 final
4f1e8d6 final2
2c9a3b7 ok now it works

Who wrote this? You. And now you have no idea what any of these commits contain without opening each one individually.

Compare that to this:

a3f8c9d fix(auth): handle expired JWT tokens on refresh
1b4e7a2 feat(cart): add quantity update on product page
c5d2f8a refactor(api): extract payment service into dedicated class
9d3e1f4 fix(checkout): prevent duplicate order on double‑click submit

Same code. Same history. The second version is documentation. The first is noise.

커밋은 단순히 저장 지점이 아니라, 이 코드를 읽는 다음 사람에게 보내는 메시지입니다 — 거의 항상 미래의 당신에게요.

Atomic Commits

“Atomic commit”(원자 커밋)이라는 용어는 설명 없이 떠돌아다니는 경우가 많습니다. 실제로는 하나의 커밋, 하나의 존재 이유를 의미합니다.

  • 하나의 파일만을 위한 것이 아닙니다.
  • 하나의 함수만을 위한 것이 아닙니다.
  • 하나의 이유만을 위한 것입니다.

리팩터와 버그 수정은 두 가지 이유이며, 같은 파일을 건드리더라도 별개의 커밋이어야 합니다. 기능 그 테스트는 하나의 이유입니다 — 테스트는 기능의 일부이기 때문이죠. 의존성을 추가하고 사용하는 것도 하나의 이유입니다 — 의존성이 없으면 사용도 불가능합니다.

Test for atomicity

“그리고(and)”라는 단어를 쓰지 않고, 이 커밋이 무엇을 하는지 한 문장으로 설명할 수 있나요?

✅ Good❌ Bad
“Fix the null pointer exception on empty cart checkout”“Fix cart bug and update user model and add some tests”
“Add email validation to the registration form”

두 번째 유형은 설명하기 어려울 뿐만 아니라, 되돌리기, 체리‑픽, 그리고 코드 리뷰에서 이해하기도 더 어렵습니다. 커밋 설명에 “and”가 들어간다면 그 커밋은 분리되어야 한다는 신호입니다.

The Staging Area

대부분의 개발자는 git add . 로 모든 파일을 한 번에 스테이징하고, 스테이징 영역에 대해 생각하지 않습니다. 이는 이메일을 한 번에 전체 작성하는 것과 비슷합니다 — 동작은 하지만, 도구가 설계된 대로 사용하고 있는 것은 아닙니다.

스테이징 영역은 작업 디렉터리와 무관하게 다음 커밋에 포함하고 싶은 내용만 정확히 구성할 수 있게 해줍니다. 예를 들어 다섯 파일에 걸쳐 세 가지 서로 다른 변경이 있고, 이를 각각 별도 커밋으로 만들고 싶을 때 스테이징 영역을 활용합니다.

Stage specific files

git add src/auth/login.php
git add src/auth/logout.php
git commit -m "feat(auth): add login and logout handlers"

git add src/user/profile.php
git commit -m "feat(user): add profile page"

Stage specific lines within a file

git add -p src/user/model.php

이 명령은 파일의 각 변경을 차례로 보여주며, 어떻게 처리할지 물어보는 인터랙티브 프롬프트를 엽니다:

@@ -15,6 +15,10 @@ class User extends Model
     protected $fillable = ['name', 'email'];
+
+    public function orders() {
+        return $this->hasMany(Order::class);
+    }
+
     public function profile() {
Stage this hunk [y,n,q,a,d,/,e,?]?
  • y – 해당 청크를 스테이징
  • n – 건너뛰기
  • e – 스테이징하고 싶은 정확한 라인 편집

git add -p 를 꾸준히 사용하면, 원래는 별개의 커밋이어야 할 세 가지가 섞인 커밋을 만들게 되는 상황을 방지할 수 있습니다. 이제 코드를 짜면서 커밋을 생각하게 되고, 작업이 끝난 뒤가 아니라 코딩 중에 커밋을 설계하게 됩니다.

Conventional Commits

다음과 같은 간단한 형식입니다:

type(scope): description

Optional longer body explaining what and why,
not how (the code shows how).

Optional footer: Closes #123

주요 타입: feat, fix, refactor, docs, test, chore, perf.

이를 강제하는 도구가 필요하지 않습니다; 습관만 있으면 됩니다. 이 방식을 사용하면 얻을 수 있는 장점:

  • 스캔 가능한 히스토리feat vs fix vs refactorgit log 를 읽을 때 즉시 컨텍스트를 파악할 수 있습니다.
  • 자동화된 체인지로그sema와 같은 도구가 커밋 메시지를 기반으로 변경 로그를 자동 생성합니다.

(이하 내용은 다음 파트에서 계속됩니다.)

Source:

ntic‑release*와 conventional‑changelog은 커밋 메시지를 파싱해 릴리즈 노트를 자동으로 생성합니다.

  • 코드 리뷰에서 의도 명확화feat(payment): add Razorpay integration, test(payment): add unit tests for webhook handler, docs(payment): add setup guide와 같은 커밋이 포함된 PR은 리뷰어에게 각 커밋에서 기대할 수 있는 내용을 정확히 알려줍니다.

실제 예시

git commit -m "feat(auth): add OTP login via Fast2SMS"
git commit -m "fix(cart): prevent negative quantity on decrement"
git commit -m "refactor(api): extract HTTP client into service layer"
git commit -m "chore(deps): upgrade Laravel from 10 to 11"
git commit -m "perf(images): lazy load product images on listing page"

이 커밋들이 복잡해서 인상적인 것은 아닙니다. 명확함 때문에 인상적인 것이죠. 새로 팀에 합류한 개발자는 3개월 치 히스토리를 읽고 제품이 무엇을 하고 있었는지 이해할 수 있습니다.

Subject vs. Body

Subject 라인 (-m 부분)은 무엇을 다루고 있습니다.
Body를 다룹니다.

대부분의 커밋은 Body가 필요하지 않습니다. 하지만 상황에 따라 컨텍스트가 중요할 때—결정이 명확하지 않을 때, 직관에 반하는 수정을 할 때, 미래의 자신이 컨텍스트가 필요할 때—그 설명은 Body에 적어야 합니다.

에디터 사용하기

git commit

이 명령은 에디터를 엽니다. Subject를 쓰고, 빈 줄을 하나 둔 뒤 Body를 작성합니다:

fix(payment): retry failed Razorpay webhooks on 5xx errors

Razorpay occasionally returns 503 on their webhook endpoint during
high load. Without retry logic, missed webhooks left orders stuck
in "pending" state with no automated resolution path.

Added exponential backoff (3 retries, 2s/4s/8s delays). If all
retries fail, the webhook is queued for manual review.

Closes #247

6개월 뒤, 이 코드를 디버깅하는 사람은 위 내용을 읽고 무엇이 바뀌었는지뿐만 아니라 왜 바뀌었는지도 이해하게 됩니다. 그 사람은 거의 확실히 여러분일 것입니다.

경험 법칙: 코드 리뷰에서 이 변경 사항을 설명해야 한다면, 그 설명을 커밋 Body에 적으세요. 미래의 개발자는 리뷰어가 가졌던 동일한 컨텍스트를 가질 자격이 있습니다.

흔한 상황

하루 동안 기능을 작업하면서 커밋이 다음과 같이 되어 버렸다고 가정해 보세요:

wip: halfway through auth refactor
fix typo
add

위와 같이 대신, 앞서 소개한 기법을 사용해 원자적이고 잘 설명된 커밋으로 나누세요. 히스토리는 더 깔끔해지고, 리뷰는 원활해지며, 미래의 자신도 고마워할 것입니다.

병합하기 전에 커밋 정리하기

“커밋은 백업이 아니다. 그것은 메시지이다.”

아래는 인터랙티브 리베이스를 사용해 진행 중인 히스토리를 정리하는 단계별 가이드입니다. 구조와 내용은 그대로 두고 읽기 쉽게 번역했습니다.

1️⃣ 정리할 범위 식별

# Rebase the last 5 commits (adjust the number as needed)
git rebase -i HEAD~5

이렇게 하면 마지막 다섯 커밋을 보여주는 편집기가 열립니다:

pick a3f8c9d wip: halfway through auth refactor
pick 1b4e7a2 fix typo
pick c5d2f8a add missing return statement
pick 9d3e1f4 actually fix the auth refactor
pick 7a2b5c8 remove debug logs

2️⃣ 리베이스 todo 리스트 편집

최종 히스토리를 만들기 위해 명령을 변경합니다:

reword a3f8c9d wip: halfway through auth refactor
squash 1b4e7a2 fix typo
squash c5d2f8a add missing return statement
squash 9d3e1f4 actually fix the auth refactor
squash 7a2b5c8 remove debug logs
  • reword – 커밋 메시지를 편집합니다.
  • squash (또는 s) – 커밋을 이전 커밋에 합칩니다.
  • drop (또는 d) – 커밋을 완전히 제거합니다.

편집기를 저장하고 닫습니다. Git이 최종 커밋 메시지 편집 과정을 안내해 주며, 하나의 깔끔한 커밋이 만들어져 병합 준비가 됩니다.

한 가지 규칙: 아직 공유 브랜치에 푸시되지 않은 커밋만 다시 작성하세요. 다른 사람이 이미 받아간 히스토리를 다시 쓰면 충돌이 발생합니다.

3️⃣ 유용한 Git 명령

ActionCommand
특정 파일 스테이징git add path/to/file
특정 라인을 인터랙티브하게 스테이징git add -p path/to/file
스테이징되지 않은 변경사항 보기git diff
스테이징된 변경사항 보기git diff --staged
제목만으로 커밋git commit -m "feat(scope): description"
제목 + 본문으로 커밋 (편집기 열림)git commit
푸시하기 전에 마지막 N 커밋 정리git rebase -i HEAD~N
가장 최근 커밋 수정 (메시지 또는 내용)git commit --amend
마지막 커밋 되돌리되 변경사항은 스테이징 유지git reset --soft HEAD~1
마지막 커밋 되돌리고 변경사항 언스테이징 (파일은 유지)git reset HEAD~1

4️⃣ 사고방식 전환

git commit -m "fix" 를 쓸 때, 단순히 작업을 저장하는 것이 아니라, 코드베이스를 읽게 될 모든 사람에게, 2 am에 6개월 후의 당신에게도 전달되는 편지를 쓰는 것입니다. 좋은 커밋 메시지는 지금 몇 초만 더 투자하면 나중에 수많은 시간을 절약할 수 있습니다.

5️⃣ 추가 읽을거리

파트 1: [How Git Actually Thinks]
파트 3: [Branching Without Fear]

이 내용이 도움이 되었다면, 전체 시리즈를 23‑페이지 PDF 레퍼런스로 정리했습니다 – 체크리스트, 훅 템플릿, 80개 이상의 명령, reflog·bisect 심층 탐구, 그리고 12가지 실제 비상 상황에 대한 복구 플레이북 포함.

Git 마스터리 필드 가이드 →

0 조회
Back to Blog

관련 글

더 보기 »

두려움 없이 브랜칭

Git 마스터리 시리즈 파트 3 ← 파트 2: 의도적인 커밋 https://dev.to/itxshakil/committing-with-intention-the-art-of-a-good-commit-p90 | 파트 4: C...