Series 09 — Git 생존기 · 01

commit, push,
기도

Git 명령어 생존 6종 세트부터 GitHub Flow까지. 233만 한국 개발자 중 52%가 매달 Git으로 고생한다 — 당신이 알아야 할 최소한의 것.

Part I

일단 이것만 알면 죽지는 않는다

Git을 처음 마주한 주니어 개발자의 일과는 대략 이렇다. git add를 치고, git commit을 치고, git push를 치고, 그리고 기도한다. 충돌이 나지 않기를. 시니어가 코드 리뷰에서 "이 커밋 왜 이래?"라고 묻지 않기를. Stack Overflow 2024 Developer Survey에 따르면 전 세계 개발자의 93.6%가 Git을 사용한다. 그 중 상당수가 아직도 기도에 의존한다.

2024년 Hutte의 Git 협업 리서치에서 더 구체적인 수치가 나왔다. 개발자의 52%가 매달 Git 관련 문제에 부딪히고, 평균적으로 주당 3.4시간을 머지 충돌, 브랜치 혼란, 되돌리기에 소비한다. 대한민국 소프트웨어 개발자 추정 수가 233만 명이라면, 매달 약 120만 명이 Git 때문에 고통받고 있다는 계산이 된다.

93.6% 전 세계 개발자의
Git 사용률
52% 매달 Git 관련 문제에
부딪히는 비율
3.4h 주당 머지 충돌/브랜치
문제 해결 시간

문제의 핵심은 Git이 어렵다는 게 아니다. 제대로 배울 기회가 없다는 것이다. 대학에서 가르치지 않고, 부트캠프에서는 "일단 push 하세요"라고만 말하고, 회사에서는 "알아서 익히세요"가 온보딩의 전부다. Stack Overflow에서 2번째로 많이 투표된 질문이 "How do I undo the most recent local commits in Git?" — 커밋을 되돌리는 법이라는 사실이 모든 것을 말해준다. 262만 표. Git의 되돌리기를 262만 명이 검색한 것이다.

생존 6종 세트

Git에는 170개가 넘는 명령어가 있다. 하지만 실무에서 매일 쓰는 것은 6개다. 이 6개만 확실하게 이해하면 죽지는 않는다. 나머지는 필요할 때 배우면 된다.

생존 6종 세트 git init # 현재 폴더를 Git 저장소로 만든다. 프로젝트 시작점. git clone # 원격 저장소를 내 컴퓨터로 복제한다. git clone <url> git add # 변경 사항을 스테이징(대기열)에 올린다. git commit # 스테이징된 변경 사항을 히스토리에 기록한다. git push # 로컬 커밋을 원격 저장소로 보낸다. git pull # 원격 저장소의 변경 사항을 가져와 합친다.

이 6개의 관계를 한 문장으로 정리하면 이렇다. init이나 clone으로 시작하고, add로 준비하고, commit으로 기록하고, push로 보내고, pull로 받는다. 이것이 Git의 전부는 아니지만, 주니어 1년 차가 아침에 출근해서 퇴근할 때까지 쓰는 명령어의 90%를 차지한다.

Command 01

git add

변경 파일을 스테이징 영역에 올린다. 커밋에 포함할 파일을 "선택"하는 과정이다. git add .은 모든 파일을 올리고, git add -p는 파일 내부에서 원하는 부분만 골라 올린다.

Command 02

git commit

스테이징된 변경 사항을 영구 기록한다. 40자리 SHA-1 해시로 식별되는 스냅샷이 생성된다. 메시지 없는 커밋은 없다. -m "메시지"는 필수다.

Command 03

git push

로컬의 커밋을 원격 저장소(GitHub, GitLab)로 전송한다. push 전까지 모든 작업은 내 컴퓨터에만 존재한다. push 하는 순간 팀 전체에 공개된다.

git status를 습관처럼 쳐야 하는 이유

생존 6종 세트에 포함되지 않았지만, 사실상 가장 자주 쳐야 하는 명령어가 있다. git status다. 이 명령어는 아무것도 변경하지 않는다. 현재 상태를 보여줄 뿐이다. 어떤 파일이 수정되었는지, 어떤 파일이 스테이징되었는지, 어떤 파일이 추적되지 않는지. 실수의 80%는 현재 상태를 모르는 데서 시작한다.

git status 출력 예시 On branch feature/login Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: src/auth/login.js ← 스테이징 완료. 커밋하면 포함된다 Changes not staged for commit: (use "git add <file>..." to update what will be committed) modified: src/auth/signup.js ← 수정했지만 아직 add 안 한 상태 Untracked files: (use "git add <file>..." to include in what will be committed) src/auth/reset-password.js ← Git이 아직 모르는 새 파일

프로 개발자와 아마추어의 차이는 여기서 갈린다. 프로는 커밋 전에 반드시 git status를 확인하고, git diff로 변경 내용을 눈으로 본다. 아마추어는 git add .으로 전부 때려넣고 push한 다음, PR에서 .env 파일이 올라간 것을 발견한다.

git add -p: 수술칼로 커밋하기

하나의 파일에서 버그 수정과 리팩토링을 동시에 했다고 가정하자. git add 파일명을 하면 두 변경 사항이 하나의 커밋에 묶인다. 나중에 버그 수정만 되돌리고 싶을 때 리팩토링까지 같이 날아간다. git add -p는 이 문제를 해결한다. 파일 내부의 변경 사항을 hunk 단위로 쪼개서, 하나씩 스테이징 여부를 선택할 수 있다.

git add -p 실행 흐름 $ git add -p src/auth/login.js diff --git a/src/auth/login.js b/src/auth/login.js @@ -12,7 +12,7 @@ function validateEmail(email) { - return email.includes('@') + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ← 버그 수정 (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,s,e,?]? y @@ -28,6 +28,7 @@ function handleLogin(credentials) { + const sanitized = sanitizeInput(credentials) ← 리팩토링 (2/2) Stage this hunk [y,n,q,a,d,j,J,g,/,s,e,?]? n

y는 이 변경을 스테이징, n은 건너뛰기, s는 더 작은 단위로 분할. 이 습관 하나가 커밋 히스토리의 품질을 결정한다. "버그 수정"과 "리팩토링"이 분리된 커밋은, 6개월 뒤 코드를 디버깅할 미래의 동료(혹은 미래의 나)에게 선물이 된다.

Stack Overflow에서 2번째로 많이 투표된 질문:
"커밋을 되돌리는 법" — 262만 표.

Stack Overflow, 2024
Part II

브랜치는 무서운 게 아니다

브랜치(branch)는 Git에서 가장 강력하면서 가장 오해받는 개념이다. 많은 주니어가 브랜치를 "뭔가 잘못 건드리면 코드가 날아가는 것"으로 인식한다. 사실은 정반대다. 브랜치는 코드를 보호하기 위해 존재한다. main 브랜치에서 직접 작업하는 것이야말로 코드를 위험에 노출시키는 행위다.

브랜치의 본질은 포인터다. 커밋 히스토리의 특정 지점을 가리키는 이름표에 불과하다. 새 브랜치를 만들어도 파일이 복사되지 않는다. 41바이트짜리 포인터 파일 하나가 생성될 뿐이다. 그래서 Git의 브랜치 생성은 거의 0초가 걸린다. SVN에서 브랜치를 만들면 전체 소스를 복사했던 것과는 근본적으로 다르다.

브랜치 기본 명령어 git branch # 현재 브랜치 목록 확인 git branch feature/login # 새 브랜치 생성 (이동하지 않음) git switch feature/login # 해당 브랜치로 이동 git switch -c feature/login # 생성 + 이동을 한 번에 git merge feature/login # 현재 브랜치에 feature/login을 합침 git branch -d feature/login # 머지 완료된 브랜치 삭제
git switch vs git checkout

2019년 Git 2.23 버전에서 git checkout이 두 개의 명령어로 분리되었다. git switch(브랜치 이동)와 git restore(파일 복원). 이유는 단순하다. checkout이 너무 많은 일을 했기 때문이다. 브랜치 이동, 파일 복원, 커밋 체크아웃 — 하나의 명령어가 세 가지 역할을 맡으면 실수가 발생한다. git checkout -- .을 잘못 쳐서 작업 중인 파일이 전부 날아간 경험, 주니어라면 한 번쯤 있을 것이다.

Before — Git 2.22

git checkout

git checkout feature — 브랜치 이동
git checkout -- file.js — 파일 복원
git checkout abc123 — 커밋 체크아웃

하나의 명령어, 세 가지 역할. 혼란의 원천.

After — Git 2.23+

git switch + git restore

git switch feature — 브랜치 이동
git restore file.js — 파일 복원
git switch --detach abc123 — 커밋

역할별 분리. 실수할 여지가 줄어든다.

시나리오: main에서 직접 개발하다 충돌 지옥에 빠진 신입

입사 첫 주. 김 주니어는 main 브랜치에서 직접 로그인 기능을 개발한다. "브랜치 만드는 거 귀찮은데, 나만 작업하니까 괜찮겠지." 3일 후 push를 시도한다. 에러. 다른 팀원 3명이 같은 기간 동안 main에 20개의 커밋을 올려놨다. git pull을 치니 충돌(conflict)이 12개 파일에서 터진다.

브랜치를 만들었다면 이 상황은 발생하지 않았다. git switch -c feature/login으로 독립된 공간에서 작업하고, 완료 후 PR을 통해 머지했다면, 충돌이 발생해도 내 브랜치 안에서 해결할 수 있었다. main은 항상 깨끗하게 유지되고, 충돌 범위도 최소화된다.

브랜치 워크플로우의 핵심 원칙은 하나다. main에서 직접 작업하지 않는다. 기능이든, 버그 수정이든, 문서 수정이든 — 반드시 브랜치를 만들고, 브랜치에서 작업하고, PR(Pull Request)을 통해 머지한다. 이것만 지켜도 Git 관련 사고의 절반이 사라진다.

브랜치 워크플로우 — 기본 패턴 # 1. main에서 최신 상태로 시작 git switch main git pull origin main # 2. 작업용 브랜치 생성 git switch -c feature/user-profile # 3. 작업, 커밋 (반복) git add src/profile.js git commit -m "feat: add user profile page" # 4. 원격에 push git push -u origin feature/user-profile # 5. GitHub에서 PR 생성 → 코드 리뷰 → 머지 # 6. 로컬 정리 git switch main git pull origin main git branch -d feature/user-profile
Part III

커밋 메시지, 미래의 나에게 보내는 편지

커밋 메시지는 코드의 "왜"를 기록하는 유일한 수단이다. 코드는 "무엇"과 "어떻게"를 보여주지만, "왜 이렇게 바꿨는지"는 보여주지 못한다. 6개월 뒤, "이 코드 왜 이렇게 짰지?"라는 질문에 답할 수 있는 건 커밋 메시지뿐이다. Git 로그는 팀의 공유 기억(shared memory)이다. 기억이 부정확하면 팀 전체가 같은 실수를 반복한다.

Conventional Commits

Conventional Commits는 커밋 메시지에 일관된 구조를 부여하는 규약이다. Angular 팀에서 시작되어, 현재 대부분의 오픈소스 프로젝트와 기업에서 채택하고 있다. 구조는 단순하다: type: description.

Conventional Commits — 타입 feat: 새 기능 추가. MINOR 버전 증가에 해당. fix: 버그 수정. PATCH 버전 증가에 해당. docs: 문서 변경. README, 주석, JSDoc 등. refactor: 기능 변경 없이 코드 구조 개선. test: 테스트 추가 또는 수정. chore: 빌드, CI, 의존성 등 유지보수 작업. style: 코드 포맷팅. 세미콜론, 공백 등. perf: 성능 개선.

타입 뒤에 선택적으로 스코프를 추가할 수 있다. feat(auth): add OAuth2 login처럼 괄호 안에 변경 영역을 명시한다. 이렇게 하면 git log --oneline만 봐도 어떤 모듈에서 무슨 변경이 있었는지 한눈에 파악된다. 자동화 도구(semantic-release, standard-version)가 이 규약을 읽어서 자동으로 버전 넘버를 올리고 CHANGELOG를 생성한다.

Before / After
Bad fix bug updated stuff WIP asdf login 수정 여러 가지 변경
Good fix(auth): prevent duplicate login sessions feat(cart): add quantity validation on checkout docs(api): update rate limit documentation refactor(db): extract query builder from repository fix(payment): handle timeout in PG callback test(auth): add edge cases for token refresh

왼쪽의 "fix bug"는 6개월 뒤 아무런 정보도 제공하지 못한다. 어떤 버그인지, 어디에서 발생한 건지, 왜 이렇게 고쳤는지 — 모든 것이 불명확하다. 오른쪽의 fix(auth): prevent duplicate login sessions는 인증 모듈에서 중복 로그인 세션 버그를 방지했다는 사실을 명확하게 전달한다. 커밋 메시지는 미래의 나에게 보내는 편지다. 구체적이지 않은 편지는 읽을 가치가 없다.

git commit --amend: 마지막 기회

커밋을 하고 나서 오타를 발견했거나, 파일 하나를 빠뜨렸을 때. git commit --amend가 마지막 커밋을 수정해준다. 메시지만 바꿀 수도 있고, 빠뜨린 파일을 추가할 수도 있다.

git commit --amend 사용법 # 메시지만 수정 git commit --amend -m "fix(auth): prevent duplicate sessions" # 파일을 빠뜨렸을 때 git add forgotten-file.js git commit --amend --no-edit # 메시지는 그대로, 파일만 추가
--amend 사용 시 주의사항
  1. push 전에만 사용한다. --amend는 기존 커밋을 삭제하고 새 커밋을 만든다. 이미 push한 커밋을 amend하면 히스토리 불일치가 발생하고, force push가 필요해진다
  2. force push는 팀의 히스토리를 망가뜨린다. 공유 브랜치(main, develop)에서 force push는 다른 팀원의 로컬 히스토리와 충돌을 일으킨다. 최악의 경우 다른 사람의 커밋이 사라진다
  3. 이미 push한 커밋의 실수는 새 커밋으로 수정한다. git commit --amend 대신 git commit -m "fix: correct typo in auth module"로 새로 커밋하는 것이 안전하다

"어떤 바보가 이 코드를 이렇게 짰지?" — git blame.
그 바보는 3개월 전의 나다.

Part IV

이 워크플로우 하나로 시작하라

Git 워크플로우에는 여러 가지가 있다. Git Flow, GitHub Flow, GitLab Flow, Trunk-based Development. 주니어에게 가장 먼저 추천하는 것은 GitHub Flow다. 이유는 단순하다. 규칙이 적고, 브랜치 전략이 직관적이고, PR 기반이라 코드 리뷰가 자연스럽게 따라온다.

GitHub Flow — 전체 흐름

GitHub Flow의 규칙은 단 6개다. main은 항상 배포 가능 상태. 기능 개발은 main에서 브랜치를 만들어서. 로컬에서 커밋하고 원격에 push. PR을 열어서 코드 리뷰를 받는다. 리뷰가 승인되면 main에 머지. 머지하면 즉시 배포. 끝이다.

01 Branch main에서
브랜치 생성
02 Commit 기능 개발
커밋 반복
03 PR Pull Request
코드 리뷰
04 Merge main 머지
배포

한국의 주요 테크 기업들이 GitHub Flow를 기반으로 운영한다. 토스는 Trunk-based Development에 가깝지만 PR 리뷰를 필수로 하고, 당근은 GitHub Flow를 기본으로 피처 플래그를 결합한다. 배달의민족은 Git Flow에서 GitHub Flow로 전환한 이력이 있다. 공통점은 하나다. main 브랜치를 보호한다. 직접 push를 막고, PR을 통해서만 코드가 합쳐진다.

PR 작성법 — What / Why / How to test

PR(Pull Request)은 코드 변경의 맥락을 전달하는 문서다. 코드만 보면 "무엇"이 바뀌었는지 알 수 있지만, "왜" 바꿨는지, "어떻게 테스트했는지"는 알 수 없다. 좋은 PR은 세 가지 질문에 답한다.

PR Template

What (무엇을 변경했는가)

사용자 프로필 페이지에 프로필 이미지 업로드 기능을 추가했다. AWS S3에 직접 업로드하는 presigned URL 방식을 사용한다. 이미지 리사이징은 클라이언트에서 처리한다.

Why (왜 이 변경이 필요한가)

사용자 피드백에서 프로필 이미지 기능 요청이 월 평균 47건으로 2위를 차지했다. 현재 기본 아바타만 제공되어 사용자 식별이 어렵다는 CS도 지속적으로 접수되었다.

How to test (어떻게 확인하는가)

1. /profile 페이지에서 "이미지 변경" 버튼 클릭
2. 5MB 이하의 JPG/PNG 파일 선택
3. 크롭 영역 조정 후 "저장" 클릭
4. 프로필 이미지가 변경되었는지 확인
5. 새로고침 후에도 이미지가 유지되는지 확인

Google의 연구에 따르면, 코드 리뷰의 품질은 변경 사항이 200줄 이하일 때 최고점에 도달한다. 200줄을 넘어가면 리뷰어의 집중력이 떨어지고, 결함 발견율이 급격히 하락한다. PR을 작게 유지하는 것이 품질의 핵심이다. 하나의 PR에 하나의 목적만 담는다. "로그인 기능 추가 + 버그 3개 수정 + 리팩토링"을 하나의 PR에 넣으면 리뷰가 불가능해진다.

Rule 01

200줄 이하

Google 연구에 따르면 변경 200줄 이하에서 리뷰 품질이 최고점. 그 이상이면 결함 발견율이 급격히 하락한다. 큰 기능은 여러 PR로 분할한다.

Rule 02

하나의 PR, 하나의 목적

"feat + fix + refactor"를 하나의 PR에 넣으면 리뷰가 불가능하다. 기능, 버그 수정, 리팩토링은 반드시 분리한다.

Rule 03

셀프 리뷰 먼저

PR을 올리기 전에 직접 코드를 한 번 더 읽는다. console.log, TODO 주석, 하드코딩된 값, 불필요한 파일 — 셀프 리뷰에서 잡히는 것들이다.

브랜치 네이밍 컨벤션

브랜치 이름에도 규칙이 있다. 팀마다 다르지만, 가장 보편적인 패턴은 type/description 형식이다. 커밋 메시지의 Conventional Commits와 같은 원리다.

브랜치 네이밍 예시 feature/user-profile # 새 기능 개발 fix/duplicate-login # 버그 수정 hotfix/payment-timeout # 긴급 수정 (프로덕션) refactor/extract-auth-module # 리팩토링 docs/update-api-readme # 문서 수정 chore/upgrade-dependencies # 유지보수 작업

Jira나 Linear 같은 이슈 트래커를 쓴다면, 브랜치 이름에 이슈 번호를 포함하는 것이 좋다. feature/PROJ-123-user-profile처럼. 이렇게 하면 브랜치에서 이슈로, 이슈에서 브랜치로 양방향 추적이 가능하다. 6개월 뒤 "이 코드 왜 이렇게 됐지?"라는 질문에 이슈 번호가 답을 준다.

절대 하지 말아야 할 것
  1. main/develop에 force push 금지. git push --force는 원격 히스토리를 덮어쓴다. 다른 팀원의 작업이 사라질 수 있다. 개인 브랜치에서만 허용
  2. 공유 브랜치에서 rebase 금지. rebase는 커밋 히스토리를 재작성한다. 이미 다른 사람이 pull한 커밋을 rebase하면 히스토리 충돌이 발생한다
  3. git reset --hard를 이해 없이 사용 금지. 이 명령어는 커밋과 작업 내용을 모두 삭제한다. 복구가 극히 어렵다. 대신 git revert로 되돌리는 커밋을 새로 만드는 것이 안전하다
  4. 비밀 정보를 커밋하지 않는다. .env, API 키, 패스워드, 인증서. 한 번 push하면 히스토리에 영원히 남는다. .gitignore를 먼저 설정한다

코드 리뷰의 품질은
변경 200줄 이하에서 최고점에 도달한다.

Google Engineering Practices

Git은 외울 필요가 없다
이해하면 된다

170개의 명령어를 암기하는 것은 불가능하다. 6개의 핵심 명령어가 무엇을 하는지 이해하고, 브랜치가 포인터라는 사실을 알고, 커밋 메시지에 "왜"를 기록하는 습관을 들이면 된다. 나머지는 필요할 때 검색하면 된다.