혼자 프로젝트할 땐 git push로 main에 바로 올렸어요. 그러다 팀에 들어가니 규칙이 생기더라고요. main엔 직접 못 올리고, 무조건 풀 리퀘스트(Pull Request, 줄여서 PR)를 거쳐야 한다고요. 처음엔 번거로웠는데, 이게 왜 있는지 알고 나선 없으면 오히려 불안해졌어요. 오늘은 PR과 코드 리뷰의 흐름을 이야기해볼게요.

77.1 왜 바로 push하지 않고 PR을 만드나요?

풀 리퀘스트는 이름 그대로 내 브랜치를 받아달라고 요청하는 거예요. 내가 작업한 브랜치를 main에 합치기 전에, 동료에게 먼저 보여주고 확인받는 절차죠. 왜 이런 절차가 생겼을까요? main에 바로 올리면 실수한 코드가 곧장 전체 서비스에 반영돼요. 새벽에 혼자 올린 오타 하나가 결제 페이지를 멈추게 할 수도 있죠.


PR을 거치면 합치기 전에 다른 사람 눈이 한 번 더 코드를 봐요. 버그를 미리 잡고, 더 나은 방법을 제안받고, 무엇보다 내 코드가 어떻게 돌아가는지 팀이 알게 돼요. 이게 코드 리뷰(code review)고요. 저는 PR을 혼자 일하다 팀으로 일하게 만드는 관문이라고 생각해요.


그래서 대부분의 팀은 아예 main 브랜치에 보호 규칙을 걸어둬요. 리뷰 승인 없이는, 또는 자동 검사를 통과하지 못하면 합치기 버튼 자체가 안 눌리게 막아두는 거죠. 그러면 누구도 검증 안 된 코드를 실수로 main에 못 올려요. 번거로워 보이지만, 사고 한 번 크게 겪고 나면 이 규칙이 얼마나 고마운지 알게 돼요.

77.2 PR은 어떤 순서로 만드나요?

순서는 늘 비슷해요. 먼저 main에서 작업 브랜치를 따요. 브랜치 이름은 뭘 하는지 드러나게 짓는 게 좋아요.


$ git checkout -b feature/login


브랜치에서 작업하고 커밋을 쌓아요. 이때 커밋을 의미 단위로 나눠두면 나중에 리뷰가 편해요. 화면 만들기 커밋 따로, 검증 로직 커밋 따로, 이런 식으로요. 한 커밋에 온갖 걸 뭉뚱그리면 리뷰어가 어디가 뭔지 못 읽거든요. 다 됐으면 이 브랜치를 원격에 올려요.


$ git push -u origin feature/login
remote: Create a pull request for 'feature/login' on GitHub by visiting:
remote: https://github.com/team/app/pull/new/feature/login


보이시죠? push하면 깃허브가 친절하게 PR 만들 주소를 알려줘요. 그 주소로 들어가거나 깃허브 화면의 Compare & pull request 버튼을 누르면 PR 작성 화면이 떠요. 여기서 어느 브랜치를 어디로 합칠지(보통 feature/loginmain으로), 그리고 제목과 설명을 적어요.


설명이 은근 중요해요. 뭘 왜 바꿨는지, 어떻게 테스트했는지 두세 줄만 적어도 리뷰어가 훨씬 빨리 이해해요. 저는 무엇을, , 어떻게 확인했는지 세 가지를 꼭 넣어요.

77.3 리뷰는 어떻게 주고받나요?

PR이 열리면 리뷰어가 코드를 줄 단위로 보면서 의견을 달아요. 깃허브 리뷰는 보통 세 가지 상태로 끝나요. Comment는 그냥 의견만 남기는 거고, Approve는 합쳐도 좋다는 승인이에요. Request changes는 이대로는 안 되니 고쳐달라는 거고요.


고쳐달라는 의견을 받으면 어떻게 할까요? 새 PR을 다시 만들 필요 없어요. 같은 브랜치에서 코드를 고치고 커밋해서 다시 push하면, 그 커밋이 기존 PR에 자동으로 붙어요.


$ git commit -am "fix: 리뷰 반영, 빈 값 검증 추가"
$ git push


리뷰어는 바뀐 부분만 다시 보면 되고요. 이 과정을 몇 번 오가다 Approve가 나오면, Merge 버튼을 눌러 main에 합쳐요. 합친 브랜치는 지워도 돼요. 역할을 다했으니까요.

77.4 합칠 때 방식이 여러 개던데요?

Merge 버튼을 누르려고 보면 방식이 보통 세 가지예요. 처음엔 뭘 골라야 하나 싶은데, 결과 히스토리가 조금씩 달라요. Create a merge commit은 내 커밋들을 그대로 두고 합침 커밋 하나를 얹어 합쳐요. 브랜치에서 무슨 일이 있었는지 다 남죠.


Squash and merge는 브랜치의 커밋을 하나로 뭉쳐서 main에 올려요. wip 커밋이 잔뜩이어도 main엔 깔끔한 커밋 하나만 남아요. 많은 팀이 이걸 기본으로 써요. Rebase and merge는 합침 커밋 없이 내 커밋들을 main 위에 일직선으로 얹고요. 정답은 없어요. 팀이 정한 방식을 따르면 되고, 저희 팀은 히스토리가 단순한 squash 방식을 주로 써요.

77.5 PR 올렸는데 자동 검사가 돌아요

PR을 열면 아래쪽에 초록 체크나 빨간 엑스가 뜨는 걸 보게 돼요. 이건 자동 검사(CI, 지속적 통합)예요. PR이 올라오면 서버가 알아서 테스트를 돌리고 빌드가 되는지 확인해줘요. 빨간 엑스가 뜨면 사람이 리뷰하기 전에 뭔가 깨졌다는 신호라, 그것부터 고쳐야 해요. 저는 이 자동 검사 덕에 리뷰어를 귀찮게 하기 전에 실수를 여러 번 조용히 잡았어요.


그리고 아직 리뷰받기 이른 작업은 초안 PR(Draft PR)로 열 수 있어요. 방향만 먼저 보여주고 싶을 때 좋아요. 작업하는 사이 main이 앞서가서 충돌이 날 수도 있는데, 그땐 내 브랜치에서 최신 main을 받아(merge나 rebase) 충돌을 풀고 다시 push하면 PR이 다시 초록불로 돌아와요.

77.6 리뷰에서 자주 나오는 이야기들

제가 리뷰를 주고받으며 배운 것 몇 개를 나눌게요. 첫째, PR은 작게 쪼개세요. 파일 오십 개 바뀐 PR은 아무도 제대로 못 봐요. 리뷰어가 십오 분 안에 다 읽을 크기가 이상적이에요. 기능 하나, PR 하나가 편해요.


둘째, 커밋 메시지와 PR 설명에 맥락을 담으세요. 코드는 무엇을 했는지 보여주지만 그렇게 했는지는 안 보여줘요. 그 왜를 적어두면 리뷰어도, 반년 뒤의 나도 고마워해요.


셋째, 리뷰 의견을 나에 대한 지적으로 받지 마세요. 코드에 대한 이야기지 나에 대한 이야기가 아니에요. 반대로 리뷰를 줄 때도 이건 왜 이렇게 했어요?처럼 질문형으로 부드럽게 물으면 대화가 잘 풀려요. 리뷰는 싸움이 아니라 같이 코드를 좋게 만드는 자리니까요.


넷째, 달린 의견에는 하나하나 답하세요. 고쳤으면 고쳤다고, 안 고칠 거면 왜 그대로 두는지 짧게라도 남기는 거예요. 답 없이 조용히 넘어가면 리뷰어는 자기 의견이 무시당했나 싶어 서운하고, 뭐가 반영됐는지도 몰라요. 저는 사소한 의견이라도 반영했어요 한 줄은 꼭 달아요. 그거 하나로 리뷰 분위기가 확 좋아지거든요.

77.7 PR, 이것만 기억해요

흐름을 다시 정리할게요. 첫째, 작업 브랜치를 따서 커밋을 쌓고 push해요. 둘째, 깃허브에서 PR을 열어 무엇을 왜 바꿨는지 설명을 적어요. 셋째, 리뷰를 받고 필요하면 같은 브랜치에서 고쳐 다시 push해요. 넷째, Approve가 나면 Merge로 main에 합쳐요.


PR은 코드를 늦추는 절차처럼 보이지만, 실은 사고를 막고 팀의 지식을 나누는 안전장치예요. 작게 올리고, 맥락을 적고, 서로 존중하며 주고받으면 리뷰가 부담이 아니라 배움의 시간이 돼요. 다음에 PR 화면을 열 때, 이 흐름을 떠올려보세요.