본문 바로가기
CI.CD/GIT

[GIT] 오픈소스 기여(Fork, PR)와 대규모 다중 저장소(Submodule), 생산성 향상 팁

반응형

우리가 매일 사용하는 방대한 라이브러리들과 프레임워크들의 대다수는 전 세계 수많은 개발자들이 대가 없이 기여하는 '오픈소스(Open Source)' 형태로 운영됩니다. 그렇다면 내가 권한을 가지고 있지도 않은 다른 사람의 저장소에 어떻게 내 코드를 안전하게 기여할 수 있을까요? 혹은 사내에서 서로 다른 팀의 프로젝트를 수정해야 할 때는 어떤 방식을 사용해야 할까요? 이때 등장하는 핵심 개념이 바로 Fork(포크)Pull Request(풀 리퀘스트, PR)입니다.

이번 포스팅에서는 GitHub를 중심으로, 남의 저장소를 복제해와서 코드를 수정하고 원작자에게 병합을 요청하는 아름다운 협업 프로세스에 대해 상세히 알아보겠습니다.

1. 남의 저장소를 내 계정으로 복사하기: Fork

오픈소스 프로젝트나 타 팀의 저장소는 보통 접근 권한이 제한되어 있어 내가 마음대로 git push를 할 수 없습니다. 이때 해당 저장소를 내 GitHub 계정 산하로 통째로 복사해오는 작업이 바로 Fork(포크)입니다.

  1. 기여하고 싶은 대상의 GitHub 저장소 페이지에 접속합니다.
  2. 우측 상단에 위치한 'Fork' 버튼을 클릭합니다.
  3. 몇 초 정도 기다리면 내 계정(https://github.com/my-id/project-name)에 원본 저장소와 완전히 똑같은 복사본 저장소가 생성됩니다. 이 복사본은 내 소유이므로 이제 마음대로 수정하고 Push 할 수 있습니다.

Fork한 저장소 로컬로 가져오기

내 계정으로 Fork한 원격 저장소를 다시 내 컴퓨터(로컬)로 가져와 본격적인 작업을 시작해야 합니다.

# 1. 내 계정에 있는 Fork 저장소를 로컬로 클론
$ git clone https://github.com/my-id/project-name.git

# 2. 기능을 수정하기 위해 새로운 브랜치 생성 후 이동
$ git switch -c feature-bug-fix

이후 평소 하던 대로 코드를 수정하고, git addgit commit을 거쳐 로컬 브랜치의 변경 사항을 내 원격 저장소(Fork한 곳)로 git push 합니다.

2. "내 코드를 합쳐주세요!": Pull Request (PR)

내 복사본 저장소에 버그를 수정한 코드가 성공적으로 올라갔습니다. 이제 남은 일은 이 훌륭한 변경 사항을 원작자에게 알려서 원본 저장소에 병합해 달라고 요청하는 것입니다. 이 요청 과정 자체를 Pull Request (PR)라고 부릅니다.

  1. 내 Fork 저장소의 GitHub 페이지에 접속하면, 방금 Push한 브랜치 옆에 'Compare & pull request'라는 녹색 버튼이 활성화된 것을 볼 수 있습니다. 이 버튼을 클릭합니다.
  2. 어떤 파일을 수정했는지, 기존 방식에서 어떻게 문제를 해결했는지 리뷰어가 쉽게 이해할 수 있도록 PR의 제목과 본문을 정성스럽게 작성합니다. 오픈소스의 경우 정해진 템플릿 양식을 준수하는 것이 중요합니다.
  3. 작성을 마친 뒤 'Create pull request' 버튼을 누르면 원본 저장소 관리자에게 알림이 전송되며 PR 생성이 완료됩니다.

코드 리뷰와 피드백 반영

PR을 생성했다고 해서 즉시 원본 코드에 병합되는 것은 아닙니다. 원본 저장소의 메인테이너(관리자)들은 내가 제출한 코드를 한 줄씩 꼼꼼히 확인하는 코드 리뷰(Code Review)를 진행합니다.

만약 관리자가 "변수 이름을 조금 더 직관적으로 바꿔주실 수 있나요?"라고 피드백을 남겼다면, PR을 새로 만들 필요가 없습니다. 내 컴퓨터 로컬에서 변수명을 수정한 뒤 다시 커밋하고 기존 작업 브랜치로 다시 Push하기만 하면, 열려있는 해당 PR에 수정된 커밋이 자동으로 추가 업데이트됩니다. 이러한 리뷰와 수정의 핑퐁 과정이 성공적으로 끝나면 마침내 관리자가 'Merge pull request' 버튼을 눌러 내 코드를 원본 프로젝트의 일부로 받아들이게 됩니다.

3. 원본 저장소와 최신 상태 동기화하기 (Upstream)

내가 PR을 준비하는 동안 원본 저장소에는 이미 수많은 다른 사람들의 PR이 병합되어 최신 상태로 업데이트되었을 것입니다. 내 Fork 저장소는 과거 복제해 온 시점에 멈춰있으므로, 충돌을 방지하기 위해서는 주기적으로 원본 저장소의 최신 코드를 내 로컬로 당겨와야(Pull) 합니다.

# 1. 원본 저장소의 URL을 'upstream'이라는 이름으로 추가
$ git remote add upstream https://github.com/original-author/project-name.git

# 2. 원본 저장소(upstream)의 최신 master 코드 가져오기
$ git fetch upstream
$ git merge upstream/master

# 3. 최신화된 로컬 상태를 내 Fork 원격 저장소(origin)에도 반영
$ git push origin master

1. Submodule이란 무엇인가?

Git 서브모듈은 쉽게 말해 "Git 저장소 안에 들어있는 또 다른 독립된 Git 저장소"입니다. 메인 프로젝트(상위 저장소) 안에 특정 디렉토리 형태로 하위 프로젝트(서브모듈)를 복제해 넣을 수 있습니다. 중요한 점은 두 저장소의 커밋 히스토리가 완전히 독립적으로 관리된다는 것입니다. 상위 프로젝트는 하위 프로젝트의 '특정 커밋(버전)'을 가리키는 링크(참조값)만을 가질 뿐, 하위 프로젝트 내부의 구체적인 파일 변경 내역을 직접 추적하지 않습니다.

2. 프로젝트에 서브모듈 추가하기

현재 작업 중인 메인 프로젝트에 외부 라이브러리(예: 공통 결제 모듈)를 서브모듈로 추가하려면 git submodule add 명령어를 사용합니다.

# 외부 저장소 URL을 'payment-lib'라는 폴더 이름의 서브모듈로 추가
$ git submodule add https://github.com/my-company/payment-module.git payment-lib

# 상태 확인
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged ..." to unstage)
        new file:   .gitmodules
        new file:   payment-lib

명령어를 실행하면 payment-lib라는 폴더가 생기고 그 안에 라이브러리 코드가 들어옵니다. 그리고 .gitmodules라는 특별한 파일이 하나 생성되는데, 이 파일 안에는 서브모듈이 위치한 로컬 경로와 원본 저장소의 URL 매핑 정보가 텍스트로 기록되어 있습니다. 상위 프로젝트를 커밋할 때 이 .gitmodules 파일도 함께 커밋해야 다른 팀원들이 프로젝트를 내려받을 때 서브모듈을 제대로 인식할 수 있습니다.

3. 서브모듈이 포함된 프로젝트 복제하기 (Clone)

다른 팀원이 서브모듈이 포함된 메인 프로젝트를 처음 git clone으로 받아오면 payment-lib 폴더는 비어있는 채로 받아집니다. 상위 프로젝트는 링크만 가지고 있기 때문입니다. 폴더 안의 실제 코드를 채우려면 서브모듈 초기화 명령어를 추가로 실행해야 합니다.

# 1. 메인 프로젝트 복제 (서브모듈 폴더는 비어있음)
$ git clone https://github.com/my-company/main-project.git

# 2. 서브모듈을 위한 로컬 설정 파일(.git/config) 초기화
$ git submodule init

# 3. 서브모듈 원격 저장소에서 실제 데이터를 가져와 폴더를 채움 (업데이트)
$ git submodule update

만약 프로젝트를 처음 복제하는 시점에 서브모듈까지 한 번에 꽉 채워서 가져오고 싶다면 clone 명령어에 --recurse-submodules 옵션을 붙여주면 위의 세 단계를 한 줄로 끝낼 수 있습니다.

# 메인 프로젝트와 내부에 포함된 모든 서브모듈을 한 번에 복제
$ git clone --recurse-submodules https://github.com/my-company/main-project.git

4. 서브모듈 업데이트 관리의 주의점

서브모듈 프로젝트(결제 모듈)에서 새로운 버전이 출시되어 원격 저장소에 업데이트가 일어났다고 가정해 봅시다. 메인 프로젝트 안의 서브모듈 폴더로 이동하여 git pull을 수행하면 최신 코드를 받을 수 있습니다. 이때 주의할 점은, 상위 프로젝트 입장에서 보면 "서브모듈이 가리키는 참조값(버전)이 변경"된 것입니다. 따라서 메인 프로젝트 폴더로 다시 나와서 git addgit commit을 통해 "우리는 이제 최신 버전의 서브모듈을 사용하겠다"고 새롭게 커밋을 남겨주어야 합니다.

1. 타자 치는 시간 줄이기: Git Alias

하루에도 수십 번씩 입력하는 git status, git commit -m, git checkout 등의 명령어는 은근히 손가락을 피곤하게 만듭니다. Git Alias를 설정하면 길고 복잡한 명령어들을 짧은 단축어로 맵핑하여 사용할 수 있습니다.

# 자주 사용하는 명령어들에 대한 단축어 설정
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.cm commit
$ git config --global alias.br branch

# 이제 'git st'만 입력해도 'git status'와 똑같이 동작합니다.
$ git st
On branch master
nothing to commit, working tree clean

복잡한 명령어 단축하기

Alias의 진가는 옵션이 주렁주렁 달린 복잡한 명령어를 사용할 때 발휘됩니다. 이전 포스팅에서 다뤘던 화려한 그래프 형태의 로그 출력 명령어(git log --oneline --graph --all)를 git lg라는 단축어로 만들어 보겠습니다.

$ git config --global alias.lg "log --oneline --graph --all"

# 실행
$ git lg
* a1b2c3d (HEAD -> master) feat: 새로운 기능 추가
* b2c3d4e fix: 오타 수정

2. 반복 작업 자동화하기: Git Hooks

Git Hooks는 Git에서 커밋, 푸시 등 특정 이벤트가 발생했을 때 자동으로 실행되는 스크립트입니다. 저장소를 git init으로 생성하면 .git/hooks 폴더 안에 pre-commit.sample, pre-push.sample 등의 예제 파일들이 자동으로 생성됩니다. 이 파일의 .sample 확장자를 지우고 쉘 스크립트를 작성하면 곧바로 작동합니다.

자주 쓰이는 Hook의 종류

  • pre-commit: 커밋 메시지를 작성하기 직전에 실행됩니다. 코드를 커밋하기 전 자동으로 코드 포맷터(Prettier 등)를 실행하거나 정적 분석 도구(ESLint)를 돌려 에러가 있으면 커밋을 강제로 취소시키는 용도로 아주 많이 사용됩니다.
  • commit-msg: 커밋 메시지가 작성된 직후에 실행됩니다. 팀에서 정한 커밋 메시지 규칙을 지켰는지 검사하고, 규칙에 어긋나면 커밋을 거절합니다.
  • pre-push: 원격 저장소로 push 하기 직전에 실행됩니다. 푸시 전 무거운 통합 테스트 코드를 자동으로 실행하여, 테스트를 통과하지 못한 코드가 원격 서버에 올라가는 것을 방지합니다.

프론트엔드 환경에서는 Husky 같은 라이브러리를 사용하면 복잡한 쉘 스크립트 작성 없이도 패키지 매니저(npm)를 통해 Git Hooks를 아주 쉽게 설정하고 팀원들과 공유할 수 있습니다.

3. 협업을 위한 언어: 커밋 메시지 컨벤션

"수정 완료", "최종", "진짜 최종"과 같은 커밋 메시지는 나중에 코드를 리뷰하거나 디버깅을 할 때 아무런 도움을 주지 못합니다. 좋은 커밋 메시지는 동료와 미래의 나를 위한 최고의 문서입니다. 전 세계적으로 가장 많이 쓰이는 Udacity (또는 Angular) 커밋 메시지 컨벤션을 따르면 매우 깔끔한 히스토리를 유지할 수 있습니다.

타입: 제목 (Title) - 50자 이내로 변경 사항을 명확히 요약

본문 (Body) - (선택 사항) 왜 변경했는지, 어떤 문제를 해결했는지 상세히 작성. 어떻게(How)보다는 무엇을, 왜(Why) 했는지에 집중.
(한 줄 띄우고 작성, 줄바꿈은 72자마다)

꼬리말 (Footer) - (선택 사항) "Fixes #123"과 같이 이슈 트래커의 번호를 참조할 때 사용.

커밋 타입(Type) 종류

  • feat: 새로운 기능 추가
  • fix: 버그 수정
  • docs: 문서 수정 (README 등)
  • style: 코드 포맷팅, 세미콜론 누락, 코드 변경이 없는 경우
  • refactor: 코드 리팩토링 (기능 변화 없이 코드 구조 개선)
  • test: 테스트 코드 추가 및 수정
  • chore: 빌드 테스트 업데이트, 패키지 매니저 설정(package.json 등) 수정
반응형