혼자 Git을 쓸 때는 사실 좀 막 써도 별 탈이 안 나요. 그냥 main 브랜치에 커밋 쌓고 밀면 되니까요. 그런데 팀에 들어가서 서너 명이 같은 저장소를 만지기 시작하면 얘기가 완전히 달라져요. 내가 밀려는 순간 옆자리 동료도 밀고, 서로 같은 파일을 건드리고, 갑자기 rejected 같은 빨간 글씨가 뜨면서 push가 막히죠. 오늘은 팀에서 실제로 도는 협업 워크플로(collaboration workflow, 여럿이 함께 코드를 합치는 흐름)를 상황별로 따라가 볼게요.
86.1 팀에서는 브랜치를 왜 나눠 쓰나요?
핵심 규칙은 딱 하나예요. main 브랜치는 언제나 배포 가능한 상태로 지킨다. main은 실제 운영 서버에 나가는 코드라서, 여기에 반쯤 짜다 만 기능을 직접 커밋하면 사이트가 깨질 수 있어요. 그래서 팀에서는 각자 자기 기능을 별도 브랜치에서 만들고, 다 완성돼서 검토까지 끝난 것만 main에 합쳐요.
이 방식을 기능 브랜치 전략(feature branch, 기능마다 가지를 따로 치는 방식)이라고 불러요. 로그인 기능은 feature/login, 결제 기능은 feature/payment 하는 식으로 브랜치 이름에 뭘 하는 가지인지 적어두면 팀원들이 목록만 봐도 누가 뭘 하는지 알 수 있어요. 서로의 작업이 완성되기 전까지는 섞이지 않으니까, 내가 실험하다 코드를 왕창 망가뜨려도 남한테 피해가 안 가죠.
86.2 내 작업은 어떻게 브랜치로 올리나요?
순서는 늘 똑같아요. 브랜치 만들기, 작업, 커밋, 원격에 올리기. 로그인 화면을 만든다고 해볼게요. 먼저 main에서 새 가지를 쳐요.
$ git switch -c feature/login
Switched to a new branch 'feature/login'
git switch -c는 브랜치를 새로 만들면서 곧바로 그 브랜치로 옮겨 타는 명령이에요. 여기서 파일을 만들고 커밋해요.
$ git add .
$ git commit -m "로그인 화면 추가"
[feature/login 8f21b70] 로그인 화면 추가
1 file changed, 1 insertion(+)
여기까지는 내 컴퓨터 안에서만 벌어진 일이에요. 팀원들은 아직 이 브랜치를 못 봐요. 원격 저장소(remote, 깃허브 같은 공용 서버)에 올려야 공유가 되죠.
$ git push -u origin feature/login
* [new branch] feature/login -> feature/login
branch 'feature/login' set up to track 'origin/feature/login'.
여기서 -u 옵션이 중요해요. 이건 내 로컬 브랜치와 원격 브랜치를 짝지어주는(track, 추적) 옵션이에요. 한 번 -u로 올려두면 다음부터는 그냥 git push, git pull만 쳐도 깃이 알아서 이 원격 브랜치를 상대해요. 이제 깃허브에서 Pull Request(PR, 내 브랜치를 main에 합쳐달라는 검토 요청)를 열면 동료가 코드를 보고 합쳐주는 거예요.
86.3 push했더니 rejected가 떴어요?
협업하다 제일 자주 만나는 벽이에요. 내가 열심히 커밋하고 밀었는데 이런 빨간 글씨가 뜨죠.
$ git push origin main
! [rejected] main -> main (fetch first)
error: failed to push some refs
hint: Updates were rejected because the remote contains work that
hint: you do not have locally.
겁먹을 거 없어요. 이건 사고가 아니라 안전장치예요. 뜻을 풀면 이래요. "네가 마지막으로 받아간 뒤에 다른 사람이 먼저 원격에 뭔가를 밀었어. 그 사람 작업을 네가 아직 안 가지고 있으니, 그대로 밀면 그 사람 커밋을 덮어버리게 돼. 그러니 일단 막을게." 괄호 안의 fetch first가 딱 그 얘기예요. 밀기 전에 먼저 원격 걸 받아오라는 뜻이죠.
여기서 절대 하면 안 되는 게 -f(강제 push)로 밀어버리는 거예요. 그러면 동료 커밋이 정말로 사라져요. 정답은 원격 작업을 내 쪽으로 먼저 합치는 거예요.
86.4 pull은 merge랑 rebase 중 뭘 쓰죠?
원격 걸 받아 합치는 명령이 git pull인데, 여기에 두 가지 방식이 있어요. 그냥 git pull은 원격과 내 작업을 merge(병합)해서 합치는 커밋을 하나 더 만들어요. 반면 git pull --rebase는 원격 작업을 먼저 깔고 그 위에 내 커밋을 다시 얹어요(rebase, 내 커밋의 출발점을 옮겨 붙이기). 팀에서는 히스토리가 지저분한 병합 커밋 없이 한 줄로 깔끔하게 남는 --rebase를 선호하는 곳이 많아요.
$ git pull --rebase origin main
* branch main -> FETCH_HEAD
Auto-merging shared.txt
CONFLICT (content): Merge conflict in shared.txt
같은 파일 같은 줄을 나랑 동료가 둘 다 고쳤으면 이렇게 충돌(conflict)이 나요. 이것도 사고가 아니에요. 깃이 "둘 중 뭘 남길지 나는 못 정하겠으니 네가 골라"라고 넘겨준 거예요. 파일을 열면 이렇게 표시돼 있어요.
<<<<<<< HEAD
alice line
=======
bob line
>>>>>>> bob 작업
위쪽이 원격에 있던 동료 작업, 아래쪽이 내 작업이에요. 표시 기호(<<<, ===, >>>)를 지우고 최종적으로 남길 내용만 손으로 정리한 다음, git add로 해결됐다고 표시하고 git rebase --continue로 마저 진행해요.
$ git add shared.txt
$ git rebase --continue
Successfully rebased and updated refs/heads/main.
$ git push origin main
b8a5abd..556b346 main -> main
이제야 push가 통과했죠. 원격 작업을 내 쪽에 얹었으니 더 이상 남의 커밋을 덮을 위험이 없어서 깃이 순순히 받아준 거예요.
86.5 오늘 정리
협업 워크플로를 한 줄로 요약하면 이래요. main은 건드리지 말고, 내 기능은 브랜치에서 만들어 PR로 합친다. 브랜치는 git switch -c로 만들고 git push -u origin 브랜치명으로 처음 한 번 올려두면 그다음부턴 push, pull만 쳐도 돼요. 밀다가 rejected (fetch first)가 뜨면 강제로 밀지 말고 git pull --rebase로 원격 걸 먼저 받아 합치세요. 충돌이 나도 당황하지 말고 표시 기호 사이에서 남길 내용만 골라 정리하면 돼요. 이 흐름만 몸에 배면 팀 저장소에서 코드가 꼬일 일이 확 줄어요.