gh stack으로 PR 쌓아 올리기
PR 하나에 파일 30개가 바뀌고 코드가 2,000줄 넘게 추가된 걸 리뷰해달라고 받아본 적 있으신가요? 😅 리뷰어 입장에서는 어디서부터 봐야 할지 막막하고, 작성자 입장에서는 피드백을 기다리는 시간이 길어지죠.
“PR은 작게 만들어라”라는 원칙은 누구나 알고 있지만 실제로 지키기가 쉽지 않습니다. 큰 기능을 구현하다 보면 인증 레이어, API 엔드포인트, 프론트엔드 화면이 서로 물려 있어서 하나만 떼어내기 어렵거든요. 억지로 쪼개봤자 의존하는 브랜치끼리 rebase를 수동으로 맞추느라 시간을 허비하게 됩니다.
이 문제를 해결하기 위해 GitHub이 직접 만든 도구가 바로 gh stack입니다. 큰 변경사항을 여러 개의 작은 PR로 쪼개고, 그 PR들 사이의 의존 관계와 rebase를 자동으로 관리해줍니다.
Stacked PR이란?
Stacked PR은 하나의 큰 변경을 여러 PR로 나눠서 아래에서 위로 쌓아 올리는 방식입니다. 각 PR이 바로 아래 PR의 브랜치를 base로 삼고, 가장 아래 PR만 main을 직접 가리킵니다.
flowchart TB
accTitle: main 위에 쌓인 세 개의 PR
accDescr: main 브랜치를 기반으로 auth-layer PR이 놓이고, 그 위에 api-routes PR과 frontend PR이 차례로 쌓인다. 각 PR은 바로 아래 단계의 브랜치를 base로 사용한다.
main["main<br/>(trunk)"]
auth["PR #1<br/>auth-layer"]
api["PR #2<br/>api-routes"]
frontend["PR #3<br/>frontend"]
main -->|"base"| auth
auth -->|"base"| api
api -->|"base"| frontend
classDef trunk stroke:#64748b,stroke-width:3px
classDef pullRequest stroke:#0284c7,stroke-width:2px
class main trunk
class auth,api,frontend pullRequest
이렇게 쌓으면 각 PR이 하나의 논리적 단위만 담당합니다. 리뷰어는 PR #1에서 인증 코드만, PR #2에서 API 라우트만, PR #3에서 화면 코드만 보면 되죠. PR 하나당 변경 범위가 작으니 리뷰 품질도 올라가고 피드백 주기도 빨라집니다.
문제는 이걸 수동으로 관리하면 고통스럽다는 겁니다. PR #1에 수정이 생기면 PR #2와 #3을 순서대로 rebase해야 하고 브랜치 base도 직접 바꿔줘야 하고 충돌도 하나씩 잡아야 해요. gh stack은 이 과정을 자동화합니다.
gh stack이란?
gh stack은 GitHub이 공식으로 만든 gh CLI 확장(extension)입니다. 터미널에서 gh stack 명령어 하나로 스택을 생성하고, PR을 만들고, 계단식 rebase를 수행하고, 전체 스택을 한눈에 볼 수 있습니다.
기존에도 Graphite, ghstack(Meta) 같은 서드파티 도구가 있었는데요. gh stack이 결정적으로 다른 점은 GitHub 플랫폼 자체가 스택을 이해한다는 것입니다. 서드파티 도구는 GitHub의 기존 PR 모델 위에서 우회적으로 동작하지만, gh stack을 쓰면 GitHub UI에 스택 내비게이터가 나타납니다. 브랜치 보호 규칙도 스택 전체에 제대로 적용되고 GitHub Actions도 각 PR이 main을 대상으로 하는 것처럼 트리거돼요.
Stacked PR은 비공개 대기 목록으로 시작해 2026년 7월 30일 퍼블릭 프리뷰를 거쳤고, 10월 6일 정식 출시(GA)됐습니다. 이제 github.com의 모든 요금제에서 별도 신청 없이 쓸 수 있고, GitHub Enterprise Server에도 다음 릴리스에 포함될 예정이에요. CLI도 이 글을 갱신한 시점 기준 v0.2.1까지 올라왔습니다.
설치
gh CLI가 이미 설치되어 있다면 확장을 추가하기만 하면 됩니다.
gh extension install github/gh-stack
gh CLI는 v2.0 이상, Git은 v2.36 이상이 필요합니다. 예전에 확장을 설치했다면 gh extension upgrade stack으로 최신 버전으로 올려 두세요. v0.2.0부터는 새 버전이 나오면 명령 실행 후 업그레이드 안내도 띄워 줍니다. 설치 후 gh stack 명령어가 동작하는지 확인해보세요.
gh stack --help
gh stack은 타이핑할 게 좀 있으니 짧은 별칭을 등록해두면 편합니다.
gh stack alias # gs라는 별칭 생성
gh stack alias gst # 원하는 이름으로 지정 가능
이제 gs만 쳐도 gh stack과 동일하게 동작합니다. 이후 예제에서도 gs를 사용하겠습니다.
스택 만들기
새로운 기능 작업을 시작해봅시다. 사용자 인증, API 엔드포인트, 프론트엔드 화면 세 단계로 나눠서 스택을 쌓아볼게요.
먼저 스택을 초기화합니다.
gs init
이 명령은 현재 브랜치를 스택의 첫 번째 레이어로 등록합니다. --base 플래그로 base 브랜치를 지정할 수 있는데, 기본값은 main입니다.
예전에는
--prefix/--numbered옵션으로 브랜치 이름에 번호를 자동으로 붙일 수 있었지만, v0.0.8부터 두 옵션이 모두 사라졌습니다. 지금은 입력한 이름을 그대로 브랜치명으로 씁니다.
첫 번째 레이어에서 인증 관련 코드를 작성한 뒤, 다음 레이어를 추가합니다.
# 인증 코드 작성 후 커밋
git add -A
git commit -m "Add JWT authentication middleware"
# 스택에 새 레이어 추가
gs add api-routes
gs add는 현재 레이어 위에 새 브랜치를 만들어 줍니다. 다음 레이어의 코드를 먼저 작성해 두었다면 -A(모든 변경사항 스테이징)와 -m(커밋 메시지) 플래그로 브랜치 생성과 커밋을 한 번에 할 수 있습니다.
# API 라우트 코드를 작성한 뒤 (아직 커밋하지 않은 상태)
gs add api-routes -Am "Add REST API endpoints for user management"
여기서 헷갈리기 쉬운 점이 있는데요. 커밋은 현재 레이어가 아니라 새로 만든 api-routes 브랜치에 들어갑니다. 즉 “다음 레이어 코드를 쓰고 나서 add”하는 흐름이에요. 한 가지 예외로, 현재 브랜치에 아직 커밋이 하나도 없으면 새 브랜치를 만들지 않고 현재 브랜치에 커밋한 뒤 경고를 띄웁니다.
⚠ Branch profile-api has no prior commits — adding your commit here instead of creating a new branch
실습하다 이 경고를 보고 나서야 레이어 두 개가 한 브랜치에 합쳐진 걸 알아챘는데요. 😅 빈 레이어를 만든 직후라면 -Am 대신 평소처럼 git commit을 먼저 하는 편이 안전합니다.
스택 확인하기
현재 스택의 상태를 확인하려면 view 명령을 사용합니다.
gs view
Stack #4
● profile-ui (current) ○ https://github.com/DaleSeo/gh-stack-demo/pull/3
│ c5d5dba · 38 seconds ago
│ Create ProfilePage component
│
○ profile-api ○ https://github.com/DaleSeo/gh-stack-demo/pull/2
│ 7d4b931 · 46 seconds ago
│ Add profile API endpoints
│
○ profile-models ○ https://github.com/DaleSeo/gh-stack-demo/pull/1
│ 3aba6d1 · 46 seconds ago
│ Add user profile data models
│
└ main
스택의 각 레이어가 위에서 아래로 표시되고, 현재 체크아웃한 브랜치에 (current)가 붙습니다. PR을 이미 만들었다면 PR 링크와 상태도 함께 나와요. 맨 위의 Stack #4는 GitHub이 스택마다 붙이는 번호입니다.
간단한 한 줄 요약이 필요하면 --short 플래그를, CI 도구에서 파싱하려면 --json 플래그를 쓸 수 있습니다.
PR 만들기
스택의 모든 레이어를 한 번에 push하고 PR을 생성하려면 submit 명령을 사용합니다.
gs submit
터미널에서 실행하면 PR마다 제목과 설명, 드래프트 여부를 한 화면에서 정하는 편집기가 열리고, Ctrl+S로 한꺼번에 제출합니다. 편집기 없이 자동 생성된 제목으로 바로 올리고 싶다면 --auto를 씁니다. 이때 새 PR은 드래프트로 만들어지는데, 바로 리뷰 가능한 상태로 열려면 --open을 함께 줍니다.
gs submit --auto --open
Pushing to origin...
✓ Created PR #1 (https://github.com/DaleSeo/gh-stack-demo/pull/1) for profile-models
✓ Created PR #2 (https://github.com/DaleSeo/gh-stack-demo/pull/2) for profile-api
✓ Created PR #3 (https://github.com/DaleSeo/gh-stack-demo/pull/3) for profile-ui
✓ Stack created on GitHub with 3 PRs (stack #4)
✓ Pushed and synced 3 branches
예전 버전의
--draft플래그는 사라졌습니다. 드래프트 여부는 편집기의 CREATE AS 토글로 고르거나,--auto의 기본 동작을 이용하세요.
모든 브랜치가 원격에 push되고 레이어마다 PR이 만들어졌습니다. base 브랜치도 스택 구조에 맞게 알아서 설정돼요. PR #3의 base는 PR #2의 브랜치, PR #2의 base는 PR #1의 브랜치가 되는 식이죠.
GitHub에서 PR 목록을 열어 보면 각 PR 옆에 스택 안에서의 위치가 표시됩니다.

PR 화면에서는 제목 아래 고정 헤더에 2/3 같은 스택 버튼이 붙는데요. 누르면 스택 내비게이터(stack navigator)가 열리고, 스택의 PR이 위에서 아래로 나열됩니다.

내비게이터 아래쪽 안내처럼 Shift+J와 Shift+K로 스택의 아래위 PR을 오갈 수 있어서, 리뷰어가 마우스 없이 스택 전체를 훑어보기 좋아요. 헤더는 스크롤해도 따라오기 때문에 긴 PR을 리뷰하다가도 지금 어느 층에 있는지 놓치지 않습니다.
스택 안에서 이동하기
스택의 레이어 사이를 이동하는 명령어도 있습니다.
gs up # trunk에서 먼 방향으로 한 칸 위로
gs down # trunk 방향으로 한 칸 아래로
gs top # 스택 맨 위로
gs bottom # 스택 맨 아래로 (trunk 바로 위)
숫자를 붙이면 여러 칸을 한 번에 이동할 수 있습니다.
gs up 2 # 두 칸 위로
gs down 3 # 세 칸 아래로
gs trunk # 스택의 trunk(main)로
gs switch # 인터랙티브 피커로 원하는 레이어 선택
기존에 git switch나 git checkout으로 브랜치 이름을 직접 타이핑하던 것보다 훨씬 편합니다. 스택 구조를 기억하고 있으니 “위로”, “아래로”라는 상대적 개념으로 이동할 수 있는 거죠.
다른 사람이 만든 스택을 받아서 리뷰하려면 checkout 명령을 사용합니다. 인자 없이 실행하면 로컬/원격 스택을 한데 모아 보여주는 인터랙티브 피커가 뜹니다.
gs checkout # 인터랙티브 피커로 스택 선택
gs checkout 42 # 스택 번호 또는 PR 번호로 해당 스택 전체를 체크아웃
gs checkout auth-layer # 브랜치 이름으로도 가능
스택 구조 바꾸기
레이어를 추가하는 것 말고도 스택 자체의 구성을 바꿔야 할 때가 있습니다. 중간에 빠뜨린 작업을 끼워 넣거나, 두 레이어를 하나로 합치거나, 순서를 바꾸고 싶을 때는 modify 명령을 씁니다.
gs modify
인터랙티브 TUI가 열리고, 커서가 있는 레이어를 삭제(drop)하거나 아래 레이어와 합치거나(fold) 이름을 바꿀 수 있습니다. Shift+↑/Shift+↓로 레이어 순서를 바꾸고, i/I로 커서 아래/위에 새 레이어를 끼워 넣을 수도 있어요.
변경 사항은 확정하는 순간 원자적으로 적용되고, 필요하면 계단식 rebase와 충돌 해결까지 함께 처리됩니다. 충돌이 나면 파일을 수정한 뒤 gs modify --continue로 이어가거나 gs modify --abort로 되돌릴 수 있습니다.
계단식 Rebase
Stacked PR의 핵심이자 수동으로 할 때 가장 고통스러운 부분이 바로 rebase입니다. 스택 아래쪽 레이어에 변경이 생기면 위쪽 레이어도 전부 rebase해야 하거든요.
gs rebase는 이걸 한 방에 처리합니다.
gs rebase
현재 레이어부터 위쪽 레이어를 순서대로 rebase합니다. 스택이 세 개 레이어인데 맨 아래 PR에 수정을 했다면 가운데와 맨 위 레이어가 차례로 rebase돼요.
특정 방향만 rebase하고 싶다면 플래그를 쓸 수 있습니다.
gs rebase --downstack # 현재 레이어 아래쪽만 rebase
gs rebase --upstack # 현재 레이어 위쪽만 rebase
rebase 중 충돌이 발생하면 일반 Git rebase처럼 충돌을 해결한 뒤 계속할 수 있습니다.
# 충돌 파일 수정 후
git add .
gs rebase --continue
충돌 해결이 복잡해서 포기하고 싶다면 --abort로 원래 상태로 돌아갈 수 있습니다.
gs rebase --abort
웹에서도 같은 일을 할 수 있습니다. PR 화면의 Rebase stack 기능을 쓰면 GitHub이 서버에서 스택 전체를 rebase해 주는데요. GA와 함께 두 가지가 좋아졌습니다. 먼저 rebase 후에도 바뀌지 않은 코드의 승인(approval)이 유지돼요. 새 커밋이 올라오면 기존 승인을 무효로 돌리도록 설정한 저장소에서도 마찬가지입니다. 아래 레이어를 고칠 때마다 위쪽 PR의 승인을 처음부터 다시 받아야 했던 게 Stacked PR의 가장 큰 불편이었는데, 이 문제가 해결된 거죠. 또 rebase로 새로 만들어지는 커밋에 GitHub이 서명을 해 주고 원래 작성자도 보존해서, 서명된 커밋을 요구하는 저장소에서도 막히지 않습니다.
gh stack은 git rerere도 알아서 켜줍니다. rerere는 “reuse recorded resolution”의 약자로, 한 번 해결한 충돌 방식을 기억했다가 같은 충돌이 다시 나타나면 자동 적용해주는 기능이에요. 스택 작업에서는 같은 충돌을 반복적으로 만나기 쉬우니 꽤 유용합니다.
동기화
rebase와 push를 한 번에 하고 싶다면 sync 명령이 있습니다.
gs sync
sync는 원격에서 최신 변경을 fetch하고 스택 전체를 rebase한 뒤 모든 브랜치를 push하고 PR 상태까지 맞춰줍니다. 아침에 출근해서 작업 시작 전에, 또는 리뷰 피드백을 반영한 뒤에 돌리면 좋아요.
--prune을 붙이면 이미 머지된 레이어의 로컬 브랜치도 함께 정리합니다. 아래 머지 전략에서 실제 출력을 볼게요.
브랜치만 push하고 PR 상태 동기화는 건너뛰고 싶다면 push 명령을 별도로 쓸 수도 있습니다.
gs push
push는 내부적으로 --force-with-lease --atomic 옵션을 씁니다. --force-with-lease는 누군가 원격 브랜치를 수정했을 때 push를 거부해서 작업 유실을 막아주고, --atomic은 모든 브랜치가 한꺼번에 push되거나 아예 push되지 않도록 보장해줍니다.
머지 전략
Stacked PR을 머지할 때는 반드시 아래에서 위로 순서를 지켜야 합니다. 가운데 PR만 쏙 빼서 머지할 수는 없어요. 대신 가운데 PR에서 머지하면 그 아래 PR까지 함께 머지됩니다.

PR #2 화면의 머지 박스에 “Merging this pull request will also merge 1 pull request below it”이라고 나오죠. Merge stack 버튼의 숫자 2는 #1과 #2를 함께 머지한다는 뜻입니다.
CLI에서는 gs merge 명령으로 같은 일을 합니다. 인자 없이 실행하면 현재 체크아웃한 스택을, 스택 번호나 PR 번호를 주면 그 지점까지 포함한 모든 PR을 원자적으로 머지합니다 — 하나라도 머지할 수 없으면 전체가 취소돼요.
gs merge # 현재 스택 전체를 병합
gs merge 42 # PR #42까지 포함해서 병합
gs merge --yes --squash # 확인 없이 스쿼시 머지로 헤드리스 실행
터미널에서는 어디까지 머지할지, 어떤 머지 방식을 쓸지 순서대로 물어보는 인터랙티브 마법사가 뜨고, --yes를 주거나 TTY가 없는 환경(예: CI, 에이전트)에서는 --merge/--squash/--rebase/--merge-method 플래그로 곧바로 실행됩니다. base 브랜치에 머지 큐가 걸려 있으면 직접 머지 대신 큐에 자동으로 올라가요.
GitHub이 지원하는 세 가지 머지 방식 모두 사용할 수 있습니다.
- Merge commit: 머지 커밋을 만들어서 합침
- Squash merge: 여러 커밋을 하나로 합쳐서 머지
- Rebase merge: 커밋을 base 위에 재배치
전부 머지하지 않고 아래쪽 일부만 먼저 머지할 수도 있습니다. 실습 저장소에서 맨 아래 PR #1만 머지해 봤어요.
gs merge 1 --yes --squash
Merging #1 into main via squash...
✓ Merged #1 into main (7762d68)
PR #1이 main에 머지되자 PR #2의 base가 profile-models에서 main으로 자동으로 바뀌었습니다. 이어서 gs sync --prune을 돌리면 남은 레이어를 새 main 위로 rebase하고, 머지된 브랜치는 로컬에서 지워 줍니다.
✓ Pruned profile-models (merged)
✓ Pruned 1 merged branch
✓ Stack synced
Stacked on main (7762d68)
gs view로 보면 머지된 레이어가 구분선 아래로 내려가 있어요.
● profile-ui (current) ○ https://github.com/DaleSeo/gh-stack-demo/pull/3
│ a961135 · 2 minutes ago
│ Create ProfilePage component
│
○ profile-api ○ https://github.com/DaleSeo/gh-stack-demo/pull/2
│ 9488b5f · 2 minutes ago
│ Add profile API endpoints
│
╌╌╌ merged ╌╌╌
○ profile-models ✓ https://github.com/DaleSeo/gh-stack-demo/pull/1
│
└ main
GA에서는 base 브랜치가 아예 삭제되는 경우도 챙겼습니다. 예전에는 스택의 base 브랜치를 지우면 맨 아래 PR이 닫혀 버렸는데, 이제는 GitHub이 스택을 다른 base로 자동 재지정합니다.
GitHub의 머지 큐(merge queue)와도 연동됩니다. GA부터는 스택이 머지 큐에 하나의 머지 그룹으로 들어가 함께 머지돼요. Merge commit 방식을 쓰면 스택 전체에 머지 커밋 하나가 아니라 PR마다 머지 커밋이 하나씩 생겨서, 히스토리에서도 레이어 구분이 남습니다. 스택 단위 자동 머지(auto-merge)도 GA 발표 시점부터 몇 주에 걸쳐 순차 배포되고 있어서, 머지 조건이 채워지면 선택한 PR들이 한꺼번에 머지되도록 걸어 둘 수 있어요.
브랜치 보호 규칙과 CI
서드파티 도구를 쓸 때 가장 골치 아픈 부분 중 하나가 브랜치 보호 규칙입니다. 일반적으로 보호 규칙은 main 브랜치를 대상으로 하는 PR에만 적용되는데, Stacked PR은 중간 브랜치를 base로 삼기 때문에 규칙이 우회되는 문제가 있었습니다.
gh stack은 GitHub 플랫폼 수준에서 이 문제를 해결합니다. 스택 내 각 PR이 중간 브랜치를 base로 삼더라도 보호 규칙은 스택의 최종 base(main)를 기준으로 적용돼요. 필수 리뷰어 승인이나 CI 통과 같은 조건이 모든 PR에 똑같이 강제되죠.
GitHub Actions 워크플로우도 마찬가지예요. on: pull_request에 branches: [main]을 지정해두면 스택의 모든 PR에서 워크플로우가 트리거됩니다. 각 PR이 마치 main을 직접 대상으로 하는 것처럼 동작하니 별도 설정이 필요 없어요.
규칙 우회 권한이 있는 사람은 그 권한으로 스택에서 아직 머지되지 않은 맨 아래 PR을 머지할 수 있습니다. 단, gs merge는 우회를 지원하지 않으니 이 경우에는 웹에서 머지해야 해요. 자동화 쪽에서는 PR이 스택에 들어갈 때 pull_request 웹훅이 stacked 액션으로 전달되므로, 스택에 합류한 PR에만 반응하는 봇도 만들 수 있습니다.
실전 워크플로우 예시
전체 흐름을 처음부터 끝까지 한번 따라가 보겠습니다. 사용자 프로필 기능을 추가하는 시나리오입니다.
# 1. 스택 시작 — 브랜치 이름을 직접 지정
gs init profile-models
# 2. 데이터 모델 작성 후 커밋
# ... User, Profile 모델 코드 작성 ...
git add -A && git commit -m "Add user profile data models"
# 3. API 코드를 작성한 뒤, 새 레이어를 만들면서 커밋
# ... API 라우트 코드 작성 ...
gs add profile-api -Am "Add profile API endpoints"
# 4. 화면 코드를 작성한 뒤, 마지막 레이어를 만들면서 커밋
# ... React 컴포넌트 코드 작성 ...
gs add profile-ui -Am "Create ProfilePage component"
# 5. 한 번에 push하고 PR 생성 (편집기에서 제목과 설명 작성)
gs submit
# 6. 스택 확인
gs view
리뷰어가 PR #1에 “유효성 검사를 추가해달라”고 피드백을 남겼다고 해봅시다.
# 7. 맨 아래 레이어로 이동
gs bottom
# 8. 피드백 반영
# ... 유효성 검사 코드 추가 ...
git add -A && git commit -m "Add input validation for profile fields"
# 9. 위쪽 레이어 전부 rebase
gs rebase
# 10. 전체 push
gs push
리뷰가 통과되면 맨 위 PR에서 Merge stack을 누르거나 gs merge로 스택 전체를 한 번에 머지합니다.
worktree와 AI 에이전트
v0.2.0부터는 Git worktree를 지원합니다. 스택 추적 정보가 명령을 실행한 디렉터리가 아니라 저장소 공용 디렉터리(.git/gh-stack)에 저장되기 때문에, worktree마다 서로 다른 스택을 두거나 한 스택의 레이어를 여러 worktree에 나눠 체크아웃해도 추적이 끊기지 않아요. rebase, sync, modify는 각 브랜치를 체크아웃하고 있는 worktree에서 알아서 실행되고, 다른 worktree에 체크아웃된 브랜치로 이동하려 하면 그 경로를 알려 줍니다.
cd "$(gs up --print-path)" # 위 레이어를 들고 있는 worktree로 이동
여러 에이전트에게 레이어를 하나씩 맡겨 병렬로 작업시키는 흐름과 잘 맞는데요. 에이전트가 스택 사용법을 알도록 공식 스킬도 제공됩니다. gh skill로 설치하면 됩니다.
gh skill install github/gh-stack
기존 브랜치로 스택 구성하기
이미 의존 관계가 있는 브랜치를 만들어둔 상태라면 init에 브랜치 이름을 여러 개 나열하기만 하면 됩니다. 기존 브랜치는 별도 옵션 없이 자동으로 채택됩니다(과거엔 --adopt 옵션이 필요했지만 지금은 deprecated 처리되어 있습니다).
gs init auth-layer api-routes frontend
이렇게 하면 auth-layer → api-routes → frontend 순서의 스택이 만들어집니다. 이미 PR이 생성되어 있다면 submit 시 새 PR을 만드는 대신 기존 PR에 스택 정보를 연결합니다.
스택 해제와 삭제
스택 구조가 더 이상 필요 없으면 해제할 수 있습니다.
gs unstack # GitHub과 로컬 양쪽에서 스택 해제 (브랜치와 PR은 유지)
gs unstack --local # 로컬 추적 정보만 삭제
gs unstack 4 # 스택 번호로 특정 스택 해제
unstack은 GitHub 쪽의 스택 연결을 풀고 로컬 추적 정보를 제거하지만, 브랜치와 PR 자체는 그대로 둡니다. 각 PR이 독립된 일반 PR로 돌아가는 거죠. 머지 큐에 들어갔거나 자동 머지가 걸린 PR은 스택에 그대로 남습니다. 예전의 gs delete는 이제 unstack의 별칭이라 로컬 브랜치를 지우지 않아요.
반대로 이미 열려 있는 PR들을 로컬 추적 없이 GitHub에서만 스택으로 묶고 싶다면 gs link를 씁니다.
대안 도구 비교
gh stack 외에도 Stacked PR을 지원하는 도구가 여럿 있습니다.
Graphite는 가장 성숙한 상용 서비스입니다. 자체 웹 UI에 AI 코드 리뷰까지 갖추고 있어서 기능이 풍부하지만, GitHub 위에 별도 레이어를 올리는 방식이라 조직 전체가 Graphite를 도입해야 제대로 쓸 수 있습니다.
Aviator의 av는 오픈소스 CLI로 Aviator의 머지 큐와 통합됩니다. ghstack(Meta)은 커밋 단위로 PR을 만드는 방식으로 Meta 내부의 Phabricator 워크플로우에서 영향을 받았습니다. Sapling(Meta)과 Jujutsu(jj)는 Git과는 다른 버전 관리 도구인데, 둘 다 스택형 커밋을 기본으로 지원합니다.
gh stack의 가장 큰 장점은 GitHub 플랫폼과 네이티브로 통합된다는 점입니다. 다른 도구가 GitHub PR 모델 위에서 간접적으로 동작하는 반면, gh stack을 쓰면 UI에 스택 내비게이터가 뜨고 보호 규칙과 CI가 스택을 인식하며 머지 큐도 스택 단위로 처리돼요. 별도 서비스 가입이나 팀 전체 도입 없이 gh 확장 하나만 설치하면 되는 것도 장점입니다.
다만 교차 포크(cross-fork) 스택은 지원하지 않아서 모든 브랜치가 같은 저장소에 있어야 한다는 점은 알아 두세요. 오픈소스 기여처럼 포크에서 PR을 보내는 흐름에는 아직 쓸 수 없습니다.
호환성
흥미로운 점은 gh stack CLI가 아닌 다른 도구로도 GitHub의 Stacked PR 플랫폼 기능을 쓸 수 있다는 것입니다. PR의 base 브랜치만 제대로 설정하면 Jujutsu, Sapling, git-town 같은 도구로 만든 PR도 GitHub이 스택으로 인식해요. gh stack CLI는 편의 도구일 뿐, 플랫폼 기능 자체는 CLI에 종속되지 않습니다.
GitHub 웹 UI에서도 직접 스택을 만들 수 있습니다. PR 생성 시 base 브랜치를 다른 PR의 브랜치로 지정하면 GitHub이 스택 관계를 알아서 인식하고 스택 내비게이터를 띄워줍니다.
마치며
PR이 커지면 리뷰 품질이 떨어지고, 리뷰 품질이 떨어지면 버그가 늘고, 버그가 늘면 또 PR이 커지는 악순환. Stacked PR은 이 고리를 끊는 확실한 방법인데 그동안은 수동 관리가 너무 번거로워서 실제로 적용하기 어려웠죠.
gh stack은 GitHub의 첫 번째 공식 Stacked PR 도구로서 CLI와 플랫폼 양쪽에서 스택을 지원한다는 점에서 의미가 큽니다. 2026년 10월 정식 출시와 함께 rebase 후 승인 유지, 서명 커밋 보존, 머지 큐 그룹 처리처럼 실무 도입을 막던 걸림돌이 대부분 사라졌어요. 이제는 팀 저장소에서 큰 기능 하나를 골라 바로 스택으로 쪼개 보셔도 좋겠습니다.
실습에 쓴 스택은 DaleSeo/gh-stack-demo 저장소에서 직접 보실 수 있어요.
더 자세한 내용은 gh-stack 공식 문서를 참고하세요.
This work is licensed under CC BY 4.0