GitHub Pull Request를 Changeset 중심으로 재편하기
원문은 Mitchell Hashimoto님이 에 게재했습니다. 이 블로그 구독하기
저는 1,000명 이상의 기여자가 참여한 초대형 오픈소스 프로젝트의 메인테이너로, 초대형 클로즈드소스 기업 프로젝트의 엔지니어로, 그리고 그보다 작은 다양한 규모의 프로젝트에서 GitHub를 사용해 왔습니다. 지금까지의 경험을 통틀어 보면, GitHub에서 보내는 시간의 거의 대부분은 pull request에서 보내고 있으며, 안타깝게도 제게는 GitHub에서 가장 답답한 부분이기도 합니다.
pull request에 바라는 개선점은 많지만, 제가 겪는 문제의 상당 부분은 하나의 핵심 기능으로 해결될 수 있습니다. 바로 changesets입니다. 이 글에서는 이 제안을 구체적으로 설명하고 제가 바라는 모습을 이야기하려 합니다.
알림: 여기에 담긴 아이디어는 독창적인 것이 아닙니다! 제가 처음 고안했다고 주장하지 않습니다. 여기서 제안하는 내용은 이미 충분히 검증된 Git 워크플로에 기반하고 있으며, Gerrit, Phabricator 혹은 전통적인 이메일 기반 패치 리뷰 같은 다른 제품들에서 부분적으로 혹은 전체적으로 이미 구현되어 있습니다.
지금의 문제점
오늘날 GitHub pull request의 생명주기는 사실상 하나의 거대한 가변 changeset과 같습니다. 완전히 엉망입니다!
오늘날 전형적인 PR은 이렇게 진행됩니다. 기여자가 브랜치에 커밋들을 푸시하고 PR을 열면, 그 PR은 해당 브랜치 자체가 됩니다. 사람들은 댓글로 PR에 대해 논의합니다. 기여자가 새로운 변경 사항을 푸시하면 같은 PR에 바로 나타나 즉시 업데이트됩니다. 리뷰어가 댓글을 남기는 동시에 기여자가 변경 사항을 푸시할 수도 있고, 그 모든 것이 동일한 PR을 갱신합니다.
이 방식에는 많은 문제가 있습니다:
리뷰어가 PR의 이전 상태에 대해 리뷰를 남겼는데, 리뷰가 진행되는 동안 기여자가 변경 사항을 푸시하면서 그 리뷰가 즉시 구식이 되어 버릴 수 있습니다.
더 나쁜 경우, 리뷰가 부분적으로만 구식이 되면서 나머지 피드백이 기여자가 푸시한 변경 사항의 맥락에서는 이해되지 않을 수 있습니다. 예를 들어 라인 코멘트에 ‘이전 코멘트와 동일한 피드백’이라고 적혀 있는데, 기여자가 해당 라인을 옮기는 변경을 푸시하면서 앞선 코멘트가 사라지거나 숨겨져 버리는 경우가 있습니다.1
리뷰에는 어떤 커밋에 달린 것인지에 대한 메타데이터가 없고 제출된 시점의 타임스탬프만 있습니다. 사용자는 타임스탬프를 커밋과 대략 연관 지어 볼 수 있지만 완전히 정확하지는 않습니다. 리뷰 도중에 커밋이 들어오면 타임스탬프상으로는 가장 최근 커밋 이후에 작성된 것처럼 보이지만, 실제 리뷰는 이전 커밋을 대상으로 한 것일 수 있기 때문입니다. 😕
리뷰 피드백을 반영하기 위한 작업 중인 커밋도 브랜치에 푸시되는 즉시 공개됩니다. 때문에 기여자는 모든 피드백을 하나의 커밋으로 처리해야 하거나, 리뷰어가 부분적으로만 반영된 피드백을 감수해야 합니다.
PR의 이전 상태를 쉽게 되돌려 볼 수 없습니다. 이후 커밋을 제외하고 이전 커밋들만 리뷰하고 싶다면, 직접 ‘compare’ 뷰를 만들거나 로컬에서 체크아웃해야 합니다(저는 후자를 사용합니다). 하지만 어느 쪽을 쓰더라도 코드 변경 사항만 볼 수 있을 뿐, 해당 시점의 리뷰 피드백까지 함께 볼 수는 없습니다!
위와 비슷하게, 기여자가 여러 개의 새 커밋을 푸시한 경우에도 새로운 커밋 묶음을 이전과 쉽게 비교할 수 없습니다. 실제로는 한 번에 하나의 커밋씩만 훑어볼 수 있습니다. 이 경우에도 결국 로컬
git으로 직접 diff를 만들어 비교해야 합니다.그 외에도 더 있습니다… 뒤에서 몇 가지를 더 다루겠지만, 일단 요점은 충분히 전달되었다고 생각합니다.
위에서 언급한 몇 가지 세부 사항은 제가 틀렸을 수도 있습니다. 누군가는 ‘문제 5(a)는 그냥 이렇게 하면 해결되는데’라고 말할지도 모릅니다. 그런 지적은 도움이 됩니다! 하지만 제가 말하고자 하는 요점은 한 걸음 물러서서 보면 이런 문제들을 근본적으로 일으키는 원인이 진짜 문제라는 것입니다. 바로 브랜치를 커밋 단위로 추적하는 단일 가변 changeset입니다.
Changesets
해결책은 changesets입니다: pull request를 단조 증가하는 번호(v1, v2, …)로 버전을 관리할 수 있게 하는 것입니다. 이러한 버전을 흔히 ‘changeset’이라고 부릅니다.
각 changeset은 특정 시점에 고정된 브랜치의 상태를 가리킵니다. 이 버전들은 불변입니다. 새로운 커밋이 푸시되면 그것은 새로운 changeset의 일부가 됩니다. 기여자가 브랜치를 force push하더라도 마찬가지로 새로운 changeset이 됩니다. 이전 changeset은 영원히 보관됩니다.
새 changeset은 즉시(커밋 단위로) 게시될 수도 있고, 기여자가 리뷰를 위해 새 버전을 제안하기로 결정할 때까지 미룰 수도 있습니다. 후자의 방식이라면 기여자가 이전 피드백을 반영해 여러 커밋을 만든 뒤 준비가 되었을 때만 변경 사항을 게시할 수 있습니다.
changeset 방식에서는 피드백이 특정 changeset에 귀속됩니다. 리뷰어가 어떤 changeset을 리뷰하기 시작했는데 새로운 changeset이 게시되더라도, 리뷰 자체가 원자적 단위로 이전 changeset에 연결되어 있기 때문에 문제가 되지 않습니다.
이후의 changeset에서는 이전 changeset에 아직 해결되지 않은 코멘트가 있는 파일이나 라인을 표시해 주는 것이 유용한 경우가 많습니다. 이를 통해 이전 changeset에 대한 피드백이 유실되지 않고, 어떤 changeset이 승인되기 전에 반드시 처리되도록 할 수 있습니다.
일반적으로 각 changeset은 서로 다른 Git ref로 표현됩니다. 예를 들어 오늘날 GitHub pull request는 보통 refs/pr/1234이며 로컬에서 git으로 이렇게 pull request를 체크아웃할 수 있습니다. changeset이라면 가령 refs/pr/1234/v2(가상) 같은 형태가 되어 개별 changeset도 체크아웃할 수 있게 됩니다.
PR 자체를 ‘승인’하고 머지하는 대신, 리뷰어는 특정 changeset을 승인합니다. 이는 기여자가 하나의 PR 안에 문제에 대한 서로 다른 접근 방식을 담은 여러 changeset을 올릴 수 있고, 메인테이너가 반드시 최신이 아닌 changeset을 머지할 변경 사항으로 선택할 수도 있음을 의미합니다.
GitHub, 부탁합니다!
Changeset은 많은 오픈소스 프로젝트와 기업에서 이미 잘 확립된 패턴입니다. Gerrit나 Phabricator 같은 기존 제품들에서 사용자 경험 문제로도 이미 충분히 탐구되었습니다. 또한 저는 changeset이 기존 기능을 깨뜨리지 않는 방식으로 도입될 수 있다고 믿습니다(현재의 PR은 단일 가변 changeset 모드와 같기 때문입니다).
Changeset이 도입되면 pull request는 더 큰 프로젝트와 조직에서도 훨씬 더 확장 가능해질 것입니다. 확장성뿐 아니라, pull request에 참여하는 양쪽 모두에게 리뷰 과정을 더 깔끔하고 안전하게 만들어 줍니다.
물론 제 자신과 제 경험에 대해서만 말할 수 있지만, 이 하나의 핵심 기능만으로도 GitHub를 사용하는 동안 제 삶의 질과 역량이 크게 향상될 것입니다.2
각주
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기