아래는 이 실습의 핵심 명령을 실행한 화면을 재현한 것이다. 화면이 보이면 자동으로 재생되고, 오른쪽 위 버튼으로 다시 볼 수 있다.
1용어와 흐름
| 용어 | 뜻 | PM이 보는 것 |
|---|---|---|
| 커밋(commit) | 파일 변경 한 묶음과 그 설명 메시지 | 메시지 첫 줄: 무엇을 바꿨는가 |
| 브랜치(branch) | 기본 줄기(main)에서 갈라져 작업하는 공간 | 이름에 업무 키가 있는가 |
| 풀 리퀘스트(PR) | 브랜치의 변경을 main에 합쳐 달라는 요청 | 연결 이슈, 리뷰, 검사 |
| 리뷰(review) | 다른 사람이 변경을 확인하고 승인하거나 수정을 요청하는 것 | 승인됨, 변경 요청, 대기 |
| 검사(check) | 테스트처럼 PR마다 자동으로 돌아가는 확인 작업 | 통과, 실패, 진행 중 |
| 병합(merge) | PR의 변경을 main에 합치는 것 | 병합 시각, 닫힌 이슈 |
- 브랜치docs/lp-01-policy
- 커밋docs: 사전신청 정책 추가 (LP-01)
- PR본문에 Closes #1
- 리뷰와 검사승인, 통과
- 병합main에 반영
- 이슈 종료LP-01 자동 Closed
PR 본문에 Closes #1처럼 닫는 키워드와 이슈 번호를 쓰면 GitHub가 PR과 이슈를 연결하고, PR이 기본 브랜치에 병합될 때 이슈를 닫는다. 닫는 키워드는 close, closes, fix, fixes, resolve, resolves 등이다. Refs #1처럼 쓰면 언급만 남고 이슈는 닫히지 않는다.
2개발자 역할: PR 하나 만들기
LP-01의 결과물인 정책 문서를 PR로 올린다. 실제 팀에서는 개발자나 기획자 본인이 하는 일이며, PM이 PR이 만들어지는 과정을 한 번 거쳐 보는 것이 목적이다.
PR 본문 준비
먼저 작업 폴더에서 PR 본문을 만든다. ${PM_POLICY_URL##*/}는 주소의 마지막 / 뒤, 곧 이슈 번호만 남긴다. 이 블록의 <<EOF에는 따옴표가 없으므로 $PM_POLICY_NUMBER가 실제 번호로 바뀌어 저장된다.
cd ~/gh-pm-lab
source pm-lab/env.sh
PM_POLICY_NUMBER=${PM_POLICY_URL##*/}
cat > pm-lab/pr-lp01.md <<EOF
## 연결 업무
Closes #$PM_POLICY_NUMBER
## 바꾼 것
- docs/signup-policy.md 추가: 필수 입력, 재신청, 성공 안내, 실패 안내 기준
## 확인 방법
- 표의 네 행이 LP-01 완료 조건 네 개와 하나씩 대응하는지 확인한다.
## 일정과 범위에 영향을 주는 것
- 재신청 시 기존 신청을 유지하기로 했다. LP-03의 저장 로직에 반영해야 한다.
EOF
cat pm-lab/pr-lp01.md
저장소 복제, 브랜치, 커밋, push
gh repo clone "$PM_REPO" "$PM_REPO_NAME" cd "$PM_REPO_NAME" git switch -c docs/lp-01-policy mkdir -p docs cat > docs/signup-policy.md <<'EOF' # 사전신청 정책 | 항목 | 기준 | | - | - | | 필수 입력 | 이메일, 개인정보 수집·이용 동의 | | 재신청 | 같은 이메일은 기존 신청을 유지하고 "이미 신청한 이메일이다"라는 안내를 보여 준다 | | 성공 안내 | 신청 완료 사실과 베타 초대 예정일(2026-11-06)을 보여 준다 | | 실패 안내 | 빈 값, 형식 오류, 동의 누락의 이유를 입력란 아래에 보여 준다 | EOF git add docs/signup-policy.md git commit -m "docs: 사전신청 정책 추가 (LP-01)" \ -m "재신청은 기존 신청을 유지하고 안내 문구만 보여 준다." git push -u origin docs/lp-01-policy
| 명령 | 하는 일 |
|---|---|
gh repo clone | 저장소를 작업 폴더 안의 launchpad-pm-lab 폴더로 내려받는다. |
git switch -c 이름 | 새 브랜치를 만들고 그 브랜치로 옮긴다. 이름에 업무 키를 넣는다. |
git add | 커밋에 넣을 파일을 고른다. |
git commit -m 제목 -m 본문 | 커밋을 만든다. 첫 -m은 제목 한 줄, 둘째 -m은 이유를 적는 본문이다. |
git push -u origin 브랜치 | 브랜치를 GitHub에 올린다. |
PR 만들기와 상태 변경
PM_PR_URL=$(gh pr create --title "[LP-01] 사전신청 정책 문서" \ --body-file ../pm-lab/pr-lp01.md) cd .. echo "$PM_PR_URL" echo "PM_PR_URL=\"$PM_PR_URL\"" >> pm-lab/env.sh gh project item-edit "$PM_PROJECT" --owner "$PM_OWNER" \ --url "$PM_POLICY_URL" --field Status --value "In Review"
https://github.com/gildong/launchpad-pm-lab/pull/7 Edited item "[LP-01] 사전신청 정책을 확정한다"
PR도 이슈와 같은 번호 체계를 쓴다. 이슈가 #1~#6까지 있으므로 PR은 #7이 된다. cd ..로 작업 폴더에 돌아왔는지 pwd로 확인한다. 이후 명령은 모두 작업 폴더에서 실행한다. 이 PR은 이 실습에서 병합하지 않는다.
3PM 역할: PR을 읽는 다섯 질문
| 질문 | 명령 | 볼 곳 |
|---|---|---|
| 1. 어떤 PR이 열려 있고, 리뷰를 기다리는가 | gh pr list | 제목, 초안 여부, 리뷰 결정 |
| 2. 이 PR은 어느 업무인가 | gh pr view --json closingIssuesReferences | 연결 이슈 번호 |
| 3. 무엇을 바꿨는가 | gh pr view --json files,commits | 파일 이름, 커밋 제목 |
| 4. 자동 검사는 통과했는가 | gh pr checks | pass, fail, pending |
| 5. 최근 무엇이 반영되었는가 | gh pr list --state merged, gh api .../commits | 병합된 PR, 기본 브랜치 커밋 |
PM은 코드 한 줄 한 줄을 읽지 않는다. 연결 이슈, 리뷰 상태, 검사 결과, 파일 이름, 커밋 제목까지가 진행 상황 판단에 쓰는 정보다.
질문 1. 열린 PR
gh pr list -R "$PM_REPO" gh pr list -R "$PM_REPO" --state open \ --json number,title,author,isDraft,reviewDecision,updatedAt \ --jq '.[] | "#\(.number) \(.title) · @\(.author.login) · 초안 \(.isDraft) · 리뷰 \(.reviewDecision) · 갱신 \(.updatedAt[:10])"'
Showing 1 of 1 open pull request in gildong/launchpad-pm-lab ID TITLE BRANCH CREATED AT #7 [LP-01] 사전신청 정책 문서 docs/lp-01-policy about 2 minutes ago #7 [LP-01] 사전신청 정책 문서 · @gildong · 초안 false · 리뷰 · 갱신 2026-10-15
reviewDecision은 리뷰가 필수로 설정된 저장소에서 REVIEW_REQUIRED, APPROVED, CHANGES_REQUESTED 중 하나다. 실습 저장소처럼 리뷰 규칙이 없으면 빈 값이다.
질문 2·3. 연결 업무와 변경 내용
gh pr view "$PM_PR_URL"
gh pr view "$PM_PR_URL" \
--json number,state,closingIssuesReferences,files,commits \
--jq '"PR #\(.number) · \(.state)",
"연결 이슈: \([.closingIssuesReferences[] | "#\(.number)"] | join(", "))",
"변경 파일: \([.files[] | "\(.path) +\(.additions) -\(.deletions)"] | join(", "))",
"커밋: \([.commits[].messageHeadline] | join(" / "))"'
PR #7 · OPEN 연결 이슈: #1 변경 파일: docs/signup-policy.md +8 -0 커밋: docs: 사전신청 정책 추가 (LP-01)
연결 이슈가 비어 있으면 PR 본문에 닫는 키워드가 없다. 이런 PR은 업무 목록 밖의 작업이거나, 연결을 빠뜨린 작업이다. 어느 쪽인지는 회의에서 확인할 질문이 된다.
질문 4. 자동 검사
gh pr checks "$PM_PR_URL"
no checks reported on the 'docs/lp-01-policy' branch
실습 저장소에는 자동 검사가 없으므로 위 메시지가 나온다. 검사가 설정된 팀 저장소에서는 아래처럼 검사 이름별 결과가 나온다.
Some checks were not successful 0 cancelled, 1 failing, 2 successful, 0 skipped, and 0 pending checks X CI/test (pull_request) 1m12s https://github.com/... ✓ CI/lint (pull_request) 24s https://github.com/... ✓ CI/build (pull_request) 2m3s https://github.com/...
줄 앞의 X는 실패, ✓는 통과, *는 진행 중, -는 건너뜀이나 취소다.
질문 5. 최근 반영된 것
SINCE=$(date -d '7 days ago' +%F 2>/dev/null || date -v-7d +%F)
echo "$SINCE"
gh pr list -R "$PM_REPO" --state merged --search "merged:>=$SINCE" --limit 100
gh api "repos/$PM_REPO/commits?since=${SINCE}T00:00:00Z&per_page=100" \
--jq '.[] | "\(.commit.author.date[:10]) \(.sha[:7]) \(.commit.message | split("\n")[0])"'
첫 줄은 7일 전 날짜를 구한다. Linux와 Git Bash는 앞의 date -d가, macOS는 뒤의 date -v-7d가 동작한다. 실습 저장소는 아직 병합된 PR이 없고, 기본 브랜치에는 저장소를 만들 때의 커밋(Initial commit) 하나만 있다. 기본 브랜치 커밋 목록은 병합 방식에 따라 PR 하나가 커밋 하나로 보이기도 하고 여러 개로 보이기도 한다.
4공개 저장소에서 읽기 연습
실습 저장소에는 PR이 하나뿐이다. 실제로 여러 사람이 작업하는 저장소의 PR을 읽어 보기 위해 GitHub CLI 자체의 공개 저장소 cli/cli를 조회한다. 조회만 하며 아무것도 쓰지 않는다.
gh pr list -R cli/cli --state merged --limit 5 \ --json number,title,author,mergedAt \ --jq '.[] | "#\(.number) \(.mergedAt[:10]) @\(.author.login) \(.title)"'
목록에서 PR 번호 하나를 골라 아래 번호 자리에 넣는다.
gh pr view 번호 -R cli/cli \
--json title,body,closingIssuesReferences,latestReviews,files \
--jq '"제목: \(.title)",
"연결 이슈: \([.closingIssuesReferences[] | "#\(.number)"] | join(", "))",
"리뷰: \([.latestReviews[] | "\(.author.login)=\(.state)"] | join(", "))",
"변경 파일 수: \(.files | length)"'
gh pr view 번호 -R cli/cli
읽은 PR 하나에 대해 다음 네 가지를 한 문장씩 적는다. 무엇을 바꿨는가. 왜 바꿨는가(본문 또는 연결 이슈). 누가 승인했는가. 남은 일이 있는가.
5회의 안건으로 올릴 신호
아래 상태는 기록만으로 찾을 수 있고, 회의에서 결정이나 도움이 필요한 경우가 많다. 기준 일수는 팀이 정한다. 표의 3일은 예시다.
| 신호 | 찾는 방법 | 회의에서 정할 것 |
|---|---|---|
| 리뷰 없이 3일 넘게 열린 PR | gh pr list의 updatedAt | 리뷰 담당자와 리뷰 기한 |
| 변경 요청 상태의 PR | reviewDecision이 CHANGES_REQUESTED | 요청 내용이 범위 변경인지 여부 |
| 검사 실패 | gh pr checks의 fail | 원인 확인 담당자 |
| 연결 이슈가 없는 PR | closingIssuesReferences가 빈 목록 | 업무로 등록할지, 기존 업무에 연결할지 |
| In Progress인데 PR이 없는 업무 | Project Status와 연결 PR 비교 | 막힘이 있는지, 업무를 쪼갤지 |
커밋 수와 PR 수는 업무의 크기와 비례하지 않는다. 오타 한 줄을 고친 PR 1건과 신청 저장 기능 전체를 담은 PR 1건은 같은 1건이다. 개인별 커밋 수나 PR 수를 비교하는 표는 만들지 않는다.
6읽기 쉬운 PR을 위한 팀 약속
PM이 기록만으로 진행을 읽으려면 PR과 커밋이 일정한 형식을 따라야 한다. 아래 세 가지는 개발 방식에 관여하지 않고 연결 정보만 요구한다.
| 약속 | 예 |
|---|---|
| PR 제목에 업무 키 | [LP-03] 신청 입력 검증과 저장 |
| PR 본문에 연결 업무 | Closes #3 또는 QA 전이면 Refs #3 |
| 커밋 제목은 무엇을 바꿨는지 | “수정” 대신 fix: 같은 이메일 재신청 시 기존 신청 유지 (LP-03) |
실습 키트의 templates/pull_request_template.md를 저장소의 .github/pull_request_template.md로 두면 새 PR 본문에 연결 업무, 바꾼 것, 확인 방법, 일정 영향 칸이 자동으로 채워진다.
- PR이 만들어졌고
pm-lab/env.sh에PM_PR_URL이 저장되어 있다. - PR 상세 조회의 “연결 이슈”에 LP-01 번호가 나온다.
- Sprint 1 보드에서 LP-01이 In Review 열에 있다.
cli/cli의 병합된 PR 하나를 골라 네 문장을 적었다.- 회의 안건으로 올릴 신호 다섯 가지 중 세 가지를 말할 수 있다.
막히면
| 증상 | 확인과 해결 |
|---|---|
Author identity unknown | Git 사용자 이름과 메일이 없다. 수업 전 준비 3단계의 git config 두 줄을 실행하고 git commit부터 다시 한다. |
push에서 비밀번호를 물음, Authentication failed | gh auth setup-git을 실행하고 다시 push한다. |
destination path 'launchpad-pm-lab' already exists | 이미 복제했다. cd "$PM_REPO_NAME"로 들어가 git switch -c부터 진행한다. |
No commits between main and docs/lp-01-policy | 커밋이 없다. git status로 파일이 추가되었는지, git log --oneline -3으로 커밋이 있는지 확인한다. |
--body-file 파일을 찾지 못함 | 복제 폴더 안에서 실행해야 ../pm-lab/pr-lp01.md가 맞다. pwd로 위치를 확인한다. |
date: illegal option -- d | macOS의 date다. 같은 줄 뒤쪽의 date -v-7d가 대신 실행되므로 echo "$SINCE" 결과가 날짜면 문제없다. |
마치며...
LP-01의 정책 문서가 PR로 올라가 이슈와 연결되었고, Status는 In Review가 되었다. PR 목록, 연결 이슈, 변경 파일, 커밋 제목, 검사 결과를 명령으로 읽었고, 공개 저장소의 PR 하나를 같은 방법으로 읽었다. 실습 10에서는 스프린트의 목표와 결정을 모아 둘 운영 이슈를 만든다.