3P by GN⁺ | ★ favorite | 댓글 1개
  • GitHub식 PR 리뷰는 원래 변경 위에 수정 커밋을 계속 쌓기 쉬워, 리뷰 과정과 최종 코드 이력이 뒤섞이는 문제가 생김
  • 좋은 패치 시리즈는 리팩터링, 새 API 추가, 기존 사용자 마이그레이션처럼 논리적으로 분리된 커밋을 순서대로 읽을 수 있어야 함
  • “fix review”나 "minor" 같은 커밋이 쌓이면 변경 의도가 흐려지고, git blamegit bisect로 문제 원인을 찾는 비용이 커짐
  • interdiff 리뷰는 패치 시리즈의 새 버전을 발행하고 git range-diff main..v1 main..v2로 커밋별 변경 차이를 비교함
  • 리뷰어는 500줄 전체를 다시 읽지 않고 50줄 증분만 확인할 수 있으며, 작성자는 “address review” 같은 잡음 커밋 없이 최종 커밋 구조를 유지할 수 있음

코드 리뷰 도구와 출발점

  • Gerrit Code Review는 Git 저장소와 함께 동작하는 오픈소스 코드 리뷰 도구로, 패치를 제출하고 다른 사람이 코드를 검토하며 코멘트를 남기는 흐름을 지원함
  • 코드 리뷰 방식은 Gerrit, GitHub, Phabricator뿐 아니라 버그 트래커에 .patch 파일을 올리는 방식, git send-email, 동료와 긴 논의 끝에 구현을 유도하는 방식까지 다양함
  • Go 프로그래밍 언어는 Gerrit을 사용하고, KDE와 LLVM은 Phabricator를 사용했으며, Jujutsu는 Gerrit 사용을 검토한 적이 있음
  • Linux 커널은 이메일 기반 워크플로를 특별한 방식으로 사용하고, 많은 프로젝트는 사용이 쉽고 시작 비용이 낮은 GitHub를 선택함
  • 핵심 문제는 도구들이 같은 리뷰 모델을 제공하지 않으며, GitHub의 기본 경험이 항상 충분하지는 않다는 점임

좋은 패치 시리즈의 조건

  • 이상적인 패치 시리즈는 하나의 큰 변경을 논리적 단계로 나눠 제출하는 형태임
    • 먼저 우연히 발견한 코드 정리를 수행함
    • 다음으로 새 API를 추가함
    • 마지막으로 기존 API 사용자들을 새 API로 마이그레이션함
  • 각 패치는 독립적인 이유를 가져야 하고, 순서대로 적용했을 때 코드가 단계적으로 진화하는 모습이 드러나야 함
  • 이런 구조는 작성자와 리뷰어뿐 아니라, 나중에 blame이나 bisect로 변경 이유를 찾아야 하는 유지보수자에게도 유리함
  • 실제 패치 크기는 상황에 따라 달라짐
    • 리팩터링이 없을 수도 있음
    • 100줄일 수도 있음
    • 핵심 API 변경처럼 모든 호출 지점을 고쳐야 하면 500줄이 될 수도 있음

GitHub식 “diff soup”가 만드는 혼합 이력

  • GitHub는 리뷰 코멘트를 반영할 때 원래 커밋 위에 새 커밋을 추가하는 흐름을 명시적·암묵적으로 유도함
  • 예시에서는 원래 3개 커밋 뒤에 Alice와 Bob의 리뷰 반영, 테스트 추가, "minor" 구현 변경 커밋이 차례로 쌓임
  • 커밋 그래프에는 부모·자식 관계만 남기 때문에, 리뷰 수정 커밋이 원래 어떤 커밋의 어떤 문제를 고쳤는지 드러나지 않음
    • 하나의 “fix review” 커밋이 여러 원래 커밋을 동시에 건드리면 개념 모델이 더 복잡해짐
    • 한 수정 커밋이 정확히 하나의 지적사항만 반영하게 만들 수는 있지만, 그만큼 fixup 커밋 수가 늘어남
  • 이렇게 큰 변경 묶음과 리뷰 수정이 한 그릇에 섞인 형태가 “diff soup”임

git blamegit bisect의 신호가 흐려짐

  • git blame은 줄 단위로 동작하므로, 리뷰 반영 커밋이 기존 줄의 작은 부분만 바꿔도 해당 줄의 출처가 "fix alice review" 같은 커밋으로 보일 수 있음
  • 실제 변경 이유를 찾으려면 원래 커밋과 리뷰 수정 커밋을 다시 따라가는 커밋 고고학이 필요해짐
  • git bisect에서도 "minor" 커밋이 실제 원인인지, 새 API가 처음 추가된 시점부터 있던 버그가 뒤늦게 드러난 것인지 불명확해질 수 있음
  • 새 API가 큰 성능 향상을 제공한다면 되돌리기가 바람직하지 않거나 불가능할 수도 있음
  • 회귀가 몇 주 뒤에 나타나면, P1 회의 같은 긴급 상황에서 "minor"라는 커밋 메시지만 보고 추가 조사를 해야 하는 비용이 생김
  • 리뷰가 두 번만 오간 예시에서도 이력이 복잡해지며, 오픈소스 프로젝트처럼 여러 차례 리뷰가 반복되면 문제는 더 커짐

GitHub UX가 이 흐름을 강화하는 방식

  • GitHub에서 증분 리뷰를 하려면 새 커밋을 위에 추가하는 방식이 사실상 가장 쉬움
    • 새 API 500줄을 다시 읽는 것보다 +/- 10줄짜리 "fix alice review" 패치를 보는 편이 리뷰어에게 부담이 적음
  • PR의 diff 탭은 기본적으로 모든 커밋을 하나의 diff로 합쳐 보여줌
    • 이는 git diff master..foo-branch를 보는 것과 비슷함
    • 사람들이 변경을 작성하고 읽는 방식과 잘 맞지 않음
  • GitHub UX는 이름 붙은 브랜치 모델에 강하게 맞춰져 있어, 브랜치 외의 단위 간 차이를 보기 어렵게 만듦
  • force push 후 PR의 “Force Push” 줄에 있는 “Compare” 버튼으로 diff를 볼 수는 있음
    • 하지만 이전 브랜치 head와 현재 브랜치 head 사이의 전체 diff만 보여줌
    • 더 세밀한 비교를 하려면 커밋 ID를 직접 찾아 URL에 넣어야 함

interdiff 리뷰와 git range-diff

  • interdiff 리뷰는 원래 3개 커밋 위에 수정 커밋을 추가하지 않고, 같은 3개 커밋으로 이루어진 새 버전을 발행함
  • 예를 들어 v1 패치 시리즈에 Bob의 코멘트를 반영해야 하면, 첫 번째 커밋과 세 번째 커밋을 수정해 v2 시리즈를 만듦
  • 이후 Alice의 코멘트, 테스트 추가, "minor" 변경까지 같은 방식으로 반영해도 최종 결과는 여전히 3개 커밋으로 남음
  • v1의 커밋 A, B, C가 v2의 X, Y, Z로 진화했다면, git range-diff는 각 위치의 커밋끼리 차이를 보여줌
git range-diff \
  main..v1 \
  main..v2
  • 이 명령은 v1 브랜치의 3개 커밋과 v2 브랜치의 3개 커밋 사이의 쌍별 diff를 보여줌
    • 첫 번째 커밋은 diff(A, X)
    • 두 번째 커밋은 diff(B, Y)
    • 세 번째 커밋은 diff(C, Z)

interdiff 방식의 실무 효과

  • GitHub가 보통 diff(main, C) 또는 diff(main, Z)처럼 전체 브랜치 diff를 보여주는 것과 달리, interdiff는 커밋 버전 간 차이를 보여줌
  • 리뷰어는 이미 본 500줄짜리 API 변경을 다시 읽지 않고, 수정된 50줄만 확인할 수 있음
  • 작성자는 “address review” 같은 잡음 커밋 30개를 히스토리에 쌓지 않아도 됨
  • git blame은 줄을 더 적은 잡음으로 원래 변경 커밋에 연결할 가능성이 높아짐
  • git bisect는 회귀를 새 API 커밋 같은 더 의미 있는 단위로 가리킬 가능성이 높아짐
  • 결과적으로 기본 진단 도구의 신호대잡음비가 좋아짐

병합 전략과 rebase에 대한 메모

  • 패치 병합 전략에 대한 설명은 TODO로 남아 있음
  • git rebase는 공개 브랜치에 다른 사람이 커밋을 기반으로 쌓을 것으로 기대되는 경우가 아니라면 괜찮다는 입장임
  • interdiff 리뷰 시스템은 더 작고 빠르게 병합 가능한 패치를 장려함
    • 5개 커밋이 모두 준비될 때까지 기다리지 않아도 됨
    • 앞의 3개가 괜찮고 뒤의 2개가 더 필요하면 3개만 병합할 수 있음
  • 목표는 사람들이 장기 브랜치가 아니라 main 브랜치를 기반으로 작업하게 만들어, 여러 원격 브랜치가 서로 얽혀 병합되는 상황을 줄이는 것임
  • Linux 커널처럼 공개 브랜치를 다른 저장소와 병합하거나 다른 브랜치 위에 명시적으로 공개하는 프로젝트도 있음
    • Linux 개발자들은 패치 시리즈를 만들고 다듬을 때 git rebase를 사용할 수 있음
    • 하지만 공개 브랜치를 만든 뒤에는 누군가의 트리에 병합될 수 있으므로 force push하지 않고 새 커밋이 있는 새 브랜치를 만듦

댓글과 토론

Hacker News 의견들
  • GitHub에서 대체로 이런 흐름을 쓰고 있는데, 단점은 내가 할 일이 더 많고 협업자에게 직관적이지 않다는 점임
    그래도 리뷰어가 자기 피드백만 반영된 차이를 볼 수 있고, git blamegit bisect를 망가뜨리지 않는 장점은 그대로 있음
    리뷰 피드백을 반영할 때 git commit --fixup 으로 커밋하고, 푸시한 뒤 리뷰 코멘트 답글에 fixup 커밋 해시를 남김
    PR이 승인되어 병합 직전이 되면 git rebase --interactive origin/main --autosquash를 실행해 fixup 커밋을 원래 커밋에 합치고, 마지막으로 git push --force-with-lease 후 병합함
    리뷰가 끝나기 전 force push는 주의해야 함. 그러면 리뷰어가 마지막 리뷰 이후 추가된 내용을 볼 수 없게 됨
    터미널 자동완성에 크게 의존해서 git re 정도만 쳐도 긴 명령까지 갈 수 있지만, 다소 투박하니 이 흐름을 지원하고 장려하는 도구가 있으면 좋겠음. 그래도 GitHub에 묶여 있다면 이 정도면 괜찮음

    • 나도 그렇게 말하려 했음. 대화형 rebase는 글쓴이가 불평한 “diff soup” 문제를 꽤 많이 해결해 줌
      다만 git의 고급 기능을 일부러 배우는, 규율 있는 엔지니어링 팀에서나 실제로 쓰이는 편임
    • ~/.gitconfig에 아래 설정을 넣으면 됨
      [rebase] autosquash = true
      그러면 이후 git rebase -i origin/main만으로도 fixup/squash 커밋이 자동으로 재정렬됨. 작은 설정이지만 작업 흐름이 크게 좋아졌음
    • https://news.ycombinator.com/item?id=37086022
    • fixup 커밋도 좋은 접근이지만 개인적으로는 좋아하지 않음. Sapling의 absorb 명령처럼 내부 SCCS weave 위에서 관련 차이에 변경을 자동 흡수하고, 대화형 UI로 확인시키는 방식이 훨씬 우아하다고 봄
      여러 브랜치가 얹힌 커밋 시리즈를 rebase할 때 --update-refs가 필요한 것과 비슷한 영역임. 사람이 그래프 관계를 추적하고 커밋을 직접 넣고 브랜치를 옮기는 일을 왜 해야 하나 싶음. 컴퓨터는 그래프를 잘 다루니 맡기면 됨
      git absorb도 있지만 Sapling 구현만큼 견고하지는 않음[1]
      진짜 문제는 대화형 rebase 여부가 아니라, 대체로 리뷰 도구 자체의 사용자 경험과 그것이 유도하는 순환 구조임. 예를 들어 fixup 커밋은 GitHub가 기준점 간 차이를 보여주는 문제를 해결하지 못하고, 기준점이 크면 리뷰를 완전히 망칠 수 있음. 예컨대 새 커밋 10개 위로 rebase한 경우가 그렇다
      Git 자체의 UX에도 불만은 있지만, 원문은 주로 GitHub에 대한 불평에 가깝다
      [1] 두 기반 알고리즘의 차이를 보여주는 예시는 이 GitHub 이슈에 있음: https://github.com/martinvonz/jj/issues/170
    • Git 별칭은 정말 마법 같음
  • 이 방식이 이상적이라는 데 100% 동의함. GitHub 방식은 정말 끔찍하고, 너무 많은 사람이 그걸 당연하게 받아들이게 된 게 비극임
    예전에는 Phabricator로 이렇게 했고, 모든 리뷰를 한 번에 갱신하는 명령줄 매크로 덕분에 어느 정도 수동으로 가능했음. 그래도 명시적인 UI가 있으면 더 좋겠음

    • 내가 글쓴이인데, 첫머리에 “코드 리뷰는 대체로 꽤 좋은 아이디어다”라는 표현을 일부러 썼음. 예전에 Phabricator 홈페이지에 있던 판매 문구 중 하나였기 때문임 :) 그립다
  • 맞음. 머릿속에서 진짜 코드 리뷰 스타일로 상상하던 게 바로 이거고, GitHub가 하는 방식은 아님. 이름이 있다는 걸 알게 되어 반가움
    추가로, 리뷰 시스템이 준비된 패치를 리뷰에서 “밖으로” 밀어낼 수 있으면 좋겠음. 큰 기능을 작업하다가 만든 작은 버그 수정은 작고 독립된 패치여야 하고, 리뷰어와 빠르게 합의될 가능성이 큼
    그러면 그 패치를 전체 시리즈에서 빼내 main에 cherry-pick하고, 리뷰를 새 HEAD 위로 rebase하고 싶음. 또는 최신 패치 시리즈를 main 위로 rebase하되 합의된 패치를 맨 앞으로 재정렬한 다음, main을 그 패치로 fast-forward할 수도 있음
    본질적으로 리뷰 범위를 “아직 논의 중인 부분”으로 좁히되, 버그 수정은 준비되는 즉시 병합되게 하자는 뜻임
    반론은 “그냥 별도 리뷰/PR로 만들라”겠지만, 그러면 패치셋 A가 패치셋 B에 의존하다가 B가 병합된 뒤에는 main에 의존하는 식의 복잡함이 생김

    • 계속 반복해서 말하지만, Gerrit은 기본적으로 그걸 해줌 :) Gerrit에서는 임의의 두 패치 사이 관계를 볼 수 있고, 더 중요하게는 각 패치를 개별적으로 보여줌. 그래서 A -> B -> C 시리즈에서 B가 작고 괜찮으면 바로 넣을 수 있음
      여기에는 Attention Set 같은 똑똑한 UX 아이디어도 한몫함. 기본적으로 “다음 행동을 해야 하는 사람이 누구인가?”를 나타내며, 턴제 게임처럼 동작함. 리뷰를 방금 했다면 그 패치의 attention set에는 더 이상 내가 없고 작성자가 있음
      그래서 Gerrit UI에서는 그 항목이 내 큐 아래쪽으로 내려감. 큐 맨 위에는 내가 attention set에 들어간 항목들이 있음. 자연스럽게 일이 이런 식으로 묶임
      GitHub UX의 다른 짜증나는 자잘한 문제까지 다루지는 않았지만, pull request 목록조차 대안보다 나쁨. 어떤 항목이 어떤 상태인지 알 수 없고, 결국 전체를 읽어야 함
    • Gerrit이 정확히 그걸 할 수 있음. x-b-c-d-e 체인을 push하면 UI에 쌓인 형태로 보이지만, b를 main에 쉽게 cherry-pick하고 CI 통과와 일반 리뷰를 확인한 뒤, 나머지를 그 위로 rebase할 수 있음
      맨 아래인 x라면 바로 제출하고 나머지를 계속 진행할 수 있음
  • 코드 리뷰에 대한 새로운 접근을 보는 건 항상 흥미로움. GitHub도 장점은 있지만 완벽과는 거리가 멂
    제시한 시나리오라면 세 패치를 서로 의존하는 별도 pull request로 나누는 걸 생각해볼 수 있음. GitHub가 기본 지원하지는 않지만, 적절한 코드 리뷰 도구라면 pull request를 작게 유지하면서도 의존성을 관리할 수 있어야 함. 예를 들어 패치 3이 패치 2에, 패치 2가 패치 1에 의존하게 할 수 있음
    도구가 제공하는 의존성 추적 덕분에 필요하다면 모두 함께 배포되도록 보장할 수 있음
    각 패치를 따로 리뷰하면 피드백이 더 명확하고 대응도 쉬워짐. pull request 안에서 커밋을 squash해 개별 변경을 정확히 반영하는 깔끔한 커밋 기록도 만들 수 있음. 더 나아가 적절한 도구라면 AI로 pull request와 리뷰를 요약해, 수작업 없이 정확한 커밋 메시지 작성도 간소화할 수 있음
    좋은 코드 리뷰 도구는 rebase, merge, force push 같은 git 작업에 휘둘리지 않아야 함. 뒤에서 어떤 복잡한 git 작업이 일어나든 리뷰어는 마지막 리뷰 이후 변경만 봐야 함. 그러면 큰 diff를 다시 리뷰하지 않고 새 내용에 집중할 수 있고, 리뷰 기록은 커밋 기록과 분리되어 깨끗하게 유지됨
    이런 식으로 pull request를 나누고 상호 의존성을 추적하는 방식이 요구를 충족할지 궁금함

    • “코드 리뷰에 대한 새로운 접근”이라는 표현은 긍정적이고 기존 강자에게도 예의 있는 좋은 말이지만, 이건 전혀 새로운 아이디어가 아님
      말 그대로 git이 지원하도록 설계된 작업 흐름이고, GitHub가 pull request를 갖춘 이후 줄곧 제기된 오래된 비판의 핵심임
      이 아이디어가 달력을 확인해 보니 15년 넘게 “메일링 리스트 퇴물들”의 방식으로 폄하되다가 이제야 유행하는 개발 문화권으로 올라온 이유가 궁금함
      많은 소프트웨어 개발자가 가장 많이 쓰는 도구 중 하나를 제대로 배우길 오래도록 거부했기 때문에, 일부러 나쁜 작업 흐름을 선택하고 있다고 생각했음. 그래서 회사를 git으로 옮길 때 그런 도구와 흐름을 선택하고 만들었음. 어쨌든 IBM을 샀다고 해고되지는 않으니까
    • 내가 알기로 시리즈를 개별 PR로 나누는 방식은 저장소에 커밋 권한이 있어야만 제대로 동작함. 그래야 한 PR을 main이 아니라 메인 저장소의 다른 브랜치 위에 올릴 수 있음
      외부 기여자가 저장소 fork를 쓰는 경우, 세 PR은 각각 A, A+B, A+B+C를 점진적으로 포함하게 됨. 그러면 마지막 두 PR에서는 이미 다른 PR에서 리뷰한 코드까지 다시 diff로 봐야 해서 리뷰가 어려워짐
    • 작성자는 이미 브랜치와 그 커밋들을 갖고 있음. 그렇다면 브랜치를 커밋 하나씩 담은 세 pull request로 나눌 목적이 무엇임?
      각 커밋은 독립적으로 리뷰 가능함. 커밋 간 의존성 추적도 가능함. AI가 내장되어 있지 않은 것도 좋은 점임. PR과 달리 커밋 간 interdiff도 볼 수 있음
      커밋은 Git의 기본 재료임. 그냥 커밋을 쓰면 됨
    • 이 스레드의 다른 곳에서도 나왔듯, 이건 Sapling이 따르는 접근이기도 함
      GitContext는 fixup, rebase, 재정렬 같은 작업을 거친 커밋을 어떻게 추적하는지 궁금함
    • 그냥 오래된 merge train 방식으로 하면 안 되나? pull request A가 브랜치 B를 가리키고, B가 master를 가리키게 한 다음 B를 master에 병합하고 이후 A를 다시 master로 돌리면 되는 것 아닌가, 내가 요점을 놓친 건가?
  • 이 글을 쓴 사람임. 무엇이든 물어보세요
    추가로, 규칙에 어긋나는지 모르겠지만 최근 새 직장이 필요해졌음. 개발 도구와 어려운 문제를 다루는 일을 좋아함. 이 글이 마음에 들었거나, 개발팀 생산성을 높이고 싶거나, 내 훌륭하고 가끔 절충적인 취향을 경험하고 즐기고 싶다면 프로필에 이메일이 있음

    • 좋은 웃음을 줘서 고마움 :)
      진지하게 말하면 이력서가 인상적임. 잘, 그리고 빨리 자리 잡길 바람. 아주 최근 구직 경험상 현재 채용 시장은 치명적으로 병들어 있음. 더 시니어이고 경험이 많을수록 미친 면접·HR 관행과 설명 안 되는 거절이 영혼을 더 아프게 함
      친구가 아래 링크들을 보내줬음
      https://danluu.com/hiring-lemons/
      https://danluu.com/programmer-moneyball/
      https://danluu.com/algorithms-interviews/
      행운을 빔
    • interdiff 리뷰 스타일의 사고 모델과 GitHub 코드 리뷰 접근의 문제를 아주 잘 요약했음
      아직 다루지 않은 한 가지는, diff soup 때문에 사람들이 fixup 커밋의 “잡음”을 없애려고 squash merge 전략을 선호하게 될 수 있다는 점임. 그러면 처음의 좋은 원자적 커밋 3개까지 함께 버리게 됨
      interdiff 리뷰 스타일에서는 처음 3개 커밋이 남고, 개별로 반영할지 squash할지는 전적으로 그 커밋 자체와 실제 원자성에 따라 결정할 수 있음
    • GitHub 무료 부분을 써본 경험으로는 diff soup라는 표현이 아주 잘 맞음. 유료 버전은 더 나은가? GitLab은 Gerrit에 가까워질 수 있나?
      GitHub의 고통을 줄이려는 외부 서비스들도 있고, stacked diff 지원을 제공하지만 꽤 비싸 보임. 특히 self-hosted Gerrit과 비교하면 더 그렇다. 이런 도구들도 살펴봤는지 궁금함
    • Merge Request Dependencies와 Squash+Merge 병합 전략을 설정한 GitLab에 대해서는 어떻게 생각하는지 궁금함
      예전에 MR 시리즈 패턴을 꽤 쉽게 구성했던 기억이 있음. 3개의 MR을 병합하면 각 MR마다 단일 squash 커밋을 가진 3개의 merge commit이 생겼음. MR의 커밋 기록과는 무관했음
      트레이드오프는 리뷰 중 앞쪽 시리즈 브랜치가 바뀌면 뒤쪽 시리즈 브랜치에 다시 merge해야 한다는 점임
      그래도 리뷰 중에는 사람들이 커밋 기록을 원하는 대로 다뤄도 됨. 어차피 squash되니까 상관없음
      다만 해본 지 오래됐고, 요즘은 GitHub에 묶여 지내다 보니 장밋빛 기억으로 보고 있을 수도 있음
  • Review Board [https://www.reviewboard.org]에서 2006년에 이미 interdiff를 만들었음. 사실 그 용어를 내가 만들었거나 독립적으로 도달했을 수도 있음
    지금도 제품과 코드 리뷰 과정에서 가장 좋아하는 부분이고, 사람들이 GitHub 같은 것으로 옮길 때 가장 그리워한다고 많이 듣는 기능 중 하나임
    fix-it 커밋은 제대로 된 대안이라고 느낀 적이 없음
    첫째, 상위 변경이 커밋 시리즈에 어떻게 반영됐는지 알려주지 않음
    둘째, 일시적이라도 커밋 그래프를 흐트러뜨리고 리뷰를 어렵게 함. 리뷰를 계속 따라오던 사람은 fix-it 커밋이 고치는 코드를 이미 읽었을 수 있지만, 새로 들어온 사람은 그 코드가 무엇을 하려는지나 구조가 어떤지 나쁜 첫인상을 갖고 시작할 수 있음
    셋째, 모두가 Git이나 여러 커밋을 다룰 수 있는 형상 관리 시스템을 쓰지는 않음. 게임 개발에서는 Perforce를, 칩 제조사에서는 Keysight SOS 같은 특수 형상 관리 시스템을 쓰는 경우도 많음. 그런 곳에서는 fix-it 커밋 자체가 선택지가 아님
    제대로 된 interdiff 지원 코드 리뷰 시스템이라면 한 리뷰어는 처음 공개된 리뷰 요청부터 모든 업데이트를 따라가며 바뀐 것만 볼 수 있고, 다른 리뷰어는 최신 전체 변경으로 바로 들어가 fix-it 시리즈를 신경 쓰지 않아도 됨. 그리고 이건 형상 관리 시스템과 무관하게 가능함
    제대로 만들면 여러 커밋으로 된 변경과도 공존할 수 있음
    예를 들어 작은 프로젝트를 리뷰를 돕기 위해 API 핸들러, 프런트엔드 UI, 문서의 여러 커밋으로 나눴고, 커밋들이 밀접히 연관되어 하나의 리뷰 요청으로 올리는 게 적합하다고 판단할 수 있음. 서로 관련이 낮다면 의존성 체인으로 여러 리뷰 요청을 만드는 편이 이상적일 것임
    리뷰 피드백에 따라 그 커밋들 중 하나 또는 전부에 일련의 변경을 만들 수 있음. 사람들이 업데이트를 리뷰할 때는 fix-it 커밋과 그 변경을 대응되는 원래 변경에 머릿속으로 매핑하지 않고, 각 부분이 어떻게 진화했는지 볼 수 있으면 좋음
    그래서 interdiff는 정말 훌륭함. 작은 커밋이 많든 큰 커밋이든, 단일 커밋 리뷰 요청이든 다중 커밋이든 더 많은 사람이 써야 함

    • 정말 끝내준다. 사실 이 글의 2부에서 이런 도구들까지 오게 된 역사와 정치적 맥락을 다룰 예정임
      2013년쯤 Glasgow Haskell Compiler를 농담처럼 말한 “버그 리포터에서 .patch 파일 읽기” 방식에서 Phabricator 사용으로 옮겼음. 이유가 몇 가지 있었지만, 당시에는 stacked diff 때문이 아니었음. GitHub가 리뷰에 너무 나빠서 side-by-side diff조차 없었기 때문임. 그 이유만으로도 완전히 논외였고, 다른 이유도 있었음
      하지만 코드 리뷰 도구로 팀을 옮긴 건 그때가 처음이 아니었음. 2009년 첫 직장은 Houston의 한 방에 모인 아주 작고 끈끈한 엔지니어 팀이었고, 다른 사람에게 내 코드를 리뷰받고 그들이 쓴 것을 읽으면 코드베이스를 더 잘 이해할 수 있겠다고 생각했음
      그래서 입사 후 처음 몇 달 동안 매니저를 졸라 ReviewBoard를 설치하게 했고, 모두가 정말 좋아했음
      그러니까 RB에 고맙다는 뜻임. 아직도 가끔 좋은 기억으로 떠올림. 그 덕분에 코드 리뷰는 거의 첫날부터 내 커리어의 아주 큰 부분이었고, 지금도 더 많이 할 수 있다고 생각함
  • 전반적으로 코드 리뷰 우선이고 rebase 중심인 Gerrit 같은 시스템이 코드를 훨씬 리뷰하기 쉽다고 느꼈음
    가장 좋은 점 중 하나는 여러 패치를 쌓는 기능을 기본 지원해서 사람들이 더 작고 리뷰하기 쉬운 패치를 만들게 된다는 것임
    GitHub의 코드 리뷰는 나쁜 사후 추가 기능처럼 느껴짐. 포럼 스레드처럼 보이는 공간 낭비 UI, rebase를 넘어 추적하지 못하는 점 등이 그렇다

    • Gerrit에서 커밋 스택을 push할 때 아쉬운 점이 하나 있음. 개별 커밋이 아니라 스택 전체에 대해 이야기할 중앙 장소가 없음
      이런 “큰 그림”이면서도 기술적인 논의가 너무 자주 이슈 트래커에서 일어남. 하지만 어디에 둬야 할지도 모르겠음. 이 스택은 너무 일시적이고 다음 push에서 완전히 달라질 수 있음
  • 이런 전략을 주장하는 걸 몇 번 들었지만 설득되지는 않음. 내가 작업하는 대부분의 프로젝트는 feature branch를 단일 커밋으로 squash해 병합하고, 브랜치 기록은 지움
    글쓴이가 설명한 경우가 있다면 리팩터링, 새 API, 업데이트를 그냥 3개 커밋으로 만들 것임
    내게 확실히 좋은 관행이었던 건 오래 사는 브랜치를 피하는 것임. 이런 다단계 커밋 시나리오는 보통 거기서 생김. 누군가 기능을 며칠, 심하면 몇 주 동안 작업한 뒤 한 번에 던지고 싶어 하는 상황 말임
    나는 매일, 또는 하루에도 여러 번 main으로 옮기는 걸 훨씬 선호함. 이를 위한 한 방법은 기능 플래그를 써서 진행 중인 커밋도 문제 없이 반영되게 하는 것임. 시스템이 제대로 구성되어 있으면 개발·스테이징 환경에서 켜 테스트할 수 있다는 장점도 있음. blue/green 배포와도 한 발짝 거리임
    큰 커밋을 더 쉽게 리뷰하는 방법은 원하지 않음. 팀이 작은 변경을 일찍 자주 커밋하도록 강제하고 싶음. 모두가 동의하지는 않는다는 건 이해함

    • stacked diff를 선호하는 사람들은 크기와 상관없이 변경을 더 빨리 통합하기 쉬워진다고 말함
      그 이유 중 하나는 GitHub의 PR과 달리 스택 일부만 반영할 수 있기 때문임. 글의 예시로 보면 “작은 리팩터링” diff가 준비됐다면 “새 API”와 “API 사용자 마이그레이션” diff를 함께 반영하지 않고도 먼저 넣을 수 있음
      브랜치가 아니라 커밋 중심으로 두면 오래 사는 브랜치보다 전체 커밋이 더 작아지는 효과가 있음
    • feature branch를 단일 커밋으로 squash해 브랜치 기록을 지우는 건 끔찍함
      그러면 git blamegit bisect가 사실상 쓸모없어짐
      회귀가 있으면 git bisect가 단일 패치까지 좁혀줄 수 있음. 그래서 특정 기능에 대해 8000줄짜리 패치 하나보다 160줄짜리 패치 50개가 git 기록에 있는 편이 좋음
      어떤 코드 한 줄이 수상해 보이면 git blame 또는 연속된 git blame 명령이 8000줄짜리 커밋이 아니라, 자세한 커밋 메시지를 가진 160줄짜리 커밋으로 안내해주길 원함
      원래 커밋의 순서도 보존해야 함. 몇 년 뒤 개별 커밋 메시지를 순서대로 읽는 것만으로도 원래 설계를 이해하는 데 매우 도움이 될 수 있음. 물론 원래 패치셋은 의존성 순서로 구성되어야 하고, 각 단계에서 컴파일되어야 하는 등 기능 구현 위에 별도의 개발 단계가 필요함. 코드는 논리적 단계로 제시되어야 함
  • 흥미로움. 직장에서는 글쓴이가 커밋을 쓰는 방식으로 PR을 쓰고, 마지막에는 실제로 squash-and-merge함
    하지만 이 방식은 앞쪽 PR을 바꿀 때마다 뒤쪽 PR들을 rebase해야 함. 꽤 번거롭고, “force-push 금지” 규칙에도 걸리며, 엔지니어가 배우는 데 오래 걸리고, GitHub UI의 기존 코드 리뷰 코멘트를 깨뜨리는 경향이 있음. 그래도 두세 PR 정도라면 그럭저럭 동작함
    우리 흐름에서 커밋은 작업 단위라기보다 저장 지점에 가까움
    stash도 많이 쓰고 Emacs의 undo-tree-mode도 많이 씀. 그러면 소스 코드 기록을 추적하는 방법이 네 가지가 되는데, 중복처럼 들리지만 실제로는 괜찮게 굴러감
    Git에서 이걸 다루는 사용성은 꽤 나쁨. Phabricator가 더 낫긴 했지만 그래도 불필요하게 어려웠음. 어쩌면 새 형상 관리 도구가 커밋과 브랜치보다 더 높은 수준의 개념을 일급으로 지원할 수도 있고, 어쩌면 그러면 더 쓰기 나빠질 수도 있음

    • 커밋을 PR처럼 쓰고 있음. 그래서 PR 의존성 때문에 일이 복잡해짐
      Git보다 나은 도구가 있으면 좋겠느냐? 물론 나도 흠이 적고 더 사용자 친화적인 도구를 원함. 하지만 임시방편으로 커밋을 커밋처럼 다루면 어떨까? 굳이 스스로 어렵게 만들지 말고
      GitHub가 사람들에게 PR 색안경을 씌운 책임이 얼마나 큰지 궁금함
    • jj, sapling, mercurial을 살펴보면 좋겠음
  • 블로그 글이 좋았고, 코드 리뷰가 어떻게 동작해야 하는지 실제로 고민하는 사람들이 보여서 반가움
    서로 다른 장단점을 가진 네 가지 코드 리뷰 시스템을 많이 써봤음: Critique(Google 내부), Gerrit(Google에서 썼지만 외부와 같음), GitHub, 그리고 내가 만든 CodeApprove
    Critique가 압도적으로 최고였지만, Google의 monorepo와 자체 형상 관리 시스템, 커스텀 lint/test 도구에 완벽히 맞춰져 있기 때문에 가능했음. CodeApprove는 그 장점을 최대한 GitHub로 가져오려고 설계했지만, 실제로 거기에 가까워지기는 어려움
    Gerrit은 리뷰어 경험 측면에서 두 번째로 좋았지만, 작성자 입장에서는 늘 싫었음. 올바른 방법보다 잘못할 방법이 더 많아 보였고, UI도 딱히 아름답지 않음
    GitHub는 작성자에게는 매우 친화적이고 우리가 생각하는 방식대로 동작함. 코드를 쓰고, 피드백을 받고, 더 코드를 쓰는 식임. PR 끝에서 squash and merge하면 글쓴이가 말한 기록 문제는 없음
    하지만 리뷰어나 팀에는 그다지 친화적이지 않음. 증가분 diff가 강조되지 않고, diff와 대화가 다른 탭에 있으며, force push와 rebase가 기록을 파괴함. 코멘트는 “outdated”로 사라지고, diff 창 밖 파일에는 코멘트를 달 수 없으며, 큰 파일은 기본으로 숨겨지는 등 문제가 많음. GitHub는 이걸 별로 신경 쓰지 않는 게 분명하고, 어쩌면 내가 모르는 무언가를 알고 있을지도 모름
    결국 가장 답답한 건 많은 팀이 형상 관리 플랫폼에 내장된 코드 리뷰 도구를 그냥 받아들인다는 점임. 노트북에 기본 탑재된 IDE를 그대로 쓰는 것과 같음. 지금은 훨씬 나은 선택지가 많음. CodeApprove 외에 내가 좋아하는 건 GitContext, Reviewable, Graphite이고, 그 밖에도 훌륭한 선택지를 여섯 개는 더 댈 수 있음. 기본값을 받아들이지 말아야 함

    • GitHub가 리뷰어나 팀에 친화적이지 않다는 점이 문제임
      리뷰어와 유지보수자는 기여자보다 훨씬 희소함. 작업 흐름과 UI는 리뷰 처리량을 최적화해야 한다고 봄
      언급한 다른 세 도구는 써본 적 없어서, 내 주장은 일반론임