운영 이력과 협업을 위한 Git 브랜치, 버전 전략
우리 팀은 그동안 main 브랜치 하나로 개발해왔다. main에서 작업 브랜치를 따 작업하고 다시 main으로 머지하는 단순한 흐름이었고, 브랜치 전략이라 부를 만한 건 따로 없었다.
이 상태에서 운영 배포 이력을 관리할 필요가 생겼다. 지금 운영에 떠 있는 코드가 정확히 무엇인지, 지난 배포 이후 무엇이 바뀌었는지를 한눈에 보고 싶었다. main 히스토리만으로는 답이 잘 안 나왔다. 배포 시점의 커밋과 그 사이의 모든 개발 커밋이 섞여 있어서, 어디부터 어디까지가 한 번의 운영 배포였는지 추적이 어렵다.
Git Flow와 GitLab Flow는 과했다
가장 흔한 답은 Git Flow였다. develop, release, hotfix, master 같은 브랜치를 두는 방식이다. 정기 릴리스 사이클이 있고 동시에 여러 기능을 release 후보로 묶어야 하는 팀에는 잘 맞는다.
우리는 그렇지 않았다. 동시 작업이 많지 않았고 정기 릴리스 주기도 없었다. develop과 release를 따로 운영하는 비용이 얻을 가치보다 컸다.
GitLab Flow도 흐름은 비슷한데, 그 안에 production 브랜치라는 개념이 따로 있다. 운영에 배포된 코드만 흘러가는 브랜치다. 우리에게 필요한 건 정확히 이 하나였다. 그래서 다른 브랜치는 두지 않고 production 브랜치 개념만 가져왔다.
도입한 규칙: 브랜치 3종
| 브랜치 | 역할 | 비고 |
|---|---|---|
main |
개발 브랜치 (기존과 동일) | 평소 작업 브랜치의 base |
prod |
운영 배포 이력 관리 | 배포 시점의 스냅샷, 버전 태그 기록 |
hotfix/* |
운영 긴급 수정 | prod에서 분기, prod 머지 + main 체리픽 |
평소 개발 방식은 그대로 두고 prod 브랜치만 신설했다. develop이나 release는 만들지 않았다.
평소 흐름
일반 개발
1. main 에서 작업 브랜치 생성
2. 작업 후 main 에 머지 (Squash Merge)
3. 운영 배포 시점에 main → prod 머지 (Merge Commit, "Release v1.1.0")평소 개발은 기존과 같다. 배포할 때 한 번 main을 prod로 머지하는 단계만 추가됐다.
Hotfix
1. prod 에서 hotfix/* 브랜치 생성
2. 수정 후 prod 에 머지 (Merge Commit, "Hotfix v1.1.1")
3. main 에 해당 커밋을 cherry pick운영 긴급 수정은 main에 섞여 있는 미배포 작업까지 함께 나가지 않도록 prod에서 직접 분기해 수정한다. 수정이 끝나면 prod에 머지해 배포하고, main에는 cherry pick으로 동기화한다.
병합 방식을 방향마다 다르게 둔 이유
같은 브랜치 구성이라도 머지 방식에 따라 히스토리 가독성이 크게 달라진다. 방향별로 다음과 같이 갈랐다.
| 방향 | 병합 방식 | 이유 |
|---|---|---|
main ← feature |
Squash Merge | 작업 브랜치의 커밋들이 하나로 합쳐져 main 히스토리가 깔끔. 기능 단위로 되돌리기 쉬움 |
prod ← main |
Merge Commit | 배포 시점이 머지 커밋으로 명확히 남아 스냅샷 역할이 분명 |
prod ← hotfix + main |
Merge Commit + Cherry Pick | 운영 흐름은 머지로 남기고, 개발 브랜치는 cherry pick으로 동기화 |
특히 prod ← main을 Merge Commit으로 두는 게 핵심이다. Squash나 Fast-forward를 쓰면 이 시점이 배포였다는 단 하나의 커밋이 안 남고 main에서 가져온 커밋들이 그대로 펼쳐진다. Merge Commit이 있으면 그 커밋 하나만 보고 v1.1.0 배포 = 이 머지 = 이 변경분이라는 답이 깔끔하게 잡힌다.
버전은 SemVer 그대로
브랜치 규칙과 짝을 이루는 게 버전 정책이다. 우리는 Major.Minor.Patch의 SemVer를 그대로 따른다.
| 타입 | 설명 |
|---|---|
| Major | 하위 버전과 호환되지 않는 변화. 대대적인 변화(재개발 규모). 거의 변경될 일 없음 |
| Minor | 새로운 기능 추가 |
| Patch | 기존 운영 배포 버전에 발생한 버그 수정 (hotfix) |
예를 들어 v1.23.5는 "최초 개발 후 23번의 기능 개발이 있었고, 그 위에서 5번의 버그 픽스가 있었다"로 읽힌다.
운영 단계에서 지키는 규칙은 단순하다.
- 특정 버전으로 운영 배포된 후에는 그 버전의 내용은 절대 변경하지 않는다.
- 변경 사항이 있다면 반드시 새 버전으로 배포한다.
- Major 변경 시 Minor / Patch는 0으로 초기화, Minor 변경 시 Patch는 0으로 초기화.
운영 배포된 버전은 불변. 가장 중요한 규칙이다. 한 번 prod에 머지된 v1.1.0의 내용을 사후에 손대지 않으면, 추적·롤백·소통 모든 단계에서 버전을 신뢰할 수 있다.
실제 prod 브랜치
실제 운영 프로젝트의 prod 브랜치는 이렇게 찍혀 있다.
prod에는 Release v1.0.2, Release v1.1.0, Release v1.2.0 같은 머지 커밋만 또렷이 남는다. 어느 시점에 운영에 무엇이 나갔는지가 prod 로그 한 화면이면 답이 된다. main은 그 사이의 모든 개발 커밋을 그대로 보존한다. 운영 시점만 보고 싶다는 요구는 prod로 해결된다.
효과와 트레이드오프
도입 후 얻은 것.
- 운영 이력 관리.
prod브랜치가 배포 시점 스냅샷이 된다. 가장 큰 도입 요인이었다. - 운영 코드 보호.
prod에 직접 push를 막아두면 운영 반영은 머지로만 일어난다. - 비정기 배포 대응. hotfix 브랜치로 긴급 수정 경로가 명시적으로 생긴다.
- 전환 공수 0. 평소 개발 방식은 그대로다.
prod브랜치를 한 번 만들고 머지 규칙만 합의하면 끝난다.
대신 포기한 것.
- 동시 릴리스 후보 관리.
develop/release브랜치가 없으니, 여러 기능을 한 묶음으로 후보화해서 별도 검증 트랙에 올리는 흐름은 만들 수 없다. 우리는 배포 주기가 그 정도로 빡빡하지 않아 필요가 없었다.
브랜치 전략은 팀 규모와 배포 주기에 맞춰 짜야지, 표준이 있다고 그대로 가져올 일이 아니다. develop이 필요해지는 시점이 오면 그때 한 단계 더 도입하면 된다.