GitHub PR은 Git의 변경 그래프에 협업 레이어를 더한 단위다. 단순한 머지 버튼 화면이 아니라, 변경 가시화 + 리뷰 의사결정 + CI 게이트를 하나로 모으는 단위다.

Pull Request

PR은 source branch와 target branch 사이의 diff 묶음이다. 그 diff 묶음에 GitHub이 협업에 필요한 메타데이터 — 리뷰 시스템, conversation thread, status check, branch protection — 를 결합한 형태가 PR이다.

flowchart LR
    Open["PR open
(diff 묶음)"] --> Review["리뷰
(comment / approve / request changes)"] Review --> Checks["CI status check
(test / build / lint)"] Checks --> Merge["merge"] Merge --> Close["close"]

이 네 단계가 평소엔 자연스럽게 흐르지만, 단위 설계가 잘못되면 어느 한 곳에서 막힌다. 가장 흔한 막힘이 리뷰 단계다.

PR 단위 설계

작은 PR이 빠른 리뷰를 만든다. 200~400 LOC 수준이 일반적인 권장선인데, 분량 자체보다 한 PR에 한 가지 의도라는 원칙이 더 본질적이다. 리팩터링과 새 기능을 섞으면 리뷰어가 둘을 분리해서 평가해야 하고, 결과적으로 어느 쪽도 충분히 보지 못한다.

큰 변경이라도 단계별로 쪼개면 단일 의도를 유지할 수 있다. 인터페이스 추가 → 호출부 마이그레이션 → 옛 인터페이스 제거 같은 분리가 그렇다. 의도 단위로 쪼개야 PR도 작아지고 리뷰도 가벼워진다.

draft PR은 미완성 변경에 대한 조기 피드백을 받는 도구다. 머지 가능 상태가 아닌 채로 PR을 열고, 코드 방향에 대한 의견을 받은 뒤 본 작업을 진행한다. 큰 변경에 들어가기 전 확인 단계로 유용하다.

Code Review 사이클

리뷰는 코드의 옳고 그름을 평가하는 자리만은 아니다. 의도 검증, 지식 공유, 머지 가능성 판단이 함께 일어나는 단계다.

작성자는 리뷰 요청 전에 자기 PR을 한 번 본다. 자기가 만든 변경을 처음 보는 사람의 눈으로 다시 읽어 보면, 머지 직전에 자주 보이는 작은 실수들 — 디버깅용 코드, 무관한 변경, 빠진 테스트 — 을 미리 잡아낼 수 있다.

리뷰어는 네 가지 액션 중 하나를 선택한다.

  • comment: 정보 공유, 질문, 제안
  • suggestion: 직접 적용 가능한 작은 코드 변경 제안
  • request changes: 머지 전 반드시 반영해야 할 변경 요구
  • approve: 머지해도 좋다는 동의

여기서 의사결정의 핵심은 blocker와 nit의 구분이다. 코드의 의도·정확성·안전성에 영향을 주는 것은 blocker로 짚고, 스타일이나 사소한 선호 차이는 nit으로 분명히 표시한다. 모든 코멘트가 동등한 무게를 가진 것처럼 다뤄지면 리뷰가 무거워지고, blocker가 묻혀 진짜 위험이 머지된다.

리뷰어가 코멘트를 남기면 작성자는 답변하거나 코드를 수정한 뒤 thread를 resolve한다. resolve가 모이면 머지 가능 상태가 만들어진다.

머지 전략

PR을 main에 합칠 때 GitHub은 세 가지 옵션을 준다.

전략그래프 결과특성
merge commit분기·합류 모양 보존PR 단위가 그래프에 그대로 남음
squash mergelinear, PR 하나 = commit 하나history 단순. PR 내부 commit 사라짐
rebase mergelinear, PR 내부 commit 그대로linear history. PR 단위 흐릿함
flowchart TB
    subgraph Source ["PR 내부"]
        s1((c1)) --> s2((c2)) --> s3((c3))
    end
    subgraph MergeCommit ["merge commit"]
        m1((m1)) --> m2((m2)) --> mc((merge))
        m1 --> mc1((c1)) --> mc2((c2)) --> mc3((c3)) --> mc
    end
    subgraph Squash ["squash merge"]
        sq1((m1)) --> sq2((m2)) --> sq3((squashed))
    end
    subgraph Rebase ["rebase merge"]
        r1((m1)) --> r2((m2)) --> rc1((c1')) --> rc2((c2')) --> rc3((c3'))
    end

팀 컨벤션의 핵심은 main의 history 모양에 대한 선택이다. 한 PR이 한 변경 단위로 깔끔하게 보이길 원하면 squash merge가 단순하다. PR 내부 commit이 의미 있는 단계라면(예: refactor → feature → cleanup) rebase merge가 그 흐름을 보존한다. 머지 흐름 자체가 협업의 흔적으로 가치 있다고 보면 merge commit이 자연스럽다.

선택 자체보다 일관성이 더 중요하다. main에 squash와 merge commit이 섞이면 그래프가 부분만 단순하고 부분만 분기 모양인 어색한 형태가 된다.

CI 게이트와 branch protection

PR이 단순히 사람의 리뷰만으로 머지되면 회귀가 자주 새어 나간다. status check이 자동 검증의 자리를 차지한다.

GitHub은 PR마다 등록된 status check(test, build, lint)의 결과를 본다. branch protection rule로 main에 머지하려면 특정 check이 모두 성공해야 한다고 선언해두면, 한 번 깨진 PR이 main으로 들어오는 일이 막힌다.

required reviewer 수, codeowner 자동 할당, 머지 전 최신 main으로의 강제 update 같은 옵션도 함께 잡아두면 PR 단계에서 빠뜨리는 검증이 줄어든다.

흔한 PR 함정

너무 큰 PR

수천 LOC PR은 리뷰어가 형식적으로 끝낸다. “전체적으로 OK"라는 코멘트만 남고 진짜 위험은 그대로 머지된다. 큰 작업은 의도 단위로 쪼개야 리뷰의 실질이 살아난다.

bikeshedding

코드의 본질과 무관한 사소한 선호(들여쓰기, 변수 이름의 어감)에 시간을 쓰는 패턴이다. 자동화 가능한 항목(linter, formatter)은 도구로 강제해 리뷰에서 빼는 게 가장 깨끗한 해결이다.

“LGTM” 자동 승인

매번 자동으로 approve가 찍히는 패턴이 굳어지면 리뷰가 형식만 남는다. 체크리스트(테스트 커버리지, 의도 변경 여부, 보안 영향)를 두거나 large PR은 다중 리뷰어를 강제하는 식으로 자동화에 의존하지 않는 안전판이 필요하다.

머지 충돌 누적

PR이 오래 열려 있으면 main과의 차이가 커지고, 머지할 때 충돌이 커진다. 짧은 PR + 빠른 리뷰가 가장 단순한 해결책이고, 그게 어렵다면 PR을 main과 자주 동기화해야 한다.

정리

GitHub PR은 Git 그래프 위에 협업의 단위를 더한 추상이다.

  • PR 단위: 한 가지 의도, 작게 쪼개기
  • Code Review: blocker와 nit 구분, 작성자가 먼저 자기 PR 보기
  • 머지 전략: 팀 컨벤션 안에서 일관성 유지
  • CI 게이트: branch protection으로 자동 검증을 머지 조건에 묶기

결국 PR의 가치는 단위 설계(의도 하나), Code Review(blocker / nit 구분), 머지 전략(일관성), CI 게이트 네 고리의 합으로 결정된다. 어느 하나라도 느슨해지면 PR의 마지막 단계에서 막힌다.

다음 편은 그 자동 검증 자체 — GitHub Actions로 PR을 어떻게 자동으로 빌드·테스트·배포하는가다.