A-6: 브랜치 전략과 오픈소스 기여
목차 27
또 만났네요, 홍순구 튜터입니다. 지난 시간 우리는 기능 브랜치를 따서 Pull Request 를 올리고, 동료의 리뷰를 받아 main 에 합치고, 다 쓴 브랜치를 깔끔히 지우는 협업 한 바퀴를 통째로 돌려봤어요. 브랜치 → 커밋 → push → PR → 리뷰 → 병합 → 정리. 이 리듬이 이제 좀 익숙해졌죠?
그런데 제가 그 끝에서 슬쩍 흘린 말이 있어요. "우리가 돌린 이 한 바퀴엔 사실 이름이 있다" 고요. 오늘 그 이름을 드디어 알려드릴게요. 우리가 한 그 방식의 이름은 GitHub Flow 예요. 그리고 팀이 git 으로 협업하는 약속엔 GitHub Flow 말고도 몇 가지가 더 있어요. 어떤 팀은 더 빠르게, 어떤 팀은 더 엄격하게 일하거든요.
오늘은 그 협업의 약속들 — 브랜치 전략 — 을 정리하고, 거기서 한 걸음 더 나아가요. 지금까지는 전부 내 저장소에서만 PR 을 올렸는데, 세상엔 내가 권한 없는 남의 공개 저장소(오픈소스) 가 가득해요. 거기에 내 코드를 보태려면 어떻게 할까요? 그때 등장하는 게 fork(포크)와 upstream(업스트림) 이에요. A-4 에서 잠깐 예고했던 그 두 번째 원격을 오늘 드디어 만나요.
처음 듣는 단어가 우르르 나올 거예요. GitHub Flow, 트렁크 기반, Git Flow, fork, 태그, SemVer... 괜찮아요. 현업 신입도 입사 첫 주에 "우리 팀 브랜치 전략이 뭐예요?" 라는 질문에 다들 한 번씩 멍해져요. 오늘 끝나면 그 질문에 또박또박 답할 수 있게 될 거예요. 천천히 하나씩 가봅시다.
오늘의 여정 — 팀의 협업 약속과 오픈소스 기여
① 우리가 한 그 흐름의 이름 — GitHub Flow
② 한 발 더 빠르게 — 트렁크 기반 개발
③ 버전을 끊어 파는 소프트웨어 — Git Flow
④ 우리 팀은 뭘 골라야 할까 — 세 전략 비교
⑤ 남의 공개 저장소에 기여하기 — fork와 upstream
⑥ 오픈소스 첫 PR 한 바퀴 — fork 흐름 실전
⑦ 버전에 이름표 달기 — 태그·릴리스와 SemVer
①②③에서 팀이 협업하는 세 가지 약속을 하나씩 보고, ④에서 셋을 나란히 견줘 "우리 팀은 뭘 쓸까" 를 정리해요. ⑤⑥에서는 내 저장소를 넘어 남의 오픈소스에 기여하는 fork 흐름을 익히고, 마지막 ⑦에서 완성된 버전에 이름표(태그)를 달아 릴리스하는 법까지 배웁니다.
💡 오늘 수업의 핵심 — "팀이 git 으로 협업하는 약속을 브랜치 전략이라 한다. 가장 단순한 GitHub Flow(main + 기능 브랜치 + PR)가 기본이고, 트렁크 기반은 다음 단계, Git Flow 는 특수·레거시다. 남의 오픈소스엔 fork 와 upstream 두 원격으로 기여하고, 완성된 버전엔 SemVer 규칙으로 태그를 달아 릴리스한다."
🎯 학습 목표
- 팀의 협업 약속인 브랜치 전략 세 가지(GitHub Flow·트렁크 기반·Git Flow)를 구분하고, 우리 팀에 맞는 것을 고르는 기준을 세웁니다.
- fork 와 upstream 두 원격을 이해하고, 권한 없는 공개 저장소(오픈소스)에 PR 로 기여하는 한 바퀴를 익힙니다.
- 태그·릴리스로 버전에 이름표를 달고, SemVer 와 Conventional Commits 규칙으로 버전과 커밋을 일관되게 관리합니다.
Step 1: "우리가 한 그 흐름엔 이름이 있다 — GitHub Flow"
먼저 "브랜치 전략" 이라는 말부터 풀어요. 거창해 보이지만 뜻은 단순해요. 팀이 브랜치를 어떻게 따고, 언제 합치고, 무엇을 main 으로 삼을지 미리 정해둔 약속이에요. 축구팀에 포지션 약속이 있는 것처럼, 개발팀엔 브랜치 약속이 있어요.
이 약속이 없으면 어떻게 될까요? 누구는 main 에 바로 밀어 넣고, 누구는 거대한 브랜치를 한 달씩 묵히다가, 합칠 때 서로 엉켜서 사고가 나요. 그래서 팀은 "우리는 이렇게 일하자" 를 미리 정해두는 거예요.
그런데 사실 여러분은 이미 그 약속 하나를 몸으로 해봤어요. 지난 시간에 돌린 그 한 바퀴 — main 은 건드리지 않고, 짧은 기능 브랜치를 따서, PR 로 검토받고 합친 뒤, 브랜치를 지운 그 흐름 — 그게 바로 가장 널리 쓰이는 브랜치 전략, GitHub Flow 예요.
GitHub Flow — 가장 단순한 협업 약속
main ●─────────────●─────────────▶ ← 항상 배포 가능한 상태
\ /
●──●──●───┘
└ 짧은 기능 브랜치 → PR → 리뷰 → 병합 → 삭제
GitHub Flow 가 지키는 약속은 딱 다섯 가지예요. 하나하나 보면 전부 우리가 지난 시간에 이미 해본 것들이에요.
main은 항상 배포 가능한 상태로 둔다.main에 있는 코드는 언제 꺼내도 돌아가야 해요. 그래서 검증 안 된 코드를main에 바로 넣지 않죠.- 새 작업은
main에서 짧은 기능 브랜치를 따서 한다. 브랜치 이름은feature/login처럼 무슨 작업인지 알 수 있게 짓고요. - 변경은 PR 로만 합친다.
main에 직접 push 하지 않고, 반드시 PR 을 거쳐요(지난 시간의 보호된 브랜치). - PR 에서 리뷰와 대화를 거친다. 동료가 봐주고 승인하면 합쳐요.
- 합치면 기능 브랜치를 지운다. 역할을 다한 브랜치는 깔끔히 정리해요.
보세요, 전부 지난 시간에 직접 해본 것들이죠? 그래서 GitHub Flow 는 따로 외울 게 없어요. 여러분은 이미 GitHub Flow 를 쓸 줄 알아요. 오늘은 거기에 "이게 GitHub Flow 라는 이름의 전략이었구나" 하는 라벨만 붙이는 거예요.
왜 이게 1순위 기본일까요? 가장 단순해서 사고 칠 여지가 적기 때문이에요. 긴 브랜치도, 복잡한 단계도 없어요. 브랜치 하나 따서, 며칠 안에 PR 로 합치고, 지운다. 이 단순함 덕분에 작은 스타트업부터 큰 회사의 웹 서비스 팀까지 대부분이 GitHub Flow(또는 그 변형)로 일해요. 그래서 신입으로 입사하면 가장 높은 확률로 만나는 게 이 방식이에요.
⚠️ GitHub Flow 의 핵심은 "브랜치를 짧게 유지" 하는 거예요. 기능 브랜치를 몇 주씩 끌면, 그 사이
main이 저만치 앞서가서 나중에 합칠 때 충돌이 산더미처럼 쌓여요(지난 시간에 본 그 충돌이요). 며칠 안에 작게작게 합치는 습관이 GitHub Flow 를 잘 굴러가게 하는 비결이에요.
💡 한 줄 정리
브랜치 전략은 팀이 브랜치를 따고 합치는 약속이다. 그중 가장 단순한 GitHub Flow 는 main 을 항상 배포 가능하게 두고, 짧은 기능 브랜치를 PR 로만 합친 뒤 지운다. 지난 시간 우리가 돌린 한 바퀴가 바로 이것이고, 가장 널리 쓰이는 1순위 기본이다.
🙋 학생 질문 — "튜터님, 그럼 브랜치 전략이 GitHub 에만 있는 건가요? 이름에 GitHub 가 들어가서요."
좋은 질문이에요. 이름 때문에 헷갈리기 딱 좋죠.
GitHub Flow 는 GitHub 가 제안해서 유명해진 이름일 뿐, GitHub 에서만 쓰는 건 아니에요. GitLab 이든 Bitbucket 이든, 심지어 회사 자체 서버든, "main 을 보호하고 짧은 기능 브랜치를 PR(또는 Merge Request)로 합친다" 는 똑같은 방식을 쓸 수 있어요. 전략은 일하는 방식이지 특정 회사의 제품이 아니거든요.
비슷하게 GitLab 은 자기 식으로 살을 붙인 "GitLab Flow" 를 제안하기도 했어요. 하지만 뼈대는 다 같아요 — main 하나를 깨끗하게 지키고, 짧은 브랜치로 작업해서 검토받고 합친다. 이름이 뭐든 이 뼈대만 기억하면 어디 가서도 통해요.
Step 2: "한 발 더 빠르게 — 트렁크 기반 개발"
GitHub Flow 를 익혔으니, 한 걸음 더 나아간 약속을 봐요. 바로 트렁크 기반 개발(Trunk-Based Development) 이에요. 이름의 "트렁크(trunk)" 는 나무의 몸통 줄기를 뜻해요. 여기선 main 브랜치를 그 몸통 줄기로 보고, 모두가 이 줄기 하나에 아주 자주 합치는 방식이에요.
GitHub Flow 와 뭐가 다를까요? GitHub Flow 도 기능 브랜치가 짧긴 했어요. 트렁크 기반은 그걸 극단적으로 더 짧게 가져가요. 브랜치를 아예 안 따고 main 에 바로 작은 커밋을 올리거나, 따더라도 하루 안에 닫아버려요. 하루에도 여러 번 main 에 합치는 거죠.
트렁크 기반 개발 — 줄기 하나(main)에 하루에도 여러 번 합친다
main ●─●─●─●─●─●─●─●─●─●─▶ ← 모두가 자주 합치는 몸통 줄기
↑ ↑ ↑ ↑ ↑ ↑ ↑
작은 변경들이 하루에도 여러 번 들어온다
(브랜치를 따더라도 하루 안에 닫는다)
왜 이렇게 자주 합칠까요? 역설적이게도 충돌을 줄이려고 그래요. 브랜치가 짧을수록 그 사이 main 이 변하는 양이 적어서, 합칠 때 부딪힐 거리가 거의 없거든요. "조금씩 자주 합치면 아플 일이 없다" 는 거예요. 한 달치를 한 번에 합치는 것보다, 하루치를 매일 합치는 게 훨씬 안 아파요.
그런데 여기서 의문이 들죠. "아직 안 끝난 기능을 main 에 올리면, 반쪽짜리 코드가 배포되는 거 아닌가요?" 맞아요, 그게 트렁크 기반의 숙제예요. 그래서 이 방식은 기능 플래그(feature flag) 라는 장치를 같이 써요. 미완성 기능을 코드엔 올리되, "이 기능은 아직 꺼둠" 이라는 스위치로 사용자에게 안 보이게 가려두는 거예요. 코드는 합쳐졌지만 기능은 잠들어 있다가, 다 완성되면 스위치를 켜요.
⚠️ 트렁크 기반은 아무 팀이나 바로 쓸 수 있는 게 아니에요. 하루에도 여러 번
main에 합치려면, 합칠 때마다 코드가 안 깨졌는지 자동으로 검사해주는 장치(자동화된 테스트) 가 탄탄해야 해요. 그게 없으면 깨진 코드가main에 수시로 섞여 들어가요. 그래서 트렁크 기반은 보통 "테스트 자동화가 잘 갖춰진 성숙한 팀이 지향하는 다음 단계" 로 봐요. (그 자동 검사 장치를 만드는 본격적인 이야기는 A-8 에서 맛보고, 인프라 과목에서 깊이 다뤄요.)
그러니 순서로 정리하면 이래요. 입문이나 대부분의 웹 서비스 팀은 GitHub Flow 로 시작하고, 팀의 테스트·배포 자동화가 무르익으면 트렁크 기반으로 옮겨가는 거예요. 트렁크 기반은 GitHub Flow 의 "브랜치를 짧게" 정신을 끝까지 밀어붙인 모습이라고 보면 돼요.
💡 한 줄 정리
트렁크 기반 개발은 모두가 main(트렁크) 줄기 하나에 하루에도 여러 번 작게 합치는 방식이다. 짧을수록 충돌이 적다는 원리이고, 미완성 기능은 기능 플래그로 가린다. 탄탄한 테스트 자동화가 전제라, GitHub Flow 다음 단계로 성숙한 팀이 지향한다.
🙋 학생 질문 — "튜터님, GitHub Flow 도 브랜치가 짧다면서요. 트렁크 기반이랑 결국 같은 거 아닌가요?"
거의 형제처럼 닮았어요. 둘 다 "main 하나를 깨끗하게, 브랜치는 짧게" 라는 정신을 공유하거든요. 그래서 헷갈리는 게 당연해요.
차이는 "얼마나 짧으냐" 의 정도예요. GitHub Flow 의 기능 브랜치는 보통 며칠 살아 있고, PR 리뷰를 꼭 거쳐서 합쳐요. 트렁크 기반은 그걸 더 극단으로 밀어서, 브랜치를 하루 안에 닫거나 아예 main 에 바로 올리고, 하루에도 여러 번 합쳐요. 그만큼 자동 테스트에 더 많이 기대고요.
쉽게 말하면 트렁크 기반은 "GitHub Flow 를 아주 빠르게 돌리는 고급 버전" 이에요. 그래서 입문자는 GitHub Flow 부터 익히고, 팀의 자동화가 받쳐줄 때 트렁크 기반으로 넘어가면 자연스러워요.
Step 3: "버전을 끊어 파는 소프트웨어 — Git Flow"
이번엔 좀 다른 약속이에요. Git Flow 는 앞의 둘과 정반대로, 브랜치를 여러 개 길게 유지하는 복잡한 전략이에요. 한때 가장 유명한 전략이었는데, 지금은 특수한 경우에만 쓰는 레거시(예전 방식)로 봐요. "왜 옛날엔 이렇게 복잡하게 했나" 를 알아두면, 반대로 GitHub Flow 가 왜 단순함을 택했는지가 또렷해져요.
Git Flow 는 오래 사는 브랜치를 두 개나 둬요. main(배포된 안정 버전)과 develop(다음 버전을 위해 개발 중인 코드)이에요. 거기에 더해 작업 성격마다 브랜치를 따로 만들어요.
Git Flow — 여러 브랜치가 길게 공존하는 복잡한 약속
main ●──────────────●────────────●──▶ 배포된 안정 버전 (+ 태그)
\ / \ /
release \ / ●───●──── 출시 직전 마무리 브랜치
\ / /
develop ●───●──●───●───●──────●──────▶ 다음 버전 개발 줄기
\ / \ /
feature ● ●──● 기능별 작업 브랜치
hotfix ●──▶ 배포된 버전의 긴급 수정
이 다섯 종류 브랜치가 각자 역할을 나눠 가져요. 기능은 feature 에서 만들어 develop 에 모으고, 출시할 때가 되면 release 브랜치에서 마무리해서 main 에 올리고, 이미 배포된 버전에 급한 버그가 터지면 hotfix 로 긴급 수정해요. 규칙이 빡빡한 만큼 단계가 분명하다는 장점은 있어요.
문제는 무겁다는 거예요. 브랜치가 많고 합치는 단계가 복잡해서, 하루에도 여러 번 배포하는 요즘 웹 서비스엔 너무 굼떠요. 그래서 일반적인 웹 서비스 팀은 Git Flow 를 떠나 GitHub Flow 로 많이 옮겨갔어요.
그럼 Git Flow 는 이제 안 쓰냐고요? 아니에요, 여전히 어울리는 곳이 있어요. 핵심은 "버전을 끊어서 배포하는 소프트웨어" 예요. 예를 들면 이런 곳들이에요.
- 모바일 앱 — 앱스토어 심사를 거쳐 v3.1, v3.2 처럼 버전을 끊어 내보내고, 여러 버전이 사용자 폰에 동시에 깔려 있어요.
- 설치형·온프렘 소프트웨어 — 고객사 서버에 버전별로 설치되고, 옛 버전도 한동안 같이 유지보수해야 해요.
- 라이브러리·SDK — 남들이 가져다 쓰는 거라 버전마다 안정성이 중요하고, 옛 버전 버그도 따로 고쳐줘야 해요.
이런 곳은 "여러 버전을 동시에 관리하고 급한 수정도 버전별로 해야" 하니까, release·hotfix 같은 브랜치가 실제로 쓸모가 있어요. 웹 서비스처럼 "항상 최신 하나만 돌아가는" 곳과는 사정이 다른 거죠.
⚠️ Git Flow 를 소개하는 옛 자료들이
main대신master라는 이름을 쓰는 걸 자주 보게 될 거예요.master는 예전 기본 브랜치 이름이고, 지금은main이 표준이에요(A-1 에서 짚었죠). 이름만 바뀐 거니, 옛 자료에서master를 보면 머릿속으로main으로 바꿔 읽으면 돼요.
💡 한 줄 정리
Git Flow 는 main·develop·feature·release·hotfix 등 여러 브랜치를 길게 유지하는 복잡한 전략이다. 무거워서 일반 웹 서비스엔 레거시로 밀려났지만, 모바일 앱·설치형 SW·라이브러리처럼 버전을 끊어 배포하고 여러 버전을 동시에 유지하는 곳에선 여전히 어울린다.
🙋 학생 질문 — "튜터님, 그럼 Git Flow 는 이제 안 배워도 되는 옛날 거 아닌가요?"
"안 써도 된다" 가 아니라 "언제 쓰는지를 알아야 한다" 가 정확해요.
대부분의 웹 서비스 회사에 입사하면 GitHub Flow(나 그 변형)를 만날 가능성이 높아요. 그래서 실전 기본기는 GitHub Flow 예요. 하지만 모바일 앱 팀, 게임 클라이언트 팀, 설치형 솔루션 회사에 가면 Git Flow 나 그 비슷한 다층 브랜치를 실제로 만날 수 있어요. 그때 "이게 왜 이렇게 복잡하지?" 하고 당황하지 않으려면 오늘 개념을 알아둬야 해요.
그리고 옛 코드베이스나 오래된 오픈소스를 보면 Git Flow 흔적(develop 브랜치 같은)이 남아 있는 경우도 많아요. 그걸 읽어내려면 개념을 알아야 하고요. 그러니 "직접 새로 시작할 땐 GitHub Flow, 남이 만들어둔 Git Flow 도 읽을 줄 안다" 가 딱 좋은 자세예요.
Step 4: "우리 팀은 뭘 골라야 할까 — 세 전략 비교"
세 가지 약속을 다 만났어요. 이제 나란히 놓고 견줘봐요. 신입으로 입사하면 "우리 팀은 왜 이 방식으로 일하지?" 가 궁금할 텐데, 이 비교표 하나면 그 답이 보여요.
| 전략 | 브랜치 모양 | 배포 주기 | 누가 쓰나 | 한 줄 평 |
|---|---|---|---|---|
| GitHub Flow | main + 짧은 기능 브랜치 |
수시로(연속 배포) | 대부분의 웹 서비스·스타트업 | 단순해서 사고가 적은 기본 |
| 트렁크 기반 | main 줄기에 초단기 합침 |
하루에도 여러 번 | 테스트 자동화가 성숙한 팀 | 가장 빠르지만 자동화가 전제인 다음 단계 |
| Git Flow | 여러 브랜치 길게 공존 | 버전을 끊어서 | 모바일·설치형·라이브러리 | 무겁지만 버전 관리엔 강한 특수·레거시 |
표를 한 문장으로 요약하면 이래요. "단일 최선은 없고, 팀의 배포 주기와 성숙도가 전략을 정한다." 항상 최신 하나만 돌리는 웹 서비스면 GitHub Flow 로 시작해서 자동화가 익으면 트렁크 기반으로, 버전을 끊어 파는 소프트웨어면 Git Flow 로. 이게 2026 년 현업의 큰 그림이에요.
고를 때 스스로에게 던질 질문은 딱 두 개예요.
우리 팀 브랜치 전략 고르기 — 두 가지 질문
Q1. 우리는 버전을 끊어서 배포하나?
예 → Git Flow 계열 (모바일·설치형·라이브러리)
아니오(항상 최신 하나) → Q2 로
Q2. 테스트 자동화가 하루 여러 번 배포를 받쳐줄 만큼 탄탄한가?
아직 아니다 → GitHub Flow (기본)
충분히 탄탄 → 트렁크 기반 (다음 단계)
그런데 여기서 꼭 안심시켜드릴 게 있어요. 신입인 여러분이 이 전략을 혼자 정하는 일은 거의 없어요. 전략은 보통 팀 리드나 선배들이 이미 정해뒀고, 여러분은 그 약속에 맞춰 따라가면 돼요. 오늘 이걸 배우는 이유는 직접 고르려는 게 아니라, "우리 팀은 왜 이렇게 일하는지" 를 이해하고 자연스럽게 녹아들기 위해서예요. 입사 첫 주에 "우리 브랜치 전략이 GitHub Flow 군요, 그래서 브랜치를 짧게 가져가는 거네요" 하고 알아채면, 그게 바로 빠르게 적응하는 신입이에요.
💡 한 줄 정리
세 전략에 단일 정답은 없다. 버전을 끊어 파나? 면 Git Flow, 항상 최신 하나면 GitHub Flow(기본)로 시작해 자동화가 익으면 트렁크 기반으로 간다. 신입은 보통 팀이 정해둔 전략을 따르므로, 고르는 능력보다 "왜 이걸 쓰는지" 를 이해하는 게 먼저다.
🙋 학생 질문 — "튜터님, 한번 정한 전략은 계속 그걸로 가야 하나요? 중간에 바꿔도 돼요?"
바꿔도 돼요. 전략은 법이 아니라 약속이라, 팀 사정이 변하면 같이 변해요.
실제로 많은 팀이 처음엔 단순한 GitHub Flow 로 시작했다가, 사람이 늘고 테스트 자동화가 탄탄해지면 트렁크 기반으로 옮겨가요. 반대로 너무 복잡한 Git Flow 를 쓰다가 "우리한텐 과해" 하고 GitHub Flow 로 가볍게 내려오기도 하고요. 중요한 건 "지금 우리 팀에 맞는가" 지, "한번 정했으니 끝까지" 가 아니에요.
다만 전략을 바꾸는 건 팀 전체의 약속을 바꾸는 일이라, 혼자 슬쩍 다른 방식으로 일하면 안 돼요. 팀이 함께 정한 약속은 함께 지키고, 바꿀 땐 함께 의논해서 바꾸는 거예요. 신입 때는 일단 팀의 현재 약속에 잘 맞춰 따라가는 것부터 시작하면 충분해요.
Step 5: "남의 공개 저장소에 기여하기 — fork와 upstream"
지금까지 우리가 PR 을 올린 곳은 전부 내 저장소였어요. 내가 만들었으니 push 권한이 있죠. 그런데 세상엔 내가 권한이 전혀 없는 멋진 공개 저장소가 가득해요. 리액트, 스프링, 파이썬 같은 유명한 도구들도 전부 GitHub 에 공개돼 있고, 누구나 코드를 볼 수 있어요. 이렇게 소스가 공개돼 모두가 함께 키우는 프로젝트를 오픈소스(open source) 라고 해요.
"저기 오타 하나가 보이는데 고쳐주고 싶다" 거나 "이 기능에 작은 개선을 보태고 싶다" 면 어떻게 할까요? 내 저장소가 아니니 브랜치를 따서 push 할 수가 없어요. 권한이 없으니까요. 여기서 등장하는 게 fork(포크) 예요.
fork 는 남의 공개 저장소를 내 GitHub 계정으로 통째로 복사해오는 거예요. 복사본은 내 것이니 마음대로 브랜치 따고 push 할 수 있죠. 거기서 고친 다음, "제가 이렇게 고쳤는데 원본에 합쳐주실래요?" 하고 원본 저장소에 PR 을 보내는 거예요.
여기서 두 개의 원격이 등장해요. 지난 시간까지 우리가 알던 원격은 origin 하나뿐이었죠. 이제 하나가 더 생겨요.
오픈소스 기여 — 두 개의 원격(remote)
upstream ● 원본 저장소 (남의 것, 나는 읽기만 가능)
│
│ fork (내 계정으로 복사)
▼
origin ● 내 fork (내 계정, 읽기·쓰기 자유)
│
│ clone
▼
로컬 ● 내 컴퓨터
origin— 내 fork(내 계정에 복사된 저장소)예요. 내 것이라 push 할 수 있어요.upstream— fork 의 원본, 즉 진짜 원본 저장소예요. "위쪽 물줄기" 라는 뜻인데, 내 fork 가 흘러나온 본류라고 보면 돼요. 나는 보통 여기에 직접 push 못 하고, 변경 사항을 받아오기(fetch)만 해요.
A-4 에서 제가 "origin 말고 두 번째 원격을 나중에 만난다" 고 예고했었죠? 그 두 번째 원격이 바로 이 upstream 이에요. 오픈소스에 기여할 때 비로소 등장하는 거예요.
왜 굳이 두 개를 둘까요? 원본의 최신 변화를 따라가기 위해서예요. 내가 fork 를 뜬 뒤에도 원본(upstream)은 다른 기여자들 덕분에 계속 발전해요. 내 fork(origin)는 복사한 그 순간에 멈춰 있고요. 그래서 가끔 upstream 에서 최신 변경을 받아와 내 fork 를 따라잡게 해줘야 해요. 그 동기화 흐름은 다음 Step 에서 직접 해봐요.
💡 한 줄 정리
오픈소스처럼 권한 없는 공개 저장소엔 fork 로 내 계정에 복사본을 만들어 기여한다. 이때 원격이 둘이 된다 — 내 복사본인 origin(push 가능)과 원본인 upstream(받아오기만). A-4 에서 예고한 두 번째 원격이 바로 upstream 이다.
🙋 학생 질문 — "튜터님, fork 랑 clone 이랑 헷갈려요. 둘 다 복사하는 거 아닌가요?"
둘 다 "복사" 라 헷갈리기 딱 좋아요. 핵심 차이는 "어디로 복사하느냐" 예요.
fork 는 남의 저장소를 내 GitHub 계정(원격)으로 복사하는 거예요. GitHub 안에서 일어나는 일이고, 결과물은 인터넷에 있는 내 fork 저장소예요. clone 은 (내 fork든 원본이든) 원격 저장소를 내 컴퓨터(로컬)로 내려받는 거예요. 내 손 안의 폴더로 가져오는 거죠.
그래서 오픈소스에 기여할 땐 보통 순서가 이래요. 먼저 원본을 fork 해서 내 계정에 복사본을 만들고(GitHub 에서 버튼 한 번), 그 내 fork 를 clone 해서 내 컴퓨터로 내려받아요(터미널에서 git clone). fork 가 먼저, clone 이 그다음이에요. 다음 Step 에서 이 순서를 직접 밟아볼 거예요.
Step 6: "오픈소스 첫 PR 한 바퀴 — fork 흐름 실전"
이제 fork 로 오픈소스에 기여하는 한 바퀴를 직접 밟아봐요. 실제로 어떤 공개 저장소든 하나 골라(연습용으론 octocat/Spoon-Knife 같은 연습 저장소가 유명해요) 따라 하면 돼요. 순서는 여섯 단계예요.
오픈소스 기여 한 바퀴 — fork에서 PR까지
① 원본을 fork (GitHub 에서 버튼 한 번 → 내 계정에 복사)
② 내 fork를 clone git clone (내 컴퓨터로)
③ upstream 원격 등록 git remote add upstream <원본 주소>
④ 기능 브랜치에서 작업 git switch -c fix/typo → 수정 → commit
⑤ 내 fork(origin)로 push git push -u origin fix/typo
⑥ 원본을 향해 PR 내 fork → 원본 방향으로 Pull Request
먼저 ①은 GitHub 웹에서 해요. 원본 저장소 페이지 오른쪽 위의 "Fork" 버튼을 누르면, 내 계정 아래로 복사본이 생겨요. 그다음부터는 터미널이에요.
# ② 내 fork를 내 컴퓨터로 내려받기 (A-4의 clone)
git clone https://github.com/내아이디/Spoon-Knife.git
cd Spoon-Knife
# ③ 원본 저장소를 upstream이라는 이름의 두 번째 원격으로 등록
git remote add upstream https://github.com/octocat/Spoon-Knife.git
# 원격이 두 개가 됐는지 확인
git remote -v
# origin https://github.com/내아이디/Spoon-Knife.git ← 내 fork (push 가능)
# upstream https://github.com/octocat/Spoon-Knife.git ← 원본 (받아오기만)
git clone 으로 받으면 내 fork 가 자동으로 origin 으로 등록돼요(A-4 에서 봤죠). 거기에 git remote add upstream 으로 원본을 두 번째 원격으로 더해주면, git remote -v 에 두 줄이 보여요. 이제 준비 끝이에요.
# ④ 기능 브랜치를 따서 작업 (A-2의 switch)
git switch -c fix/readme-typo
# (파일을 고치고)
git add README.md
git commit -m "README 오타 수정"
# ⑤ 내 fork(origin)에 push (A-4의 push)
git push -u origin fix/readme-typo
보세요, ④⑤는 우리가 이미 수십 번 한 브랜치·커밋·push 그대로예요. 오픈소스라고 특별한 명령이 있는 게 아니에요. 딱 하나 다른 건 ⑥ PR 의 방향이에요.
내 fork 에서 push 한 뒤 GitHub 에 가면 "Compare & pull request" 버튼이 떠요(A-5 그대로). 누르면 PR 화면이 나오는데, 여기서 방향을 잘 봐야 해요. base(합쳐질 곳)가 원본 저장소의 main, compare(합쳐질 변경)가 내 fork 의 브랜치로 돼 있어요. 즉 "내 fork 의 변경을 원본에 합쳐주세요" 라는 요청이에요. 내 저장소 안에서 PR 올릴 때와 화면은 똑같은데, base 가 남의 원본을 가리킨다는 점만 달라요.
🌟 GUI 로는 — ① fork 는 GitHub 웹 저장소 페이지의 "Fork" 버튼으로, ② clone 은 GitHub Desktop 의 "Clone repository" 로 해요. 터미널을 떠나기 싫다면 GitHub CLI 로 한 번에 fork+clone 도 돼요 —
gh repo fork octocat/Spoon-Knife --clone한 줄이면 fork 를 뜨고 내 컴퓨터로 내려받고upstream까지 자동으로 등록해줘요.
PR 을 올리고 나면, 원본의 관리자가 검토하고 합칠지 말지를 결정해요. 합쳐지면 축하해요 — 여러분의 코드가 전 세계가 쓰는 오픈소스에 들어간 거예요! 그런데 한 가지 숙제가 남아요. 시간이 지나면 원본(upstream)은 다른 사람들 기여로 계속 앞서가는데, 내 fork(origin)는 뒤처져요. 그래서 다음에 또 기여하려면 내 fork 를 원본 최신 상태로 따라잡게 해줘야 해요.
# 내 fork를 원본(upstream) 최신으로 동기화하기
git switch main # 내 로컬 main으로 이동 (A-2)
git fetch upstream # 원본의 최신 변경을 받아오기만 (A-4의 fetch)
git merge upstream/main # 받아온 원본 최신을 내 main에 합치기 (A-2의 merge)
git push origin main # 따라잡은 내 main을 내 fork에도 올리기 (A-4의 push)
이 네 줄이 fork 기여에서 신입이 가장 많이 빠뜨리는 부분이에요. fetch upstream 으로 원본 변화를 받아와 merge 로 내 것에 합치고, push 로 내 fork 까지 맞춰주면, 내 fork 가 원본과 같은 출발선에 다시 서요. 이걸 게을리하면 한참 뒤처진 fork 에서 작업하다가 PR 올릴 때 충돌투성이가 돼요.
⚠️ fork 동기화는 자주 해주는 게 약이에요. 특히 새 기능에 손대기 직전엔 꼭 한 번
upstream을 받아와 따라잡고 시작하세요. GitHub Flow 에서 "브랜치를 짧게" 가 비결이었듯, fork 기여에선 "원본을 자주 따라잡기" 가 충돌을 막는 비결이에요.
💡 한 줄 정리
오픈소스 기여는 fork → clone → upstream 등록 → 기능 브랜치 작업 → 내 fork(origin)에 push → 원본을 향해 PR 의 여섯 단계다. 작업 명령은 우리가 쓰던 브랜치·커밋·push 그대로이고, PR 의 base 가 원본을 가리키는 점만 다르다. 기여 뒤엔 fetch upstream + merge + push 로 내 fork 를 원본 최신에 맞춘다.
🙋 학생 질문 — "튜터님, 오픈소스에 PR 올리는 거 너무 무서워요. 코드 잘 못 짜는데 폐 끼치는 거 아닐까요?"
이 마음 정말 많이들 가져요. "내 허접한 코드를 전 세계가 보는 유명 프로젝트에 올린다니" 하고 손이 떨리죠. 먼저 안심부터 시켜드릴게요 — 오픈소스는 원래 그렇게 여럿이 조금씩 보태서 자라는 거예요.
거창한 기능을 넣어야만 기여인 게 아니에요. 오타 하나 고치기, 깨진 링크 고치기, 문서에 빠진 설명 한 줄 더하기 — 이런 작은 기여가 오히려 환영받아요. 실제로 많은 개발자의 "첫 오픈소스 기여" 가 문서 오타 수정이에요. 그리고 관리자가 검토해서 마음에 안 들면 안 합치면 그만이라, 여러분이 뭘 망가뜨릴 일은 없어요. PR 은 "제안" 이지 "강제 반영" 이 아니거든요.
그러니 너무 겁먹지 말고, 작은 것 하나부터 올려보세요. 첫 PR 이 합쳐지는 순간의 짜릿함은 정말 특별해요. 그 경험 하나가 "나도 오픈소스에 기여하는 개발자" 라는 자신감을 줘요.
Step 7: "버전에 이름표 달기 — 태그·릴리스와 SemVer"
마지막이에요. 기능을 다 만들어 "이게 우리 서비스의 1.0 버전이다!" 하고 도장을 찍고 싶을 때가 있어요. 수많은 커밋 중 특정 순간을 콕 집어 "여기가 그 버전" 이라고 이름표를 붙이는 게 태그(tag) 예요.
커밋 해시는 a3f9c2e 처럼 외우기 힘든 글자죠. 태그는 그 위에 v1.0.0 같은 사람이 읽기 좋은 이름을 달아줘요. "그 버전 코드 좀 줘" 할 때 해시 대신 v1.0.0 으로 정확히 그 순간을 꺼낼 수 있어요.
# 지금 이 커밋에 버전 이름표 달기 (annotated 태그 — 권장)
git tag -a v1.0.0 -m "첫 정식 출시"
# 태그 목록 보기
git tag
# v1.0.0
# 태그는 자동으로 안 올라간다 — 따로 push 해야 원격에 보인다
git push origin v1.0.0
태그엔 두 종류가 있어요. 위에서 쓴 -a 옵션을 붙인 annotated 태그는 만든 사람·날짜·메시지까지 담긴 정식 이름표라, 출시 버전엔 이걸 써요. -a 없이 git tag v1.0.0 만 하면 lightweight 태그 — 그냥 포스트잇처럼 이름만 붙는 가벼운 거예요. 잠깐 표시용이면 가벼운 거, 진짜 출시면 annotated, 이렇게 기억하면 돼요.
여기서 GitHub 의 릴리스(release) 가 한 단계 더 얹혀요. 릴리스는 태그를 바탕으로 GitHub 가 만들어주는 출시 안내 페이지예요. "v1.0.0 에서 뭐가 바뀌었는지(변경 사항)" 와 내려받을 파일을 보기 좋게 정리해 보여줘요. 사용자는 이 페이지에서 원하는 버전을 골라 받아갈 수 있죠.
🌟 GUI 로는 — GitHub 저장소의 "Releases" → "Draft a new release" 에서 태그를 고르고 변경 사항을 적으면 릴리스가 만들어져요. 터미널파라면
gh release create v1.0.0 --notes "첫 정식 출시"한 줄로도 돼요.
그런데 버전 번호를 아무렇게나 매기면 사용자가 헷갈려요. v1.0.0 다음이 v1.1.0 인지 v2.0.0 인지 규칙이 있어야죠. 그 약속이 바로 SemVer(Semantic Versioning, 유의적 버전) 예요. 버전을 점 두 개로 끊어 MAJOR.MINOR.PATCH(주·부·수)로 읽어요.
SemVer — 버전 번호 X.Y.Z 의 의미
1 . 4 . 2
│ │ │
│ │ └ PATCH : 버그 수정 (기존과 호환됨)
│ └──────── MINOR : 기능 추가 (기존과 호환됨)
└──────────────── MAJOR : 호환이 깨지는 큰 변경
규칙은 단순해요. 버그만 고쳤으면 맨 뒤(PATCH)를 올리고, 기존 걸 안 깨면서 기능을 더했으면 가운데(MINOR)를 올리고, 기존 사용 방식이 깨지는 큰 변경이면 맨 앞(MAJOR)을 올려요. 예를 들어 1.4.1 에서 버그만 고치면 1.4.2, 거기에 호환되는 기능을 더하면 1.5.0, 사용 방식이 깨지는 큰 변경이면 2.0.0 이 되는 식이에요. 사용자는 번호만 보고 "아, 가운데만 올랐으니 안심하고 올려도 되겠네" 하고 판단할 수 있어요. 버전 번호가 일종의 약속 신호가 되는 거예요.
마지막으로 가볍게 하나 더. 커밋 메시지에도 비슷한 약속이 있어요. Conventional Commits(컨벤셔널 커밋) 라고, 커밋 메시지 앞에 종류를 붙이는 규칙이에요.
git commit -m "feat: 프로필에 소개글 기능 추가" # feat = 기능 추가
git commit -m "fix: 로그인 버튼 터치 오류 수정" # fix = 버그 수정
git commit -m "docs: README에 설치법 추가" # docs = 문서
feat(기능)·fix(버그)·docs(문서)처럼 앞에 한 단어를 붙여요. 이게 왜 좋냐면, A-1 에서 배운 "좋은 커밋 메시지" 를 한 발 더 밀어서 기계도 알아볼 수 있게 만들거든요. feat 가 쌓이면 SemVer 의 MINOR 를, fix 가 쌓이면 PATCH 를 올리는 식으로, 도구가 커밋만 보고 다음 버전 번호와 변경 목록을 자동으로 만들어줄 수도 있어요. 좋은 커밋 메시지가 규약을 만나 자동화로 이어지는 거예요.
💡 한 줄 정리
태그(git tag -a v1.0.0)는 특정 커밋에 사람이 읽을 버전 이름표를 단다(따로 push 필요). GitHub 릴리스는 그 태그로 출시 안내 페이지를 만든다. 버전 번호는 SemVer 의 MAJOR.MINOR.PATCH 규칙으로 매기고, 커밋 메시지는 Conventional Commits(feat·fix·docs)로 일관되게 적으면 자동화까지 이어진다.
🙋 학생 질문 — "튜터님, 태그랑 브랜치랑 뭐가 달라요? 둘 다 특정 지점을 가리키는 거 아닌가요?"
날카로운 질문이에요. 둘 다 커밋을 가리키는 이름표인 건 맞아요. 차이는 "움직이느냐, 고정이냐" 예요.
브랜치는 움직이는 이름표예요. main 브랜치에 새 커밋을 하면 main 이라는 이름표가 그 새 커밋으로 따라 움직이죠. 계속 자라는 작업 줄기를 가리키니까요. 반면 태그는 한번 달면 고정된 이름표예요. v1.0.0 태그는 그 순간의 커밋을 영원히 가리켜요. 그 위에 커밋을 더 해도 v1.0.0 은 꼼짝 안 해요.
그래서 용도가 갈려요. 계속 작업이 이어지는 줄기엔 브랜치, "이 순간이 그 버전" 이라고 역사에 고정해둘 땐 태그. 출시한 버전은 나중에 그 시점 코드를 정확히 다시 꺼내야 하니, 움직이지 않는 태그로 표시하는 거예요.
마무리
오늘 우리는 git 을 "여럿이 함께 쓰는 도구" 에서 "팀과 세상과 함께 협업하는 도구" 로 넓혔어요. 팀이 협업하는 약속인 브랜치 전략 세 가지를 구분하고, 우리 팀에 맞는 걸 고르는 기준을 세웠죠. 그리고 내 저장소를 넘어 남의 오픈소스에 fork 로 기여하는 한 바퀴를 익히고, 완성된 버전에 태그를 달아 SemVer 규칙으로 릴리스하는 법까지 손에 넣었어요. 이제 여러분은 "어느 팀, 어느 프로젝트에 가도 그들의 협업 약속에 자연스럽게 녹아들 수 있는" 협업의 뼈대를 갖췄어요.
오늘 배운 핵심 세 가지
- 💡 하나 — 브랜치 전략은 팀의 협업 약속이고, 정답은 상황이 정한다. 가장 단순한 GitHub Flow 가 기본, 자동화가 익으면 트렁크 기반, 버전을 끊어 파는 소프트웨어엔 Git Flow. 신입은 보통 팀이 정한 약속을 따르므로 "왜 이걸 쓰는지" 이해가 먼저다.
- 💡 둘 — 오픈소스엔 fork 와 두 원격으로 기여한다. 원본을 fork 해 내 계정에 복사하고,
origin(내 fork)과upstream(원본) 두 원격을 둔다. 작업 명령은 쓰던 그대로, PR 의 base 가 원본을 가리키는 점만 다르다. 기여 뒤엔fetch upstream으로 내 fork 를 자주 따라잡는다. - 💡 셋 — 완성된 버전엔 이름표를 단다. 태그로 특정 커밋에
v1.0.0을 달고 릴리스로 출시 페이지를 만든다. 버전 번호는 SemVer(MAJOR.MINOR.PATCH), 커밋 메시지는 Conventional Commits(feat·fix)로 일관되게.
다음 시간 예고
오늘 fork 동기화에서 merge 로 원본 최신을 합쳤죠. 그런데 합치는 방법이 merge 만 있는 게 아니에요. 지난 시간 GitHub 가 버튼으로 대신 해주던 합치기 중에 "Rebase" 가 있었던 거 기억하세요? 다음 시간엔 그 rebase 를 직접 손으로 다뤄요. merge 와 rebase 가 역사를 어떻게 다르게 만드는지(다이어그램으로 나란히), 지저분한 커밋 여러 개를 어떻게 깔끔히 정리하는지(rebase -i)를 배워요.
그리고 한 가지 꼭 짚을 게 있어요. rebase 는 역사를 고쳐 쓰는 강력한 도구라, "이미 남과 공유한 브랜치는 절대 rebase 하지 않는다" 는 황금률이 있어요. 이 약속을 어기면 동료의 저장소와 엉켜요. 다음 시간엔 이 황금률과, 부득이할 때 안전하게 강제 push 하는 --force-with-lease 까지 함께 익힙니다. 협업 git 의 마지막 고급 조각이에요.
과제
오늘 배운 건 눈으로만 보면 절대 안 익어요. 특히 fork 는 직접 한 바퀴 돌려봐야 "아, 두 원격이 이렇게 도는구나" 가 몸에 새겨져요. 꼭 여러분 GitHub 계정으로 직접 해보세요.
[기초] 내 저장소를 GitHub Flow 규칙대로 한 바퀴 돌리기
연습 저장소에서 GitHub Flow 의 다섯 규칙을 의식하며 한 바퀴를 돌려보세요.
git switch -c feature/연습 으로 짧은 기능 브랜치를 따고, 파일을 고쳐 커밋한 뒤 push 하고, PR 을 올려 합치고, 기능 브랜치를 지우세요(전부 지난 시간에 한 흐름이에요). 그러고 나서 "내가 방금 한 이 흐름이 왜 GitHub Flow 라고 불리는지, 다섯 규칙 중 어떤 걸 지켰는지" 를 한두 문장으로 적어보세요.
[응용] 실제 오픈소스를 fork 해서 두 원격 세팅까지
GitHub 에서 마음에 드는 공개 저장소를 하나 골라(연습용 octocat/Spoon-Knife 도 좋아요) fork → clone → upstream 등록까지 해보세요.
원본을 fork 하고, 내 fork 를 git clone 으로 내려받고, git remote add upstream <원본주소> 로 두 번째 원격을 등록한 뒤, git remote -v 로 origin 과 upstream 두 줄이 다 보이는지 확인하세요. (실제 PR 제출까지 가보면 더 좋지만, 무리하지 않아도 돼요.) 마지막으로 "origin 과 upstream 이 각각 무엇을 가리키는지, 왜 둘을 나눠 두는지" 를 한 문장으로 적어보세요.
상상해보세요 — 입사 후 팀이 쓰는 사내 라이브러리에 작은 개선을 보태야 하는 상황이요. 그때도 똑같이 fork 하고 두 원격을 세팅하게 될 거예요. 오늘 연습이 그대로 실무가 됩니다.
[심화] 버전에 이름표 달고 릴리스까지
연습 저장소의 어느 커밋에 SemVer 규칙으로 태그를 달고, GitHub 릴리스로 만들어보세요.
git tag -a v1.0.0 -m "첫 출시" 로 annotated 태그를 달고 git push origin v1.0.0 으로 올리세요. 그다음 GitHub "Releases" 에서 그 태그로 릴리스를 하나 만들어보고요. 여유가 되면 커밋 두세 개를 Conventional Commits 형식(feat:·fix:)으로 적어보세요. 마지막으로 "버그만 고쳤다면 다음 버전 번호가 v1.0.1 인지 v1.1.0 인지, 왜 그런지" 를 SemVer 규칙으로 설명해보세요.
생각해볼 주제
1. 우리 팀은 GitHub Flow 일까, 트렁크 기반일까
두 전략은 형제처럼 닮았지만, "브랜치를 얼마나 짧게" 와 "자동화에 얼마나 기대느냐" 에서 갈렸어요. 만약 여러분이 5명짜리 작은 팀의 리드라면, 막 시작한 단계에서 어느 쪽을 택하겠어요? 그리고 1년 뒤 팀이 20명으로 커지고 자동 테스트가 탄탄해졌다면, 그 선택을 바꿀까요? 트렁크 기반이 주는 속도의 이득과, 그걸 받치려면 먼저 갖춰야 하는 비용(테스트 자동화)을 견줘보세요.
2. Git Flow 가 한물갔다는데, 아직도 쓰는 곳이 있는 이유
요즘 웹 서비스는 대부분 Git Flow 를 떠났는데, 모바일 앱이나 설치형 소프트웨어 팀은 여전히 비슷한 다층 브랜치를 써요. 같은 git 인데 왜 어떤 곳은 단순함을, 어떤 곳은 복잡함을 택할까요? 힌트는 "배포의 모양" 에 있어요. 항상 최신 하나만 돌리는 서비스와, 여러 버전이 사용자에게 동시에 깔려 있는 소프트웨어의 차이를 떠올려보세요. 전략은 취향이 아니라 그 소프트웨어가 세상에 나가는 방식이 정한다는 걸 풀어보면 좋아요.
3. fork 동기화를 게을리하면 6개월 뒤 무슨 일이
fork 를 뜬 뒤 upstream 을 한 번도 안 따라잡고 6개월이 지났다고 해봐요. 그 사이 원본은 수백 개의 커밋이 쌓였고요. 이제 내 fork 에서 새 기능을 만들어 PR 을 올리려 하면 어떤 일이 벌어질까요? 왜 "기여 직전에 원본을 자주 따라잡기" 가 GitHub Flow 의 "브랜치를 짧게" 와 같은 정신인지, 충돌이라는 키워드로 엮어 생각해보세요.
✅ 예시 답안정답 보기
이 문서는 A-6 「브랜치 전략과 오픈소스 기여」의 과제와 생각해볼 주제에 대한 예시답안입니다. 정답을 외우는 용도가 아니라, 직접 손으로 해본 결과를 어떻게 정리하고 설명하는지 흐름을 참고하는 용도로 보세요. 특히 fork 는 직접 한 바퀴 돌려봐야 두 원격이 어떻게 도는지 감이 잡힙니다. 답안을 보기 전에 꼭 먼저 여러분 GitHub 계정에서 실제로 해보세요.
과제 예시답안
🎯 [과제 1 예시답안] 내 저장소를 GitHub Flow 규칙대로 한 바퀴 돌리기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| 짧은 기능 브랜치 | 25% | main 에서 짧은 기능 브랜치를 따 작업했는가 |
| PR 로만 합치기 | 30% | main 직접 push 가 아니라 PR 을 거쳐 합쳤는가 |
| 브랜치 정리 | 20% | 병합 뒤 기능 브랜치를 삭제했는가 |
| 규칙 설명 | 25% | 다섯 규칙 중 무엇을 지켰는지, 왜 GitHub Flow 인지 적었는가 |
풀이 예시
GitHub Flow 의 다섯 규칙(① main 은 항상 배포 가능 ② 짧은 기능 브랜치 ③ PR 로만 합치기 ④ 리뷰 ⑤ 합치면 삭제)을 의식하며 한 바퀴를 돌려요.
git switch -c feature/연습
echo "오늘의 메모 한 줄" >> notes.txt
git add notes.txt
git commit -m "메모 한 줄 추가"
git push -u origin feature/연습
GitHub 에서 "Compare & pull request" 로 PR 을 열어 제목·설명을 채우고(셀프 연습이면 그대로) "Merge pull request" 로 합쳐요. 병합 직후 "Delete branch" 로 원격 브랜치를 지우고, 로컬도 정리해요.
git switch main
git pull
git branch -d feature/연습
# Deleted branch feature/연습 (was a1b2c3d).
규칙 설명에 대해: 방금 한 흐름이 GitHub Flow 라고 불리는 건, main 을 직접 건드리지 않고(① 항상 배포 가능 상태 유지) 짧은 기능 브랜치(②)를 PR(③)로만 합치고, 끝난 브랜치를 지웠기(⑤) 때문이에요. 셀프 연습이라 ④ 리뷰는 가볍게 지나갔지만, 팀이라면 여기서 동료의 승인이 들어가요. 가장 단순한 이 다섯 규칙이 GitHub Flow 의 전부예요.
💡 튜터의 한마디 — 이번 과제의 진짜 목적은 "내가 이미 GitHub Flow 를 쓸 줄 안다" 는 걸 스스로 확인하는 거예요. 지난 시간에 배운 흐름에 이름표만 붙은 거라, 새로 외울 게 하나도 없죠? 입사 첫날 "우리 팀은 GitHub Flow 써요" 라는 말을 들으면, 이제 속으로 "아 그 흐름이구나" 하고 바로 알아챌 수 있어요.
🎯 [과제 2 예시답안] 실제 오픈소스를 fork 해서 두 원격 세팅까지
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| fork + clone | 30% | 원본을 fork 하고 내 fork 를 git clone 했는가 |
| upstream 등록 | 35% | git remote add upstream <원본주소> 로 두 번째 원격을 더했는가 |
| 두 원격 확인 | 20% | git remote -v 로 origin·upstream 두 줄을 확인했는가 |
| 역할 설명 | 15% | 두 원격이 각각 무엇을 가리키는지, 왜 나눠 두는지 적었는가 |
풀이 예시
GitHub 웹에서 원본 저장소(연습용 octocat/Spoon-Knife)의 "Fork" 버튼을 눌러 내 계정에 복사본을 만들어요. 그다음 터미널에서 내 fork 를 내려받고 원본을 upstream 으로 등록해요.
git clone https://github.com/내아이디/Spoon-Knife.git
cd Spoon-Knife
git remote add upstream https://github.com/octocat/Spoon-Knife.git
git remote -v
# origin https://github.com/내아이디/Spoon-Knife.git ← 내 fork (push 가능)
# upstream https://github.com/octocat/Spoon-Knife.git ← 원본 (받아오기만)
git remote -v 에 두 줄이 다 보이면 세팅 완료예요. 여기까지만 해도 "오픈소스 기여 준비" 가 끝난 거고, 실제로 한 줄 고쳐 PR 까지 보내보면 첫 오픈소스 기여가 됩니다.
역할 설명에 대해: origin 은 내 계정에 복사된 내 fork 라서 내가 자유롭게 push 할 수 있어요. upstream 은 진짜 원본이라 보통 직접 push 는 못 하고, 원본의 최신 변경을 받아오기(fetch)만 해요. 둘을 나눠 두는 이유는 내 fork 가 원본에 뒤처지지 않게 따라잡기 위해서예요. 원본은 다른 기여자들로 계속 발전하니, upstream 에서 최신을 받아와 내 fork 를 맞춰줘야 충돌 없이 기여할 수 있어요.
💡 튜터의 한마디 — git remote -v 에 두 줄이 뜨는 순간, "아 원격이 진짜 두 개가 됐네" 하고 멘탈 모델이 또렷해질 거예요. 이 두 원격 구조가 오픈소스 기여의 핵심이에요. 입사하면 사내 라이브러리나 공통 모듈에 기여할 때 똑같은 구조를 만나요 — 오늘 연습이 그대로 실무가 됩니다. 그리고 다음 Step 에서 강조한 "기여 직전에 upstream 따라잡기" 를 습관으로 들이면, fork 작업이 훨씬 매끄러워져요.
🎯 [과제 3 예시답안] 버전에 이름표 달고 릴리스까지
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| 태그 생성 + push | 35% | git tag -a 로 annotated 태그를 달고 git push origin <태그> 했는가 |
| 릴리스 생성 | 25% | GitHub "Releases" 에서 그 태그로 릴리스를 만들었는가 |
| Conventional Commits | 15% | 커밋을 feat:·fix: 형식으로 적어봤는가(여유 항목) |
| SemVer 설명 | 25% | 버그 수정 시 다음 번호가 왜 v1.0.1 인지 규칙으로 설명했는가 |
풀이 예시
먼저 출시할 커밋에 annotated 태그를 달고 원격에 올려요.
git tag -a v1.0.0 -m "첫 정식 출시"
git push origin v1.0.0
GitHub 저장소의 "Releases" → "Draft a new release" 에서 방금 올린 v1.0.0 태그를 고르고 변경 사항을 적으면 릴리스 페이지가 만들어져요. 터미널을 선호하면 gh release create v1.0.0 --notes "첫 정식 출시" 한 줄로도 돼요. 여유가 되면 커밋도 Conventional Commits 형식으로 적어봐요.
git commit -m "feat: 메모에 작성 날짜 표시 추가"
git commit -m "fix: 빈 메모가 저장되던 오류 수정"
SemVer 설명에 대해: v1.0.0 상태에서 버그만 고쳤다면 다음 번호는 v1.0.1 이에요. SemVer 의 MAJOR.MINOR.PATCH 규칙에서 버그 수정은 맨 뒤(PATCH)를 올리거든요. 만약 기존 걸 안 깨면서 기능을 더했다면 가운데가 올라 v1.1.0 이 되고, 사용 방식이 깨지는 큰 변경이면 맨 앞이 올라 v2.0.0 이 돼요. 그래서 v1.0.1 이 맞아요 — 버그 수정은 PATCH 니까요.
💡 튜터의 한마디 — 태그와 SemVer 는 "버전을 남에게 정확히 전달하는 약속" 이에요. 혼자 쓸 땐 와닿지 않다가, 남이 내 코드를 가져다 쓰는 순간(라이브러리·오픈소스) 진가가 드러나요. v1.0.1 과 v2.0.0 의 차이만 봐도 "안심하고 올려도 되는지" 를 사용자가 판단할 수 있으니까요. 그리고 Conventional Commits 까지 익혀두면, A-1 에서 배운 좋은 커밋 메시지가 자동 버전 관리로까지 이어지는 그림이 보일 거예요.
생각해볼 주제 예시답안
🤔 [생각해볼 주제 1] 우리 팀은 GitHub Flow 일까, 트렁크 기반일까
문제 상황 요약
GitHub Flow 와 트렁크 기반은 "브랜치를 짧게" 정신을 공유하는 형제다. 5명짜리 작은 팀의 리드라면 막 시작한 단계에서 어느 쪽을 택할지, 그리고 1년 뒤 팀과 자동화가 커지면 그 선택을 바꿀지를 견줘보는 주제다. 속도의 이득과 그걸 받치는 비용을 저울질하는 것이 핵심이다.
튜터의 가이드 및 해설
핵심은 "트렁크 기반의 속도는 공짜가 아니라 자동화라는 입장료를 낸다" 는 점이다.
막 시작한 5명 팀이라면 대부분 GitHub Flow 가 정답이다. 이유는 단순하다. 트렁크 기반은 하루에도 여러 번 main 에 합치는 방식인데, 그게 안전하려면 합칠 때마다 코드가 안 깨졌는지 자동으로 검사하는 장치(테스트 자동화)가 탄탄해야 한다. 막 출발한 팀은 보통 그게 아직 없다. 자동 검사 없이 트렁크 기반을 흉내 내면, 깨진 코드가 main 에 수시로 섞여 들어가 오히려 더 자주 멈춘다. 그래서 단순하고 안전한 GitHub Flow 로 시작해, PR 리뷰라는 관문으로 품질을 지키는 게 합리적이다.
1년 뒤 팀이 20명으로 커지고 자동 테스트가 탄탄해졌다면, 이때는 트렁크 기반을 고려할 만하다. 사람이 많아질수록 긴 브랜치들이 서로 엉키는 비용이 커지는데, 트렁크 기반의 "아주 짧게 자주 합치기" 가 그 충돌을 줄여준다. 자동화가 받쳐주니 빠르게 합쳐도 안전망이 있고, 기능 플래그로 미완성 기능을 가리면 반쪽 코드가 배포될 걱정도 던다. 즉 "자동화라는 입장료" 를 이미 냈다면, 트렁크 기반의 속도라는 이득을 누릴 자격이 생긴 것이다.
정리하면, 선택은 팀의 현재 성숙도가 정한다. 자동화가 없으면 GitHub Flow 의 단순함과 안전이 더 값지고, 자동화가 무르익으면 트렁크 기반의 속도가 더 값지다. 전략은 한번 정하고 끝이 아니라, 팀이 자라면서 함께 진화하는 약속이다.
💡 핵심을 한마디로
트렁크 기반의 속도는 테스트 자동화라는 입장료를 먼저 내야 누릴 수 있다. 막 시작한 팀은 단순하고 안전한 GitHub Flow 로 출발하고, 자동화가 탄탄해지고 사람이 늘면 트렁크 기반으로 옮겨가는 게 자연스럽다. 전략은 팀의 성숙도가 정하고, 팀이 자라면 함께 바뀐다.
🤔 [생각해볼 주제 2] Git Flow 가 한물갔다는데, 아직도 쓰는 곳이 있는 이유
문제 상황 요약
요즘 웹 서비스는 대부분 Git Flow 를 떠났는데, 모바일 앱이나 설치형 소프트웨어 팀은 여전히 비슷한 다층 브랜치를 쓴다. 같은 git 인데 왜 어떤 곳은 단순함을, 어떤 곳은 복잡함을 택할까. 답의 힌트는 "그 소프트웨어가 세상에 나가는 방식" 에 있다.
튜터의 가이드 및 해설
핵심은 "브랜치 전략은 취향이 아니라 배포의 모양이 정한다" 는 점이다.
웹 서비스를 떠올려보자. 사용자가 쓰는 건 항상 서버에 올라간 최신 버전 하나다. 옛 버전을 쓰는 사용자, 두 버전 전 걸 쓰는 사용자가 따로 있지 않다. 그래서 "여러 버전을 동시에 관리할" 이유가 없고, main 하나만 깨끗하게 유지하면 충분하다. Git Flow 의 release·develop 같은 여러 브랜치는 이런 환경에선 그저 무거운 짐이다. 단순한 GitHub Flow 로 충분한 이유다.
이제 모바일 앱을 보자. 앱은 앱스토어 심사를 거쳐 v3.1, v3.2 처럼 버전을 끊어 내보내고, 그 버전들이 사용자 폰에 제각각 깔려 있다. 누구는 최신을, 누구는 두 버전 전 걸 쓰고 있다. 그런데 옛 버전에서 심각한 버그가 터지면? 최신만 고쳐선 안 되고, 그 옛 버전에도 긴급 수정을 따로 내보내야 한다.
설치형 소프트웨어나 라이브러리도 마찬가지다 — 고객사마다, 사용자마다 다른 버전을 쓰고 있어 버전별 유지보수가 필요하다. 바로 이 지점에서 Git Flow 의 release·hotfix 브랜치가 실제로 쓸모를 갖는다. 여러 버전을 동시에 살려두고 각각을 따로 손볼 길을 열어주기 때문이다.
그러니 "Git Flow 가 한물갔다" 는 말은 정확히는 "항상 최신 하나만 돌리는 웹 서비스에선 과하다" 는 뜻이지, 모든 곳에서 틀렸다는 게 아니다. 배포 형태가 다르면 어울리는 전략도 다르다. 같은 git 도구로도 어떤 팀은 단순함을, 어떤 팀은 복잡함을 택하는 건, 그들이 만드는 소프트웨어가 세상에 나가는 방식이 다르기 때문이다.
💡 핵심을 한마디로
항상 최신 하나만 돌리는 웹 서비스엔
main하나면 충분해 Git Flow 가 과하지만, 여러 버전을 끊어 내보내고 옛 버전도 따로 유지보수해야 하는 모바일·설치형·라이브러리엔release·hotfix브랜치가 실제로 쓸모가 있다. 브랜치 전략은 취향이 아니라 그 소프트웨어의 배포 모양이 정한다.
🤔 [생각해볼 주제 3] fork 동기화를 게을리하면 6개월 뒤 무슨 일이
문제 상황 요약
fork 를 뜬 뒤 upstream 을 한 번도 따라잡지 않고 6개월이 지났고, 그 사이 원본엔 수백 개의 커밋이 쌓였다. 이제 내 fork 에서 새 기능을 만들어 PR 을 올리려 하면 무슨 일이 벌어질까. 왜 "기여 직전에 원본을 따라잡기" 가 GitHub Flow 의 "브랜치를 짧게" 와 같은 정신인지, 충돌이라는 키워드로 엮어보는 주제다.
튜터의 가이드 및 해설
핵심은 "내 fork 와 원본이 멀어질수록, 합칠 때 부딪힐 거리가 커진다" 는 점이다.
내 fork 는 fork 를 뜬 그 순간에 멈춰 있다. 그런데 원본(upstream)은 6개월 동안 다른 기여자들 덕분에 수백 커밋 앞서 나갔다. 이 상태에서 내가 6개월 전 코드를 기준으로 새 기능을 짜면, 내 변경은 "이미 한참 바뀐 원본" 위에 얹히려는 셈이다.
같은 파일을 원본의 누군가도 그새 고쳤다면, PR 을 올릴 때 충돌이 터진다. 그것도 한두 군데가 아니라, 6개월치 변화가 한꺼번에 부딪혀 충돌투성이가 되기 쉽다. 최악의 경우엔 "이 변경이 지금 원본과 맞는지" 부터 다시 따져봐야 해서, 기능을 새로 짜다시피 해야 할 수도 있다.
해결은 단순하다. 새 작업에 손대기 직전에 upstream 을 받아와 내 fork 를 원본 최신에 맞추는 것이다. git fetch upstream 으로 원본 변화를 받아와 git merge upstream/main 으로 내 것에 합치고, git push origin main 으로 내 fork 까지 맞춰주면, 내 fork 가 원본과 같은 출발선에 다시 선다. 이 상태에서 기능 브랜치를 따면, 내 변경이 "최신 원본" 위에 얹히니 부딪힐 거리가 거의 없다.
이게 바로 GitHub Flow 의 "브랜치를 짧게" 와 정확히 같은 정신이다. 둘 다 "멀어지기 전에 자주 맞춰서, 한꺼번에 부딪히는 사태를 막는다" 는 원리다. 브랜치를 며칠 안에 닫는 것도, fork 를 자주 따라잡는 것도, 결국 "조금씩 자주 합치면 아플 일이 없다" 는 같은 지혜다. 충돌은 거리에서 나오고, 자주 맞추면 그 거리가 쌓이지 않는다.
💡 핵심을 한마디로
fork 를 6개월 방치하면 원본과 수백 커밋만큼 멀어져, PR 올릴 때 6개월치 변화가 한꺼번에 부딪히는 충돌 지옥이 된다. 새 작업 직전에
fetch upstream+merge로 자주 따라잡으면 그 거리가 안 쌓인다. "멀어지기 전에 자주 맞춘다" — GitHub Flow 의 "브랜치를 짧게" 와 같은 지혜다.