Git 워크플로우 완벽 가이드 | 브랜치 전략부터 협업까지
이 글의 핵심
Git 브랜치 전략, 커밋 컨벤션, PR 리뷰, 충돌 해결 등 실무 Git 워크플로우를 상세히 설명합니다. Git Flow, GitHub Flow, Trunk-Based Development를 비교하고 팀 규모별 권장 전략을 제시합니다.
들어가며: 혼자 쓸 때와 팀으로 쓸 때는 완전히 다른 도구입니다
혼자 개발할 때는 커밋 이력이 개인 일기장과 다를 바 없습니다. 어떤 방식으로 커밋을 남기든, 어떤 브랜치를 만들든 문제가 되지 않습니다. 하지만 팀 단위로 작업하는 순간, main 브랜치는 여러 사람의 작업이 동시에 얽히는 공용 자원이 됩니다. 브랜치 전략이 없으면 배포 시점을 예측할 수 없고, 커밋 메시지 규칙이 없으면 이력을 추적하기 어려우며, PR 리뷰 문화가 없으면 버그가 그대로 프로덕션에 올라갑니다.
이 글에서는 실무에서 자주 쓰이는 세 가지 브랜치 전략(Git Flow, GitHub Flow, Trunk-Based Development)을 비교하고, Conventional Commits 기반의 커밋 컨벤션, PR을 작고 리뷰하기 쉽게 유지하는 방법, merge와 rebase를 언제 구분해서 써야 하는지, 그리고 머지 충돌을 예방하고 해결하는 실전 절차를 다룹니다. 참고로 이 글은 rebase 명령어 자체의 세부 옵션(--onto, --autosquash 등)이나 CI/CD 파이프라인 구축은 다루지 않습니다. 해당 내용은 각각 리베이스 전용 글과 GitHub Actions 관련 글에서 더 깊게 설명하고 있으니, 이 글은 팀 차원의 브랜치 전략과 협업 규칙에 집중합니다.
1. 브랜치 전략 비교
브랜치 전략은 “어떤 이름의 브랜치를 언제 만들고, 언제 병합하며, 언제 배포하는가”에 대한 팀의 합의입니다. 정답은 없으며, 팀 규모와 배포 주기, 자동화 수준에 따라 적합한 전략이 달라집니다.
Git Flow
Git Flow는 빈센트 드리센(Vincent Driessen)이 2010년에 제안한 모델로, main, develop, feature/*, release/*, hotfix/*라는 다섯 종류의 브랜치를 역할별로 분리해서 사용합니다. develop은 다음 릴리스를 준비하는 통합 브랜치이고, main은 항상 배포 가능한 상태를 유지합니다. 새 기능은 develop에서 feature 브랜치를 파서 작업하고, 릴리스가 임박하면 release 브랜치를 만들어 QA와 버그 수정을 진행한 뒤 main과 develop 양쪽에 병합합니다. 긴급 수정은 main에서 바로 hotfix 브랜치를 파서 처리합니다.
아래는 Git Flow에서 기능 개발부터 릴리스까지의 흐름을 나타낸 다이어그램입니다.
gitGraph
commit
branch develop
checkout develop
commit
branch feature/login
checkout feature/login
commit
commit
checkout develop
merge feature/login
branch release/1.0
checkout release/1.0
commit
checkout main
merge release/1.0 tag: "v1.0"
checkout develop
merge release/1.0
실제 명령어로 옮기면 다음과 같습니다.
# develop에서 기능 브랜치 생성
git checkout develop
git checkout -b feature/login
# ... 기능 구현 및 커밋 ...
# 완료 후 develop으로 병합
git checkout develop
git merge --no-ff feature/login
# 릴리스 준비
git checkout -b release/1.0 develop
# QA, 버그 수정 커밋 진행
# 릴리스 브랜치를 main과 develop 양쪽에 반영
git checkout main
git merge --no-ff release/1.0
git tag v1.0
git checkout develop
git merge --no-ff release/1.0
Git Flow는 릴리스 주기가 달력에 명확히 찍혀 있고, QA 프로세스가 길며, 버전 번호(v1.2.0)가 실제로 의미를 갖는 조직에 잘 맞습니다. 다만 브랜치 종류가 많고 절차가 무겁기 때문에, 하루에도 여러 번 배포하는 스타트업이 이 모델을 그대로 가져오면 develop과 main이 항상 미묘하게 어긋난 상태로 방치되는 경우가 흔합니다. 브랜치가 늘어날수록 병합 지점도 늘어나고, 그만큼 충돌 가능성도 커진다는 점을 감안해야 합니다.
GitHub Flow
GitHub Flow는 main에서 바로 기능 브랜치를 만들고, 작업이 끝나면 PR을 통해 다시 main으로 병합하는 단순한 흐름입니다. develop이나 release 같은 중간 브랜치가 없고, main에 병합되는 시점이 곧 배포 시점에 가깝습니다.
gitGraph
commit
branch feature/login
checkout feature/login
commit
commit
checkout main
merge feature/login tag: "deploy"
git checkout main
git pull origin main
git checkout -b feature/login
# ... 커밋 ...
git push origin feature/login
# GitHub에서 PR 생성 → 리뷰 → main으로 병합 → 자동 배포
이 전략이 제대로 작동하려면 main을 항상 배포 가능한 상태로 유지해야 합니다. “일단 main에 병합하고 나중에 배포하자”는 습관이 생기면 main이 반쯤 깨진 상태로 쌓이는 통합 창고가 되기 쉽습니다. 자동화된 테스트와 CI가 뒷받침되지 않는 상태에서 GitHub Flow만 도입하면, 배포 직전에야 문제를 발견하는 상황이 반복될 수 있습니다.
Trunk-Based Development
Trunk-Based Development는 브랜치의 수명을 극단적으로 짧게 유지하는 전략입니다. 기능 브랜치를 만들더라도 하루 이내, 길어도 며칠 안에 main(트렁크)으로 병합하는 것을 원칙으로 하며, 아직 완성되지 않은 기능은 피처 플래그(feature flag)로 숨겨둔 채 배포합니다. 이 방식은 팀이 이미 테스트 자동화, 피처 플래그, CI 파이프라인을 안정적으로 운영하고 있을 때 가장 효과적입니다. 반대로 팀 구성원 대부분이 Git 경험이 적거나 자동화 인프라가 갖춰지지 않은 상태에서 무리하게 도입하면, 미완성 코드가 main에 자주 섞여 들어가면서 오히려 안정성이 떨어질 수 있습니다.
전략 비교 요약
- Git Flow — 브랜치 구조가 복잡하지만, 릴리스와 핫픽스에 명확한 이름과 절차가 부여됩니다. 릴리스 주기가 길고 버전 관리가 중요한 조직에 적합합니다.
- GitHub Flow — 구조가 단순하고 배포 속도가 빠릅니다. 다만 지속적 배포(CD) 문화와 테스트 자동화가 뒷받침되지 않으면
main의 안정성을 담보하기 어렵습니다. - Trunk-Based Development — 브랜치 수명이 가장 짧아 충돌 면적이 작습니다. 대신 자동화된 테스트와 피처 플래그 없이는 운영 리스크가 큽니다.
팀 규모별 권장 전략
- 1~3명 — GitHub Flow가 대체로 적합합니다. 팀원 간 직접 소통으로 규칙을 조율할 수 있는 규모이기 때문입니다.
- 4~10명 — GitHub Flow에 필요 시 릴리스 브랜치를 추가하는 방식을 권장합니다. 리뷰 규칙과 CI만 탄탄하면 충분히 운영 가능합니다.
- 10명 이상 — Git Flow 쪽으로 무게 중심이 옮겨가는 경우가 많습니다. 다만 형식만 Git Flow를 따르고 실제 릴리스 절차나 QA 프로세스가 뒷받침되지 않으면, 오히려 관리 비용만 늘어날 수 있습니다.
팀 인원수보다 더 중요한 기준은 “릴리스를 어떤 단위로, 어떤 절차로 약속하는가”입니다. 인원이 적어도 릴리스 주기가 엄격하다면 Git Flow에 가까운 절차가 필요할 수 있고, 인원이 많아도 지속적 배포 체계가 잘 갖춰져 있다면 GitHub Flow만으로 충분할 수 있습니다.
2. 커밋 컨벤션 — Conventional Commits
커밋 메시지가 제각각이면 변경 이력을 추적하기 어렵고, 자동으로 CHANGELOG를 생성하거나 시맨틱 버저닝을 적용하기도 힘듭니다. Conventional Commits는 다음과 같은 고정된 형식을 사용해 이 문제를 해결합니다.
<type>(<scope>): <subject>
[본문(선택)]
[꼬리말(선택)]
type으로는 feat(새 기능), fix(버그 수정), docs(문서), refactor(리팩터링), test(테스트 추가/수정), chore(빌드/설정 등 기타 작업)를 주로 사용합니다. 실무에서 자주 발생하는 문제는 애매한 변경 사항을 전부 chore로 몰아넣는 것입니다. 기능 변경인지 버그 수정인지 애매할 때일수록 커밋을 작은 단위로 쪼개서 정확한 타입을 붙이는 편이 이력 추적에 도움이 됩니다.
git commit -m "feat(auth): 로그인에 소셜 연동 추가"
git commit -m "fix(pricing): 할인 적용 시 가격이 0원으로 표시되는 버그 수정"
git commit -m "refactor(invoice): InvoiceMapper 클래스 분리"
커밋 단위는 작게 유지하는 것이 원칙입니다. 하나의 PR에 커밋이 10개, 20개씩 쌓여 있으면 리뷰어가 변경의 맥락을 따라가기 어렵습니다. 작업 중간에 생긴 실수 커밋이나 wip 커밋은 git rebase -i로 정리한 뒤 PR을 올리는 것을 권장합니다. 이 작업은 아직 push하지 않은, 나 혼자만 보고 있는 브랜치에서만 수행해야 합니다. 이미 원격에 올라가 다른 사람이 pull 받은 브랜치를 리베이스하고 강제 푸시(git push --force)하면, 팀원의 로컬 이력과 원격 이력이 어긋나면서 혼란이 발생합니다.
3. Pull Request 워크플로우 모범 사례
PR은 코드 변경을 팀에 공유하고 검증받는 관문입니다. PR을 잘 쓰는 팀과 그렇지 않은 팀의 차이는 리뷰 속도와 배포 안정성에 직접적으로 영향을 줍니다.
PR 설명은 반드시 “왜/무엇을/어떻게 테스트했는지” 세 가지를 포함합니다
PR 템플릿을 강제하지 않는 팀이라도, 최소한 다음 세 가지는 본문에 적어두는 것이 좋습니다.
## 왜 (Why)
결제 완료 후 재고 차감이 비동기로 처리되면서, 동시 주문 시 재고가 음수로
떨어지는 경합 조건(race condition)이 발생했습니다.
## 무엇을 (What)
- 재고 차감 로직에 낙관적 락(optimistic lock)을 적용
- 재고 부족 시 사용자에게 명확한 에러 메시지 반환
## 어떻게 테스트했는지 (How tested)
- 동시 요청 100건을 시뮬레이션하는 통합 테스트 추가
- 스테이징 환경에서 실제 결제 플로우로 수동 검증
이렇게 기록을 남겨두면, 나중에 장애가 발생했을 때 어떤 PR이 원인인지 훨씬 빠르게 추적할 수 있습니다. 리뷰어 입장에서도 코드만 보고 의도를 추측하는 것보다, 배경을 먼저 읽고 나서 diff를 보는 편이 리뷰 속도와 정확도 모두 높습니다.
PR 크기는 작게 유지합니다
변경 라인 수 200~400줄 이내를 기준으로 삼는 것이 일반적입니다. 이는 절대적인 규칙이라기보다, “리뷰어가 30분 안에 맥락을 이해하고 리뷰할 수 있는 크기”를 가늠하는 지표에 가깝습니다. 이보다 훨씬 큰 PR은 리뷰어가 제대로 검토하지 못한 채 형식적으로 승인하게 될 가능성이 높습니다. 큰 변경이 불가피하다면, 인터페이스 변경과 구현체 변경을 분리하거나, 기능을 여러 단계로 나눠 순차적으로 PR을 올리는 방식을 고려해야 합니다.
리뷰 요청 전 셀프 리뷰를 먼저 합니다
PR을 올리기 전 자신의 diff를 GitHub 웹 화면에서 한 번 훑어보는 습관만으로도, 디버그용으로 남겨둔 로그나 주석 처리된 코드, 오타 등을 상당수 걸러낼 수 있습니다. 리뷰어의 시간은 팀 전체의 비용이라는 점을 감안하면, 사소한 실수를 줄이는 것만으로도 리뷰 효율이 크게 올라갑니다.
4. merge와 rebase, 언제 무엇을 쓸까
git merge와 git rebase는 둘 다 브랜치를 합치는 도구이지만, 결과로 남는 이력의 형태가 다릅니다.
git merge는 두 브랜치가 갈라졌다가 다시 합쳐진 지점을 커밋 그래프에 그대로 남깁니다. 리뷰어나 나중에 이력을 살펴보는 사람이 “이 브랜치가 언제 main에서 갈라졌고, 언제 다시 합쳐졌는지”를 명확히 확인할 수 있습니다.
git rebase는 내 브랜치의 커밋들을 대상 브랜치의 최신 커밋 위에 다시 쌓아 올립니다. 결과적으로 이력이 한 줄로 곧게 이어진 것처럼 보여 읽기는 편하지만, 원래 커밋의 정체성(해시)이 바뀝니다. 이미 원격에 push되어 다른 사람이 내려받은 브랜치를 리베이스한 뒤 강제 푸시하면, 그 브랜치를 기반으로 작업하던 동료의 이력과 충돌이 발생합니다.
이 둘을 실무에서 구분하는 기준은 다음과 같습니다.
- 여러 사람이 함께 push하는 공유 브랜치:
merge를 기본으로 사용합니다. 이력을 임의로 재작성하지 않아야 다른 사람과의 충돌 위험을 줄일 수 있습니다. - 나 혼자만 작업 중인 feature 브랜치, PR을 올리기 전:
rebase -i로 커밋을 정리하는 것을 권장합니다. 실수로 남긴 커밋이나wip커밋을 눈에 보이지 않게 정리해서, 리뷰어가 읽기 좋은 이력으로 만들 수 있습니다. main에 병합할 때 rebase를 쓰고 싶은 팀: 반드시 규칙을 문서로 먼저 정해야 합니다. “원격에 push하기 전에만 리베이스를 허용한다” 같은 규칙이 없으면, 누군가 이미 공유된 브랜치를 리베이스해버리는 사고가 발생하기 쉽습니다.
# 개인 feature 브랜치를 정리할 때
git fetch origin
git rebase -i origin/main
# 충돌이 발생하면 파일을 수정한 뒤
git add <파일>
git rebase --continue
# 공유 브랜치에 최신 main을 반영할 때
git fetch origin
git merge origin/main
두 방식 중 어느 쪽이 절대적으로 옳다고 말하기는 어렵습니다. 히스토리를 깔끔하게 유지하고 싶다면 rebase, 실제 작업 순서를 있는 그대로 남기고 싶다면 merge를 선택하면 됩니다. 다만 팀 차원의 규칙 없이 구성원마다 다른 방식을 쓰면, 이력이 뒤섞이고 충돌이 잦아지는 원인이 됩니다.
5. 머지 충돌 예방과 해결
충돌은 같은 파일의 같은 위치를 서로 다른 브랜치에서 동시에 수정했을 때 발생합니다. 아래 다이어그램은 전형적인 충돌 발생 시나리오를 보여줍니다.
gitGraph
commit id: "A"
branch feature
checkout feature
commit id: "B (파일 수정)"
checkout main
commit id: "C (같은 파일 수정)"
checkout feature
merge main id: "충돌 발생"
충돌 해결 기본 절차
충돌이 발생하면 Git은 해당 파일에 충돌 마커(<<<<<<<, =======, >>>>>>>)를 삽입합니다. 기본적인 해결 절차는 다음과 같습니다.
git status
# Unmerged paths로 표시된 파일을 열어 충돌 마커 확인
# 예시: InvoiceMapper.ts 안의 충돌 구간
# <<<<<<< HEAD
# const taxRate = 0.1;
# =======
# const taxRate = calculateDynamicTaxRate(region);
# >>>>>>> feature/invoice-v2
# 두 변경을 검토해 올바른 코드로 수정한 뒤
git add InvoiceMapper.ts
git commit
여기서 중요한 점은, 충돌 마커를 지우는 작업이 단순한 텍스트 편집이 아니라는 것입니다. 두 브랜치가 각각 어떤 의도로 코드를 수정했는지 이해하지 못한 채 한쪽을 기계적으로 선택(git checkout --ours 또는 --theirs)하면, 이미 다른 브랜치에 반영된 중요한 로직이 조용히 사라질 수 있습니다. 특히 금액 계산이나 권한 검증처럼 되돌리기 어려운 로직을 다루는 파일에서는, 충돌을 해결한 뒤 반드시 관련 테스트를 실행해 두 변경 사항이 모두 살아있는지 확인해야 합니다.
충돌을 줄이는 실전 방법
- 자주 동기화합니다: 하루에 한 번, 작업을 시작하기 전에
main의 최신 변경을 내 브랜치에 반영하는 루틴을 들이면 충돌이 누적되는 것을 막을 수 있습니다.
git checkout main
git pull origin main
git checkout my-feature
git merge main
# 팀 규칙에 따라 rebase로 대체 가능
- PR을 작게 유지합니다: 변경 범위가 작을수록 다른 사람의 작업과 겹칠 파일과 라인 수도 줄어듭니다.
- 파일을 적절히 분리합니다: 여러 기능이 하나의 거대한 파일에 몰려 있으면 충돌이 구조적으로 반복됩니다. 책임이 명확히 분리된 여러 파일로 쪼개면, 같은 시기에 여러 사람이 작업하더라도 겹치는 영역이 줄어듭니다.
- 머지 도구를 활용합니다: VS Code나 JetBrains IDE의 3-way merge 뷰,
git mergetool같은 도구를 쓰면 텍스트 에디터에서 마커를 직접 지우는 것보다 실수를 줄일 수 있습니다.
6. 운영 관점에서 함께 점검할 것들
브랜치 전략과 커밋 규칙이 잘 갖춰져 있어도, 실제 배포 운영과 맞물리지 않으면 효과가 떨어집니다. 다음 항목들을 함께 점검하는 것을 권장합니다.
- 관측 가능성: 요청 상관관계 ID, 응답 시간 p95, 타임아웃 발생률이 대시보드에서 확인 가능한지 점검합니다.
- 권한과 비밀 관리: 배포 경로마다 접근 권한과 시크릿 관리가 일관되게 적용되는지 확인합니다.
- 재시도와 신뢰성: 재시도 로직이 멱등성(idempotency)이 보장되는 연산에만 적용되는지, 서킷 브레이커나 백오프 전략이 있는지 확인합니다.
- 롤백 절차: 배포 롤백 절차, 카나리 배포, 마이그레이션 순서가 문서로 정리되어 있는지 확인합니다.
문제 상황별로 우선 확인할 순서는 다음과 같습니다.
- 간헐적으로 실패하는 경우: 최소 재현 조건을 만들고, 분산 트레이싱으로 원인이 되는 구간을 좁혀갑니다.
- 점점 느려지는 경우: 프로파일링을 먼저 수행하고, N+1 쿼리나 락 경합을 의심합니다.
- 메모리 사용량 문제: 구독 해제 누락이나 무한 캐시 같은 누수인지, 일시적인 스파이크인지 구분합니다.
- CI에서만 실패하는 경우: 환경 변수, lockfile 버전, 컨테이너 이미지 버전 차이를 확인합니다.
- 설정이 환경마다 다르게 동작하는 경우: 프로필, 시크릿 값, 기본값이 환경별로 일치하는지 확인합니다.
배포 전 작업 순서는 팀마다 다를 수 있지만, 일반적으로 git add → git commit → git push → 배포 스크립트 실행 순서를 지키는 팀이 많습니다. 이 순서를 스크립트나 CI로 강제해두면, 사람이 실수로 순서를 건너뛰는 사고를 줄일 수 있습니다.
마치며
Git 워크플로우는 명령어를 얼마나 잘 아는지보다, 팀이 어떤 규칙에 합의했는지가 더 중요합니다. 브랜치 전략을 무엇으로 선택하든, 커밋 메시지 형식을 어떻게 정하든, merge와 rebase 중 무엇을 기본으로 삼든, 그 결정이 문서로 남아 있고 팀 전체가 동일하게 이해하고 있다면 대부분의 혼란은 사라집니다. 반대로 아무리 정교한 전략을 도입해도 팀원마다 다르게 해석하고 있다면, 결국 같은 문제가 반복됩니다.
작은 팀이라면 GitHub Flow로 시작해 빠르게 PR을 돌리는 것을, 릴리스 주기가 명확한 조직이라면 Git Flow에 가까운 구조를 갖추는 것을 권장합니다. 어떤 전략을 택하든 커밋은 작게, PR은 리뷰하기 쉬운 크기로, 충돌은 자주 동기화하는 습관으로 예방하는 것이 실무에서 가장 효과가 큰 원칙입니다.
자주 묻는 질문
Q. 이 워크플로우를 언제 적용해야 하나요? A. 두 명 이상이 같은 저장소에서 협업하는 시점부터 적용하는 것이 좋습니다. 브랜치 전략과 커밋 컨벤션은 팀 규모가 작을 때 도입하는 편이, 나중에 인원이 늘어난 뒤 급하게 규칙을 만드는 것보다 훨씬 수월합니다.
Q. 실제 충돌 해결 사례를 더 보고 싶다면 어디를 봐야 하나요? A. Git 머지 충돌 해결 실전 사례에서 구체적인 충돌 상황과 해결 과정을 확인할 수 있습니다.
Q. rebase 명령어 자체를 더 깊이 배우고 싶다면요?
A. Git 고급 주제에서 rebase의 세부 옵션과 활용법을 더 자세히 다루고 있습니다.
함께 보면 좋은 글
검색 키워드
Git, GitHub, 브랜치전략, GitFlow, 협업, 버전관리, PR, CodeReview
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Git 머지 충돌 해결 실전 사례 | 대규모 리팩토링 브랜치 병합기
- Git 고급 심화 — 객체 저장소·세 트리·머지 전략·리베이스 메커니즘·프로덕션 운영
- Git 브랜치와 병합 | ‘merge conflict 났어요’ 충돌 해결 방법 (branch, merge)