사내에서 사용중인 git branch 전략에 대해 설명해보고자 합니다.
저희 프로젝트는 서비스 운영 전에는 단일 서버로만 운영되었고, 약 5명의 프론트엔드 개발자들이 동시에 많은 개발 작업을 진행하다 보니, develop 브랜치 하나에 모든 커밋을 쌓아가며 개발을 진행했습니다. 그러나 실제 서비스가 운영되면서 서버가 두 개(운영, 개발)로 나뉘게 되었고, 프론트엔드 Git 브랜치 관리에 새로운 전략이 필요해졌습니다.
서비스 운영 당시에는 저를 포함한 두 명의 프론트엔드 개발자가 메인 개발을 담당했고, 어떻게 하면 가장 편리하고 효율적으로 서로 작업할 수 있을지 고민했습니다. 이후 몇 번의 시행착오를 거친 끝에, 현재도 유지 중인 회사의 Git 브랜치 전략을 마련할 수 있었습니다.
서비스 운영시점부터 실제 사용자들은 운영 서버를 통해 업무를 수행했습니다. 운영 서버는 매우 민감하게 돌아가고, 예상치 못한 상황을 대비해 테스트는 최대한 지양하고 있습니다. 특히 금전과 관련된 기능들이 많기 때문에 잘못된 저장 한 번으로 큰 문제가 발생할 수 있어, 초기에는 운영 서버가 공포의 대상이었으며 지금도 여전히 긴장을 늦추지 않고 있습니다.
이로인해, 개발자들이 실제 개발한 내용들을 운영 서버에서 테스트하기가 어려워졌기 때문에, 개발자와 기획자 등 프로젝트 유지보수 관리자는 주로 개발 서버에서 테스트와 검증을 수행해야 했습니다. 로컬 환경을 제외한 대부분의 테스트를 dev 서버에서 진행합니다. dev 브랜치는 master, prd에 비해 비교적 유연하게 동작합니다. 업무에는 Jira를 함께 활용해 기능별로 티켓을 배정하고 각 브랜치에 하나의 티켓을 매핑하고 있으나, dev에서의 개발 내용, 커밋명 등은 언제든 수정이 가능합니다.
대부분의 작업이 dev 브랜치를 기준으로 진행되기 때문에, 브랜치 간 충돌도 주로 dev에서 발생합니다. (prd, master에서는 아직 충돌이 발생한 적이 없습니다.)
사내에서 도입한 git branch 전략의 주요 브랜치에 대한 설명은 다음과 같습니다.
master : 운영계에 올라갈 코드들만을 관리하는 main 브랜치, Git Flow의 release 역할
prd: 운영에 실제로 배포가 되는 브랜치, Git Flow의 master 역할
dev: 운영 배포 전 테스트가 필요한 개발건들을 가지고 있는 브랜치, Git Flow의 develop 역할
🔀 플로우 설명
다음은 v1.0.1 배포 이후의 환경에서 v1.0.2 배포 준비 및 배포 후 플로우에 대한 설명입니다.
💡 개발 요청 A, B, C가 들어왔다고 가정해보겠습니다.
- 개발자는 dev 브랜치를 기준으로 각 기능(A, B, C)에 대해 브랜치를 생성합니다.
- 기능 개발을 완료하면 각 기능 브랜치를 dev 브랜치에 병합(merge)합니다.
이 시점에서의 git graph는 위와 같습니다.
아직까지 A, B, C에 대한 개발내용을 master는 알지 못합니다.
이후, 프로세스팀에서 개발건 A, B에 대해서 테스트를 완료한 뒤 운영 서버로 배포 요청을 하였습니다.
그렇다면, 이제 운영서버 배포를 위해 수행해야 할 작업은 다음과 같습니다.
- dev에 있는 A, B 커밋을 master에서 브랜치 생성 후 cherry-pick으로 가져온다.
- 가져온 A, B 커밋을 master에 머지한다.
- master를 prd에 머지한다.
- prd에서 새로운 버전 태그를 딴 뒤에 배포한다.
- dev와 master는 배포된 prd와 싱크를 맞춘다.
위 1~5번 과정이 사실상 개발 후 배포 프로세스의 전부입니다.
(1~2번)
dev에 있는 A, B를 배포해야하는 상황
- master branch에서 branch 생성
- git cherrypick (A의 고유넘버) (B의 고유넘버) 를 수행
- 이후, git push origin branchName 으로 올린 뒤 PR 만들어 master에 머지 하기
여기까지 진행된 시점의 git graph는 위와 같습니다.
(3~4번)
이제 배포할 내용 A, B가 담긴 master를 prd에 머지한 뒤 배포할 내용의 태그를 생성합니다.
(이때, prd를 기준으로 태그를 생성해야지 제대로 된 태그가 생성됩니다. master x)
(5번)
이제 남은 작업은 master와 dev를 prd와 싱크를 맞춰주면 됩니다.
master에서 prd 머지 후, 싱크를 맞춰주는 깃 명령어는 다음과 같습니다.
master에서는 prd를 rebase, dev에서는 master를 rebase 해주면 되는데
특별한 경우가 아닌 이상 웬만하면, 아래 명령어 순서대로 진행이 됩니다.
- git checkout master (master 브랜치로 이동)
- git rebase origin/prd (prd 브랜치를 rebase)
- git push origin master (master 브랜치에 부어 현시점으로 맞추기)
- git checkout dev (dev 브랜치로 이동)
- git rebase origin/master (master 브랜치를 rebase)
- git push origin dev (dev 브랜치에 부어 현시점으로 맞추기, 기존 dev에 다른 기능들이 포함되어 있는 경우 force push, 이때 몇 번이고 확인하고 진행하기. (force push는 항상 신중히 !!)
최종적으로 완료된 git graph는 위와 같습니다.
가장 이상적인 브랜치 모습
해당 브랜치 모습처럼 깔끔하게 유지하는 것이 이전 버전 정보, 커밋 정보들을 파악하기 용이하며 git 충돌 시 해결하기가 조금 더 수월해집니다.
위 과정이 저희 팀에서 개발을 진행하고 배포를 하여 운영서버에 반영하는 프로세스입니다.
이렇게 몇 달간 작업하면서 느낀 장단점들은 다음과 같습니다.
장점
- 흐름이 비교적 복잡하지 않고 단순하다. 이는 충돌 시 맥락 파악에 도움이 된다.
- 깃 그래프만 봐도 어느 버전에 어떤 기능들이 배포되었는지 쉽게 파악이 가능함. (그래프가 단순하다)
- 3명 이하의 개발자들이 관리하기 적합하다고 느낀다.
단점
- 3명을 넘어가는 팀에서 운영하기에는 무리가 있어보인다.
- force push의 위험성 존재
- 3명 이하의 개발자들이 관리하기 적합하다고 했는데 다음의 규칙들을 지켰을 때이다.
- 서로가 공통된 파일을 수정하지 않는다.
- 작업 중, 다른 개발자가 머지하면 바로 rebase를 받고 다시 이어서 작업한다.
이외에 단점은 아니고 운영하면서 팀원간 지켜야할 규칙은 다음과 같습니다.
- 브랜치를 단순하게 가져간다는 장점을 살리려면 브랜치 정리에 힘 써야한다. 머지된 브랜치는 바로바로 정리하기! (매우 중요)
- 브랜치 간 충돌시 해당 브랜치 작업자들끼리 서로 확인 후 정리. (임의로 정리 X)
- 커밋 명 = 지라 링크(추후 확인 편의성 위함) + 구현 내용
- 운영 서버 배포 시, 각 지라 티켓에 대응하는 배포 커밋은 반드시 하나의 커밋으로 이루어져야 한다. (즉, 지라 티켓 하나당 커밋 하나. dev 브랜치에서 동일한 지라 티켓을 가진 여러 커밋 존재시 rebase -i HEAD 명령어 사용해서 squash 진행)
위 개발자들 간의 주의 사항만 잘 지킨다면 업무에 특별히 문제가 생기지 않을 것이라 예상합니다.
가령, 우리 프론트팀과 다른 브랜치 전략을 가지고 있던 백엔드 팀은 4~5명의 개발자들로 이뤄져 있었는데 충돌이 빈번하게 나서 동료가 애를 먹은 적이 있기도 합니다.
개인적으로 이 브랜치 전략을 사용해보고 여러 시행착오를 겪어보면서 git에 대한 이해도도 많이 올랐다고 생각이 듭니다.
cherry-pick, rebase, push를 이리저리 사용해보면서 익힐 수 있는 기회였습니다.
여려 명령어들 중, 꼭 알아둬야하는 명령어들이 있다면 다음과 같습니다.
- git rebase -i HEAD~(숫자)
- git cherry-pick (브랜치 고유번호)
- git rebase origin/브랜치
- squash
- reword
위 명령어들은 정말!! 자주 사용되니 꼭 숙지합시다.
그리고 요즘 다른 분들을 보면 git을 사용할 때 VSCode나 intelliJ의 GUI를 많이 사용하는 것 같습니다.
굉장히 편리하게 이용할 수 있는 기능이지만, 저는 오히려 여기에 너무 익숙해지면 독이라 생각합니다.
저는 신입때부터 모든 git 명령어를 순수 타이핑해서 치도록 배워 왔고, GUI는 거의 사용하지 않습니다.
처음에는 헷갈렸지만, 지금은 눈감고도 할 수 있을정도로 손에 익었습니다.
이렇게 익혀두면 좋은 것이 충돌이 나든 어떠한 git의 문제가 생겼을때 대처가 유연하게 가능하다 생각합니다.
사실 GUI를 제대로 사용해본적이 없어서 모르는 것일 수도 있습니다.
다만, 경험상 GUI로만 git을 관리하면 터미널로 conflict 해결하기가 어려워집니다
그러니 자주 쓰는 동작의 깃 명령어 정도는 손에 익혀두는 것이 나중에 결국 내가 편한일이라 생각합니다.
그리고 브랜치 정리 미리미리 잘 해둡시다… 나중에 하기 더 힘들어집니다.
사수분이 예전에 브랜치 정리 하면 좋다고 여러 번 얘기해주셨습니다.
그 당시에는 그렇게 큰 필요성을 못 느꼈었는데, 이 브랜치 전략을 사용하면서 아주 절실히 깨닫고 있습니다.
이제는 약간의 강박으로 안쓰는 브랜치들을 모조리 지워버리고 저렇게 단순한 패턴을 가질 수 있도록 힘쓰고 있습니다.
왼쪽의 경우 충돌이 났을때 브랜치 파악하기가 너무너무 어렵다 (심지어 사진은 dev 브랜치만 사용할 때이다. 지금 저러면… 어후!!)
그러니 미리미리 해둡시다!
Branch Sync 자동화
추가로 매 배포시 Branch Sync를 맞추는 반복된 작업이 귀찮다면, Shell Script를 사용해서 편리하게 하는 방법도 있습니다.
사용 방법은 필자가 사용하는 프로젝트 기준으로 작성되어있으므로 본인의 프로젝트에 맞게 변경하면 됩니다. (ex. upstream → origin)
다만, 저는 타이핑으로 하나하나 점검해가면서 입력하는 것을 선호하여 해당 기능은 잘 사용하지 않습니다.
프로젝트 최상단에 scripts 폴더 생성 및 스크립트 파일 생성 (sync-branches.sh로 명명)
echo " $ git checkout master"
git checkout master
echo " $ git fetch upstream"
git fetch upstream
# ☑️ master sync
echo " $ git rebase upstream/prd"
git rebase upstream/prd
status=$?
# ⛔️ unstaged commit이 있을 경우
if [ $status -ne 0 ]; then
echo "Rebase failed due to unstaged changes. Adding changes and creating a temporary commit."
git checkout -b 임시저장
git add .
git commit -m 'tmp'
git checkout master
echo "Retrying the rebase..."
git rebase upstream/prd
fi
echo " $ git push upstream master"
git push upstream master
# ☑️ dev sync
echo " $ git checkout dev"
git checkout dev
echo " $ git rebase upstream/master"
git rebase upstream/master
# 아래 로직은 강제 force push 실수 위험이 있으므로 주석 처리
# 사용자에게 force push 유무를 묻고 실행할 경우 아래 코드 참고하여 수정해주세요.
#
# # ⛔️ git push할 실패한 경우 y입력시 force push
# if [ $? -ne 0 ]; then
# echo "dev로 push failed. force push를 진행하시겠습니까? (y/n)"
# read answer
# if [ "$answer" == "y" ] || [ "$answer" == "Y" ]; then
# echo "Executing git rebase upstream/dev -f"
# git push upstream/dev -f
# elif [ "$answer" == "n" ] || ["$answer" == "N" ]; then
# echo "Exiting the script."
# exit 1
# else
# echo "Invalid input. Exiting the script."
# exit 1
# fi
# fi
해당 파일에 위 명령어 작성
chmod +x ~/scripts/sync-branches.sh
작성한 명령어 수행 전 VS Code 터미널에서 위 명령어를 입력하여 권한 부여
sudo cp sync_branches.sh /usr/local/bin/sync_branches
이후, 위 명령어를 입력하여 PATH에 추가 (명령어 sync_branches만 입력되도 실행되게 하기 위함.)
💡 위는 최초 실행시에만 수행 (한 번 등록하면 또 안해도됨.)
배포 후, 터미널에 sync_branches 를 입력하여 쉘스크립트 맞추기 실행